Sua dose diária de programação

Conceito, 3 exemplos e 3 exercícios: uma pequena conquista de cada vez.

✦   Lição

Operadores aritméticos, relacionais e lógicos

Operadores são os símbolos que fazem as contas e as comparações do programa. Os aritméticos são +, -, *, / e %; os relacionais (==, !=, <, <=, >, >=) comparam dois valores; e os lógicos (&&, ||, !) combinam comparações. Em C, relacionais e lógicos não produzem um tipo booleano: o resultado é um int que vale 1 (verdadeiro) ou 0 (falso), e pode ser guardado numa variável, impresso ou até somado.

C Fundamentos Iniciante 3 exemplos + 3 exercícios
42 lições publicadas 10 linguagens novas lições todo dia útil
C / Fundamentos

Operadores aritméticos, relacionais e lógicos

por Callahan Whitmore
Iniciante

Entenda o conceito

Operadores são os símbolos que fazem as contas e as comparações do programa. Os aritméticos são +, -, *, / e %; os relacionais (==, !=, <, <=, >, >=) comparam dois valores; e os lógicos (&&, ||, !) combinam comparações. Em C, relacionais e lógicos não produzem um tipo booleano: o resultado é um int que vale 1 (verdadeiro) ou 0 (falso), e pode ser guardado numa variável, impresso ou até somado.

Quando uma expressão mistura operadores, a precedência decide a ordem: ! e o sinal de menos vêm primeiro, depois *, / e %, depois + e -, então os relacionais, && e, por último, ||. Entre operadores do mesmo nível, a avaliação vai da esquerda para a direita, então 12 / 2 * 3 vale 18. Parênteses mudam essa ordem e deixam a intenção clara.

Para atualizar uma variável, há a atribuição composta (+=, -=, *=, /=, %=): estoque += 12 é o mesmo que estoque = estoque + 12. E ++ e -- somam ou subtraem 1; na forma pós (x++) a expressão usa o valor antigo, e na forma pré (++x), o novo.

Por fim, && e || fazem curto-circuito: a parte da direita só é avaliada se a da esquerda não decidir o resultado. Isso permite proteger uma conta, como em jogadas != 0 && acertos / jogadas > 1, que nunca divide por zero.

↳ Pontos-chave
  • *, / e % vêm antes de + e -; na dúvida ou para deixar a intenção clara, use parênteses.
  • Relacionais e lógicos produzem um int: 1 para verdadeiro e 0 para falso, que pode ser guardado e somado.
  • x += 5 equivale a x = x + 5; vale o mesmo para -=, *=, /= e %=.
  • x++ usa o valor atual e depois soma 1; ++x soma primeiro. Sozinhos na linha, os dois têm o mesmo efeito.
  • Com negativos, / trunca em direção a zero e % segue o sinal do dividendo: -7 / 2 dá -3 e -7 % 2 dá -1.

Do conceito à prática

3 exemplos para ver a ideia funcionando.

01Estoque da loja com atribuição composta e ++

estoque.c
/* estoque.c: aritméticos, atribuição composta e ++/-- */
#include <stdio.h>

int main(void)
{
    int estoque = 40;
    int preco = 50;

    /* x += y é o mesmo que x = x + y, sem repetir o nome */
    estoque += 12;   /* chegou um lote */
    estoque -= 5;    /* vendas da manhã */
    printf("Estoque: %d unidades\n", estoque);

    /* Reajuste de 10%: a multiplicação vem antes da divisão */
    preco += preco * 10 / 100;
    printf("Preço reajustado: R$ %d\n", preco);

    /* / dá as caixas cheias e % as unidades avulsas */
    printf("Caixas de 5: %d | avulsas: %d\n", estoque / 5, estoque % 5);

    /* Pós-incremento: usa o valor atual e depois soma 1 */
    int pedidos = 0;
    int numero = pedidos++;
    printf("Pós: numero = %d, pedidos = %d\n", numero, pedidos);

    /* Pré-incremento: soma 1 primeiro e usa o novo valor */
    numero = ++pedidos;
    printf("Pré: numero = %d, pedidos = %d\n", numero, pedidos);

    estoque--;       /* uma unidade separada para a vitrine */
    printf("Estoque final: %d\n", estoque);
    return 0;
}

