Pergunta

Esta questão já tem uma resposta aqui:

Você vê-lo usado em para instruções de loop, mas é legal sintaxe em qualquer lugar. Que usos que você encontrou para ele em outro lugar, se houver?

Foi útil?

Solução

linguagem C (bem como C ++) é historicamente uma mistura de dois estilos de programação completamente diferente, que se pode referir como "programação declaração" e "programação expressão". Como você sabe, cada linguagem de programação procedural normalmente suporta tais construções fundamentais como sequenciamento e ramificação (veja Estruturado Programação). Estas construções fundamentais estão presentes em C / C ++ línguas em duas formas: uma para programação declaração, outro para a programação de expressão

.

Por exemplo, quando você escreve o seu programa em termos de declarações, você pode usar uma seqüência de declarações separadas por ;. Quando você quiser fazer alguma ramificação, você usa declarações if. Você também pode usar ciclos e outros tipos de declarações de transferência de controle.

Na expressão programar as mesmas construções estão disponíveis para você também. Esta é realmente onde o operador , entra em jogo. , operador em nada mais do que um separador de expressões sequenciais em C, ou seja, , operador na programação expressão serve o mesmo papel que ; faz na programação comunicado. Ramificação em programação expressão é feito através de operador ?: e, alternativamente, através de propriedades de avaliação de curto-circuito de operadores && e ||. (Programação Expression não tem ciclos embora. E substituí-los por recursão você teria que aplicar uma programação comunicado.)

Por exemplo, o seguinte código

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

que é um exemplo de programação declaração tradicional, pode ser re-escrita em termos de programação expressão como

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

É desnecessário dizer que, na declaração de prática de programação geralmente produz código muito mais legível C / C ++, então normalmente usamos programação expressão em quantidades muito bem medidos e restritos. Mas, em muitos casos, se torna útil. E a linha entre o que é aceitável eo que não é, é em grande parte uma questão de preferência pessoal e a capacidade de reconhecer e ler idiomas estabelecidos.

Como uma nota adicional: a própria concepção da linguagem é, obviamente, adaptado para declarações. As declarações podem livremente invocar expressões, mas expressões não pode invocar declarações (além de chamar funções pré-definidas). Esta situação é alterada de uma forma bastante interessante no compilador GCC, que suporta os chamados " expressões declaração " como uma extensão (simétrica à 'declarações de expressão' em C standard). "Expressões afirmação" permite que o usuário diretamente inserir código baseado em declaração em expressões, assim como eles podem inserir código baseada em expressão em declarações em C padrão.

Como outra nota adicional: em programação baseada em functor linguagem C ++ 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 em design de C ++, pode ser considerado preferível a programação declaração tradicional em muitas situações.

Outras dicas

Eu acho que geralmente vírgula de C não é um estilo bom de usar, simplesmente porque é assim muito, muito fácil perder - por alguém que tenta ler / entender / corrigir o seu código, ou você mesmo um mês para baixo da linha. Fora de declarações de variáveis ??e loops, é claro, onde é idiomático.

Você pode usá-lo, por exemplo, para embalar 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 já vi isso usado dessa forma em código real, mas não tem acesso a ele para mostrar infelizmente)

Eu vi que seja usado em macros onde a macro está fingindo ser uma função e quer retornar um valor, mas precisa de fazer algum outro trabalho em primeiro lugar. É sempre feio e muitas vezes se parece com um corte perigoso embora.

Exemplo simplificado:

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

Aqui B=SomeMacro(A) "retorna" o resultado de Permuta (A) e atribui a "B".

operador de dois vírgula assassino apresenta em C ++:

a) leitura de fluxo até seqüência específica é encontrado (ajuda a manter o DRY código):

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

b) Escrever código complexo em initializers construtor:

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

Eu tive que usar uma vírgula para fechaduras de depuração mutex para colocar uma mensagem antes O bloqueio começa a esperar.

Eu não podia, mas a mensagem de log no corpo do construtor bloqueio derivada, então eu tive que colocá-lo nos argumentos do construtor da classe base usando: baseclass ((log ( "mensagem"), actual_arg)) no lista de inicialização. Observe o parêntese extra.

Aqui está um extrato das classes:

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 impulso Assignment é um bom exemplo de sobrecarregar o operador vírgula em uma forma útil, legível. Por exemplo:

using namespace boost::assign;

vector<int> v; 
v += 1,2,3,4,5,6,7,8,9;

A partir do padrão C:

