Sua dose diária de programação

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

✦   Lição

Decisões com condicionais

Até aqui os programas executavam todas as linhas, de cima para baixo. Com condicionais, o programa passa a escolher: if (condição) { ... } só executa o bloco quando a condição é verdadeira, e o else define o que fazer no caso contrário. Em C#, a condição precisa ser obrigatoriamente um bool: if (quantidade) não compila, é preciso escrever a comparação, como if (quantidade > 0).

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

Decisões com condicionais

por Sloane Kensington
Iniciante

Entenda o conceito

Até aqui os programas executavam todas as linhas, de cima para baixo. Com condicionais, o programa passa a escolher: if (condição) { ... } só executa o bloco quando a condição é verdadeira, e o else define o que fazer no caso contrário. Em C#, a condição precisa ser obrigatoriamente um bool: if (quantidade) não compila, é preciso escrever a comparação, como if (quantidade > 0).

As condições são montadas com os operadores de comparação (==, !=, <, <=, >, >=) e combinadas com os operadores lógicos && (e), || (ou) e ! (não). Quando há mais de duas saídas, encadeie else if: as condições são testadas em ordem e só o bloco da primeira verdadeira é executado.

Para escolher entre dois valores numa só expressão existe o operador ternário (condição ? valorSeVerdadeiro : valorSeFalso). E, quando a decisão depende de um único valor, o switch deixa o código mais limpo: o switch tradicional, com case e break, e a switch expression (C# 8+), que devolve um valor e aceita padrões como < 10 e _ para "qualquer outro".

↳ Pontos-chave
  • A condição do if tem de ser um bool: C# não converte números em verdadeiro ou falso, então if (estoque) é erro de compilação.
  • Numa cadeia de else if, só o bloco da primeira condição verdadeira roda; por isso, teste os casos mais específicos primeiro.
  • && e || fazem curto-circuito: se o lado esquerdo já decide o resultado, o direito nem é avaliado.
  • Compare com ==, não com =: if (cupom = "PRIMEIRA") tenta atribuir e não compila, porque o resultado não é bool.
  • No switch tradicional cada case termina em break; a switch expression (valor switch { ... }) devolve um valor e usa _ como caso padrão.

Do conceito à prática

3 exemplos para ver a ideia funcionando.

01Devolução de livro com if, else e ternário

devolucao.cs
// Devolução de livro na biblioteca: if e else escolhem um caminho.
const int PrazoDias = 14;
const int MultaPorDiaCentavos = 150;

int diasEmprestado = 17;
bool leitorEstudante = true;

Console.WriteLine($"Livro devolvido após {diasEmprestado} dias (prazo: {PrazoDias}).");

// A condição entre parênteses precisa ser um bool
if (diasEmprestado <= PrazoDias)
{
    Console.WriteLine("Devolução no prazo. Obrigado!");
}
else
{
    int diasAtraso = diasEmprestado - PrazoDias;
    int multa = diasAtraso * MultaPorDiaCentavos;

    // if sem else: o bloco só roda quando a condição é verdadeira
    if (leitorEstudante)
    {
        multa = multa / 2;
        Console.WriteLine("Desconto de estudante aplicado: 50%.");
    }

    Console.WriteLine($"Atraso de {diasAtraso} dias. Multa: R$ {multa / 100},{multa % 100:D2}");
}

// O operador ternário escolhe entre dois valores numa só expressão
string situacao = diasEmprestado > PrazoDias ? "pendente de pagamento" : "liberado";
Console.WriteLine($"Situação do leitor: {situacao}");

O if principal divide o programa em dois caminhos: devolução no prazo ou com atraso. Dentro do else aparece um segundo if, sem else, que só aplica o desconto quando o leitor é estudante; se a condição fosse falsa, o programa simplesmente pularia o bloco. Como 17 > 14, o caminho do atraso é o escolhido e a multa de 3 dias, já com o desconto, fica em R$ 2,25.

No fim, o operador ternário escolhe um entre dois textos numa única linha. Ele é ideal para atribuições simples como essa; quando cada caminho precisa de várias instruções, o if/else com blocos continua sendo mais legível.

02Conceito do boletim com else if e operadores lógicos

conceito.cs
// Boletim: else if testa as faixas em ordem; && e || combinam condições.
int nota = 68;          // de 0 a 100
int frequencia = 82;    // em porcentagem
bool fezRecuperacao = false;

// A primeira condição verdadeira vence; as demais nem são testadas
string conceito;
if (nota >= 90)
{
    conceito = "A";
}
else if (nota >= 75)
{
    conceito = "B";
}
else if (nota >= 60)
{
    conceito = "C";
}
else
{
    conceito = "D";
}

bool frequenciaOk = frequencia >= 75;

Console.WriteLine($"Nota: {nota} | Frequência: {frequencia}%");
Console.WriteLine($"Conceito: {conceito}");

// && exige as duas condições; ! inverte um bool
if (conceito != "D" && frequenciaOk)
{
    Console.WriteLine("Resultado: aprovado");
}
else if (frequenciaOk && !fezRecuperacao)
{
    Console.WriteLine("Resultado: em recuperação");
}
else
{
    Console.WriteLine("Resultado: reprovado");
}

// || basta uma das condições ser verdadeira
bool quadroDeHonra = nota >= 90 || (nota >= 85 && frequencia == 100);
Console.WriteLine($"Quadro de honra: {(quadroDeHonra ? "sim" : "não")}");

A cadeia de else if testa as faixas da maior para a menor. Com nota 68, as duas primeiras condições são falsas e nota >= 60 é a primeira verdadeira, então o conceito é C e o else final nem é avaliado. A ordem importa: se nota >= 60 viesse primeiro, uma nota 95 também cairia nela.

A segunda decisão combina condições: && exige conceito diferente de D e frequência suficiente, e !fezRecuperacao inverte o bool. Na última linha, || aceita qualquer uma das duas formas de entrar no quadro de honra; os parênteses deixam claro qual parte é avaliada junta.

03Turno de jogo com switch e switch expression

turno.cs
// Turno de um jogo de aventura: switch para comandos e para o dado.
string comando = "leste";
int dado = 17;          // resultado de um d20 já sorteado

// switch statement: compara o valor com cada case
switch (comando)
{
    case "norte":
    case "sul":
        Console.WriteLine("Você segue pela estrada principal.");
        break;
    case "leste":
        Console.WriteLine("Você entra na floresta sombria.");
        break;
    case "oeste":
        Console.WriteLine("Você volta para a vila.");
        break;
    default:
        Console.WriteLine("Comando desconhecido.");
        break;
}

// switch expression com padrões relacionais (C# 9+):
// os braços são testados em ordem e o primeiro que casar vence
string resultado = dado switch
{
    1 => "falha crítica",
    < 10 and > 1 => "errou o golpe",
    < 20 and >= 10 => "acertou o golpe",
    20 => "acerto crítico",
    _ => "valor de dado inválido"
};

Console.WriteLine($"Dado: {dado} -> {resultado}");

// O braço _ (descarte) cobre qualquer valor que sobrou
int bonusOuro = resultado switch
{
    "acerto crítico" => 50,
    "acertou o golpe" => 10,
    _ => 0
};
Console.WriteLine($"Ouro ganho no turno: {bonusOuro}");

O switch tradicional compara comando com cada case. Dois rótulos seguidos (case "norte": e case "sul":) compartilham o mesmo bloco, e cada bloco termina com break, obrigatório em C# para não "cair" no próximo caso. O default trata qualquer texto que não foi previsto.

A switch expression devolve um valor diretamente para a variável. Os padrões relacionais (< 10, >= 10) combinados com and definem faixas do dado, e o _ cobre o que sobrou, como um dado 0 ou 25. O segundo switch mostra que a técnica também funciona com textos, transformando o resultado em ouro ganho.


Agora é com você

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

01Alerta de estoque da lojaFácil

Escreva um programa que mostre o nome e a quantidade em estoque de um produto e classifique a situação: esgotado quando a quantidade for zero, repor estoque quando estiver abaixo do mínimo de 5 unidades e ok nos demais casos.

↳ Requisitos
  • Guardar o estoque mínimo numa const int com valor 5.
  • Usar uma única cadeia if / else if / else para as três situações.
  • Testar a quantidade 3 e produzir a saída idêntica à esperada.
código inicial
const int EstoqueMinimo = 5;

string produto = "Caneca de cerâmica";
int quantidade = 3;

Console.WriteLine($"Produto: {produto}");
Console.WriteLine($"Estoque: {quantidade} unidades");

// classifique a situação com if / else if / else
saída esperada
Produto: Caneca de cerâmica
Estoque: 3 unidades
Situação: repor estoque
Ver dica

Pense na ordem dos testes: zero também é menor que 5, então o caso esgotado precisa ser verificado antes do caso repor estoque.

Ver resposta
// Alerta de estoque: três situações possíveis, testadas em ordem
const int EstoqueMinimo = 5;

string produto = "Caneca de cerâmica";
int quantidade = 3;

Console.WriteLine($"Produto: {produto}");
Console.WriteLine($"Estoque: {quantidade} unidades");

if (quantidade == 0)
{
    Console.WriteLine("Situação: esgotado");
}
else if (quantidade < EstoqueMinimo)
{
    Console.WriteLine("Situação: repor estoque");
}
else
{
    Console.WriteLine("Situação: ok");
}

Passo a passo

  1. const int EstoqueMinimo = 5; dá nome ao limite; se a loja mudar a regra, basta alterar uma linha.
  2. As duas primeiras chamadas a Console.WriteLine mostram o produto e a quantidade com interpolação.
  3. if (quantidade == 0) trata o caso mais específico primeiro: produto esgotado.
  4. else if (quantidade < EstoqueMinimo) só é testado se a quantidade não for zero; com 3 unidades, essa condição é verdadeira.
  5. O else final cobre todo o resto, ou seja, 5 unidades ou mais.
  6. Como só um bloco da cadeia é executado, o programa imprime exatamente uma linha de situação.

Por que funciona

Numa cadeia de else if, as condições são avaliadas de cima para baixo e a primeira verdadeira encerra a decisão. Colocando o zero antes do limite mínimo, cada quantidade cai em uma única faixa, sem sobreposição.

Erros comuns

  • Inverter a ordem: testar quantidade < EstoqueMinimo primeiro faz o zero cair em repor estoque, e o esgotado nunca aparece. Teste o caso específico antes.
  • Usar = na comparação: if (quantidade = 0) gera o erro CS0029, pois a atribuição resulta em int, não em bool. Use ==.
  • Três if independentes: sem o else, uma quantidade 0 imprimiria esgotado e também repor estoque. O else if garante que só uma linha saia.

Outra forma de resolver

Com uma switch expression e padrões relacionais: string situacao = quantidade switch { 0 => "esgotado", < EstoqueMinimo => "repor estoque", _ => "ok" };. Fica mais compacta e é preferível quando cada caso só escolhe um valor; o if encadeado é melhor quando cada caminho executa várias instruções.

Saída esperada

Produto: Caneca de cerâmica
Estoque: 3 unidades
Situação: repor estoque

02Cupom e frete no fechamento do carrinhoMédio

Calcule o fechamento de um carrinho de R$ 140: o cupom PRIMEIRA dá 10% de desconto e o frete de R$ 20 é grátis para clientes VIP ou para compras de R$ 150 ou mais, já com o desconto. Mostre o subtotal, o desconto, o frete (o texto grátis ou o valor) e o total.

↳ Requisitos
  • Aplicar o desconto com um if que compara o cupom com ==.
  • Decidir o frete com uma única condição usando || e o operador ternário.
  • Guardar o valor mínimo para frete grátis e o valor do frete em constantes.
  • Usar clienteVip = false e produzir a saída idêntica à esperada.
código inicial
const int FreteGratisAPartirDe = 150;
const int ValorFrete = 20;

int subtotal = 140;
string cupom = "PRIMEIRA";
bool clienteVip = false;

// calcule o desconto do cupom

// decida o frete com || e o operador ternário

// imprima subtotal, desconto, frete e total
saída esperada
Subtotal: R$ 140
Desconto do cupom: R$ 14
Frete: R$ 20
Total a pagar: R$ 146
Ver dica

O frete grátis depende do valor já com desconto: calcule o total dos produtos antes de montar a condição do frete.

Ver resposta
// Fechamento do carrinho: cupom e frete decididos por condições
const int FreteGratisAPartirDe = 150;
const int ValorFrete = 20;

int subtotal = 140;          // em reais
string cupom = "PRIMEIRA";
bool clienteVip = false;

// Cupom válido dá 10% de desconto; qualquer outro texto não dá nada
int desconto = 0;
if (cupom == "PRIMEIRA")
{
    desconto = subtotal * 10 / 100;
}

int totalProdutos = subtotal - desconto;

// || : basta ser VIP ou atingir o valor mínimo para o frete ser grátis
int frete = clienteVip || totalProdutos >= FreteGratisAPartirDe ? 0 : ValorFrete;

Console.WriteLine($"Subtotal: R$ {subtotal}");
Console.WriteLine($"Desconto do cupom: R$ {desconto}");
Console.WriteLine($"Frete: {(frete == 0 ? "grátis" : $"R$ {frete}")}");
Console.WriteLine($"Total a pagar: R$ {totalProdutos + frete}");

Passo a passo

  1. As constantes guardam as regras da loja (mínimo para frete grátis e valor do frete) com nomes claros.
  2. desconto começa em 0 e o if (cupom == "PRIMEIRA") só o altera quando o cupom é válido; assim a variável sempre tem valor, com ou sem cupom.
  3. subtotal * 10 / 100 multiplica antes de dividir, o que dá 14 sem perder nada na divisão inteira.
  4. totalProdutos guarda o valor já com desconto, 126, que é o usado na regra do frete.
  5. clienteVip || totalProdutos >= FreteGratisAPartirDe é falso nos dois lados, então o ternário escolhe ValorFrete, ou seja, 20.
  6. Na impressão, outro ternário troca o 0 pelo texto grátis; como o frete é 20, aparece R$ 20.

Por que funciona

Cada regra do enunciado virou uma condição: o cupom é uma igualdade de texto e o frete grátis é um "ou" entre duas situações. Como || é verdadeiro quando qualquer lado é verdadeiro, ser VIP já bastaria; aqui nenhum lado vale e o frete é cobrado.

Erros comuns

  • Usar o subtotal na regra do frete: subtotal >= FreteGratisAPartirDe com R$ 140 dá o mesmo resultado aqui, mas falha com um carrinho de R$ 160 e cupom, que pagaria frete. Use o valor com desconto.
  • Trocar || por &&: clienteVip && totalProdutos >= 150 exigiria as duas coisas, e um cliente comum com compra alta pagaria frete.
  • Comparar a caixa do texto sem cuidado: cupom == "primeira" é falso, porque == entre strings diferencia maiúsculas de minúsculas. Use exatamente "PRIMEIRA".

Outra forma de resolver

O desconto também cabe num ternário: int desconto = cupom == "PRIMEIRA" ? subtotal * 10 / 100 : 0;. É preferível quando há um único cupom; com vários cupons, um switch sobre o texto do cupom fica mais organizado.

Saída esperada

Subtotal: R$ 140
Desconto do cupom: R$ 14
Frete: R$ 20
Total a pagar: R$ 146

03Ataques contra o chefe da masmorraDesafio

Escreva uma função local Atacar(string arma, int dado) que use uma switch expression para o dano base da arma e uma cadeia if / else if para o efeito do dado, imprima uma linha por ataque e devolva o dano. Chame-a para quatro ataques, some os danos e diga se o chefe, com 30 de vida, foi derrotado.

↳ Requisitos
  • Dano base pela switch expression: espada 8, machado 10, arco 6 e qualquer outra arma 2, usando _.
  • Efeito do dado em ordem: 1 erra (dano 0), 20 é crítico (dobro), 15 ou mais dá bônus (+2) e o resto é normal.
  • Atacar com machado (20), arco (1), espada (16) e cajado (9), acumulando o dano total com +=.
  • Decidir a última linha com o operador ternário comparando o total com uma const int de valor 30.
código inicial
const int VidaChefe = 30;

int total = 0;
// chame Atacar para os quatro ataques e acumule o dano

// imprima o dano total e se o chefe foi derrotado

static int Atacar(string arma, int dado)
{
    // dano base com switch expression

    // efeito do dado com if / else if

    return 0;
}
saída esperada
machado (dado 20): crítico, dano 20
arco (dado 1): errou, dano 0
espada (dado 16): bônus, dano 10
cajado (dado 9): normal, dano 2
Dano total: 32
O chefe foi derrotado!
Ver dica

O dado 20 também é maior ou igual a 15: teste o 20 antes do bônus, senão o crítico nunca acontece.

Ver resposta
// Ataques contra o chefe da masmorra: switch expression e if/else if
const int VidaChefe = 30;

int total = 0;
total += Atacar("machado", 20);
total += Atacar("arco", 1);
total += Atacar("espada", 16);
total += Atacar("cajado", 9);

Console.WriteLine($"Dano total: {total}");
Console.WriteLine(total >= VidaChefe ? "O chefe foi derrotado!" : "O chefe ainda está de pé.");

// Calcula, imprime e devolve o dano de um ataque
static int Atacar(string arma, int dado)
{
    // switch expression: cada arma tem um dano base
    int danoBase = arma switch
    {
        "espada" => 8,
        "machado" => 10,
        "arco" => 6,
        _ => 2    // arma desconhecida: dano mínimo
    };

    // Casos especiais primeiro: 1 e 20 vencem o teste do bônus
    int dano;
    string efeito;
    if (dado == 1)
    {
        dano = 0;
        efeito = "errou";
    }
    else if (dado == 20)
    {
        dano = danoBase * 2;
        efeito = "crítico";
    }
    else if (dado >= 15)
    {
        dano = danoBase + 2;
        efeito = "bônus";
    }
    else
    {
        dano = danoBase;
        efeito = "normal";
    }

    Console.WriteLine($"{arma} (dado {dado}): {efeito}, dano {dano}");
    return dano;
}

Passo a passo

  1. const int VidaChefe = 30; guarda a vida do chefe; a decisão final compara o total com esse nome, não com um número solto.
  2. Cada total += Atacar(...) chama a função, que imprime a linha do ataque e devolve o dano, somado ao acumulador.
  3. Dentro de Atacar, a switch expression transforma o nome da arma em dano base; _ => 2 cobre o cajado e qualquer arma não prevista.
  4. A cadeia if / else if testa primeiro os casos especiais: dado == 1 zera o dano e dado == 20 dobra o dano base.
  5. Só depois vem dado >= 15, que soma 2 de bônus; por isso o 16 da espada dá 8 + 2 = 10.
  6. O else final mantém o dano base, como no ataque com dado 9.
  7. dano e efeito são declarados sem valor e recebem um em todos os caminhos; o compilador confere isso e aceita a leitura depois da cadeia.
  8. Na linha final, o ternário compara 32 >= 30 e escolhe a mensagem de vitória.

Por que funciona

As duas decisões têm formatos diferentes, e cada uma usa a ferramenta adequada: a arma é um único valor mapeado para outro, trabalho típico da switch expression; o dado tem regras com prioridade, que a ordem do else if resolve, porque só a primeira condição verdadeira é executada. Somando os retornos (20 + 0 + 10 + 2), o total passa da vida do chefe.

Erros comuns

  • Testar o bônus antes do crítico: com else if (dado >= 15) antes de dado == 20, o machado faria 12 em vez de 20, e o total cairia para 24, sem derrotar o chefe.
  • Esquecer o _ na switch expression: o compilador avisa (CS8509) que nem todos os valores são tratados, e o cajado lançaria SwitchExpressionException ao rodar.
  • Deixar um caminho sem atribuição: se o else final for removido, ler dano gera o erro CS0165 (variável local não atribuída). Garanta um valor em todo caminho.
  • Esquecer o return dano; no fim da função: como ela foi declarada com retorno int, o compilador acusa o erro CS0161 (nem todos os caminhos de código retornam um valor).

Outra forma de resolver

O efeito do dado também pode virar uma switch expression com tupla: (dano, efeito) = dado switch { 1 => (0, "errou"), 20 => (danoBase * 2, "crítico"), >= 15 => (danoBase + 2, "bônus"), _ => (danoBase, "normal") };. Fica mais curta e o compilador ainda confere a cobertura dos casos; a cadeia de if é preferível quando cada caso precisa de várias instruções ou de mensagens extras.

Saída esperada

machado (dado 20): crítico, dano 20
arco (dado 1): errou, dano 0
espada (dado 16): bônus, dano 10
cajado (dado 9): normal, dano 2
Dano total: 32
O chefe foi derrotado!

Últimas lições

Ver o arquivo completo →