O estoque passa de 40 para 52 com += e para 47 com -=, sem repetir o nome da variável do lado direito. No reajuste, preco * 10 / 100 é avaliado da esquerda para a direita (500 / 100 = 5), e o preço vai a R$ 55; multiplicar antes de dividir evita perder a parte decimal na divisão inteira. As 47 unidades viram 9 caixas de 5 e 2 avulsas com / e %.

O trecho dos pedidos mostra a diferença entre pós e pré-incremento. numero = pedidos++ guarda 0 e só depois pedidos vira 1; numero = ++pedidos primeiro leva pedidos a 2 e guarda o 2. No fim, estoque-- sozinho na linha apenas subtrai 1, deixando 46.

02Médias do boletim: precedência e parênteses

boletim.c
/* boletim.c: precedência, parênteses e divisão com negativos */
#include <stdio.h>

int main(void)
{
    int prova = 8;
    int trabalho = 6;
    int participacao = 9;

    /* / vem antes de +: só a participação é dividida por 3 */
    int errada = prova + trabalho + participacao / 3;
    /* Os parênteses forçam a soma antes da divisão */
    int certa = (prova + trabalho + participacao) / 3;
    printf("Sem parênteses: %d\n", errada);
    printf("Com parênteses: %d\n", certa);

    /* Média ponderada: a prova tem peso 2 */
    int ponderada = (prova * 2 + trabalho + participacao) / 4;
    printf("Média ponderada: %d\n", ponderada);

    /* Mesma precedência: avaliação da esquerda para a direita */
    printf("12 / 2 * 3 = %d\n", 12 / 2 * 3);
    printf("12 / (2 * 3) = %d\n", 12 / (2 * 3));

    /* Com negativos, a divisão inteira trunca em direção a zero */
    int ajuste = -7;
    printf("-7 / 2 = %d e -7 %% 2 = %d\n", ajuste / 2, ajuste % 2);

    /* Aritméticos vêm antes dos relacionais: a conta é feita antes do >= */
    printf("Aprovado: %d\n", (prova + trabalho) / 2 >= 6);
    return 0;
}

Sem parênteses, prova + trabalho + participacao / 3 divide só a participação e dá 8 + 6 + 3 = 17, um valor que nem cabe na escala de notas. Com parênteses, a soma 23 é dividida inteira e a média é 7. A média ponderada segue a mesma ideia: prova * 2 é calculado antes da soma, e 31 / 4 dá 7.

As linhas com 12 mostram a associatividade: / e * têm a mesma precedência, então 12 / 2 * 3 é (12 / 2) * 3 = 18. Com -7, a divisão dá -3 (trunca em direção a zero, não para baixo) e o resto dá -1; note o %% no printf para imprimir o sinal de porcentagem. Na última linha, a conta aritmética acontece antes do >=, e a comparação imprime 1.

03Rodada de dados: comparações como números e curto-circuito

dados.c
/* dados.c: comparações como valores, lógicos e curto-circuito */
#include <stdio.h>

int main(void)
{
    int dado1 = 4;
    int dado2 = 4;
    int soma = dado1 + dado2;

    /* Cada comparação vale 1 (verdadeiro) ou 0 (falso) e pode ser guardada */
    int dupla = dado1 == dado2;
    int par = soma % 2 == 0;
    int alta = soma >= 9;

    printf("Soma: %d | dupla: %d | par: %d | alta: %d\n",
           soma, dupla, par, alta);

    /* Como valem 1 ou 0, somar comparações conta as verdadeiras */
    int bonus = dupla + par + alta;
    printf("Bônus conquistados: %d\n", bonus);

    /* && exige as duas; || aceita uma; ! inverte e vem antes de || */
    printf("Jogada especial: %d\n", dupla && par);
    printf("Jogada comum: %d\n", !dupla || soma < 6);

    /* Curto-circuito: com jogadas == 0, a divisão nem é avaliada */
    int jogadas = 0;
    int acertos = 0;
    if (jogadas != 0 && acertos * 100 / jogadas >= 50) {
        puts("Bom aproveitamento");
    } else {
        puts("Ainda sem jogadas para avaliar");
    }
    return 0;
}

