Disponible la versión 6 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.1  Declaración de estructuras

§1  Sinopsis

Lo mismo que con el resto de las clases ( 4.11), el proceso de usar una estructura C++ comprende en realidad tres pasos: definir la clase; crear un objeto, e iniciar el objeto.

§2  Definir la clase

En contra de lo que ocurre con el resto de los tipos, que están pre-definidos en el lenguaje. Por ejemplo, al declarar char ch; ya se sabe exactamente que cosa es ch, en este caso hay que definir previamente el tipo. Los tipos estructura se declaran mediante la palabra clave struct. Sería algo así como:

struct  Punto;
struct  Punt2 {int x; int y; int z; };

La primera sentencia es una declaración incompleta (ver más abajo); la segunda es una definición completa de una nueva clase tipo struct denominado Punt2; tiene tres componentes perfectamente definidos (tres int: x, y, z respectivamente). Como puede verse, los componentes se determinan de forma análoga a los parámetros de las funciones, determinando tipo y nombre. Aquí es imprescindible el punto y coma ";" para separar los miembros (sin olvidar poner otro después del último).


§2.1  Ya hemos señalado que en C++ las estructuras son un tipo de clases; entonces, si nos referimos a la terminología de la POO diríamos que en esta fase estamos definiendo la clase. El conjunto de declaraciones dentro de los corchetes {...; ...; ...; } declara los nombres y tipos de sus miembros. Los miembros pueden ser de cualquier tipo con una excepción:

§2.2  Un miembro no puede ser la estructura que se declara porque daría lugar a una declaración circular (lo definido está dentro de su definición). Ejemplo:

struct mystr { mystr s };      // Ilegal

§2.3  Uno, o varios, de los miembros puede ser un puntero a la estructura que se está declarando. Ejemplo:

struct mystr { mystr *ps }     // Ok: Correcto

Nota: esta interesantísima posibilidad constituye la base de estructuras auto referenciadas; una técnica de programación que tiene amplias posibilidades de aplicación. Por ejemplo, listas enlazadas y árboles ( 4.5.8).


§2.4  También es posible que los miembros de una estructura sean a su vez estructuras previamente definidas, dando lugar a estructuras anidadas. Por ejemplo:

struct Punto {
   int x;  int y;
};
struct Linea {
   struct Punto p1;
   struct Punto p2;
} c1;

declara Linea como un tipo struct con dos miembros, cada uno de los cuales es un tipo struct Punto.

Nota: en C, un miembro no puede ser del tipo "función devolviendo...". Es decir, las funciones no pueden ser miembros de la estructuras C (de lo contrario serían clases). En cambio, sí están admitidos los "punteros a función devolviendo..." (ya que son variables, no métodos). Las estructuras C++ sí pueden incluir funciones miembro (de hecho son clases), además, en C++ la palabra struct puede omitirse.


§2.5
  Es importante tener en cuenta que, en este punto (definición), solo está permitido señalar tipo y nombre de los miembros, sin que se pueda efectuar ninguna asignación; ni aún en el caso de que se trate de una constante [1]. Por ejemplo, las definiciones que siguen serían ilegales, pues todas implican una asignación en la definición del segundo miembro:

struct  Str {int x; int y = 2; };           // Error!
struct  Str {int x; const int y = 3; };     // ídem
struct  Str {int x; char c = 'X'; };        // ídem
struct  Str {int x; char * st = "Hola"; };  // ídem
struct  Str {int x; "Hola"; };              // ídem
struct  Str {int x; int a[2] = {3, 4};};    // ídem


§3  Crear un objeto (instanciar la clase)

Un segundo paso es declarar una variable como perteneciente al nuevo tipo. Del mismo modo que para declarar una variable ch como de tipo char declarábamos: char ch;, en este caso, para declarar una variable st como estructura tipo punto se utiliza:

struct Punto pt;     // C y C++

En C++ no es necesario señalar que Punto es una estructura (suponemos que ya lo sabe el compilador por las sentencias anteriores), de forma que si no existe ninguna otra variable punto en el mismo ámbito de nombres, no hay ambigüedad y se puede poner directamente:

Punto pt;         // C++

Usando la terminología de la POO diríamos que estamos instanciando la clase, es decir, creando un objeto concreto pt, con espacio en memoria, que pertenece a (es derivado de) dicha clase. Dichos objetos sí pueden ser asignados con valores concretos en sus miembros.

§3.1    Advertir que cada declaración de (tipo de) estructura introduce un nuevo tipo, distinto de los demás (una nueva clase), de forma que las declaraciones:

struct StA {
  int i,j;
} a, a1;
struct StB {
  int i,j;
} b;

