Como o Operador de Vírgula funciona

175

Como o operador de vírgula funciona em C ++?

Por exemplo, se eu fizer:

a = b, c;  

A acaba igualando b ou c?

(Sim, eu sei que isso é fácil de testar - basta documentar aqui para alguém encontrar a resposta rapidamente.)

Atualização: Esta pergunta expôs uma nuance ao usar o operador de vírgula. Apenas para documentar isso:

a = b, c;    // a is set to the value of b!

a = (b, c);  // a is set to the value of c!

Esta questão foi realmente inspirada por um erro de digitação no código. O que era para ser

a = b;
c = d;

Se tornou

a = b,    //  <-  Note comma typo!
c = d;
Joe Schneider
fonte
Leia mais sobre isso aqui. stackoverflow.com/questions/12824378/…
Codificação Mash
1
Possível duplicata de O que o operador de vírgula `,` faz em C? . Vencê-lo por um dia. E a resposta da lillq fornece uma resposta para a pergunta sobre a = (b, c);.
JWW
5
Mas neste caso, a = b, c = d;na verdade, executa o mesmo que o pretendido a = b; c = d;?
Bondolin
@NargothBond Não necessariamente. Se be dsão avaliações de função que usam (e modificam) um estado comum, a ordem de execução não é definida até C++17.
nyronium 9/09/18

Respostas:

74

Seria igual a b.

O operador de vírgula tem uma precedência mais baixa que a atribuição.

Leon Timmermans
fonte
129

Observe que o operador de vírgula pode estar sobrecarregado em C ++. O comportamento real pode, portanto, ser muito diferente do esperado.

Como exemplo, o Boost.Spirit usa o operador vírgula de maneira inteligente para implementar inicializadores de lista para tabelas de símbolos. Assim, torna a seguinte sintaxe possível e significativa:

keywords = "and", "or", "not", "xor";

Observe que, devido à precedência do operador, o código é (intencionalmente!) Idêntico ao

(((keywords = "and"), "or"), "not"), "xor";

Ou seja, o primeiro operador chamado é o keywords.operator =("and")que retorna um objeto proxy no qual os operator,s restantes são chamados:

keywords.operator =("and").operator ,("or").operator ,("not").operator ,("xor");
Konrad Rudolph
fonte
Umm, você não pode alterar a precedência, o que significa que você provavelmente deve colocar parênteses em torno de sua lista.
Jeff Burdges
18
@ Jeff Pelo contrário. Com um parêntese ao redor da lista, isso não funcionaria desde então, o compilador apenas vê o operador de vírgula entre dois char[], que não podem ser sobrecarregados. O código intencionalmente primeiro chama o operator=e, posteriormente, operator,para cada elemento restante.
Konrad Rudolph
125

O operador de vírgula tem a menor precedência de todos os operadores C / C ++. Portanto, é sempre o último a vincular a uma expressão, o que significa:

a = b, c;

é equivalente a:

(a = b), c;

Outro fato interessante é que o operador de vírgula introduz um ponto de sequência . Isso significa que a expressão:

a+b, c(), d

é garantido que suas três subexpressões ( a + b , c () e d ) sejam avaliadas em ordem. Isso é significativo se eles tiverem efeitos colaterais. Normalmente, os compiladores têm permissão para avaliar subexpressões na ordem que acharem adequada; por exemplo, em uma chamada de função:

someFunc(arg1, arg2, arg3)

argumentos podem ser avaliados em uma ordem arbitrária. Observe que as vírgulas na chamada de função não são operadores; eles são separadores.

efotinis
fonte
15
Vale ressaltar que ,essa precedência é tão baixa que fica atrás de si mesma ;) ... Ou seja: vírgula como operador tem uma precedência menor que a vírgula como separadora . Portanto, se você quiser usar vírgula como operador em um argumento de função única, atribuição de variável ou outra lista separada por vírgula - precisará usar parênteses, por exemplo:int a = 1, b = 2, weirdVariable = (++a, b), d = 4;
underscore_d
68