Cada comparação é guardada num int: com os dados 4 e 4, dupla e par valem 1 e alta vale 0, porque a soma 8 não chega a 9. Como esses valores são números, somá-los conta quantas condições são verdadeiras, e o jogador ganha 2 bônus. Em soma % 2 == 0, o % é avaliado antes do ==, então não precisa de parênteses.

!dupla || soma < 6 mostra que o ! se aplica só a dupla (vale 0) e que < vem antes de ||; o resultado é 0. No if final, jogadas != 0 é falso, então o && já sabe que o resultado é falso e não avalia acertos * 100 / jogadas: o curto-circuito impede a divisão por zero.


Agora é com você

Tente resolver no rascunho antes de abrir a resposta. Os códigos não são executados nesta página.

01Movimento do acervo da bibliotecaFácil

O acervo começa com 120 livros. No dia chegam 15 livros de doação, dois leitores levam 8 livros emprestados, um leitor devolve 3 e um livro danificado é retirado; mostre o acervo depois de cada movimentação e quantos leitores foram atendidos.

↳ Requisitos
  • Usar += e -= nas movimentações de doação, empréstimo e devolução.
  • Usar -- para a baixa do livro danificado.
  • Contar os leitores com ++, uma vez por leitor atendido, partindo de 0.
código inicial
#include <stdio.h>

int main(void)
{
    int acervo = 120;
    int leitores = 0;

    printf("Acervo inicial: %d\n", acervo);
    /* Atualize o acervo e os leitores, imprimindo após cada movimentação */

    return 0;
}
saída esperada
Acervo inicial: 120
Após doação: 135
Após empréstimos: 127
Após devoluções: 130
Após baixa de 1 livro danificado: 129
Leitores atendidos: 3
Ver dica

Cada movimentação muda o valor que já está na variável; procure o operador que faz a conta e a atribuição de uma vez.

Ver resposta
#include <stdio.h>

int main(void)
{
    int acervo = 120;
    int leitores = 0;

    printf("Acervo inicial: %d\n", acervo);

    acervo += 15;            /* doação: soma ao valor atual */
    printf("Após doação: %d\n", acervo);

    acervo -= 8;             /* empréstimos de dois leitores */
    leitores++;
    leitores++;
    printf("Após empréstimos: %d\n", acervo);

    acervo += 3;             /* devolução de um leitor */
    leitores++;
    printf("Após devoluções: %d\n", acervo);

    acervo--;                /* baixa de um livro danificado */
    printf("Após baixa de 1 livro danificado: %d\n", acervo);

    printf("Leitores atendidos: %d\n", leitores);
    return 0;
}

Passo a passo

  1. acervo começa em 120 e leitores em 0; o contador precisa de valor inicial, senão o ++ somaria 1 a um lixo de memória.
  2. acervo += 15; é a forma curta de acervo = acervo + 15;: lê o valor atual, soma e guarda no mesmo lugar, chegando a 135.
  3. acervo -= 8; desconta os empréstimos (127). Logo depois, leitores++ aparece duas vezes, uma por leitor atendido.
  4. A devolução volta a usar += (130) e soma mais um leitor, chegando a 3 atendimentos.
  5. acervo--; tira exatamente uma unidade (129). Para mudanças de 1, ++ e -- deixam a intenção mais clara que += 1.
  6. Cada printf vem logo depois da movimentação, então a saída mostra o acervo passo a passo.

Por que funciona

Os operadores de atribuição composta (+=, -=, *=, /=, %=) combinam uma conta com a atribuição: o lado esquerdo é lido, operado com o lado direito e regravado. Já ++ e -- são o caso particular de somar ou subtrair 1. Como aqui eles aparecem sozinhos na linha, tanto faz a forma pré (++leitores) ou pós (leitores++).