definen dos tipos de estructura distintas: StA y StB; los objetos a y b1 son del tipo StA, pero a y b son de tipo distinto.

Desde la óptica de la POO la frase anterior se enunciaría como sigue: "definen dos clases distintas, StA y StB; los objetos a y b1 son instancias de StA, pero b lo es de la clase StB. Por tanto, a y b son objetos de tipo distinto.


§3.2  De la misma forma que ocurre con el resto de variables, puede declararse más de un elemento en la misma sentencia:

struct Punto p1, p2, p3,... pn;

sin embargo, de una variable de tipo estructura no es posible derivar nuevas variables, es decir no es lícita la sentencia que sigue (utilizando la terminología de C++ lo enunciaríamos diciendo: de un objeto no puede instanciarse otro objeto).

struct p1, p11, p12, p13,... p1n;    // NO!!

§4  Iniciar el objeto

Un tercer paso es inicializar dicha variable. Del mismo modo que para iniciar ch se realizaba una asignación del tipo: ch = 'x', en este caso se utiliza la asignación (análoga a la de matrices):

st = { 12, 25, 3};

Los pasos 2 y 3 se pueden realizar en la misma sentencia. Por ejemplo:

struct punto st = { 12, 25, 3};

También puede realizarse los tres pasos en una misma sentencia:

struct  punto { int x; int y; intz; } p1 = { 12, 25, 3};


Una vez definido el nuevo tipo, pueden declararse punteros y matrices de estructuras de dicho tipo. Por ejemplo:

struct stA { ... };      // define la estructura tipo stA
struct stA st, *pst, arst[10];

La segunda línea declara que st es una estructura de tipo stA; que pst es un puntero a dicho tipo, y que arst es un array de 10 estructuras tipo stA.


§4.1  Como se verá a continuación , es posible incluso declarar estructuras sin asignar un nombre al tipo correspondiente. Por ejemplo:

struct { ... } st, *pst, arst[10];

§5  Espacio de nombres de estructuras

Los nombres de tipos de estructura comparten el espacio de nombres con las uniones y enumeraciones (las enumeraciones dentro de una estructura están en un espacio de nombres diferente). Ver L.4 y L.11 en el ejemplo.

Los nombres de estructura están en un espacio de nombres diferente que el de nombres de tipos de estructura y nombres de etiqueta (en C++ solo si no tienen un constructor). Ver L.8 y L.18 del ejemplo que sigue .

Los miembros de estructuras disponen de un espacio de nombres cerrado y particular (espacio de nombres de miembros de clases), de forma que sus nombres se pueden repetir con los de miembros de otras estructuras o con los de otros espacios (ver L.6, L.7, L.12 y L.16 del ejemplo ).

La consecuencia es que dentro del mismo ámbito, los nombres de estructuras y uniones deben ser únicos (ambos comparten el espacio de nombres de clases), aunque no hay inconveniente que compartan los nombres con los miembros de los otros tres espacios: El de etiquetas; el de miembros (de clases) y el de enumeraciones [3]. ( 4.1.11 Espacion de Nombres y 4.11.4 Ambito de nombres de clase).

§5.1  Ejemplo

goto s;
    ...
s:           // L.3 Etiqueta
struct s {   // L.4 OK: nombres de tipo_de_estructura y etiqueta en
             // espacios diferentes
   int s;    // L.6 OK: nombres miembro de estructura en espacio privado
   float s;  // L.7 ILEGAL: nombre de miembro duplicado
} s;         // L.8 OK: nombre_de_estructura en espacio diferente de
             // nombre de etiqueta (L.3) y de tipo_de_estructura (L4)
             // En C++, esto solo es posible si s no tiene un constructor
union s {    // L.11 ILEGAL: nombre duplicado con el de tipo s (L.4)
   int s;    // L.12 OK: nuevo espacio de miembros
   float f;
} f;         // L.14 OK: espacio diferente que el de miembros (ver L.8)
struct t {
   int s;    // L.16 OK: nuevo espacio de miembros
    ...
} s;         // L.18 ILEGAL: nombre duplicado con el de estructura s (L8)

§6  Declaraciones incompletas

Las declaraciones incompletas, conocidas también como declaraciones anticipadas o adelantadas; consisten en que un puntero a una estructura tipoA puede aparecer en la declaración de otra, tipoB antes que tipoA haya sido declarada. Ejemplo:

struct A;          // declaración anticipada
struct B { struct A *pa };
struct A { struct B *pb };


En este caso, la primera declaración de A se denomina incompleta o anticipada, porque no existe definición para ella (cuales son sus miembros).  Este tipo de declaración sería correcta aquí, porque en la declaración de B no se necesita conocer el tamaño de A (solo el de un puntero a estructura).

  Más sobre declaraciones adelantadas de clases en 4.11.4.