O operador de vírgula:

  • tem a menor precedência
  • é associativo à esquerda

Uma versão padrão do operador de vírgula é definida para todos os tipos (interno e personalizado) e funciona da seguinte maneira exprA , exprB:

  • exprA é avaliado
  • o resultado de exprAé ignorado
  • exprB é avaliado
  • o resultado de exprBé retornado como resultado de toda a expressão

Com a maioria dos operadores, o compilador pode escolher a ordem de execução e é necessário pular a execução, se não afetar o resultado final (por exemplo false && foo(), a chamada para foo). No entanto, esse não é o caso do operador de vírgula e as etapas acima sempre acontecerão * .

Na prática, o operador de vírgula padrão funciona quase da mesma maneira que um ponto e vírgula. A diferença é que duas expressões separadas por ponto e vírgula formam duas declarações separadas, enquanto a separação por vírgula mantém tudo como uma expressão única. É por isso que o operador de vírgula às vezes é usado nos seguintes cenários:

  • A sintaxe C requer uma expressão única , não uma declaração. por exemplo, emif( HERE )
  • A sintaxe C requer uma única instrução, não mais, por exemplo, na inicialização do forloopfor ( HERE ; ; )
  • Quando você quiser pular chaves e manter uma única declaração: if (foo) HERE ;(por favor, não faça isso, é realmente feio!)

Quando uma instrução não é uma expressão, o ponto e vírgula não pode ser substituído por uma vírgula. Por exemplo, estes não são permitidos:

  • (foo, if (foo) bar)( ifnão é uma expressão)
  • int x, int y (a declaração da variável não é uma expressão)

No seu caso, temos:

  • a=b, c;, equivalente a a=b; c;, assumindo que aé do tipo que não sobrecarrega o operador de vírgula.
  • a = b, c = d;equivalente a a=b; c=d;, supondo que aseja do tipo que não sobrecarregue o operador de vírgula.

Observe que nem toda vírgula é realmente um operador de vírgula. Algumas vírgulas que têm um significado completamente diferente:

  • int a, b; --- lista de declaração variável é separada por vírgula, mas estes não são operadores de vírgula
  • int a=5, b=3; --- esta também é uma lista de declarações de variáveis ​​separadas por vírgula
  • foo(x,y)--- lista de argumentos separados por vírgula. De fato, xe ypode ser avaliado em qualquer ordem!
  • FOO(x,y) --- lista de argumentos de macro separada por vírgula
  • foo<a,b> --- lista de argumentos de modelo separados por vírgula
  • int foo(int a, int b) --- lista de parâmetros separados por vírgula
  • Foo::Foo() : a(5), b(3) {} --- lista de inicializadores separados por vírgula em um construtor de classe

* Isso não é totalmente verdade se você aplicar otimizações. Se o compilador reconhecer que determinado trecho de código não tem absolutamente nenhum impacto sobre o restante, ele removerá as instruções desnecessárias.

Leitura adicional: http://en.wikipedia.org/wiki/Comma_operator

CygnusX1
fonte
Vale a pena notar que, se a operator ,sobrecarga for perdida, você perderá qualquer garantia de associatividade (assim como as propriedades de curto-circuito da operator&&e operator||se elas estiverem sobrecarregadas)?
precisa
O operador de vírgula é associativo à esquerda, independentemente de estar sobrecarregado ou não. Uma expressão a, b, csempre significa (a, b), ce nunca a, (b, c). A última interpretação pode até levar a erros de compilação se os elementos forem de tipos diferentes. O que você pode estar procurando depois é a ordem de avaliação dos argumentos? Não tenho certeza disso, mas talvez você esteja certo: pode acontecer que cseja avaliado antes (a, b) mesmo que a vírgula seja associativa à esquerda.
CygnusX1 22/02
1
Apenas um pequeno comentário na lista de inicialização separada por vírgula em um construtor de classe, a ordem não é determinada pela posição na lista. A ordem é determinada pela posição da declaração da classe. Por exemplo, struct Foo { Foo() : a(5), b(3) {} int b; int a; }evaula b(3)antes a(5). Isto é importante se a sua lista é assim: Foo() : a(5), b(a) {}. b não será definido como 5, mas o valor não inicializado de a, sobre o qual seu compilador pode ou não alertar.
Jonathan Gawrych 29/07
Recentemente, deparei-me com um operador de vírgula com dois carros alegóricos, qual é o sentido de avaliar e descartar um número?
Aaron Franke
Eu não acho que alguém possa responder isso. Você teria que mostrá-lo em um contexto. Provavelmente uma pergunta separada?
CygnusX1
38

