Operadores: aritméticos, lógicos, bit a bit

1. Introdução aos Operadores em C

Operadores são símbolos que instruem o compilador a realizar operações matemáticas, lógicas ou de manipulação de bits sobre operandos. Em C, eles são classificados em três categorias principais quanto ao número de operandos: unários (um operando), binários (dois operandos) e ternários (três operandos). A precedência determina a ordem de avaliação em expressões com múltiplos operadores, enquanto a associatividade define a direção (esquerda para direita ou direita para esquerda) quando operadores de mesma precedência aparecem. Dominar esses conceitos é fundamental para escrever expressões corretas e previsíveis.

2. Operadores Aritméticos

Os operadores aritméticos básicos em C são + (adição), - (subtração), * (multiplicação), / (divisão) e % (módulo). A divisão merece atenção especial: quando ambos os operandos são inteiros, o resultado é truncado (parte fracionária descartada). Para obter resultado de ponto flutuante, ao menos um operando deve ser float ou double.

#include <stdio.h>

int main() {
    int a = 10, b = 3;
    float x = 10.0, y = 3.0;

    printf("Divisao inteira: %d / %d = %d\n", a, b, a / b);   // 3
    printf("Divisao real: %.1f / %.1f = %.2f\n", x, y, x / y); // 3.33
    printf("Modulo: %d %% %d = %d\n", a, b, a % b);            // 1

    return 0;
}

Os operadores de incremento (++) e decremento (--) são unários e existem nas formas pré-fixada e pós-fixada. Na forma pré-fixada (++x), o valor é incrementado antes de ser usado na expressão; na pós-fixada (x++), o valor original é usado primeiro.

#include <stdio.h>

int main() {
    int x = 5;
    printf("Pre-fixado: %d\n", ++x); // 6 (incrementa, depois imprime)
    printf("Pos-fixado: %d\n", x++); // 6 (imprime, depois incrementa)
    printf("Apos tudo: %d\n", x);    // 7
    return 0;
}

3. Operadores Relacionais e Lógicos

Os operadores relacionais comparam valores e retornam 1 (verdadeiro) ou 0 (falso): == (igual), != (diferente), <, >, <=, >=. Já os operadores lógicos combinam expressões booleanas: && (E lógico), || (OU lógico) e ! (NÃO lógico).

Uma característica crucial é o curto-circuito: em expr1 && expr2, se expr1 for falsa, expr2 não é avaliada. Em expr1 || expr2, se expr1 for verdadeira, expr2 é ignorada. Isso pode evitar erros ou causar efeitos colaterais inesperados.

#include <stdio.h>

int main() {
    int idade = 17;
    int tem_autorizacao = 1;

    // Curto-circuito: se idade >= 18 for falso, tem_autorizacao nem é verificado
    if (idade >= 18 && tem_autorizacao) {
        printf("Pode entrar\n");
    } else {
        printf("Entrada negada\n");
    }

    // Exemplo de efeito colateral com curto-circuito
    int a = 0, b = 5;
    if (a != 0 && b / a > 2) {  // a != 0 é falso, então b/a nunca é executado
        printf("Nao chega aqui\n");
    }
    return 0;
}

4. Operadores de Atribuição e Atribuição Composta

O operador de atribuição simples = armazena o valor da direita na variável da esquerda. As atribuições compostas combinam operação aritmética ou lógica com atribuição: +=, -=, *=, /=, %=, &=, |=, ^=, <<=, >>=. A expressão x += 5 equivale a x = x + 5, mas é mais concisa e potencialmente mais eficiente.

#include <stdio.h>

int main() {
    int valor = 10;
    valor += 5;   // valor = 15
    valor *= 2;   // valor = 30
    valor %= 7;   // valor = 2 (resto de 30/7)
    printf("Valor final: %d\n", valor);

    // Armadilha comum: confundir = com ==
    int teste = 0;
    if (teste = 5) {  // atribui 5 a teste, expressão vale 5 (verdadeiro)
        printf("Sempre entra! (teste = %d)\n", teste);
    }
    return 0;
}

5. Operadores Bit a Bit (Bitwise)

Os operadores bit a bit atuam diretamente nos bits dos operandos inteiros: & (E bit a bit), | (OU bit a bit), ^ (XOR bit a bit), ~ (complemento bit a bit), << (deslocamento à esquerda) e >> (deslocamento à direita). São fundamentais para programação de sistemas, controle de hardware e otimizações.

#include <stdio.h>