§7  Hay que advertir que en C++ es muy frecuente utilizar typedefs en la declaración de estructuras. De hecho, los ficheros de cabecera de los compiladores C++ están repletos de ellos.  Es muy frecuente que utilicen expresiones como [4]:

typedef struct {
  unsigned char *curp;     // Current active pointer
  unsigned char *buffer;   // Data transfer buffer
  int level;               // fill/empty level of buffer
  int bsize;               // Buffer size
  unsigned short istemp;   // Temporary file indicator
  unsigned short flags;    // File status flags
  wchar_t hold;            // Ungetc char if no buffer
  char fd;                 // File descriptor
  unsigned char token;     // Used for validity checking
} FILE;                    // This is the FILE object

Por tanto, es posible escribir sentencias como:

#include <stdio.h>

int main(void) {
   FILE *in, *out;        // define punteros a estructuras FILE
   ...

Sin embargo, dentro del mismo ámbito de nombres, C++ no permite que una estructura (o clase) tenga el mismo nombre que un typedef declarado para definir un tipo diferente. Ejemplo:

typedef void (*C)();
...
class C {    // Error declaración múltiple para C.
  ...
};

§8  Tipos anónimos

El nombre de un tipo de estructura puede omitirse, teniéndose lo que se llama un tipo anónimo (sin nombre). Pueden utilizarse en declaraciones de componentes separados por comas cuando se quiere indicar que los componentes son (o derivan de) un tipo de estructura determinado. Presenta el problema de que al no poderse referir más al tipo (por no tener nombre), no pueden declararse más objetos adicionales en ningún otro sitio. Ejemplo:

struct { ..; ..; ..; } s, *ps, arrs[10];     // tipo sin nombre


Es posible crear un typedef al mismo tiempo que se declara una estructura, con o sin nombre, como se ve en los ejemplos. Generalmente no se necesitan un typedef y un nombre al mismo tiempo, ya que cualquiera de ellos sirve para las declaraciones.

typedef struct mystruct { ..;..; } MST;
MST s, *ps, arrs[10];            // igual que struct mystruct s, etc.
typedef struct { ..; ..; } YST;  // sin nombre
YST y, *yp, arry[20];


Cuando son miembros de clases, las estructuras y uniones anónimas son ignoradas durante la inicialización.

Nota:  el ANSI C++ solamente permite estructuras anónimas que declaren un objeto. Por su parte, el ANSI C no permite estructuras anónimas, y el compilador C++ de Borland permite varios tipos de estructuras anónimas que extienden el estándar ANSI ( 4.11.3a).


§8.1  Debido a que no hay propiedades de instancia, la sintaxis C++ para las estructuras anónimas no permite referenciar un puntero this ( 4.11.6). Por consiguiente, mientras que una estructura C++ puede tener funciones miembro, las estructuras anónimas C++ no pueden tenerlas. Los miembros de las estructuras anónimas pueden ser accedidos directamente en el ámbito en que las estructuras han sido declaradas sin la necesidad de utilizar la sintaxis x.y o p->y. Por ejemplo:

struct my_struct {
  int x;
  struct {
    int i;
  };
  inline int func1(int y) { return y + i + x; }
} S;
 
int main() {
  S.x = 6;  S.i = 4;
  int y = S.func1(3);
  printf(“y is %d“, y);
  return 0;
}

§8.2  Estructuras anónimas anidadas

Borland C++ permite estructuras anónimas y que no sean usadas para declarar un objeto o cualquier otro tipo. Tienen la forma que se indica:

struct { lista-de-miembros };


Las estructuras anónimas pueden ser anidadas, es decir, declaradas dentro de otra estructura, unión o clase. En estos casos, la estructura externa debe tener nombre. Por ejemplo:

struct my_struct {
  int x;
  struct {           // estructura anónima anidada dentro de my_struct
    int i;
  };
  inline int func1(int y);
} S;


Para compatibilidad con el compilador Visual C++ de Microsoft, el compilador Borland C++ permite declarar una estructura con nombre que no declara ningún objeto. Estas estructuras huérfanas deben ser anidadas. Por ejemplo:

struct Direccion {
  char calle[25];
  int numero;
};

struct Persona {
  char nombre[35]
  struct Direccion;      // estructura huérfana
} candidato;             // una instancia de Persona
candidato.numero = 10;

  Inicio.


[1]  Si nos referimos a C++, se podrían hacer asignaciones iniciales, pero a través del constructor de la clase.

[3]  No confundir los nombres de las enumeraciones con los nombres de los enumeradores.

[4]  Definición tomada del fichero de cabecera  <stdio.h> de la Librería Estándar ( 5).