Por fin llegamos a la tercera y ultima parte de estos post que iban dirigidos para dar una visión general de los punteros y las referencias a objetos, me hubiera gustado hacerlo antes pero no he tenido apenas tiempo para ello.

Los punteros son variables que contienen una dirección de memoria y normalmente los utilizaremos para almacenar direcciones que almacenen objetos, de ahí la similitud a las referencias a objetos. Una ventaja que tienen los punteros es que no cuentan con muchas restricciones que tienen las referencias a objetos, lo que se traduce en una mayor eficiencia.

Bien, para poder usar punteros en la plataforma .Net lo primero que tenemos que hacer es ir a las opciones del proyecto y en la pestaña de generar activar la opción para permitir código no seguro.

 

A parte, no podemos declarar los punteros de cualquier forma, tenemos que utilizar la palabra reservada unsafe, que se puede aplicar a constructores, métodos, propiedades y bloques de código, ahora podemos hacer uso del denominado código inseguro, pero realmente no es que sea código inseguro. Esto es para indicar que ese código se ejecutará en un contexto no gestionado, es decir, que el CLR no controlará la asignación y liberación de la memoria.

Los punteros pueden ser de tipo valor (int, long, char…), arrays, cadenas o estructuras que no contengan miembros de tipo referencia. El uso de punteros y su aritmética, es prácticamente igual que en C/C++, un ejemplo:

    unsafe

    {

         int* p1, p2;  // p1 y p2 son de tipo int*

         int *p3, *p4; // no es correcto

 

         int x = 10;

         p1 = &x;

         p2 = p1; // p1 y p2 apuntan al objeto almacenado en x

         Console.WriteLine( p1 == p2); // imprime por pantalla True

    }

Es algo trivial pero podemos observar como podemos declarar punteros y asignar variables.

El tipo de puntero, en este caso int, indica cuál es el tipo de variable que se ha de considerar que se almacenan en la dirección de memoria almacenada por el puntero, esto permite perfectamente que un puntero de tipo int apunte realmente a un char, pero lo veremos como si fuese un int. A parte tenemos un tipo especial de puntero, el puntero void* con el que no definimos un tipo concreto para la varaible a la que apuntamos.

Podríamos tener punteros apuntado a variables almacenadas en memoria dinámica y esto podría ser un problema si el recolector de basura se pusiera en funcionamiento y cambiara alguna de estas variables de lugar, como resultado tendríamos punteros no válidos, para evitar esto, C# introduce una nueva palabra reservada: fixed, que sirve para que una variable no sea movida del lugar donde se encuentra en memoria mientras un puntero apunte a ella. Aquí tenemos un ejemplo de cómo podemos alterar el contenido de una cadena (String) mediante el uso de punteros:

    class UnsafeString

    {

        public static void Main(string[] args)

        {

            String s = "Una cadena de ejemplo";

 

            Console.WriteLine("Cadena inicial: " + s);

            unsafe

            {

                fixed (char* ps = s)

                {

                    for (int i = 0; i < s.Length; i++)

                        if (ps[i ] == ' ') // sustituimos los espacios en blanco

                            ps[i ] = '_';  // por guiones bajos

                }

            }

            Console.WriteLine("Cadena final: " + s);

        }

    }

La aritmética para punteros nos permite aumentar o disminuir la dirección a la que apunta el puntero, el operador ++ (--) no aumenta (disminuye) en uno la dirección a la que apunta el puntero, sino que le suma (resta) el tamaño del tipo al que apunta. Si utilizamos el operador + (-) aumentamos (disminuimos) el lugar al que apunta el puntero tantas veces como se indique en el valor que sumamos (restamos). Esto nos puede resultar muy útil para recorrer matrices de una forma más eficiente, pues no se comprueba el valor de los índices, eso queda para el programador. También disponemos del operador [ ] que nos permite acceder directamente a un elemento determinado de la matriz, veamos un ejemplo:

    int [,] matrix = new int[10, 10];

 

         for (int i = 0; i < 10; i++)

              for (int j = 0; j<10; j++)

                   matrix [i,j] = j + i*10;

 

    unsafe

    {

         fixed (int* pMatrix = matrix)

         {

              for (int i = 0; i < matrix.Length; i++)

                   Console.Write(*(pMatrix + i) + ", ");

                   //Console.Write(pMatrix[i ] + ", "); equivalente a la linea anterior

         }

    }

Con estos ejemplos tenemos una primera toma de contacto con el uso de los punteros, pero tal vez podríamos complicarlo tanto como quisiéramos y darnos cuenta de que pueden ser tan “horrendos” como en C/C++.