Disponible la nueva versión "donationware" 7.3 de OrganiZATOR
Descubre un nuevo concepto en el manejo de la información.
La mejor ayuda para sobrevivir en la moderna jungla de datos la tienes aquí.

Curso C++

[Home]  [Inicio]  [Índice]


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"