Os operadores são fundamentais na linguagem C, pois permitem realizar operações sobre dados, desde cálculos matemáticos até comparações e decisões lógicas. Nesta aula, exploraremos os principais operadores disponíveis em C, entendendo sua sintaxe, comportamento e particularidades. Você aprenderá a utilizá-los em expressões e a compreender a ordem de avaliação, essencial para escrever código correto e eficiente.

Dominar os operadores é um passo crucial para qualquer programador, pois eles estão presentes em praticamente todos os programas. Ao final desta aula, você terá uma base sólida para construir expressões complexas e evitar erros comuns relacionados à precedência e à avaliação de curto-circuito.

Aritméticos

Os operadores aritméticos em C realizam operações matemáticas básicas: adição (+), subtração (-), multiplicação (*), divisão (/) e módulo (%). Eles podem ser aplicados a tipos inteiros e de ponto flutuante, mas com algumas nuances importantes.

A divisão entre inteiros resulta em um inteiro, truncando a parte fracionária. Por exemplo, 7 / 2 resulta em 3, não 3.5. Para obter um resultado fracionário, pelo menos um dos operandos deve ser do tipo float ou double. O operador de módulo (%) retorna o resto da divisão inteira e só pode ser usado com inteiros.

#include <stdio.h>

int main() {
    int a = 10, b = 3;
    float c = 10.0, d = 3.0;

    printf("a + b = %d\n", a + b);  // 13
    printf("a - b = %d\n", a - b);  // 7
    printf("a * b = %d\n", a * b);  // 30
    printf("a / b = %d\n", a / b);  // 3 (divisão inteira)
    printf("a %% b = %d\n", a % b);  // 1 (resto)
    printf("c / d = %.2f\n", c / d); // 3.33 (ponto flutuante)
    return 0;
}

Além dos operadores binários, C também possui operadores unários: + (sinal positivo) e - (sinal negativo). O operador de incremento (++) e decremento (--) são especiais e podem ser usados na forma prefixada ou pós-fixada, alterando a ordem de avaliação.

Relacionais e lógicos

Os operadores relacionais comparam dois valores e retornam 1 (verdadeiro) ou 0 (falso). São eles: >, <, >=, <=, == (igualdade) e != (desigualdade). Eles são frequentemente usados em estruturas de decisão, como if e while.

Os operadores lógicos permitem combinar expressões relacionais: && (E lógico), || (OU lógico) e ! (NÃO lógico). Eles seguem as regras da lógica booleana e apresentam um comportamento importante chamado curto-circuito: no caso de &&, se a primeira condição for falsa, a segunda não é avaliada; em ||, se a primeira for verdadeira, a segunda é ignorada.

#include <stdio.h>

int main() {
    int idade = 20;
    int renda = 1500;

    if (idade >= 18 && renda > 1000) {
        printf("Apto para o crédito\n");
    }
    if (idade < 18 || renda < 500) {
        printf("Não apto\n");
    }
    if (!(idade > 21)) {
        printf("Jovem\n");
    }
    return 0;
}

É importante lembrar que em C, qualquer valor diferente de zero é considerado verdadeiro, e zero é falso. Isso permite expressões como if (x) para verificar se uma variável é diferente de zero.

Atribuição composta

Os operadores de atribuição composta combinam uma operação aritmética ou bit a bit com a atribuição, tornando o código mais conciso. Por exemplo, a += 5 é equivalente a a = a + 5. Os operadores incluem +=, -=, *=, /=, %=, e também os bit a bit &=, |=, ^=, etc.

Esses operadores não apenas reduzem a digitação, mas também podem melhorar a legibilidade quando a variável é longa. Além disso, em alguns casos, a avaliação é feita de forma mais eficiente, pois o operando esquerdo é avaliado apenas uma vez.

#include <stdio.h>

int main() {
    int x = 10;
    x += 5;   // x = x + 5; agora x = 15
    x -= 3;   // x = x - 3; agora x = 12
    x *= 2;   // x = x * 2; agora x = 24
    x /= 4;   // x = x / 4; agora x = 6
    x %= 4;   // x = x % 4; agora x = 2
    printf("x = %d\n", x); // 2
    return 0;
}

A atribuição composta é muito útil em loops e acumuladores, como no cálculo de somatórias.

Precedência

A precedência de operadores define a ordem em que as operações são avaliadas em uma expressão. Em C, os operadores têm níveis de precedência bem definidos, e os parênteses podem ser usados para alterar essa ordem. Por exemplo, na expressão a + b * c, a multiplicação é realizada antes da adição, pois * tem precedência maior que +.

