Usos do operador de vírgulas C

Você vê isso usado em instruções loop, mas é syntax legal em qualquer lugar. Quais usos você achou para ele em outro lugar, se houver?

    A linguagem C (assim como C ++) é historicamente uma mistura de dois estilos de programação completamente diferentes, aos quais podemos nos referir como “programação de declarações” e “programação de expressões”. Como você sabe, toda linguagem de programação procedural normalmente suporta construções fundamentais como sequenciamento e ramificação . Essas construções fundamentais estão presentes nas linguagens C / C ++ em duas formas: uma para programação de instruções, outra para programação de expressão.

    Por exemplo, quando você escreve seu programa em termos de instruções, você pode usar uma sequência de instruções separadas por ; . Quando você quer fazer algumas ramificações, você usa instruções if . Você também pode usar ciclos e outros tipos de instruções de transferência de controle.

    Na programação de expressões, as mesmas construções também estão disponíveis para você. Este é realmente o lugar onde o operador entra em jogo. Operador , em nada mais do que um separador de expressões sequenciais em C, isto é , operador , na programação de expressão serve o mesmo papel que ; faz na programação de instruções. Ramificação na programação de expressão é feita através do operador ?: E, alternativamente, através de propriedades de avaliação de curto-circuito de && e || operadores. (A programação de expressão não tem ciclos e, para substituí-los pela recursion, você deve aplicar a programação de instruções.)

    Por exemplo, o código a seguir

     a = rand(); ++a; b = rand(); c = a + b / 2; if (a < c - 5) d = a; else d = b; 

    que é um exemplo de programação tradicional de declarações, pode ser reescrito em termos de programação de expressão

     a = rand(), ++a, b = rand(), c = a + b / 2, a < c - 5 ? d = a : d = b; 

    ou como

     a = rand(), ++a, b = rand(), c = a + b / 2, d = a < c - 5 ? a : b; 

    ou

     d = (a = rand(), ++a, b = rand(), c = a + b / 2, a < c - 5 ? a : b); 

    ou

     a = rand(), ++a, b = rand(), c = a + b / 2, (a < c - 5 && (d = a, 1)) || (d = b); 

    Escusado será dizer que, na prática, a programação de declarações geralmente produz código C / C ++ muito mais legível, por isso normalmente usamos programação de expressão em quantidades muito bem medidas e restritas. Mas em muitos casos, é útil. E a linha entre o que é aceitável e o que não é é, em grande parte, uma questão de preferência pessoal e a capacidade de reconhecer e ler idiomas estabelecidos.

    Como nota adicional: o próprio design da linguagem é, obviamente, adaptado às declarações. As declarações podem invocar livremente expressões, mas as expressões não podem invocar instruções (além de chamar funções predefinidas). Esta situação é alterada de uma maneira bastante interessante no compilador GCC, que suporta as chamadas "expressões de instrução" como uma extensão (simétrica a "declarações de expressão" no padrão C). "Expressões de instrução" permitem que o usuário insira diretamente o código baseado em instruções em expressões, assim como elas podem inserir código baseado em expressão em instruções no padrão C.

    Como outra nota adicional: em linguagem C + +, a programação baseada em functor desempenha um papel importante, que pode ser visto como uma outra forma de "programação de expressão". De acordo com as tendências atuais do projeto C ++, ele pode ser considerado preferível em relação à programação tradicional de declarações em muitas situações.

    Eu acho que geralmente a vírgula de C não é um bom estilo para usar simplesmente porque é muito fácil de perder – seja por alguém tentando ler / entender / consertar seu código, ou você mesmo um mês depois. Fora das declarações de variables ​​e dos loops, é claro, onde é idiomático.

    Você pode usá-lo, por exemplo, para armazenar várias instruções em um operador ternário (? :), ala:

     int x = some_bool ? printf("WTF"), 5 : fprintf(stderr, "No, really, WTF"), 117; 

    mas meus deuses, por que ?? (Eu vi isso usado dessa maneira em código real, mas não tenho access a ele para mostrar, infelizmente)

    Eu vi isso usado em macros onde a macro está fingindo ser uma function e quer retornar um valor, mas precisa fazer algum outro trabalho primeiro. É sempre feio e muitas vezes parece um hack perigoso.

    Exemplo simplificado:

     #define SomeMacro(A) ( DoWork(A), Permute(A) ) 

    Aqui B=SomeMacro(A) “retorna” o resultado de Permute (A) e atribui a “B”.

    Dois resources de operador de vírgula assassino em C ++:

    a) Ler do stream até encontrar uma string específica (ajuda a manter o código DRY):

      while (cin >> str, str != "STOP") { //process str } 

    b) Escreva código complexo em inicializadores de construtor:

     class X : public A { X() : A( (global_function(), global_result) ) {}; }; 

    Eu tive que usar uma vírgula para depurar bloqueios mutex para colocar uma mensagem antes que o bloqueio comece a esperar.

    Eu não podia, mas a mensagem de log no corpo do construtor de bloqueio derivado, então eu tive que colocá-lo nos argumentos do construtor de class base usando: baseclass ((log (“message”), actual_arg)) na lista de boot. Observe o parêntese extra.

    Aqui está um extrato das classs:

     class NamedMutex : public boost::timed_mutex { public: ... private: std::string name_ ; }; void log( NamedMutex & ref__ , std::string const& name__ ) { LOG( name__ < < " waits for " << ref__.name_ ); } class NamedUniqueLock : public boost::unique_lock< NamedMutex > { public: NamedUniqueLock::NamedUniqueLock( NamedMutex & ref__ , std::string const& name__ , size_t const& nbmilliseconds ) : boost::unique_lock< NamedMutex >( ( log( ref__ , name__ ) , ref__ ) , boost::get_system_time() + boost::posix_time::milliseconds( nbmilliseconds ) ), ref_( ref__ ), name_( name__ ) { } .... }; 

    A biblioteca de atribuição de impulso é um bom exemplo de sobrecarregar o operador de vírgula de uma maneira útil e legível. Por exemplo:

     using namespace boost::assign; vector v; v += 1,2,3,4,5,6,7,8,9; 

    Do padrão C:

    O operando da esquerda de um operador de vírgula é avaliado como uma expressão vazia; há um ponto de sequência após sua avaliação. Então o operando da direita é avaliado; o resultado tem seu tipo e valor. (Um operador de vírgula não produz um lvalue.)) 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.

    Em suma, permite especificar mais de uma expressão em que C espera apenas uma. Mas na prática é usado principalmente em loops.

    Observe que:

     int a, b, c; 

    NÃO é o operador vírgula, é uma lista de declaradores.

    Às vezes é usado em macros, como macros de debugging como esta:

     #define malloc(size) (printf("malloc(%d)\n", (int)(size)), malloc((size))) 

    (Mas olhe para esse horrível fracasso , sinceramente, para o que pode acontecer quando você exagera.)

    Mas, a menos que você realmente precise disso, ou tenha certeza de que isso torna o código mais legível e fácil de manter, eu recomendaria não usar o operador vírgula.

    Você pode sobrecarregá-lo (contanto que essa questão tenha uma tag “C ++”). Eu vi algum código, onde vírgula sobrecarregada foi usada para gerar matrizes. Ou vetores, não me lembro exatamente. Não é bonito (embora um pouco confuso):

    MyVector foo = 2, 3, 4, 5, 6;

    Fora de um loop for, e mesmo assim há pode ter um aroma de cheiro de código, o único lugar que eu vi como um bom uso para o operador de vírgula é como parte de uma exclusão:

      delete p, p = 0; 

    O único valor sobre a alternativa é que você pode copiar / colar acidentalmente apenas metade desta operação se ela estiver em duas linhas.

    Eu também gosto porque se você fizer isso por hábito, você nunca esquecerá a atribuição zero. (Claro, por que p não está dentro de algum tipo de auto_ptr, smart_ptr, shared_ptr, etc wrapper é uma questão diferente.)

    Dada a citação de @Nicolas Goy do padrão, então parece que você poderia escrever uma linha for loops como:

     int a, b, c; for(a = 0, b = 10; c += 2*a+b, a < = b; a++, b--); printf("%d", c); 

    Mas bom Deus, cara, você realmente quer tornar seu código C mais obscuro dessa maneira?

    É muito útil em adicionar alguns comentários em macros ASSERT :

     ASSERT(("This value must be true.", x)); 

    Uma vez que a maioria das macros de estilo asseveram o texto inteiro de seu argumento, isso adiciona um pouco mais de informação útil na asserção.

    Em geral, evito usar o operador de vírgula porque ele apenas torna o código menos legível. Em quase todos os casos, seria mais simples e claro fazer apenas duas declarações. Gostar:

     foo=bar*2, plugh=hoo+7; 

    não oferece vantagem clara sobre:

     foo=bar*2; plugh=hoo+7; 

    O único lugar além de loops onde eu usei isto em if / else constrói, como:

     if (a==1) ... do something ... else if (function_with_side_effects_including_setting_b(), b==2) ... do something that relies on the side effects ... 

    Você poderia colocar a function antes do IF, mas se a function demorar muito para ser executada, você pode querer evitar fazê-la se não for necessário, e se a function não deve ser feita a menos que! = 1, então isso não é uma opção. A alternativa é aninhar os IF’s em uma camada extra. Isso é realmente o que eu costumo fazer porque o código acima é um pouco enigmático. Mas eu fiz o caminho da vírgula agora e depois porque o aninhamento também é enigmático.

    Costumo usá-lo para executar uma function de boot estática em alguns arquivos cpp, para evitar problemas de boot preguiçosa com singletons clássicos:

     void* s_static_pointer = 0; void init() { configureLib(); s_static_pointer = calculateFancyStuff(x,y,z); regptr(s_static_pointer); } bool s_init = init(), true; // just run init() before anything else Foo::Foo() { s_static_pointer->doStuff(); // works properly } 

    Para mim, o caso realmente útil com vírgulas em C é usá-las para realizar algo condicionalmente.

      if (something) dothis(), dothat(), x++; 

    isso é equivalente a

      if (something) { dothis(); dothat(); x++; } 

    Isso não é sobre “digitar menos”, é apenas parece muito claro às vezes.

    Também loops são assim:

     while(true) x++, y += 5; 

    É claro que ambos só podem ser úteis quando a parte condicional ou parte executável do loop for muito pequena, duas ou três operações.

    A única vez que vi o operador , usado fora de um loop for foi executar uma avaliação em uma instrução ternária. Foi há muito tempo atrás, então não consigo lembrar a declaração exata, mas era algo como:

     int ans = isRunning() ? total += 10, newAnswer(total) : 0; 

    Obviamente, nenhuma pessoa sensata escreveria código como esse, mas o autor era um gênio do mal que construía declarações c baseadas no código assembler gerado, não na legibilidade. Por exemplo, ele às vezes usava loops ao invés de if porque ele preferia o assembler gerado.

    Seu código era muito rápido, mas insustentável, estou feliz por não ter mais que trabalhar com ele.

    Eu usei para uma macro para “atribuir um valor de qualquer tipo a um buffer de saída apontado por um char * e, em seguida, incrementar o ponteiro pelo número necessário de bytes”, assim:

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

    Usar o operador vírgula significa que a macro pode ser usada em expressões ou como instruções conforme desejado:

     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)); 

    Isso reduziu algumas digitação repetitiva, mas você precisa ter cuidado para não ficar muito ilegível.

    Por favor, veja minha versão excessivamente longa desta resposta aqui .

    Pode ser útil para “code golf”:

    Code Golf: Jogando Cubes

    O , em if(i>0)t=i,i=0; salva dois caracteres.

    O qemu possui algum código que usa o operador vírgula na parte condicional de um loop for (consulte QTAILQ_FOREACH_SAFE no qemu-queue.h). O que eles fizeram se resume ao seguinte:

     #include  int main( int argc, char* argv[] ){ int x = 0, y = 0; for( x = 0; x < 3 && (y = x+1,1); x = y ){ printf( "%d, %d\n", x, y ); } printf( "\n%d, %d\n\n", x, y ); for( x = 0, y = x+1; x < 3; x = y, y = x+1 ){ printf( "%d, %d\n", x, y ); } printf( "\n%d, %d\n", x, y ); return 0; } 

    ... com a seguinte saída:

     0, 1 1, 2 2, 3 3, 3 0, 1 1, 2 2, 3 3, 4 

    A primeira versão deste loop tem os seguintes efeitos:

    • Evita fazer duas tarefas, por isso as chances de o código ficar fora de sincronia são reduzidas
    • Como ele usa && , a atribuição não é avaliada após a última iteração
    • Como a atribuição não é avaliada, ela não tentará desreferenciar o próximo elemento na fila quando estiver no final (no código do qemu, não no código acima).
    • Dentro do loop, você tem access ao elemento atual e ao próximo

    Encontrei na boot da matriz:

    Em C, o que exatamente acontece se eu usar () para inicializar uma matriz de dupla dimensão em vez da {}?

    Quando inicializo uma matriz a[][] :

     int a[2][5]={(8,9,7,67,11),(7,8,9,199,89)}; 

    e, em seguida, exibir os elementos da matriz.

    Eu recebo:

     11 89 0 0 0 0 0 0 0 0