Respostas:
Eles são EXATAMENTE equivalentes. No entanto, em
int *myVariable, myVariable2;
Parece óbvio que myVariable possui o tipo int * , enquanto myVariable2 possui o tipo int . No
int* myVariable, myVariable2;
pode parecer óbvio que ambos são do tipo int * , mas isso não está correto como o myVariable2
tipo int .
Portanto, o primeiro estilo de programação é mais intuitivo.
int* someVar
projetos pessoais. Faz mais sentido.
int[10] x
. Isso simplesmente não é a sintaxe do C. A gramática analisa explicitamente como: int (*x)
e não como (int *) x
, portanto, colocar o asterisco à esquerda é simplesmente enganoso e baseado em um mal-entendido da sintaxe da declaração C.
int* myVariable; int myVariable2;
.
Se você olhar de outra maneira, *myVariable
é do tipo int
, o que faz algum sentido.
myVariable
pode ser NULL, nesse caso, *myVariable
causa uma falha de segmentação, mas não existe um tipo NULL.
int x = 5; int *pointer = &x;
porque sugere que definimos o int *pointer
com algum valor, não o pointer
próprio.
Como o * nessa linha se liga mais estreitamente à variável do que ao tipo:
int* varA, varB; // This is misleading
Como o @Lundin aponta abaixo, o const adiciona ainda mais sutilezas para pensar. Você pode evitar isso declarando uma variável por linha, o que nunca é ambíguo:
int* varA;
int varB;
É difícil encontrar o equilíbrio entre código claro e código conciso - uma dúzia de linhas redundantes int a;
também não é boa. Ainda assim, eu padronizo uma declaração por linha e me preocupo com a combinação de código posteriormente.
int *const a, b;
. Onde o * "liga"? O tipo de a
é int* const
, então como você pode dizer que o * pertence à variável quando faz parte do próprio tipo?
Algo que ninguém mencionou aqui até agora é que esse asterisco é realmente o " operador de desreferência " em C.
*a = 10;
A linha acima não significa que eu deseja atribuir 10
a a
, isso significa que eu quero atribuir 10
a qualquer local de memória a
aponta para. E eu nunca vi ninguém escrevendo
* a = 10;
você já? Portanto, o operador de desreferência é quase sempre escrito sem espaço. Provavelmente é para distingui-lo de uma multiplicação dividida em várias linhas:
x = a * b * c * d
* e * f * g;
Aqui *e
seria enganoso, não seria?
Ok, agora o que a seguinte linha realmente significa:
int *a;
A maioria das pessoas diria:
Isso significa que a
é um ponteiro para um int
valor.
Isso é tecnicamente correto, a maioria das pessoas gosta de ver / ler dessa maneira e é assim que os padrões C modernos o definiriam (observe que a própria linguagem C é anterior a todos os padrões ANSI e ISO). Mas não é a única maneira de ver isso. Você também pode ler esta linha da seguinte maneira:
O valor referenciado do a
é do tipo int
.
Portanto, o asterisco nesta declaração também pode ser visto como um operador de desreferência, o que também explica sua localização. E esse a
é um ponteiro que não é realmente declarado, está implícito pelo fato de que a única coisa que você pode realmente desreferenciar é um ponteiro.
O padrão C define apenas dois significados para o *
operador:
E a indireção é apenas um único significado, não há um significado extra para declarar um ponteiro, há apenas a indireção, que é o que a operação de desreferência faz, ela executa um acesso indireto, portanto, também dentro de uma declaração como int *a;
essa é um acesso indireto ( *
significa acesso indireto) e, portanto, a segunda declaração acima está muito mais próxima do padrão do que a primeira.
int a, *b, (*c)();
como algo como "declare os seguintes objetos como int
: o objeto a
, o objeto apontado por b
e o objeto retornado da função apontada por c
".
*
em int *a;
não é um operador, e não é dereferencing a
(que não é mesmo ainda definidos)
*
(ele só dá um significado à expressão total, e não para o *
dentro da expressão!). Diz que "int a;" declara um ponteiro, o que ele faz, nunca reivindicou o contrário, mas sem um significado atribuído *
, a leitura como * o valor não referenciado de a
é um int ainda é totalmente válido, pois tem o mesmo significado factual. Nada, realmente nada escrito em 6.7.6.1 contradizia essa afirmação.
int *a;
é uma declaração, não uma expressão. a
não é desreferenciado por int *a;
. a
ainda não existe no momento em que *
está sendo processado. Você acha que int *a = NULL;
é um bug porque desreferencia um ponteiro nulo?
Vou sair em um membro aqui e dizer que não há uma resposta direta a esta pergunta , tanto para declarações de variáveis e para tipos de parâmetro e de retorno, o que é que o asterisco deve ir ao lado do nome: int *myVariable;
. Para entender o motivo, veja como você declara outros tipos de símbolo em C:
int my_function(int arg);
para uma função;
float my_array[3]
para uma matriz.
O padrão geral, denominado declaração após o uso , é que o tipo de um símbolo é dividido na parte anterior ao nome e nas partes ao redor do nome, e essas partes ao redor do nome imitam a sintaxe que você usaria para obter uma valor do tipo à esquerda:
int a_return_value = my_function(729);
float an_element = my_array[2];
e: int copy_of_value = *myVariable;
.
O C ++ lança uma chave inglesa nos trabalhos com referências, porque a sintaxe no ponto em que você usa as referências é idêntica à dos tipos de valor, portanto, você pode argumentar que o C ++ adota uma abordagem diferente de C. Por outro lado, o C ++ mantém o mesmo comportamento de C no caso de ponteiros, então as referências realmente são as mais estranhas a esse respeito.
Isso é apenas uma questão de preferência.
Quando você lê o código, é mais fácil distinguir entre variáveis e ponteiros no segundo caso, mas pode causar confusão quando você coloca variáveis e ponteiros de um tipo comum em uma única linha (o que muitas vezes é desencorajado pelas diretrizes do projeto, porque diminui a legibilidade).
Prefiro declarar ponteiros com o sinal correspondente ao lado do nome do tipo, por exemplo
int* pMyPointer;
Um grande guru disse uma vez "Leia da maneira do compilador, você deve."
http://www.drdobbs.com/conversationsa-midsummer-nights-madness/184403835
Concedido isso foi sobre o tópico const const, mas a mesma regra se aplica aqui.
O compilador lê como:
int (*a);
não como:
(int*) a;
Se você adquirir o hábito de colocar a estrela ao lado da variável, facilitará a leitura das suas declarações. Também evita problemas oculares, como:
int* a[10];
- Editar -
Explicar exatamente o que quero dizer quando digo que é analisado como int (*a)
, significa que isso *
se liga mais firmemente a
do que a isso int
, da mesma maneira que na expressão 4 + 3 * 7
3
se liga mais firmemente 7
do que 4
devido à maior precedência de *
.
Com desculpas pela arte ascii, uma sinopse do AST para análise int *a
é mais ou menos assim:
Declaration
/ \
/ \
Declaration- Init-
Secifiers Declarator-
| List
| |
| ...
"int" |
Declarator
/ \
/ ...
Pointer \
| Identifier
| |
"*" |
"a"
Como é claramente mostrado, *
vincula-se mais firmemente, a
já que o ancestral comum é Declarator
, enquanto você precisa subir a árvore Declaration
para encontrar um ancestral comum que envolva o int
.
(int*) a
.
int
nesse caso. Etapa 2. Leia uma declaração, incluindo qualquer tipo de decoração. *a
nesse caso. Etapa 3 Leia o próximo caractere. Se vírgula, consuma-a e volte para a etapa 2. Se o ponto-e-vírgula parar. Se alguma outra coisa gerar um erro de sintaxe. ...
int* a, b;
e obter um par de indicadores. O que estou dizendo é que o *
vínculo com a variável é analisado com ela, não com o tipo para formar o "tipo base" da declaração. Isso também faz parte do motivo pelo qual os typedefs foram introduzidos para permitir a typedef int *iptr;
iptr a, b;
criação de alguns ponteiros. Usando um typedef, você pode vincular o *
ao int
.
Declaration-Specifier
com as "decorações" Declarator
para chegar ao tipo final de cada variável. No entanto, ele não "move" as decorações para o especificador de Declaração, caso contrário int a[10], b;
, produziria resultados completamente ridículos. Ele analisa int *a, b[10];
como int
*a
,
b[10]
;
. Não há outra maneira de descrevê-lo que faça sentido.
int*a
no final é lido pelo compilador como: " a
has type int*
". Foi isso que eu quis dizer com o meu comentário original.
Porque faz mais sentido quando você tem declarações como:
int *a, *b;
int* a, b;
fez b
um ponteiro para int
. Mas eles não fizeram. E por uma boa razão. No sistema proposto, qual é o tipo da b
seguinte declaração int* a[10], b;
:?
Para declarar vários ponteiros em uma linha, prefiro o int* a, * b;
que mais intuitivamente declara "a" como ponteiro para um número inteiro e não combina estilos ao declarar "b". Como alguém disse, eu não declararia dois tipos diferentes na mesma declaração.
As pessoas que preferem int* x;
estão tentando forçar seu código a um mundo fictício, onde o tipo está à esquerda e o identificador (nome), à direita.
Eu digo "fictício" porque:
Em C e C ++, no caso geral, o identificador declarado é cercado pelas informações de tipo.
Pode parecer loucura, mas você sabe que é verdade. aqui estão alguns exemplos:
int main(int argc, char *argv[])
significa " main
é uma função que leva um int
e uma matriz de ponteiros para char
e retorna um int
". Em outras palavras, a maioria das informações de tipo está à direita. Algumas pessoas pensam que as declarações de função não contam porque são de alguma forma "especiais". OK, vamos tentar uma variável.
void (*fn)(int)
meios fn
é um ponteiro para uma função que recebe int
e não retorna nada.
int a[10]
declara 'a' como uma matriz de 10 int
s.
pixel bitmap[height][width]
.
Claramente, escolhi exemplos que têm muitas informações de tipo à direita para expressar minha opinião. Existem muitas declarações em que a maioria - se não todos - do tipo está à esquerda, como struct { int x; int y; } center
.
Essa sintaxe da declaração surgiu do desejo da K&R de fazer com que as declarações refletissem o uso. A leitura de declarações simples é intuitiva, e a leitura de declarações mais complexas pode ser dominada pelo aprendizado da regra direita-esquerda-direita (às vezes chamada de regra espiral ou apenas da regra direita-esquerda).
C é simples o suficiente para que muitos programadores adotem esse estilo e escrevam declarações simples como int *p
.
No C ++, a sintaxe ficou um pouco mais complexa (com classes, referências, modelos, classes enum) e, como uma reação a essa complexidade, você verá mais esforço para separar o tipo do identificador em muitas declarações. Em outras palavras, você poderá ver mais int* p
declarações de estilo se verificar uma grande faixa de código C ++.
Em qualquer idioma, você sempre pode ter o tipo no lado esquerdo das declarações de variáveis (1) nunca declarando várias variáveis na mesma declaração e (2) fazendo uso de typedef
s (ou alias), que, ironicamente, colocam o alias identificadores à esquerda dos tipos). Por exemplo:
typedef int array_of_10_ints[10];
array_of_10_ints a;
(*fn)
mantêm o ponteiro associado fn
ao tipo de retorno, e não ao tipo.
Eu já respondi a uma pergunta semelhante no PC e, porque ninguém mencionou isso, também aqui eu tenho que apontar que C é uma linguagem de formato livre , qualquer que seja o estilo escolhido, enquanto o analisador pode fazer uma distinção de cada token. Esta peculiaridade de C levar a um tipo muito especial de concurso chamado concurso de ofuscação C .
Muitos dos argumentos deste tópico são claramente subjetivos e o argumento sobre "a estrela se liga ao nome da variável" é ingênuo. Aqui estão alguns argumentos que não são apenas opiniões:
Os qualificadores de tipo de ponteiro esquecidos
Formalmente, a "estrela" não pertence ao tipo nem ao nome da variável; faz parte de seu próprio item gramatical chamado ponteiro . A sintaxe C formal (ISO 9899: 2018) é:
(6.7) declaração:
especificadores de declaração init-declarator-list opt;
Onde os especificadores de declaração contêm o tipo (e o armazenamento) e a lista de declaradores init contém o ponteiro e o nome da variável. Que vemos se dissecarmos mais esta sintaxe da lista de declaradores:
(6.7.6) declarator:
apontador opt direct-declarator
...
(6.7.6) ponteiro:
*
type-qualifier-list opt
*
tipo-qualifier-list opt ponteiro
Onde um declarador é a declaração inteira, um declarador direto é o identificador (nome da variável) e um ponteiro é a estrela seguida por uma lista de qualificadores de tipo opcional pertencente ao próprio ponteiro.
O que torna inconsistentes os vários argumentos de estilo sobre "a estrela pertence à variável" é que eles se esqueceram desses qualificadores de tipo de ponteiro. int* const x
, int *const x
Ou int*const x
?
Considere int *const a, b;
, quais são os tipos de a
e b
? Não é tão óbvio que "a estrela pertence à variável" por mais tempo. Em vez disso, começaria a refletir sobre ondeconst
pertence.
Você pode definitivamente argumentar que a estrela pertence ao qualificador de tipo de ponteiro, mas não muito além disso.
A lista de qualificadores de tipo para o ponteiro pode causar problemas para aqueles que usam o int *a
estilo. Aqueles que usam ponteiros dentro de um typedef
(o que não devemos, muito má prática!) E pensam que "a estrela pertence ao nome da variável" tendem a escrever esse bug muito sutil:
/*** bad code, don't do this ***/
typedef int *bad_idea_t;
...
void func (const bad_idea_t *foo);
Isso compila de forma limpa. Agora você pode pensar que o código está correto. Não tão! Este código é acidentalmente uma falsificação de correção const.
O tipo de foo
é realmente int*const*
- o ponteiro mais externo foi feito somente leitura, não o apontado para os dados. Então, dentro desta função, podemos fazer **foo = n;
e isso mudará o valor da variável no chamador.
Isso ocorre porque na expressão const bad_idea_t *foo
, o *
não pertence ao nome da variável aqui! No pseudo-código, esta declaração de parâmetro deve ser lida como const (bad_idea_t *) foo
e não como (const bad_idea_t) *foo
. A estrela pertence ao tipo de ponteiro oculto nesse caso - o tipo é um ponteiro e um ponteiro qualificado como const é escrito como *const
.
Mas a raiz do problema no exemplo acima é a prática de ocultar os ponteiros atrás de um typedef
e não o*
estilo estilo.
Em relação à declaração de várias variáveis em uma única linha
Declarar várias variáveis em uma única linha é amplamente reconhecido como uma má prática 1) . O CERT-C resume muito bem como:
DCL04-C. Não declare mais de uma variável por declaração
Apenas lendo o inglês, o senso comum concorda que uma declaração deve ser uma declaração.
E não importa se as variáveis são indicadores ou não. Declarar cada variável em uma única linha torna o código mais claro em quase todos os casos.
Portanto, a discussão sobre o programador ficar confuso int* a, b
é ruim. A raiz do problema é o uso de vários declaradores, não a colocação do *
. Independentemente do estilo, você deve escrever isso:
int* a; // or int *a
int b;
Outro argumento sólido, mas subjetivo, seria aquele dado int* a
o tipo dea
is sem dúvida int*
, a estrela pertence ao qualificador de tipo.
Mas, basicamente, minha conclusão é que muitos dos argumentos postados aqui são apenas subjetivos e ingênuos. Você realmente não pode argumentar válido para nenhum dos dois estilos - é realmente uma questão de preferência pessoal subjetiva.
1) CERT-C DCL04-C .
typedef int *bad_idea_t;
void func(const bad_idea_t bar);
Como o grande profeta Dan Saks ensina "Se você sempre coloca o const
mais à direita possível, sem alterar o significado semântico", isso cessa completamente ser um problema. Também torna suas const
declarações mais consistentes de ler. "Tudo à direita da palavra const é aquilo que é const, tudo à esquerda é seu tipo". Isso se aplicará a todos os consts em uma declaração. Experimente-o comint const * * const x;
Considerar
int *x = new int();
Isso não se traduz em
int *x;
*x = new int();
Na verdade, traduz para
int *x;
x = new int();
Isso torna a int *x
notação um tanto inconsistente.
new
é um operador C ++. Sua pergunta está marcada com "C" e não "C ++".
typedefs
, mas isso adicionará complexidade desnecessária, IMHO.