O valor de aserá b, mas o valor da expressão será c. Que está em

d = (a = b, c);

a seria igual a b, e dseria igual a c.

MobyDX
fonte
19
Quase correto. As instruções não têm valores, as expressões têm. O valor dessa expressão é c.
21413 Leon Timmermans
Por que isso é usado em vez de a = b; d = c;?
Aaron Franke
Isso me fez entender de que efeitos colaterais as pessoas estavam falando.
cadarço
8

O valor de b será atribuído a a. Nada vai acontecer com c

prakash
fonte
2

O valor de a será igual a b, já que o operador de vírgula tem uma precedência menor que o operador de atribuição.

Jason Carreiro
fonte
2

Sim O operador de vírgula tem baixa precedência que o operador de atribuição

#include<stdio.h>
int main()
{
          int i;
          i = (1,2,3);
          printf("i:%d\n",i);
          return 0;
}

Saída: i = 3
Porque o operador de vírgula sempre retorna o valor mais à direita.
No caso de operador de vírgula com Operador de atribuição:

 int main()
{
      int i;
      i = 1,2,3;
      printf("i:%d\n",i);
      return 0;
}

Ouput: i = 1
Como sabemos, o operador de vírgula tem precedência menor do que a atribuição .....

Roopam
fonte
Então, como o segundo exemplo é diferente de apenas ter i = 1;nessa linha?
Aaron Franke
-3

Primeiras coisas primeiro: vírgula não é realmente um operador, para o compilador é apenas um token que obtém um significado no contexto de outros tokens.

O que isso significa e por que se preocupar?

Exemplo 1:

Para entender a diferença entre o significado do mesmo token em um contexto diferente, vamos dar uma olhada neste exemplo:

class Example {
   Foo<int, char*> ContentA;
}

Normalmente um novato C ++ poderia pensar que esta expressão poderia / iria comparar as coisas, mas é absolutamente errado, o significado da <, >e ,fichas depent no contexto de uso.

A interpretação correta do exemplo acima é obviamente que é uma instatação de um modelo.

Exemplo 2:

Quando escrevemos um loop for tipicamente com mais de uma variável de inicialização e / ou mais de uma expressão que deve ser feita após cada iteração do loop, também usamos vírgula:

for(a=5,b=0;a<42;a++,b--)
   ...

O significado da vírgula depende do contexto de uso, aqui está o contexto da forconstrução.

O que uma vírgula no contexto realmente significa?

Para complicar ainda mais (como sempre em C ++), o operador de vírgula pode ser sobrecarregado (obrigado a Konrad Rudolph por apontar isso).

Para voltar à pergunta, o Código

a = b, c;

significa para o compilador algo como

(a = b), c;

porque a prioridade do =token / operador é maior que a prioridade do ,token.

e isso é interpretado em contexto como

a = b;
c;

(observe que a interpretação depende do contexto, aqui não é uma chamada de função / método ou uma instatiação de modelo.)

Quonux
fonte
1
-se que é, talvez eu usei a terminologia errada (para o lexer é um sinal, claro)
Quonux
2
Como se trabalha com o operador (sic), a vírgula é realmente um operador.
DragonLord 11/07
2
Embora reconhecer se o token de vírgula fornecido é reconhecido como operador de vírgula (em oposição a, por exemplo, separador de argumentos) pode ser um desafio por si só, essa pergunta é especificamente sobre operador de vírgula .
CygnusX1