O que o operador de vírgula faz?


167

O que o ,operador faz em C?



1
Como observo na minha resposta, há um ponto de sequência após a avaliação do operando esquerdo. Isso é diferente da vírgula em uma chamada de função que é apenas gramatical.
Shafik Yaghmour

2
@SergeyK. - Dado que isso foi perguntado e respondido anos antes do outro, é mais provável que o outro seja uma duplicata desta pergunta. No entanto, o outro também tem duas tags com c e c ++ , o que é um incômodo. Esta é uma sessão de perguntas e respostas apenas com C, com respostas decentes.
Jonathan Leffler

Respostas:


129

A expressão:

(expression1,  expression2)

A primeira expressão1 é avaliada, depois a expressão2 é avaliada e o valor da expressão2 é retornado para toda a expressão.


3
então se eu escrever i = (5,4,3,2,1,0) então, idealmente, ele deve retornar 0, correto? mas está sendo atribuído um valor de 5? Você pode me ajudar a entender onde estou errado?
Jayesh

19
@ James: O valor de uma operação de vírgula será sempre o valor da última expressão. Em nenhum momento haverá ios valores 5, 4, 3, 2 ou 1. É simplesmente 0. É praticamente inútil, a menos que as expressões tenham efeitos colaterais.
Jeff Mercado

6
Observe que há um ponto de sequência completo entre a avaliação do LHS da expressão de vírgula e a avaliação do RHS (consulte a resposta de Shafik Yaghmour para uma cotação do padrão C99). Essa é uma propriedade importante do operador de vírgula.
Jonathan Leffler

5
i = b, c;é equivalente a (i = b), cporque porque a atribuição =tem precedência mais alta que o operador de vírgula, . O operador de vírgula tem a menor precedência de todas.
Ciclaminist

1
Eu me preocupo que os parênteses sejam enganosos por duas razões: (1) eles não são necessários - o operador de vírgula não precisa estar entre parênteses; e (2) eles podem ser confundidos com os parênteses em torno da lista de argumentos de uma chamada de função - mas a vírgula na lista de argumentos não é o operador de vírgula. No entanto, corrigi-lo não é totalmente trivial. Talvez: Na declaração: expression1, expression2;primeiro expression1é avaliada, presumivelmente por seus efeitos colaterais (como chamar uma função), depois há um ponto de sequência, depois expression2é avaliado e o valor retornado ...
Jonathan Leffler

119

Eu já vi usado mais em whileloops:

string s;
while(read_string(s), s.len() > 5)
{
   //do something
}

Ele fará a operação e, em seguida, fará um teste com base em um efeito colateral. A outra maneira seria fazê-lo assim:

string s;
read_string(s);
while(s.len() > 5)
{
   //do something
   read_string(s);
}

21
Ei, isso é bacana! Muitas vezes tive que fazer coisas não-ortodoxas em um loop para resolver esse problema.
staticsan

6
Embora provavelmente seria menos obscuro e mais legível se você fez algo como: while (read_string(s) && s.len() > 5). Obviamente, isso não funcionaria se read_stringnão tivesse um valor de retorno (ou não tivesse um valor significativo). (Edit: Desculpe, não percebeu quantos anos este post foi.)
jamesdlin

11
@staticsan Não tenha medo de usar while (1)com uma break;declaração no corpo. Tentar forçar a parte inicial do código para o teste while ou para o teste do while, geralmente é um desperdício de energia e torna o código mais difícil de entender.
precisa saber é o seguinte

8
@jamesdlin ... e as pessoas ainda leem. Se você tem algo útil a dizer, diga. Os fóruns têm problemas com threads ressuscitados porque os threads geralmente são classificados por data da última postagem. O StackOverflow não tem esses problemas.
Dimitar Slavchev

3
@potrzebie Gosto muito da abordagem por vírgula do que while(1)e break;
Michael Michael

38

O operador de vírgula avaliará o operando esquerdo, descartará o resultado e, em seguida, avaliará o operando direito e esse será o resultado. O uso idiomático, conforme observado no link, é ao inicializar as variáveis ​​usadas em um forloop e fornece o seguinte exemplo:

void rev(char *s, size_t len)
{
  char *first;
  for ( first = s, s += len - 1; s >= first; --s)
      /*^^^^^^^^^^^^^^^^^^^^^^^*/ 
      putchar(*s);
}

Caso contrário, não existem muitos usos excelentes do operador de vírgula , embora seja fácil abusar para gerar código difícil de ler e manter.

No rascunho da norma C99, a gramática é a seguinte:

expression:
  assignment-expression
  expression , assignment-expression

e o parágrafo 2 diz:

O operando esquerdo de um operador de vírgula é avaliado como uma expressão nula; existe um ponto de sequência após sua avaliação. Então o operando certo é avaliado; o resultado tem seu tipo e valor. 97) Se for feita uma tentativa de modificar o resultado de um operador de vírgula ou de acessá-lo após o próximo ponto de sequência, o comportamento será indefinido.

A nota de rodapé 97 diz:

Um operador de vírgula não gera um valor l .

o que significa que você não pode atribuir ao resultado do operador de vírgula .

É importante observar que o operador de vírgula tem a menor precedência e, portanto, há casos em que o uso ()pode fazer uma grande diferença, por exemplo:

#include <stdio.h>

int main()
{
    int x, y ;

    x = 1, 2 ;
    y = (3,4) ;

    printf( "%d %d\n", x, y ) ;
}

terá a seguinte saída:

1 4

28

