Matrices de punteros

Temario

Podemos definir una matriz, para que sus elementos contengan, en lugar de un dato de tipo primitivo, una dirección o puntero. Por ejemplo:

Este ejemplo define una matriz p de cinco elementos, cada uno de los cuales es un puntero a un int,…

… y una variable entera b

A continuación asigna al elemento p[0] la dirección de b

… y escribe su contenido.

Este ejemplo define una matriz p de cinco elementos, cada uno de los cuales es un puntero a un int, y una variable entera b. A continuación asigna al elemento p[0] la dirección de b y escribe su contenido. Análogamente podríamos proceder con el resto de los elementos de la matriz. Así mismo, si un elemento como p[0] puede apuntar a un entero, también puede apuntar a una matriz de enteros; en este caso, el entero apuntado se corresponderá con el primer elemento de dicha matriz.

Según lo expuesto, una matriz de dos dimensiones y una matriz de punteros se pueden utilizar de forma parecida, pero no son lo mismo. Por ejemplo:

Las declaraciones anteriores dan lugar a que el compilador de C++ reserve memoria para una matriz a de 25 elementos de tipo entero y para una matriz p de 5 elementos declarados como punteros a objetos de tipo entero (int *).

Supongamos ahora que cada uno de los objetos apuntados por los elementos de la matriz p es a su vez una matriz de cinco elementos de tipo entero. Por ejemplo, hagamos que los objetos apuntados sean las filas de a:

El bucle que asigna a los elementos de la matriz p las filas de a (p[i] = a[i]), ¿podríamos sustituirlo con una sola asignación p = a? No, porque los niveles de indirección son diferentes; dicho de otra forma, los tipos de p y a no son iguales ni admiten una conversión entre ellos. Veamos:

Viendo el estudio de los tipos de arriba, la única asignación posible es la realizada desde: p[i] = a[i].

El acceso a los elementos de la matriz p puede hacerse utilizando la notación de punteros o utilizando la indexación igual que lo hariamos con a. Por ejemplo, para asignar valores a los enteros referenciados por la matriz p y despues visualizarlos, podríamos escribir el siguiente código:

Veamos un ejemplo de cómo declarar y trabajar con una matriz 5 x 5 en C++:

Al ejecutar el programa obtendríamos el resultado de la matriz de 5 x 5:

Punteros a punteros

Para especificar que una variable es un puntero a un puntero, utilizaremos la sintaxis siguiente:

Donte tipo especifica el tipo del objeto apuntado después de una doble indirección (puede ser de cualquier tipo incluyendo tipos derivados) y varpp es el identificador de la variable puntero a puntero. Por ejemplo:

Se dice que p es una variable con nivel de indirección; esto es, a través de p no se puede acceder directamente al dato, sino a la dirección que indica dónde está el dato. Si hacemos un razonamiento similar diremos que pp es una variable con dos niveles de indirección.

El siguiente código resuelve el ejemplo anterior, pero usando una variable q declarada como un puntero a un puntero. El acceso a los elementos de la matriz a utilizandfo el puntero a puntero q podemos hacerlo usando la indexación igual que si lo hicieramos con a o utilizando la notación de punteros. Sería así:

¿Porque no hemos asignado a q directamente a a (q = a)? Pues porque q es una variable con dos niveles de indirección y a es una variable con un solo nivel de indirección.

Dicho de otro modo el tipo de q es int ** y el tipo de a es int (*)[5].

En cambio, p si es una variable con dos niveles de indirección; su tipo es int *[5], que significa matriz de 5 elementos de tipo int *; pero como el nombre de la matriz es un puntero a su primer elemento que es de tipo puntero a int, estamos en el caso de un puntero a puntero.

A continuación modifiquemos el ejemplo anterior para realizar el acceso a los elementos de la matriz p utilizando la notación de punteros.

En la figura superior podemos observar que la dirección de comienzo de la matriz es q o p. Entonces, si la dirección de comienzo de de la matriz de punteros es q, suponiendo un valor de i entre 0 y 4, ¿cual es la dirección de su elemento i? Evidentemente será q + i.

