tipos que programas programa ejemplos datos completo comandos caracteristicas c++ pointers const language-lawyer declaration

que - programa c++



Declaraciones en C++ (2)

Por lo que he entendido, las declaraciones / inicializaciones en C ++ son declaraciones con ''tipo de base'' seguido de una lista de declaradores separados por comas.

Considere las siguientes declaraciones:

int i = 0, *const p = &i; // Legal, the so-called base type is ''int''. // i is an int while p is a const pointer to an int. int j = 0, const c = 2; // Error: C++ requires a type specifier for all declarations. // Intention was to declare j as an int and c an as const int. int *const p1 = nullptr, i1 = 0; // p1 is a const pointer to an int while i1 is just an int. int const j1 = 0, c1 = 2; // Both j1 and c1 are const int.

¿Es const int un tipo base o un tipo compuesto?

A partir del error en la segunda declaración anterior, parece ser un tipo base. Si es así, ¿qué pasa con la primera declaración?

En otras palabras, si la primera afirmación es legal, ¿por qué no es la segunda? Además, ¿por qué el comportamiento difiere entre las declaraciones tercera y cuarta?


Buena pregunta, con una respuesta complicada. Para realmente entender esto, necesitas entender bastante bien la estructura interna de las declaraciones de C ++.

(Tenga en cuenta que en esta respuesta, omitiré por completo la existencia de atributos para evitar la supercomplicación).

Una declaración tiene dos componentes: una secuencia de especificadores, seguida de una lista de declaradores de inicio separados por comas.

Los especificadores son cosas como:

  • especificadores de clase de almacenamiento (por ejemplo, static , extern )
  • especificadores de función (por ejemplo, virtual , en inline )
  • friend , typedef , constexpr
  • tipo de especificadores , que incluyen:
    • especificadores de tipo simple (por ejemplo, int , short )
    • cv-qualifiers ( const , volatile )
    • otras cosas (por ejemplo, decltype )

La segunda parte de una declaración son los declaradores de inicio separados por comas. Cada declarador de inicio consiste en una secuencia de declaradores, seguidos opcionalmente por un inicializador.

Qué son los declaradores:

  • identificador (por ejemplo, el i en int i; )
  • operadores tipo puntero ( * , & , && , sintaxis de puntero a miembro)
  • sintaxis del parámetro de función (ej. (int, char) )
  • sintaxis de matriz (ej. [2][3] )
  • cv-qualifiers , si estos siguen un puntero declarador.

Observe que la estructura de la declaración es estricta: primeros especificadores, luego init-declarators (cada uno de los cuales es seguido por un declarante inicializador).

La regla es: los especificadores se aplican a la declaración completa, mientras que los declaradores se aplican solo al único declarador de inicio (al único elemento de la lista separada por comas).

También observe arriba que un calificador cv puede usarse como un especificador y un declarante. Como declarante, la gramática los restringe para usarse solo en presencia de punteros.

Por lo tanto, para manejar las cuatro declaraciones que ha publicado:

1

int i = 0, *const p = &i;

La parte del especificador contiene solo un especificador: int . Esa es la parte a la que se aplicarán todos los declaradores.

Hay dos init-declarators: i = 0 y * const p = &i .

El primero tiene un declarador, i , y un inicializador = 0 . Dado que no hay un declarador de modificación de tipo, el tipo de i viene dado por los especificadores, int en este caso.

El segundo declarador de inicio tiene tres declaradores: * , const y p . Y un inicializador, = &i .

Los declaradores * y const modifican el tipo base para que signifique "puntero constante al tipo base". El tipo base, dado por los especificadores, es int , al tipo de p será "puntero constante a int ".

2

int j = 0, const c = 2;

De nuevo, un especificador: int , y dos init-declarators: j = 0 y const c = 2 .

Para el segundo declarador de inicio, los declaradores son const y c . Como mencioné, la gramática solo permite cv-qualifiers como declaradores si hay un puntero involucrado. Ese no es el caso aquí, de ahí el error.

3

int *const p1 = nullptr, i1 = 0;

Un especificador: int , dos init-declarators: * const p1 = nullptr e i1 = 0 .

Para el primer declarador de inicio, los declaradores son: * , const y p1 . Ya tratamos con dicho declarador de inicio (el segundo en el caso 1 ). Agrega el "puntero constante al tipo base" al tipo base definido por el especificador (que sigue siendo int ).

Para el segundo declarador de inicio i1 = 0 , es obvio. Sin modificaciones de tipo, utilice el / los especificador (es) tal como están. Entonces i1 convierte en int .

4

int const j1 = 0, c1 = 2;

Aquí, tenemos una situación fundamentalmente diferente de las tres anteriores. Tenemos dos especificadores: int y const . Y luego dos init-declarators, j1 = 0 y c1 = 2 .

Ninguno de estos declaradores de inicio tiene ningún declarador de modificación de tipo, por lo que ambos usan el tipo de los especificadores, que es const int .


Esto se especifica en [dcl.dcl] y [dcl.decl] como parte de la simple-declaration * y se reduce a las diferencias entre las ramas en ptr-declarator :

declaration-seq: declaration declaration: block-declaration block-declaration: simple-declaration simple-declaration: decl-specifier-seqopt init-declarator-listopt ; ---- decl-specifier-seq: decl-specifier decl-specifier-seq decl-specifier: type-specifier ← mentioned in your error type-specifier: trailing-type-specifier trailing-type-specifier: simple-type-specifier cv-qualifier ---- init-declarator-list: init-declarator init-declarator-list , init-declarator init-declarator: declarator initializeropt declarator: ptr-declarator ptr-declarator: ← here is the "switch" noptr-declarator ptr-operator ptr-declarator ptr-operator: ← allows const * cv-qualifier-seq opt cv-qualifier: const volatile noptr-declarator: ← does not allow const declarator-id declarator-id: id-expression

El fork importante en las reglas está en ptr-declarator :

ptr-declarator: noptr-declarator ptr-operator ptr-declarator

Básicamente, noptr-declarator en su contexto es solo una id-expression . No puede contener ningún cv-qualifier , sino identificadores calificados o no calificados. Sin embargo, un ptr-operator puede contener un cv-qualifier .

Esto indica que su primera declaración es perfectamente válida, ya que su segundo init-declarator

*const p = &i;

es un ptr-declarator del formulario ptr-operator ptr-declarator con ptr-operator siendo * const en este caso y ptr-declarator es un identificador no calificado.

Su segunda declaración no es legal porque no es un ptr-operator válido:

const c = 2

Un ptr-operator debe comenzar con * , & , && o un especificador de nombre anidado seguido de * . Como const c no comienza con ninguno de esos tokens, consideramos const c como noptr-declarator , que no permite const aquí.

Además, ¿por qué el comportamiento difiere entre las declaraciones tercera y cuarta?

Porque int es el type-specifier , y el * es parte del init-declarator ,

*const p1

declara un puntero constante.

Sin embargo, en int const , tenemos un decl-specifier-seq de dos decl-specifier , int (un simple-type-specifier ) y const (un cv-qualifier ), ver trailing-type-specifier . Por lo tanto, ambos forman un especificador de declaración.

* Nota: he omitido todas las alternativas que no se pueden aplicar aquí y simplifiqué algunas reglas. Consulte la sección 7 "Declaraciones" y la sección 8 " n3337 " de C ++ 11 ( n3337 ) para obtener más información.