Utilizações de C operador vírgula [duplicado]
-
06-07-2019 - |
Pergunta
Esta questão já tem uma resposta aqui:
- O que o operador vírgula, para fazer? 9 respostas
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?
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":
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:
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