int main() {
    unsigned char a = 0b11001100;  // 204 em decimal
    unsigned char b = 0b10101010;  // 170 em decimal

    printf("a & b  = 0x%02X\n", a & b);   // 10001000 (0x88)
    printf("a | b  = 0x%02X\n", a | b);   // 11101110 (0xEE)
    printf("a ^ b  = 0x%02X\n", a ^ b);   // 01100110 (0x66)
    printf("~a     = 0x%02X\n", (unsigned char)~a);  // 00110011 (0x33)

    // Deslocamentos
    unsigned char c = 0b00000001;  // 1
    printf("c << 3 = %d\n", c << 3);  // 8 (1 * 2^3)
    printf("c >> 1 = %d\n", c >> 1);  // 0 (divisao inteira por 2)

    // Uso prático: máscaras para ativar/desativar flags
    unsigned char flags = 0b00000000;
    flags |= 0b00000100;  // Ativa o bit 2
    flags &= ~0b00000010; // Desativa o bit 1
    printf("Flags: 0x%02X\n", flags);  // 0x04

    return 0;
}

6. Outros Operadores Relevantes

O operador ternário ? : é uma forma compacta de if-else: condicao ? valor_verdadeiro : valor_falso. O operador sizeof retorna o tamanho em bytes de um tipo ou variável (do tipo size_t). O cast permite conversão explícita de tipos: (tipo)expressao.

#include <stdio.h>

int main() {
    int nota = 7;
    char* resultado = (nota >= 6) ? "Aprovado" : "Reprovado";
    printf("Resultado: %s\n", resultado);

    printf("sizeof(int) = %zu bytes\n", sizeof(int));
    printf("sizeof(double) = %zu bytes\n", sizeof(double));

    // Cast explícito
    int x = 10, y = 3;
    float divisao = (float)x / y;  // converte x para float antes da divisao
    printf("Divisao com cast: %.2f\n", divisao);

    return 0;
}

7. Precedência e Associatividade na Prática

A tabela abaixo resume a precedência dos operadores abordados (do maior para o menor):

Operadores Associatividade
() [] -> . Esquerda → Direita
++ -- + - ! ~ (tipo) * & sizeof Direita → Esquerda
* / % Esquerda → Direita
+ - Esquerda → Direita
<< >> Esquerda → Direita
< <= > >= Esquerda → Direita
== != Esquerda → Direita
& Esquerda → Direita
^ Esquerda → Direita
| Esquerda → Direita
&& Esquerda → Direita
|| Esquerda → Direita
? : Direita → Esquerda
= += -= etc. Direita → Esquerda

A regra de ouro: use parênteses sempre que houver dúvida. Eles tornam a intenção explícita e evitam ambiguidades.

#include <stdio.h>

int main() {
    int a = 5, b = 10, c = 15;

    // Sem parenteses: ambiguo
    int resultado = a + b * c;  // 5 + (10*15) = 155
    printf("Sem parenteses: %d\n", resultado);

    // Com parenteses: intencao clara
    resultado = (a + b) * c;  // (5+10)*15 = 225
    printf("Com parenteses: %d\n", resultado);

    return 0;
}

8. Exemplos Integrados e Boas Práticas

Um exemplo que combina operadores aritméticos, lógicos e bit a bit para simular um sistema de permissões:

#include <stdio.h>

// Definicoes de permissoes como bits
#define PERM_LEITURA   0b0001
#define PERM_ESCRITA   0b0010
#define PERM_EXECUCAO  0b0100
#define PERM_ADMIN     0b1000

int main() {
    unsigned char permissoes = 0;

    // Conceder permissoes usando OR bit a bit
    permissoes |= PERM_LEITURA;
    permissoes |= PERM_EXECUCAO;

    printf("Permissoes iniciais: 0x%02X\n", permissoes);

    // Verificar se tem permissao de escrita (usando AND bit a bit)
    if (permissoes & PERM_ESCRITA) {
        printf("Tem permissao de escrita\n");
    } else {
        printf("Nao tem permissao de escrita\n");
    }

    // Conceder escrita se for admin (usando operador ternario)
    int eh_admin = 1;
    permissoes |= eh_admin ? PERM_ADMIN | PERM_ESCRITA : 0;

    // Verificar multiplas permissoes com operadores logicos
    if ((permissoes & PERM_LEITURA) && (permissoes & PERM_EXECUCAO)) {
        printf("Pode ler e executar\n");
    }

    printf("Permissoes finais: 0x%02X\n", permissoes);

    // Cuidado com efeitos colaterais em expressoes complexas
    int contador = 0;
    if (contador++ > 0 && (permissoes & PERM_ADMIN)) {
        // contador é incrementado mesmo se a condicao falhar? Nao, devido ao curto-circuito
        printf("Nao executa porque contador++ retorna 0\n");
    }
    printf("Contador apos expressao: %d\n", contador); // 1

    return 0;
}

Boas práticas essenciais:
- Prefira legibilidade a código excessivamente compacto
- Use parênteses para explicitar precedência, mesmo quando desnecessários
- Evite efeitos colaterais dentro de expressões lógicas ou de printf
- Em operações bit a bit, use constantes nomeadas (macros ou enums) para melhor documentação
- Lembre-se que = é atribuição e == é comparação — um erro clássico e perigoso

Referências