4.5.7 Punteros a estructuras
§1 Sinopsis
Del mismo modo que ocurre con las funciones, las estructuras tienen una dirección, asumiendo que esta es el comienzo de su almacenamiento. Esto es especialmente cierto en C, donde las estructuras representan exclusivamente conjuntos de datos. En C++ las estructuras son cierto tipo de clases que pueden contener código (funciones), pero incluso entonces, lo que realmente se almacena en el cuerpo de la estructura son punteros (datos) a las funciones correspondientes.
Nota: puesto que desde el punto de vista C++ las estructuras son un tipo de clases, les son aplicables los
mismos principios y reglas que a aquellas. En concreto todo lo señalado respecto a punteros. Ver a este respecto "Punteros
a clases" ( 4.2.1f) y
"Punteros a miembros" (
4.2.1g).
Veremos que los punteros a estructuras son de uso muy frecuente para el manejo de estas; en especial cuando se pasan como
argumentos a funciones, o son devueltas por estas. Por ejemplo, la sentencia:
struct punto {int x; int y;} pto = {3,4}, *ptr = &pto, arsp[10];
define un tipo de estructura denominado punto; una estructura (instancia) pto, y un puntero ptr a dicha instancia. Finalmente declara una matriz arsp de 10 estructuras tipo punto.
§2 Sintaxis
En general la sintaxis para declaración de punteros a estructuras sigue la sintaxis general (Declaración de punteros
4.2.1a).
<tipo_objeto> * <etiqueta_puntero> [ = <iniciador> ]
En este caso, tipo_objeto es de la forma struct punto, con lo que la declaración es:
struct punto * ptr;
Opcionalmente puede incluirse un iniciador como en el ejemplo anterior:
struct punto * ptr = &pto;
en este caso, ptr es un puntero, y su indirección *ptr, representa la estructura pto. Los miembros de la estructura pueden referenciarse mediante el operador de acceso:
(*ptr).x == 3;
(*ptr).y == 4;
Los paréntesis son necesarios, ya que el operador de acceso (.) tiene
mayor precedencia ( 4.9.0a) que el de
indirección (
4.9.11a). Por esta razón, *ptr.x es interpretada como
*(ptr.x), lo que es erróneo, ya que ptr.x no es un puntero (en este caso es un entero).
§3 La expresión:
struct { int x1; char * str; long *arr[10]; } *ptr;
Declara un puntero ptr a un tipo de estructura anónima de tres componentes
Nota: aunque sintácticamente correcta y aceptable por el compilador, una declaración como la anterior es
prácticamente inútil y muy peligrosa. Por ejemplo, la sentencia (*ptr).int = 30; puede producir una catástrofe. La razón
es la misma que se apuntó en Expresiones peligrosas (
4.2.1a).
§4 Los punteros son muy importantes para el manejo de matrices y estructuras, por lo que en este caso se ha
previsto una notación alternativa más corta (
4.5.4 Acceso a miembros):
ptr->x == 3;
ptr->y == 3;
Veamos las dos opciones de notación para acceso de miembros en un ejemplo con estructuras anidadas:
struct punto {
int x; int y;
};
struct line {
struct punto p1;
struct punto p2;
} lin, *liptr = &lin;
En las sentencias anteriores hemos definido dos tipos de estructuras; una instancia y un puntero a dicha instancia. En estas condiciones, las expresiones que siguen son equivalentes (1 y 6 son preferibles por legibilidad):
lin.p1.x // L1.
(lin.p1).x
(*liptr).p1.x
((*liptr).p1).x
liptr->p1.x // L6.
(liptr->p1).x
§5 Ejemplo.
float pi = 3.14159;
struct {
int x;
char * str;
float *arr[10];
struct punto pto;
struct cuadro cuad;
} str, *ptr = &str;
Supuestas las declaraciones anteriores, a continuación realizamos asignaciones a los miembros de str de dos formas:
directamente y mediante expresiones del puntero ptr. Observe que según lo indicado al respecto del espacio de nombres
de estructuras (
4.5.1d), es perfectamente compatible el nombre str
de estructura con el de uno de sus miembros (el puntero a carácter str.str).
§5.1 Asignaciones directas:
str.x = 30;
str.str = "Hola mundo";
str.arr[0] = & pi;
str.pto.x = 2; str.pto.y = 3;
str.cuad.p1.x = 4; str.cuad.p1.y = 5;
str.cuad.p2.x = 6; str.cuad.p2.y = 7;
§5.2 Expresiones con puntero:
ptr->x = 30;
ptr->str = "Hola mundo";
ptr->arr[0] = & pi;
ptr->pto.x = 2; str.pto.y = 3;
ptr->cuad.p1.x = 4; ptr->cuad.p1.y = 5;
ptr->cuad.p2.x = 6; ptr->cuad.p2.y = 7;
§6 En el epígrafe relativo a la asociatividad y precedencia de operadores
( 4.9.0a) se ha señalado que los
operadores:
( ) Llamada de función
[ ] Subíndices
-> Acceso a miembro de estructura (mediante puntero)
. Acceso a miembro de estructura
:: Acceso a miembro de clase
constituyen el orden más alto en la jerarquía de precedencia, y que su asociatividad es de izquierda a derecha, por lo que hay
que prestar especial atención a las expresiones en que aparecen. Para resaltar la importancia de las reglas de precedencia, en
los ejemplos que siguen se exponen algunas expresiones, referidas al ejemplo (§5
), así como una explicación de su significado.
++ptr->x
Debido a la precedencia de operadores, equivale a ++(ptr->x). Así pues, incrementa el miembro x, no el valor del puntero ptr. Resultado: str.x == 31
*ptr->x
Del mismo modo, esta indirección equivale a *(ptr->x), lo que resulta en un error, porque en el ejemplo indicado, ptr->x no es un puntero (es un int), y ya hemos visto [4.9.11] que el operando del operador de indirección debe ser un puntero.
*ptr->str
En este caso, si es correcta la aplicación del operador *; la expresión equivale a *(ptr->str), y ptr->str sí es un puntero (a carácter); concretamente señala a la primera posición de la cadena, es decir:
*ptr->str == 'H'
se puede evidenciar como sigue:
printf("Letra \"%c\"\n", *ptr->str); // -> Letra "H"
++*ptr->str
Se ha señalado que *ptr->str equivale a 'H'; ahora el compilador se enfrenta a la expresión: ++'H' == 'H'+1;. El resultado depende del contexto, por ejemplo, 'H' = 'H'+1; es un error ('H' no es un Lvalue). Sin embargo, la sentencia que sigue se compila correctamente y produce la salida indicada.
printf("Letra \"%c\"\n", ++*ptr->str); // -> Letra "I"
La explicación es que el compilador pasa a printf el valor 'H'+1 (sin pretender ninguna otra asignación), lo que, tras una conversión de tipo, se traduce en el carácter 'I' que sigue 'H' en la lista ASCII.
*ptr++->str
La expresión se traduce en: *((ptr++)->str). Si se utiliza la expresión que sigue, el resultado es el que se indica, pero cualquier posterior intento de utilizar ptr conduce a una catástrofe.
printf("Letra \"%c\"\n", *ptr++->str); // -> Letra "H"
La razón es que se envía a printf el valor *ptr->str, que produce la salida indicada y después se produce el postincremento del puntero ptr, que queda apuntando a dios sabe donde, con lo que la utilización posterior seguramente producirá el fallo del sistema por "Error grave".
Nota: si str hubiese apuntado a una matriz de estructuras y la posición actual no fuese la última, el efecto hubiese sido simplemente dejar str apuntando a la siguiente estructura de la matriz.
*++ptr->str
La expresión equivale a *(++ (ptr->str)). Nuevamente el resultado es correcto: ptr->str es un puntero p que señala al carácter 'H'; su incremento en 1 lo hace apuntar al segundo, por lo que si utilizamos la expresión que sigue se produce la salida indicada:
printf("Letra \"%c\"\n", *++ptr->str); // -> Letra "o"
*ptr->str++
Esta expresión equivale a (*(ptr->str))++. Es decir, 'H'++; nuevamente el resultado depende del contexto. En la expresión que nos viene sirviendo para control, produce la salida señalada, porque printf recibe como argumento un puntero a 'H' y el incremento se produce después.
printf("Letra \"%c\"\n", *ptr->str++); // -> Letra "H"