Operadores são os verbos de C: eles pegam valores e produzem novos. Existem cerca de 40 deles, mas uma dúzia cobre a maior parte do código, e dois desses têm armadilhas afiadas o bastante para merecerem suas próprias seções.
Operadores aritméticos
(%% na string de formato imprime um sinal de porcentagem literal - % sozinho começaria um especificador de formato.)
Os cinco operadores aritméticos binários são + - * / %, mais o - unário para negação e o +, que essencialmente não faz nada.
A armadilha da divisão inteira
a / b com dois inteiros faz divisão inteira e joga fora o resto. Não há arredondamento - o resultado é truncado em direção a zero.
Esse último caso é o bug que sobrevive até a produção. Atribuir a um double não ajuda: a divisão total / count já está completa, e já é um int, antes de a atribuição acontecer. A correção é tornar um dos operandos de ponto flutuante dentro da expressão, normalmente com uma conversão de tipo.
Duas regras relacionadas: dividir por zero com inteiros é comportamento indefinido e tipicamente derruba o programa, e INT_MIN / -1 estoura.
O operador de módulo
% devolve o resto e funciona só com inteiros:
Testar x % 2 == 0 para saber se é par e usar % n para dar a volta em um índice de array são os dois usos do dia a dia.
Com números negativos, o % de C segue o sinal do dividendo:
Então x % 2 == 1 é um teste quebrado para números ímpares quando x pode ser negativo - -7 % 2 é -1. Use x % 2 != 0 no lugar.
Para restos de ponto flutuante, % é erro de compilação; use fmod() da math.h.
Atribuição e atribuição composta
= guarda um valor. As formas compostas combinam uma operação com o armazenamento:
x += 5 significa x = x + 5, mas avalia x uma única vez - o que importa quando o alvo é algo como arr[compute_index()].
A atribuição é ela mesma uma expressão que devolve o valor atribuído, e é por isso que a = b = c = 0 funciona (ela atribui da direita para a esquerda) e por que o erro de = no lugar de == compila silenciosamente.
Incremento e decremento
++ soma um, -- subtrai um. Cada um vem em uma forma prefixa e uma pós-fixa:
Como instrução isolada os dois são intercambiáveis, e i++ é a escolha convencional no cabeçalho de um for por nenhuma razão mais profunda do que a tradição.
O que você não pode fazer é usar uma variável mais de uma vez em uma expressão na qual ela também é modificada:
int i = 5;
int x = i++ + i++; /* comportamento indefinido */
arr[i] = i++; /* comportamento indefinido */
printf("%d %d", i++, i);/* comportamento indefinido */
C não define a ordem em que essas subexpressões são avaliadas, então o resultado não é meramente não especificado - o comportamento do programa inteiro é indefinido. Separe a instrução em duas e a ambiguidade desaparece.
Operadores de comparação
São seis, e todos produzem um int: 1 para verdadeiro e 0 para falso.
O resultado ser um int comum em vez de um tipo booleano dedicado é uma característica definidora de C - veja booleanos em C para o que decorre disso, incluindo o bug de = contra ==.
Dois erros de comparação são específicos de C. Encadear não funciona do jeito que a matemática sugere: if (1 < x < 10) é sempre verdadeiro, porque 1 < x avalia para 0 ou 1 e então isso é comparado com 10. Escreva if (x > 1 && x < 10). E comparar strings com == compara ponteiros, não texto - use strcmp da string.h.
Operadores lógicos
&& é E, || é OU, ! é NÃO. Os três tratam qualquer valor diferente de zero como verdadeiro.
Eles fazem curto-circuito: && para assim que um operando é falso, e || para assim que um é verdadeiro. O resto nunca é avaliado, o que não é só uma otimização - é uma garantia em que você pode confiar por segurança:
Troque a ordem desses dois testes e a chamada com NULL desreferencia um ponteiro nulo e derruba o programa. Proteja primeiro, use depois - a ordem em uma cadeia de && faz parte da lógica.
O operador condicional
O único operador de três operandos de C escolhe entre dois valores:
condicao ? valor_se_verdadeiro : valor_se_falso. É uma expressão, então cabe onde uma instrução if não cabe - dentro de um argumento de printf, em um inicializador. Mantenha-o curto; condicionais aninhados se tornam ilegíveis rapidamente.
Operadores bit a bit, brevemente
Estes trabalham sobre os bits individuais de um inteiro:
Eles são usados para flags, máscaras e registradores de hardware. Dois cuidados: & e | não são && e || (eles não fazem curto-circuito e operam bit a bit), e deslocar um valor com sinal ou deslocar por mais do que a largura do tipo é indefinido. Use tipos sem sinal para trabalho com bits.
Precedência
Quando uma expressão não tem parênteses, esta tabela decide o que se associa a quê. Maior precedência primeiro:
| Nível | Operadores | Associatividade |
|---|---|---|
| 1 | () [] -> . x++ x-- | do início ao fim |
| 2 | ! ~ ++x --x +x -x *p &x sizeof (tipo) | do fim ao início |
| 3 | * / % | do início ao fim |
| 4 | + - | do início ao fim |
| 5 | << >> | do início ao fim |
| 6 | < <= > >= | do início ao fim |
| 7 | == != | do início ao fim |
| 8 | & | do início ao fim |
| 9 | ^ | do início ao fim |
| 10 | OU bit a bit | do início ao fim |
| 11 | && | do início ao fim |
| 12 | OU lógico | do início ao fim |
| 13 | ?: | do fim ao início |
| 14 | = += -= *= /= %= e companhia | do fim ao início |
| 15 | , | do início ao fim |
As consequências práticas:
a + b * c /* significa a + (b * c) */
a < b == c < d /* significa (a < b) == (c < d) - raramente o que voce queria */
x & 1 == 0 /* significa x & (1 == 0), ou seja x & 0 - um bug classico */
*p++ /* significa *(p++) - desreferencia p, depois avanca */
Essa terceira linha vale a pena memorizar: == se associa mais forte que &, então testes de máscara de bits precisam de parênteses - (x & 1) == 0.
Ninguém lembra dos quinze níveis, e ninguém precisa. Aprenda que * ganha de +, que comparações ganham de && que ganha de ||, e que todo o resto leva parênteses:
if ((flags & MASK) != 0 && (count > 0 || force)) { ... }
Essa linha tem parênteses redundantes e é melhor por causa deles.
Perguntas frequentes
O que o % faz em C?
% é o operador de módulo: ele devolve o resto da divisão inteira. 17 % 5 é 2. Ele funciona só com inteiros - usá-lo em um float ou double é erro de compilação, e fmod() da math.h é o equivalente para ponto flutuante.
Por que 5 / 2 dá 2 em C?
Porque os dois operandos são inteiros, então C faz divisão inteira e descarta a parte fracionária. Torne um dos lados um valor de ponto flutuante para obter 2.5: 5 / 2.0, ou converta um operando com (double)a / b.
Qual é a diferença entre i++ e ++i?
Os dois somam um a i. ++i (prefixo) incrementa primeiro e devolve o novo valor; i++ (pós-fixo) devolve o valor antigo e incrementa depois. Como instrução isolada eles são idênticos - a diferença só importa quando o resultado é usado, como em int b = a++; contra int b = ++a;.
O que é precedência de operadores em C?
É a ordem em que os operadores se associam quando uma expressão não tem parênteses. *, / e % se associam mais forte que + e -, que se associam mais forte que as comparações, que se associam mais forte que &&, depois ||, depois a atribuição. Na dúvida, acrescente parênteses - eles não custam nada e eliminam a pergunta.