O operador de vírgula combina as duas expressões de ambos os lados em uma, avaliando-as na ordem da esquerda para a direita. O valor do lado direito é retornado como o valor de toda a expressão. (expr1, expr2)é como, { expr1; expr2; }mas você pode usar o resultado de expr2uma chamada ou atribuição de função.

É frequentemente visto em forloops para inicializar ou manter várias variáveis ​​como esta:

for (low = 0, high = MAXSIZE; low < high; low = newlow, high = newhigh)
{
    /* do something with low and high and put new values
       in newlow and newhigh */
}

Além disso, eu só o usei "com raiva" em outro caso, ao encerrar duas operações que devem sempre ir juntas em uma macro. Tínhamos código que copiava vários valores binários em um buffer de bytes para enviar em uma rede e mantinha um ponteiro onde tínhamos chegado:

unsigned char outbuff[BUFFSIZE];
unsigned char *ptr = outbuff;

*ptr++ = first_byte_value;
*ptr++ = second_byte_value;

send_buff(outbuff, (int)(ptr - outbuff));

Onde os valores eram shorts ou ints, fizemos o seguinte:

*((short *)ptr)++ = short_value;
*((int *)ptr)++ = int_value;

Mais tarde, lemos que esse C não era realmente válido, porque (short *)ptrnão é mais um valor l e não pode ser incrementado, embora nosso compilador na época não se importasse. Para corrigir isso, dividimos a expressão em duas:

*(short *)ptr = short_value;
ptr += sizeof(short);

No entanto, essa abordagem contou com todos os desenvolvedores lembrando de colocar as duas declarações o tempo todo. Queríamos uma função na qual você pudesse passar o ponteiro de saída, o valor ee o tipo do valor. Sendo C, não C ++ com modelos, não poderíamos ter uma função de um tipo arbitrário, por isso decidimos por uma macro:

#define ASSIGN_INCR(p, val, type)  ((*((type) *)(p) = (val)), (p) += sizeof(type))

Ao usar o operador vírgula, pudemos usá-lo em expressões ou como instruções, conforme desejávamos:

if (need_to_output_short)
    ASSIGN_INCR(ptr, short_value, short);

latest_pos = ASSIGN_INCR(ptr, int_value, int);

send_buff(outbuff, (int)(ASSIGN_INCR(ptr, last_value, int) - outbuff));

Não estou sugerindo que nenhum desses exemplos seja de bom estilo! Na verdade, eu me lembro do Code Complete de Steve McConnell desaconselhando o uso de operadores de vírgula em um forloop: para facilitar a leitura e a manutenção, o loop deve ser controlado por apenas uma variável e as expressões na forprópria linha devem conter apenas código de controle de loop, não outros bits extras de inicialização ou manutenção de loop.


Obrigado! Foi minha primeira resposta no StackOverflow: desde então, talvez eu tenha aprendido que concisão deve ser valorizada :-).
2113 Paul Stephenson

Às vezes, valorizo ​​um pouco de verbosidade, como é o caso aqui, onde você descreve a evolução de uma solução (como chegou lá).
green diod

8

Isso causa a avaliação de várias instruções, mas usa apenas a última como um valor resultante (rvalue, acho).

Assim...

int f() { return 7; }
int g() { return 8; }

int x = (printf("assigning x"), f(), g() );

deve resultar em x sendo definido como 8.


3

Como as respostas anteriores afirmaram, avalia todas as declarações, mas usa a última como o valor da expressão. Pessoalmente, só achei útil em expressões de loop:

for (tmp=0, i = MAX; i > 0; i--)

2

O único lugar que eu vi ser útil é quando você escreve um loop funky em que deseja fazer várias coisas em uma das expressões (provavelmente a expressão init ou a expressão loop. Algo como:

bool arraysAreMirrored(int a1[], int a2[], size_t size)
{
  size_t i1, i2;
  for(i1 = 0, i2 = size - 1; i1 < size; i1++, i2--)
  {
    if(a1[i1] != a2[i2])
    {
      return false;
    }
  }

  return true;
}

Perdoe-me se houver algum erro de sintaxe ou se misturei algo que não seja estrito C. Não estou argumentando que o operador é uma boa forma, mas é para isso que você pode usá-lo. No caso acima, eu provavelmente usaria um whileloop para que as múltiplas expressões em init e loop fossem mais óbvias. (E eu inicializaria i1 e i2 inline em vez de declarar e depois inicializar ... blá blá blá.)


Eu presumo que você quer dizer i1 = 0, i2 = tamanho -1
frankster

-2

Estou revivendo isso simplesmente para responder a perguntas de @Rajesh e @JeffMercado, que eu acho muito importantes, pois esse é um dos principais hits do mecanismo de pesquisa.

Pegue o seguinte trecho de código, por exemplo

int i = (5,4,3,2,1);
int j;
j = 5,4,3,2,1;
printf("%d %d\n", i , j);

Irá imprimir

1 5

O icaso é tratado como explicado pela maioria das respostas. Todas as expressões são avaliadas na ordem da esquerda para a direita, mas apenas a última é atribuída i. O resultado da ( expressão ) is1`.

O jcaso segue regras de precedência diferentes, pois ,possui a menor precedência do operador. Devido a essas regras, o compilador vê atribuição-expressão, constante, constante ... . As expressões são novamente avaliadas na ordem da esquerda para a direita e seus efeitos colaterais permanecem visíveis, portanto, jé o 5resultado dej = 5 .

De maneira interessante, int j = 5,4,3,2,1;não é permitido pelas especificações de idioma. Um inicializador espera uma expressão de atribuição para que um ,operador direto não seja permitido.

Espero que isto ajude.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.