Erros comuns

  • Inverter a ordem do operador: acervo =+ 15; compila, mas é lido como acervo = +15; e o acervo passa a valer 15. O operador composto é +=, com o = depois.
  • Esquecer o ponto e vírgula entre movimentações: acervo -= 8 leitores++; não compila (expected ';'). Cada atualização é um comando.
  • Contador sem valor inicial: int leitores; leitores++; soma 1 a um valor imprevisível. Inicialize com 0.

Outra forma de resolver

As movimentações podem ser escritas por extenso:

acervo = acervo + 15;
leitores = leitores + 1;

O resultado é o mesmo; a forma longa ajuda só quando o lado direito usa outra variável no lugar do próprio acervo.

Saída esperada

Acervo inicial: 120
Após doação: 135
Após empréstimos: 127
Após devoluções: 130
Após baixa de 1 livro danificado: 129
Leitores atendidos: 3

02Parcelas do pedido e a ordem das contasMédio

Um pedido tem 3 camisetas de R$ 40 e 2 bonés de R$ 25, com cupom de R$ 10, pago em 3 parcelas. Alguém escreveu a parcela como 3 * 40 + 2 * 25 - 10 / 3: mostre o valor dessa expressão, corrija a conta e reparta o total sem perder nenhum real.

↳ Requisitos
  • Guardar quantidades, preços, cupom e parcelas em variáveis int e reproduzir a expressão errada com elas.
  • Calcular a parcela dividindo o total já com o cupom, com parênteses ou com uma variável intermediária.
  • Somar a sobra da divisão (%) à primeira parcela.
  • Conferir que a primeira parcela mais as demais dá o total.
código inicial
#include <stdio.h>

int main(void)
{
    int qtd_camiseta = 3;
    int preco_camiseta = 40;
    int qtd_bone = 2;
    int preco_bone = 25;
    int cupom = 10;
    int parcelas = 3;

    /* Calcule a expressão errada, o total e as parcelas */

    return 0;
}
saída esperada
Total do pedido: R$ 160
Expressão sem parênteses: R$ 167
Parcelas: 3
Primeira parcela: R$ 54
Demais parcelas: R$ 53
Conferência: R$ 160
Ver dica

Faça a expressão errada à mão, operador por operador, seguindo a precedência: qual conta o C resolve primeiro?

Ver resposta
#include <stdio.h>

int main(void)
{
    int qtd_camiseta = 3;
    int preco_camiseta = 40;
    int qtd_bone = 2;
    int preco_bone = 25;
    int cupom = 10;
    int parcelas = 3;

    /* Errado: / vem antes de -, então só o cupom é dividido */
    int sem_parenteses = qtd_camiseta * preco_camiseta
                         + qtd_bone * preco_bone - cupom / parcelas;

    /* Certo: o total (já com o cupom) é fechado antes da divisão */
    int total = qtd_camiseta * preco_camiseta + qtd_bone * preco_bone - cupom;
    int parcela = total / parcelas;
    int primeira = parcela + total % parcelas;  /* absorve a sobra */

    printf("Total do pedido: R$ %d\n", total);
    printf("Expressão sem parênteses: R$ %d\n", sem_parenteses);
    printf("Parcelas: %d\n", parcelas);
    printf("Primeira parcela: R$ %d\n", primeira);
    printf("Demais parcelas: R$ %d\n", parcela);
    /* * vem antes de +, e os parênteses fazem o - primeiro */
    printf("Conferência: R$ %d\n", primeira + parcela * (parcelas - 1));
    return 0;
}

Passo a passo

  1. Cada quantidade e preço fica numa variável int com nome, para a expressão se ler como a regra da loja e não como números soltos.
  2. Em sem_parenteses, * e / são resolvidos antes de + e -: o C calcula 3 * 40 = 120, 2 * 25 = 50 e 10 / 3 = 3, e só então faz 120 + 50 - 3 = 167. O valor não é parcela de nada.
  3. total junta os itens e desconta o cupom: 120 + 50 - 10 = 160. Guardar o total numa variável tem o mesmo efeito de pôr a soma entre parênteses: ela fica pronta antes da divisão.
  4. total / parcelas é divisão inteira: 160 / 3 dá 53, e total % parcelas devolve o real que sobrou, 1.
  5. A primeira parcela recebe a sobra (53 + 1 = 54), assim nenhum real se perde na divisão.
  6. A conferência usa precedência a favor: parcelas - 1 está entre parênteses, * vem antes de +, e 54 + 53 * 2 dá 160 de volta.