A associatividade determina a ordem de avaliação quando operadores de mesma precedência aparecem em sequência. A maioria dos operadores binários é associativa à esquerda, mas os de atribuição são associativos à direita. Conhecer a precedência evita erros sutis, como em if (x & 1 == 0), onde a comparação == tem precedência maior que o bit a bit &, resultando em x & (1 == 0), que é provavelmente indesejado. O correto seria if ((x & 1) == 0).

#include <stdio.h>

int main() {
    int a = 5, b = 3, c = 2;
    int resultado = a + b * c;   // 5 + (3*2) = 11
    int comParenteses = (a + b) * c; // (5+3)*2 = 16
    printf("resultado = %d\n", resultado);
    printf("comParenteses = %d\n", comParenteses);
    return 0;
}

Uma tabela de precedência simplificada (da maior para a menor):

  • Pós-fixados: () [] -> . ++ --
  • Unários: + - ! ~ ++ -- (tipo)
  • Multiplicativos: * / %
  • Aditivos: + -
  • Relacionais: < <= > >=
  • Igualdade: == !=
  • E lógico: &&
  • OU lógico: ||
  • Condicional: ?:
  • Atribuição: = += -= *= /= %= ...
  • Vírgula: ,

Boas práticas e observações

Para evitar erros, é recomendável usar parênteses em expressões complexas, mesmo que a precedência já defina a ordem, pois isso melhora a legibilidade. Além disso, cuidado com o operador de igualdade (==) em comparações com atribuição (=), um erro comum é escrever if (x = 5) em vez de if (x == 5). Sempre compile com warnings habilitados (-Wall) para detectar possíveis ambiguidades.

Outra dica é evitar misturar tipos inteiros e de ponto flutuante em operações aritméticas sem conversão explícita, pois podem ocorrer perdas de precisão. Entender o comportamento de curto-circuito também é útil para otimizar condições e evitar operações desnecessárias.

Exercícios

  1. Escreva um programa que leia dois números inteiros e imprima a soma, subtração, multiplicação, divisão inteira e o resto da divisão.
  2. Dado um ano, determine se é bissexto. Um ano é bissexto se for divisível por 4, exceto se for divisível por 100, a menos que seja divisível por 400. Use operadores relacionais e lógicos.
  3. Implemente um programa que calcule a média de três notas e determine se o aluno está aprovado (média >= 7) ou reprovado, usando operador ternário.
  4. Escreva um código que use atribuições compostas para calcular o fatorial de um número, mas de forma acumulada (sem usar laço). Dica: use múltiplas atribuições compostas em uma expressão?
  5. Analise a expressão int x = 5; int y = 3; int z = x++ - --y * 2; e determine o valor final de x, y e z, explicando a ordem de avaliação.

✓ Resposta: Um programa possível:
#include <stdio.h>

int main() {
    int a, b;
    printf("Digite dois inteiros: ");
    scanf("%d %d", &a, &b);
    printf("Soma: %d\n", a + b);
    printf("Subtração: %d\n", a - b);
    printf("Multiplicação: %d\n", a * b);
    if (b != 0) {
        printf("Divisão inteira: %d\n", a / b);
        printf("Resto: %d\n", a % b);
    } else {
        printf("Divisão por zero não permitida.\n");
    }
    return 0;
}

✓ Resposta:
#include <stdio.h>

int main() {
    int ano;
    printf("Digite um ano: ");
    scanf("%d", &ano);
    if ((ano % 4 == 0 && ano % 100 != 0) || (ano % 400 == 0))
        printf("%d é bissexto\n", ano);
    else
        printf("%d não é bissexto\n", ano);
    return 0;
}

✓ Resposta:
#include <stdio.h>

int main() {
    float nota1, nota2, nota3, media;
    printf("Digite três notas: ");
    scanf("%f %f %f", &nota1, &nota2, &nota3);
    media = (nota1 + nota2 + nota3) / 3;
    printf("Média: %.2f - %s\n", media, media >= 7 ? "Aprovado" : "Reprovado");
    return 0;
}

✓ Resposta: Não é possível calcular fatorial sem laço ou recursão, pois a atribuição composta não pode repetir operações. Uma solução com laço é:
#include <stdio.h>

int main() {
    int n, i;
    long fat = 1;
    printf("Digite um número: ");
    scanf("%d", &n);
    for (i = 2; i <= n; i++) {
        fat *= i;
    }
    printf("Fatorial = %ld\n", fat);
    return 0;
}

✓ Resposta: A expressão é avaliada da seguinte forma: primeiro, o pós-decremento de x (x++) é avaliado, mas o valor usado é o original (5), e x passa a 6. Depois, o pré-decremento de y (--y) é avaliado, y passa a 2 e o valor usado é 2. A multiplicação 2 * 2 = 4 é feita, e a subtração 5 - 4 = 1 é atribuída a z. Portanto: x = 6, y = 2, z = 1.

Referências