Respuestas:
Son EXACTAMENTE equivalentes. Sin embargo, en
int *myVariable, myVariable2;
Parece obvio que myVariable tiene el tipo int * , mientras que myVariable2 tiene el tipo int . En
int* myVariable, myVariable2;
Puede parecer obvio que ambos son del tipo int * , pero eso no es correcto, ya que myVariable2
tiene el tipo int .
Por lo tanto, el primer estilo de programación es más intuitivo.
int* someVar
proyectos personales. Tiene más sentido
int[10] x
. Esto simplemente no es la sintaxis de C. La gramática se analiza explícitamente como:, int (*x)
y no como (int *) x
, por lo que colocar el asterisco a la izquierda es simplemente engañoso y se basa en un malentendido de la sintaxis de la declaración C.
int* myVariable; int myVariable2;
lugar.
Si lo miras de otra manera, *myVariable
es de tipo int
, lo que tiene sentido.
myVariable
puede ser NULL, en cuyo caso *myVariable
causa una falla de segmentación, pero no hay ningún tipo NULL.
int x = 5; int *pointer = &x;
porque sugiere que establecemos el int *pointer
en algún valor, no en pointer
sí mismo.
Debido a que * en esa línea se une más estrechamente a la variable que al tipo:
int* varA, varB; // This is misleading
Como @Lundin señala a continuación, const agrega aún más sutilezas para pensar. Puede eludir esto completamente al declarar una variable por línea, que nunca es ambigua:
int* varA;
int varB;
El equilibrio entre el código claro y el código conciso es difícil de alcanzar: una docena de líneas redundantes int a;
tampoco son buenas. Aún así, prefiero una declaración por línea y me preocupo por combinar el código más adelante.
int *const a, b;
. ¿De dónde viene el * "enlace"? El tipo de a
es int* const
, entonces, ¿cómo puedes decir que el * pertenece a la variable cuando es parte del tipo en sí?
Algo que nadie ha mencionado aquí hasta ahora es que este asterisco es en realidad el " operador de desreferencia " en C.
*a = 10;
La línea anterior no quiere decir que quiera asignar 10
a a
, significa que desea asignar 10
a cualquier ubicación de memoria a
señala. Y nunca he visto a nadie escribiendo
* a = 10;
¿Tienes? Por lo tanto, el operador de desreferencia se escribe casi siempre sin un espacio. Esto es probablemente para distinguirlo de una multiplicación dividida en varias líneas:
x = a * b * c * d
* e * f * g;
Aquí *e
sería engañoso, ¿no?
Bien, ahora, ¿qué significa realmente la siguiente línea:
int *a;
La mayoría de la gente diría:
Significa que a
es un puntero a un int
valor.
Esto es técnicamente correcto, a la mayoría de la gente le gusta verlo / leerlo de esa manera y así es como los estándares C modernos lo definirían (tenga en cuenta que el lenguaje C en sí mismo es anterior a todos los estándares ANSI e ISO). Pero no es la única forma de verlo. También puede leer esta línea de la siguiente manera:
El valor desreferenciado de a
es de tipo int
.
De hecho, el asterisco en esta declaración también puede verse como un operador de desreferencia, lo que también explica su ubicación. Y ese a
es un puntero que no se declara realmente, está implícito por el hecho de que lo único que puede desreferenciar realmente es un puntero.
El estándar C solo define dos significados para el *
operador:
Y la indirección es solo un significado único, no hay un significado adicional para declarar un puntero, solo hay indirección, que es lo que hace la operación de desreferenciación, realiza un acceso indirecto, por lo que también dentro de una declaración como int *a;
esta es un acceso indirecto ( *
significa acceso indirecto) y, por lo tanto, la segunda declaración anterior está mucho más cerca del estándar que la primera.
int a, *b, (*c)();
como algo así como "declarar los siguientes objetos como int
: el objeto a
, el objeto señalado por b
, y el objeto devuelto de la función señalada por c
".
*
en int *a;
no es un operador, y no se eliminación de referencias a
(que no está aún definido aún)
*
(sólo da un significado a la expresión total y no a la *
dentro de la expresión!). Dice que "int a;" declara un puntero, lo que hace, nunca afirmó lo contrario, pero sin un significado dado *
, leerlo como * el valor desreferenciado de a
es un int sigue siendo totalmente válido, ya que tiene el mismo significado fáctico. Nada, realmente nada escrito en 6.7.6.1 contradeciría esa afirmación.
int *a;
es una declaración, no una expresión. a
no está desreferenciado por int *a;
. a
ni siquiera existe en el momento en que *
se está procesando. ¿Crees que int *a = NULL;
es un error porque desreferencia un puntero nulo?
Voy a salir en un miembro aquí y decir que hay una respuesta clara a esta pregunta , tanto para las declaraciones de variables y tipos de parámetros y retorno, y es que el asterisco debe ir al lado del nombre: int *myVariable;
. Para apreciar por qué, mire cómo declara otros tipos de símbolos en C:
int my_function(int arg);
para una función;
float my_array[3]
para una matriz
El patrón general, denominado declaración después del uso , es que el tipo de símbolo se divide en la parte anterior al nombre, y las partes alrededor del nombre, y estas partes alrededor del nombre imitan la sintaxis que usaría para obtener un símbolo. valor del tipo a la izquierda:
int a_return_value = my_function(729);
float an_element = my_array[2];
y: int copy_of_value = *myVariable;
.
C ++ arroja una llave en las obras con referencias, porque la sintaxis en el punto donde usa referencias es idéntica a la de los tipos de valor, por lo que podría argumentar que C ++ adopta un enfoque diferente a C. Por otro lado, C ++ conserva el mismo comportamiento de C en el caso de punteros, por lo que las referencias realmente se destacan como las extrañas a este respecto.
Eso es solo una cuestión de preferencia.
Cuando lee el código, distinguir entre variables y punteros es más fácil en el segundo caso, pero puede generar confusión cuando coloca variables y punteros de un tipo común en una sola línea (que a menudo se desalienta por las pautas del proyecto, porque disminuye la legibilidad).
Prefiero declarar punteros con su signo correspondiente junto al nombre del tipo, por ejemplo
int* pMyPointer;
Un gran gurú dijo una vez: "Léelo a la manera del compilador, debes hacerlo".
http://www.drdobbs.com/conversationsa-midsummer-nights-madness/184403835
De acuerdo, esto era sobre el tema de la colocación constante, pero la misma regla se aplica aquí.
El compilador lo lee como:
int (*a);
no como:
(int*) a;
Si tiene el hábito de colocar la estrella al lado de la variable, hará que sus declaraciones sean más fáciles de leer. También evita los ojos como:
int* a[10];
- Editar -
Explicar exactamente lo que quiero decir cuando digo que se analiza como int (*a)
, eso significa que se *
une más estrechamente a
de lo que lo hace int
, de la misma manera que en la expresión se 4 + 3 * 7
3
une más estrechamente 7
de lo que lo hace 4
debido a la mayor precedencia de *
.
Con disculpas por el arte ascii, una sinopsis de la AST para analizar se int *a
ve más o menos así:
Declaration
/ \
/ \
Declaration- Init-
Secifiers Declarator-
| List
| |
| ...
"int" |
Declarator
/ \
/ ...
Pointer \
| Identifier
| |
"*" |
"a"
Como se muestra claramente, se *
une más estrechamente a
ya que su antepasado común lo es Declarator
, mientras que debe subir todo el árbol Declaration
para encontrar un antepasado común que involucre al int
.
(int*) a
.
int
en este caso. Paso 2. Lea una declaración, incluyendo cualquier tipo de decoración. *a
en este caso. Paso 3 Lee el siguiente personaje. Si es una coma, consúmalo y vuelve al paso 2. Si el punto y coma se detiene. Si algo más arroja un error de sintaxis. ...
int* a, b;
y obtener un par de punteros. El punto que estoy señalando es que se *
une a la variable y se analiza con ella, no con el tipo para formar el "tipo base" de la declaración. Esa también es parte de la razón por la que se introdujeron typedefs para permitir typedef int *iptr;
iptr a, b;
crear un par de punteros. Al usar un typedef puede vincular el *
a int
.
Declaration-Specifier
con las "decoraciones" en el Declarator
para llegar al tipo final para cada variable. Sin embargo, no "mueve" las decoraciones al especificador de Declaración de lo contrario int a[10], b;
produciría resultados completamente ridículos, analiza int *a, b[10];
como int
*a
,
b[10]
;
. No hay otra manera de describirlo que tenga sentido.
int*a
es finalmente leído por el compilador como: " a
tiene tipo int*
". A eso me refería con mi comentario original.
Porque tiene más sentido cuando tienes declaraciones como:
int *a, *b;
int* a, b;
hizo b
un puntero a int
. Pero no lo hicieron. Y con buen motivo. Bajo su sistema propuesto, ¿cuál es el tipo de b
en la siguiente declaración int* a[10], b;
:?
Para declarar múltiples punteros en una línea, prefiero int* a, * b;
que declare más intuitivamente "a" como un puntero a un número entero, y no mezcle estilos cuando también declare "b". Como alguien dijo, no declararía dos tipos diferentes en la misma declaración de todos modos.
Personas que prefieren int* x;
están tratando de forzar su código en un mundo ficticio donde el tipo está a la izquierda y el identificador (nombre) está a la derecha.
Digo "ficticio" porque:
En C y C ++, en el caso general, el identificador declarado está rodeado por la información de tipo.
Eso puede sonar loco, pero sabes que es verdad. Aquí hay unos ejemplos:
int main(int argc, char *argv[])
significa " main
es una función que toma un int
y una matriz de punteros char
y devuelve un int
". En otras palabras, la mayoría de la información de tipo está a la derecha. Algunas personas piensan que las declaraciones de funciones no cuentan porque de alguna manera son "especiales". OK, intentemos una variable.
void (*fn)(int)
significa fn
es un puntero a una función que toma un int
y no devuelve nada.
int a[10]
declara 'a' como una matriz de 10 int
s.
pixel bitmap[height][width]
.
Claramente, he seleccionado ejemplos que tienen mucha información de tipo a la derecha para aclarar mi punto. Hay muchas declaraciones donde la mayoría, si no todas, del tipo está a la izquierda, como struct { int x; int y; } center
.
Esta sintaxis de declaración surgió del deseo de K&R de que las declaraciones reflejen el uso. La lectura de declaraciones simples es intuitiva, y la lectura de las más complejas se puede dominar aprendiendo la regla derecha-izquierda-derecha (a veces llamada la regla espiral o simplemente la regla derecha-izquierda).
C es lo suficientemente simple como para que muchos programadores de C adopten este estilo y escriban declaraciones simples como int *p
.
En C ++, la sintaxis se volvió un poco más compleja (con clases, referencias, plantillas, clases de enumeración) y, como reacción a esa complejidad, verás un mayor esfuerzo para separar el tipo del identificador en muchas declaraciones. En otras palabras, es posible que vea más int* p
declaraciones de estilo si revisa una gran franja de código C ++.
En cualquier idioma, siempre puede tener el tipo en el lado izquierdo de las declaraciones de variables al (1) nunca declarar múltiples variables en la misma declaración y (2) haciendo uso de typedef
s (o declaraciones de alias, que, irónicamente, ponen el alias identificadores a la izquierda de los tipos). Por ejemplo:
typedef int array_of_10_ints[10];
array_of_10_ints a;
(*fn)
mantienen el puntero asociado en fn
lugar del tipo de retorno.
Ya respondí a una pregunta similar en CP, y, como nadie mencionó eso, también tengo que señalar que C es un lenguaje de formato libre , sea cual sea el estilo que elija, está bien, mientras que el analizador puede hacer una distinción de cada token. Esta peculiaridad de C conducen a un tipo muy especial de concurso denominado C concurso de ofuscación .
Muchos de los argumentos en este tema son completamente subjetivos y el argumento sobre "la estrella se une al nombre de la variable" es ingenuo. Aquí hay algunos argumentos que no son solo opiniones:
Los calificadores de tipo puntero olvidados
Formalmente, la "estrella" no pertenece al tipo ni al nombre de la variable, es parte de su propio elemento gramatical llamado puntero . La sintaxis formal de C (ISO 9899: 2018) es:
(6.7) declaración:
declaración-especificadores init-declarator-list opt;
Donde los especificadores de declaración contienen el tipo (y el almacenamiento), y la lista de declaradores de inicio contiene el puntero y el nombre de la variable. Lo que vemos si diseccionamos aún más esta sintaxis de la lista de declarantes:
(6.7.6) declarator:
puntero opt direct-declarator
...
(6.7.6) puntero:
*
type-qualifier-list opt
*
type-qualifier-list opt puntero
Cuando un declarador es la declaración completa, un declarador directo es el identificador (nombre de la variable), y un puntero es la estrella seguida de una lista de calificador de tipo opcional que pertenece al puntero mismo.
Lo que hace que los diversos argumentos de estilo sobre "la estrella pertenece a la variable" sean inconsistentes, es que se han olvidado de estos calificadores de tipo puntero. int* const x
, int *const x
O int*const x
?
Considere int *const a, b;
, ¿cuáles son los tipos de a
y b
? Ya no es tan obvio que "la estrella pertenece a la variable" por más tiempo. Más bien, uno comenzaría a reflexionar sobre dóndeconst
pertenece.
Definitivamente puede hacer un argumento sólido de que la estrella pertenece al calificador de tipo de puntero, pero no mucho más allá de eso.
La lista de calificadores de tipo para el puntero puede causar problemas a quienes usan el int *a
estilo. Aquellos que usan punteros dentro de un typedef
(que no deberíamos, ¡muy mala práctica!) Y piensan que "la estrella pertenece al nombre de la variable" tienden a escribir este error muy sutil:
/*** bad code, don't do this ***/
typedef int *bad_idea_t;
...
void func (const bad_idea_t *foo);
Esto se compila limpiamente. Ahora puede pensar que el código está hecho correctamente. ¡No tan! Este código es accidentalmente una corrección falsa constante.
El tipo de foo
es en realidad int*const*
: el puntero más externo se hizo de solo lectura, no los datos apuntados. Entonces dentro de esta función podemos hacer **foo = n;
y cambiará el valor de la variable en la persona que llama.
Esto se debe a que en la expresión const bad_idea_t *foo
, ¡ *
no pertenece al nombre de la variable aquí! En pseudocódigo, esta declaración de parámetro debe leerse como const (bad_idea_t *) foo
y no como (const bad_idea_t) *foo
. La estrella pertenece al tipo de puntero oculto en este caso: el tipo es un puntero y un puntero calificado const se escribe como *const
.
Pero entonces la raíz del problema en el ejemplo anterior es la práctica de ocultar punteros detrás de ay typedef
no del *
estilo.
En cuanto a la declaración de múltiples variables en una sola línea
Declarar múltiples variables en una sola línea es ampliamente reconocido como una mala práctica 1) . CERT-C lo resume muy bien como:
DCL04-C. No declare más de una variable por declaración
Simplemente leyendo el inglés, el sentido común está de acuerdo en que una declaración debería ser una declaración.
Y no importa si las variables son punteros o no. Declarar cada variable en una sola línea hace que el código sea más claro en casi todos los casos.
Entonces, la discusión sobre la confusión del programador int* a, b
es mala. La raíz del problema es el uso de múltiples declaradores, no la colocación de *
. Independientemente del estilo, deberías escribir esto en su lugar:
int* a; // or int *a
int b;
Otro argumento sólido pero subjetivo sería que dado int* a
el tipo de a
es sin dudaint*
la estrella pertenece al calificador de tipo.
Pero básicamente mi conclusión es que muchos de los argumentos publicados aquí son solo subjetivos e ingenuos. Realmente no puede hacer un argumento válido para ninguno de los estilos: es realmente una cuestión de preferencia personal subjetiva.
1) CERT-C DCL04-C .
typedef int *bad_idea_t;
void func(const bad_idea_t bar);
Como el gran profeta Dan Saks enseña "Si siempre colocas lo const
más a la derecha posible, sin cambiar el significado semántico", esto cesa por completo ser un problema También hace que sus const
declaraciones sean más consistentes para leer. "Todo a la derecha de la palabra const es lo que es const, todo a la izquierda es su tipo". Esto se aplicará a todos los concursos en una declaración. Pruébelo conint const * * const x;
Considerar
int *x = new int();
Esto no se traduce en
int *x;
*x = new int();
En realidad se traduce en
int *x;
x = new int();
Esto hace que la int *x
notación sea algo inconsistente.
new
es un operador de C ++. Su pregunta está etiquetada "C" y no "C ++".
typedefs
, pero eso agregará una complejidad innecesaria, en mi humilde opinión.