O operando esquerdo de um operador vírgula é avaliada como uma expressão vazia; há um ponto sequência após a sua avaliação. Então o operando direito é avaliado; o resultado tem o seu tipo e valor. (Um operador vírgula não produzir uma Ivalue.)) Se for feita uma tentativa para modificar o resultado de um operador de vírgula ou para aceder a ele depois de o ponto seguinte sequência, o comportamento é indefinido.

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

Note que:

int a, b, c;

não é o operador vírgula, é uma lista de declarators.

Às vezes é usado em macros, como macros de depuração como este:

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

(mas olhar para esta falha horrível , por sinceramente, para o que pode acontecer quando você exagerar.)

Mas a menos que você realmente precisa dele, ou que tenha certeza que torna o código mais legível e de fácil manutenção, eu recomendaria contra usando o operador vírgula.

Você pode sobrecarregá-lo (desde que esta questão tem um "C ++" tag). Eu vi alguns códigos, onde vírgula sobrecarregado foi usada para matrizes geradoras. Ou vetores, eu 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 lá é tem pode ter um aroma de cheiro de código, o único lugar que eu vi como um bom uso para o operador vírgula é como parte de uma exclusão:

 delete p, p = 0;

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

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

Dada citação do @Nicolas Goy do padrão, então parece que você poderia escrever um forro para 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, o homem, você realmente quer fazer o seu código C mais obscurecer desta forma?

Geralmente, evite I usando o operador de vírgula, porque ele só torna o código menos legível. Em quase todos os casos, seria mais simples e clara para fazer apenas duas declarações. Como:

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

oferece acesso sem clara sobre: ??

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

O único lugar, além de alças em que eu usei-lo em if / construções mais, 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 função antes do IF, mas se a função leva muito tempo para ser executado, você pode querer evitar fazê-lo se não é necessário, e se a função não deve ser feito a menos que um! = 1, então isso não é uma opção. A alternativa é a de ninho a IF é uma camada extra. Isso é realmente o que eu costumo fazer, porque o código acima é um pouco enigmática. Mas eu fiz isso da maneira vírgula agora e, em seguida, por causa de nidificação também é enigmática.

É 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 assert irá imprimir todo o texto da sua tese, isso acrescenta um pouco mais de informações úteis para a afirmação.

muitas vezes eu usá-lo para executar uma estática initializer função em alguns arquivos CPP, para evitar problemas initalization preguiçoso 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, a um caso realmente útil com vírgulas em C é usá-los para realizar algo condicionalmente.

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

isto é equivalente a

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

Não se trata de "digitação menos", é só parece muito claro, às vezes.

Além disso loops são exatamente assim:

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

É claro que ambos só pode ser útil quando a parte condicional ou parte executável do circuito é bastante pequeno, dois e três operações.

A única vez que eu já vi o operador , usada fora de um loop for foi realizar um assingment em um comunicado ternário. Foi há muito tempo, então eu não posso lembrar-se é a declaração exata, mas era algo como:

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

Obviamente nenhuma pessoa sã iria escrever código como este, mas o autor era um gênio do mal que constroem c declarações com base no código assembler eles gerados, não legibilidade. Por exemplo, ele por vezes utilizado voltas em vez de se declarações porque ele preferiu o assembler que gerou.

Seu código foi muito rápido, mas insustentável, eu estou feliz que eu não tenho que trabalhar com ele mais.

Eu usei-o para uma macro para "atribuir um valor de qualquer tipo para um buffer de saída apontada por um char *, e, em seguida, incrementa o ponteiro pelo número necessário de bytes", como este:

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

Usando o operador vírgula significa que a macro pode ser usado em expressões ou como declarações como 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));

Ela reduziu a digitação repetitiva, mas você tem que ter cuidado que ele não fique muito ilegível.

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

Pode ser útil para "golf code":

Código Golf: Jogando cubos

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

qemu tem um código que usa o operador vírgula dentro da porção condicional de um loop (ver QTAILQ_FOREACH_SAFE em qemu-Queue.h). O que eles fizeram resume-se ao seguinte:

#include <stdio.h>

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 o seguinte resultado:

0, 1
1, 2
2, 3

3, 3

0, 1
1, 2
2, 3

3, 4

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

  • Evita fazer dois trabalhos, de modo que as chances do código ficando fora de sincronia é reduzida
  • Uma vez que ele usa &&, a atribuição não é avaliada após a última iteração
  • Uma vez que a atribuição não é avaliado, ele não vai tentar de referência o elemento seguinte na fila quando está no final (no código qemu de, não o código acima).
  • Dentro do loop, você tem acesso ao elemento atual e próximo

Encontrou em inicialização de matriz:

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

Quando eu inicializar um a[][] array:

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

e, em seguida, exibir os elementos de matriz.

eu recebo:

11 89 0 0 0 
0 0 0 0 0
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top