Y, ¿cual sería el contenido de esa dirección? Esto es, ¿cual es el valor de *(q+i) o q[i]? Pues la dirección de la fila a[i] de la matriz a; esto es, q[i] y a[i] ya que son la misma dirección, la de la fila i de la matriz a.

Si a esa dirección le sumamos un entero j entre 0 y 4 (*(q+i)+j o q[i]+j), ¿cual es el resultado? Pues otra dirección: la que corresponde al elemento j de la fila a[i]. Y, ¿cual es el contenido de esta dirección? O lo que es lo mismo, ¿cual es el valor de *(*(q+i)+j) o *(q[i]+j) o q[i][j]? Pues el valor del elemento a[i][j] de la matriz a. Viendo este analisis podemos deducir que las siguientes expresiones representan todas ellas el mismo valor.

Según lo expuesto, observe que las direcciones q+i y *(q+i) tienen significados diferentes. Por ejemplo:

De acuerdo con lo expuesto anteriormente, la versión con puntero del ejemplo anterior presenta solamente la siguiente modificación:

Matriz de punteros a cadenas de caracteres

Una matriz de punteros a cadenas de caracteres es una matriz de una dimensión (unidimensional) en la que cada elemento es de tipo char * o unsigned char *. Por ejemplo:

Este ejemplo define una matriz p de cinco elementos, cada uno de los cuales es un puntero a un carácter (char *),…

… y una variable c de tipo char iniciada con el valor ‘z’.

A continuación asigna al elemento p[0] la dirección a la variable c

… y escribe su contenido.

Similarmente podríamos proceder con el resto de los elementos de la matriz. Así mismo, si un elemento como p[0] puede apuntar a un carácter, también puede apuntar a una cadena de caracteres o matriz unidimensional de caracteres; en este caso el carácter apuntado se corresponderá con el primer elemento de la cadena. Por ejemplo:

Una asignación de la forma p[0] = «hola» asigna la dirección de la cadena especificada al elemento de la matriz indicado, que tiene que ser de tipo char * . Por otra parte, sería un error escribir una sentencia como…

… porque se entá intentando asignar un valor int (valor ASCII del carácter a) a una variable de tipo char *, y a un puntero sólo se le puede asignar otro puntero.

A lo mejor , habrás pensado en escribir el siguiente código para leer todas las cadenas de caracteres:

El código anterior es un error, porque el operador >> lee una cadena de caracteres de la entrada estándar y la almacena en la cadena de de caracteres especificada; en nuestro caso la tiene que colocar a partir de la dirección especificada por p[0], pero, ¿cual es la dirección si la matriz aún no ha sido iniciada? La solución pasa por almacenar en p[i] la dirección de un bloque de memoria que pueda ser utilizado por el programa. La mejor solución es trabajar con matrices de objetos string (visto anteriormente).

En cambio sería posible declarar una matriz de punteros a char e iniciarla con cadenas de caracteres. Por ejemplo, el siguiente programa muestra una función que recibe como parámetro el entero correspondiente a un mes y nos devuelve como resultado un puntero a la cadena que nombra a dicho mes.

En este ejemplo, mes es una matriz de 13 elementos (0 a 12) que son punteros a cadenas de caracteres. Al ejecutarlo daría el siguiente resultado:

Cada elemento de la matriz a sido iniciado con un literal.

Vemos que, los literales son de diferente longitud y todos seran finalizados por C++ automáticamente con el carácter nulo (\0). Si en lugar de utilizar una matriz de punteros, hubiéramos utilizado una matriz de dos dimensiones, el número de columnas tendría que ser el del literal más largo, más uno para el carácter nulo, con lo que la ocupación de la memoria sería mayor.

Siguiendo con el ejemplo, es fácil comprobar que las declaraciones char *mes[] y char **mes no son equivalentes. La primera declara una matriz de punteros a cadenas de caracteres y la segunda un puntero a un puntero a una cadena de caracteres. Por lo tanto, será un error sustituir en el código *mes[] por **mes.