Por que funciona

Operadores têm precedência: multiplicativos (*, /, %) antes dos aditivos (+, -), e, entre os de mesmo nível, a avaliação vai da esquerda para a direita. A expressão original estava sintaticamente correta, mas dizia outra coisa. Fechar o total antes de dividir, com parênteses ou com uma variável, faz o código dizer o que a regra pede. O par / e % então reparte o valor inteiro sem perder nenhum real.

Erros comuns

  • Confiar na ordem da escrita: a + b - c / n não divide a soma toda; só c é dividido. Agrupe com parênteses: (a + b - c) / n.
  • Perder a sobra: imprimir só total / parcelas três vezes soma 159, um real a menos. Some total % parcelas a uma das parcelas.
  • Parênteses só no fim: qtd_camiseta * preco_camiseta + (qtd_bone * preco_bone - cupom) / parcelas ainda deixa as camisetas fora da divisão e dá 133. O parêntese precisa envolver todo o total.

Outra forma de resolver

A parcela pode sair numa única expressão, sem a variável total:

int parcela = (qtd_camiseta * preco_camiseta + qtd_bone * preco_bone - cupom) / parcelas;

Serve quando o total não será usado de novo; aqui ele aparece na saída e no resto, então a variável evita repetir a conta.

Saída esperada

Total do pedido: R$ 160
Expressão sem parênteses: R$ 167
Parcelas: 3
Primeira parcela: R$ 54
Demais parcelas: R$ 53
Conferência: R$ 160

03Fornadas de cookies para a festaDesafio

Para uma festa de 23 convidados, cada um comendo 3 cookies, a receita rende 12 cookies por fornada e gasta 2 ovos; há 10 ovos em casa e tempo para no máximo 6 fornadas. Calcule as fornadas (arredondando para cima), os cookies assados, a sobra e os ovos, e diga se a festa está pronta.

↳ Requisitos
  • Calcular as fornadas arredondando para cima só com operadores inteiros, sem if e sem math.h.
  • Guardar ovos_ok e cabe_no_tempo como int recebendo diretamente o resultado de uma comparação.
  • Mostrar quantos ovos faltam sem nunca imprimir número negativo.
  • Decidir se a festa está pronta com && entre as duas condições e exibir sim ou não com o operador ternário.
código inicial
#include <stdio.h>

int main(void)
{
    int convidados = 23;
    int cookies_por_pessoa = 3;
    int por_fornada = 12;
    int ovos_por_fornada = 2;
    int ovos_em_casa = 10;
    int max_fornadas = 6;

    /* Calcule as fornadas, a sobra, os ovos e as condições */

    return 0;
}
saída esperada
Cookies necessários: 69
Fornadas: 6
Cookies assados: 72
Sobra: 3
Ovos necessários: 12
Ovos suficientes: não
Faltam ovos: 2
Cabe no tempo do forno: sim
Festa pronta: não
Ver dica

Para arredondar para cima, pense no que acontece com n / 12 se você somar 11 a n antes de dividir, com e sem resto.

Ver resposta
#include <stdio.h>

