Operadores aritméticos, relacionais e lógicos
por Callahan WhitmoreEntenda 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.
*,/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:1para verdadeiro e0para falso, que pode ser guardado e somado. x += 5equivale ax = x + 5; vale o mesmo para-=,*=,/=e%=.x++usa o valor atual e depois soma 1;++xsoma 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 / 2dá-3e-7 % 2dá-1.
Do conceito à prática
3 exemplos para ver a ideia funcionando.
01Estoque da loja com atribuição composta e ++
/* 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: 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: 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.
- 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.
#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;
}
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
acervocomeça em 120 eleitoresem 0; o contador precisa de valor inicial, senão o++somaria 1 a um lixo de memória.acervo += 15;é a forma curta deacervo = acervo + 15;: lê o valor atual, soma e guarda no mesmo lugar, chegando a 135.acervo -= 8;desconta os empréstimos (127). Logo depois,leitores++aparece duas vezes, uma por leitor atendido.- A devolução volta a usar
+=(130) e soma mais um leitor, chegando a 3 atendimentos. acervo--;tira exatamente uma unidade (129). Para mudanças de 1,++e--deixam a intenção mais clara que+= 1.- Cada
printfvem 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 comoacervo = +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.
- Guardar quantidades, preços, cupom e parcelas em variáveis
inte 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.
#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;
}
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
- Cada quantidade e preço fica numa variável
intcom nome, para a expressão se ler como a regra da loja e não como números soltos. - Em
sem_parenteses,*e/são resolvidos antes de+e-: o C calcula3 * 40 = 120,2 * 25 = 50e10 / 3 = 3, e só então faz120 + 50 - 3 = 167. O valor não é parcela de nada. totaljunta 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.total / parcelasé divisão inteira:160 / 3dá53, etotal % parcelasdevolve o real que sobrou,1.- A primeira parcela recebe a sobra (
53 + 1 = 54), assim nenhum real se perde na divisão. - A conferência usa precedência a favor:
parcelas - 1está entre parênteses,*vem antes de+, e54 + 53 * 2dá160de 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 / nnão divide a soma toda; sócé dividido. Agrupe com parênteses:(a + b - c) / n. - Perder a sobra: imprimir só
total / parcelastrês vezes soma159, um real a menos. Sometotal % parcelasa uma das parcelas. - Parênteses só no fim:
qtd_camiseta * preco_camiseta + (qtd_bone * preco_bone - cupom) / parcelasainda 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.
- Calcular as fornadas arredondando para cima só com operadores inteiros, sem
ife semmath.h. - Guardar
ovos_okecabe_no_tempocomointrecebendo 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 exibirsimounãocom o operador ternário.
#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;
}
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
- 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. convidados * cookies_por_pessoadá69cookies. Esse é o mínimo; a divisão por fornada é que define quanto será assado de fato.69 / 12daria5fornadas e faltariam cookies. Somandopor_fornada - 1antes 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.- Com 6 fornadas, saem
72cookies e sobram3; os ovos necessários são6 * 2 = 12. ovos_okecabe_no_temporecebem o resultado das comparações. Como10 >= 12é falso,ovos_okvale0;6 <= 6é verdadeiro ecabe_no_tempovale1.- O ternário evita um número negativo em
faltam: só quando os ovos não bastam ele calcula12 - 10 = 2. - Na última linha,
&&tem precedência maior que?:, entãoovos_ok && cabe_no_tempoé avaliado primeiro. Comoovos_oké0, o curto-circuito nem olhacabe_no_tempoe 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ó60cookies para os69necessários. Use(necessarios + por_fornada - 1) / por_fornada. - Parênteses esquecidos no arredondamento:
necessarios + por_fornada - 1 / por_fornadacalcula1 / 12 = 0primeiro e resulta em81. 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 valores1e0o resultado coincide, mas&avalia os dois lados sempre e, com outros valores (como2 & 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