int main(void)
{
    int convidados = 23;
    int cookies_por_pessoa = 3;
    int por_fornada = 12;
    int ovos_por_fornada = 2;
    int ovos_em_casa = 10;
    int max_fornadas = 6;   /* o que dá para assar até a festa */

    int necessarios = convidados * cookies_por_pessoa;

    /* Divisão arredondada para cima só com inteiros */
    int fornadas = (necessarios + por_fornada - 1) / por_fornada;

    int assados = fornadas * por_fornada;
    int sobra = assados - necessarios;
    int ovos_necessarios = fornadas * ovos_por_fornada;

    /* Comparações guardadas direto: valem 1 ou 0 */
    int ovos_ok = ovos_em_casa >= ovos_necessarios;
    int cabe_no_tempo = fornadas <= max_fornadas;
    int faltam = ovos_ok ? 0 : ovos_necessarios - ovos_em_casa;

    printf("Cookies necessários: %d\n", necessarios);
    printf("Fornadas: %d\n", fornadas);
    printf("Cookies assados: %d\n", assados);
    printf("Sobra: %d\n", sobra);
    printf("Ovos necessários: %d\n", ovos_necessarios);
    printf("Ovos suficientes: %s\n", ovos_ok ? "sim" : "não");
    printf("Faltam ovos: %d\n", faltam);
    printf("Cabe no tempo do forno: %s\n", cabe_no_tempo ? "sim" : "não");
    /* && só é verdadeiro se as duas condições forem */
    printf("Festa pronta: %s\n", ovos_ok && cabe_no_tempo ? "sim" : "não");
    return 0;
}

Passo a passo

  1. Todos os dados da receita e da festa ficam em variáveis int; nenhuma conta aqui precisa de parte decimal, porque fornadas, cookies e ovos são contados em unidades inteiras.
  2. convidados * cookies_por_pessoa dá 69 cookies. Esse é o mínimo; a divisão por fornada é que define quanto será assado de fato.
  3. 69 / 12 daria 5 fornadas e faltariam cookies. Somando por_fornada - 1 antes de dividir, (69 + 11) / 12 = 80 / 12 = 6: qualquer sobra empurra o resultado para a fornada seguinte, e uma divisão exata não muda.
  4. Com 6 fornadas, saem 72 cookies e sobram 3; os ovos necessários são 6 * 2 = 12.
  5. ovos_ok e cabe_no_tempo recebem o resultado das comparações. Como 10 >= 12 é falso, ovos_ok vale 0; 6 <= 6 é verdadeiro e cabe_no_tempo vale 1.
  6. O ternário evita um número negativo em faltam: só quando os ovos não bastam ele calcula 12 - 10 = 2.
  7. Na última linha, && tem precedência maior que ?:, então ovos_ok && cabe_no_tempo é avaliado primeiro. Como ovos_ok é 0, o curto-circuito nem olha cabe_no_tempo e a festa não está pronta.

Por que funciona

O arredondamento para cima é aritmética de inteiros pura: somar divisor - 1 ao dividendo faz a divisão truncada "passar" para o próximo inteiro sempre que houver resto. Não é preciso ceil, double nem if.

As decisões vêm dos relacionais, que em C produzem um int 1 ou 0. Guardando esse valor numa variável com nome, a condição final se lê como frase (ovos_ok && cabe_no_tempo) e cada parte pode ser impressa e conferida separadamente.

Erros comuns

  • Divisão sem arredondar: int fornadas = necessarios / por_fornada; dá 5, e só 60 cookies para os 69 necessários. Use (necessarios + por_fornada - 1) / por_fornada.
  • Parênteses esquecidos no arredondamento: necessarios + por_fornada - 1 / por_fornada calcula 1 / 12 = 0 primeiro e resulta em 81. A soma precisa ficar entre parênteses.
  • Subtrair sem conferir: int faltam = ovos_necessarios - ovos_em_casa; daria um valor negativo quando sobram ovos. Proteja com a comparação, como no ternário.
  • & no lugar de &&: com valores 1 e 0 o resultado coincide, mas & avalia os dois lados sempre e, com outros valores (como 2 & 1), dá 0. Para lógica, use &&.

Outra forma de resolver

O arredondamento também pode somar o resultado de uma comparação:

int fornadas = necessarios / por_fornada + (necessarios % por_fornada != 0);

A comparação vale 1 quando há resto e 0 quando a divisão é exata. É preferível quando necessarios + por_fornada puder estourar o limite do int, já que não soma nada ao dividendo.

Saída esperada

Cookies necessários: 69
Fornadas: 6
Cookies assados: 72
Sobra: 3
Ovos necessários: 12
Ovos suficientes: não
Faltam ovos: 2
Cabe no tempo do forno: sim
Festa pronta: não

Últimas lições

Ver o arquivo completo →