C++ switch

Um caixa eletrônico mostra: "1 - Saldo, 2 - Depósito, 3 - Sair". Dá para tratar cada opção com else if (if (opcao == 1) … else if (opcao == 2) …), mas, quando você compara uma variável com uma lista de valores exatos, o C++ tem uma estrutura feita sob medida: o switch ("chave", como a chave de trilhos que desvia o trem). Nesta aula você vai montar o menu do caixa, entender a famosa "queda" de um case para o outro e conhecer os erros que o switch provoca quando usado fora do que ele aceita.

Você vai aprender
  • As peças do switch: case, break e default.
  • O que acontece quando falta o break (o fall-through) e o aviso -Wimplicit-fallthrough.
  • Como juntar vários valores na mesma resposta e como cair de propósito com [[fallthrough]].
  • Por que o switch só aceita inteiros, char e enum (e o erro com std::string).
  • O erro jump to case label ao criar uma variável dentro de um case, e o conserto com { }.

Todos os programas desta aula foram compilados com o g++ 15.2 na máquina fictícia da Ana (usuária ana, computador notebook), com g++ -std=c++23 -Wall -Wextra arquivo.cpp -o programa.


A ideia: o elevador

No painel de um elevador, você aperta um número exato: 1, 2, 3. O elevador não pergunta "o andar é maior que 2?"; ele vai direto ao andar pedido. Se você apertar um botão que não existe, nada acontece (ou toca um alarme).

O switch é assim: ele olha o valor de uma variável e pula direto para o case ("caso") com aquele valor. Se nenhum case combinar, vai para o default ("padrão"), o "alarme" para valores inesperados.


1. O menu do caixa eletrônico

#include <iostream>

int main() {
    int opcao = 0;
    std::cout << "1-Saldo 2-Depósito 3-Sair: ";
    std::cin >> opcao;

    switch (opcao) {
        case 1:
            std::cout << "Seu saldo é R$ 150\n";
            break;
        case 2:
            std::cout << "Quanto quer depositar?\n";
            break;
        case 3:
            std::cout << "Até logo!\n";
            break;
        default:
            std::cout << "Opção inválida\n";
    }
}
switch (opcao)
A variável a ser examinada, entre parênteses. O switch calcula o valor dela uma vez.
{ … }
O corpo do switch, com todos os casos dentro de um único par de chaves.
case 1:
Um rótulo: "se o valor for 1, comece aqui". Termina com dois-pontos, não com ponto e vírgula. O valor precisa ser uma constante (um número escrito no código, um char como 'S', uma const).
break;
"Quebre": sai do switch e continua depois da }. Sem ele, o programa continua executando o case de baixo (seção 2).
default:
Para qualquer valor que não tenha case. É opcional, mas quase sempre útil. Como é o último, não precisa de break (muita gente coloca assim mesmo, por hábito).
$ g++ -std=c++23 -Wall -Wextra menu.cpp -o menu
$ ./menu
1-Saldo 2-Depósito 3-Sair: 1      « a Ana digitou 1 e Enter
Seu saldo é R$ 150
$ ./menu
1-Saldo 2-Depósito 3-Sair: 3
Até logo!
$ ./menu
1-Saldo 2-Depósito 3-Sair: 9
Opção inválida

O mesmo menu com else if funcionaria igual. A vantagem do switch é a leitura: fica claro que tudo depende de uma variável só, e cada valor aparece alinhado, como numa tabela. Para muitas opções, o compilador também consegue gerar um código que pula direto ao caso certo, sem testar um por um.


2. Sem break: o programa "cai" no próximo caso

Este é o comportamento mais estranho do switch, herdado da linguagem C. O case marca só onde começar; não marca onde parar. Quem para é o break. Veja o menu sem nenhum break, com a opção 1:

#include <iostream>

int main() {
    int opcao = 1;

    switch (opcao) {
        case 1:
            std::cout << "Seu saldo é R$ 150\n";
        case 2:
            std::cout << "Quanto quer depositar?\n";
        case 3:
            std::cout << "Até logo!\n";
        default:
            std::cout << "Opção inválida\n";
    }
}
$ g++ -std=c++23 -Wall -Wextra cai.cpp -o cai
cai.cpp: In function ‘int main()’:
cai.cpp:8:26: warning: this statement may fall through [-Wimplicit-fallthrough=]
    8 |             std::cout << "Seu saldo é R$ 150\n";
      |                          ^~~~~~~~~~~~~~~~~~~~~~
cai.cpp:9:9: note: here
    9 |         case 2:
      |         ^~~~
cai.cpp:10:26: warning: this statement may fall through [-Wimplicit-fallthrough=]
   10 |             std::cout << "Quanto quer depositar?\n";
      |                          ^~~~~~~~~~~~~~~~~~~~~~~~~~
cai.cpp:11:9: note: here
   11 |         case 3:
      |         ^~~~
cai.cpp:12:26: warning: this statement may fall through [-Wimplicit-fallthrough=]
   12 |             std::cout << "Até logo!\n";
      |                          ^~~~~~~~~~~~~
cai.cpp:13:9: note: here
   13 |         default:
      |         ^~~~~~~
$ ./cai
Seu saldo é R$ 150
Quanto quer depositar?   « caiu no case 2…
Até logo!                « …no case 3…
Opção inválida           « …e no default

O switch pulou para o case 1 e foi executando tudo para baixo, passando por cima dos outros rótulos como se eles não existissem. Isso se chama fall-through ("cair através").

warning: this statement may fall through
"Esta instrução pode cair para o próximo caso".
note: here
"Aqui": mostra o case em que ela vai cair.
[-Wimplicit-fallthrough=]
O nome do aviso. No g++, ele vem com o -Wextra: mais um motivo para nunca compilar sem ele.

No editor do guia (Clang), esse aviso não vem com -Wall -Wextra: é preciso pedir -Wimplicit-fallthrough. A caixa abaixo já pede. A mensagem do Clang é outra: warning: unannotated fall-through between switch labels [-Wimplicit-fallthrough] ("queda não anotada entre rótulos do switch").

Missão: com a opção 2, este menu mostra três mensagens e três avisos. Coloque os break que faltam para que apareça só Quanto quer depositar? e os avisos sumam.

#include <iostream>

int main() {
    int opcao = 2;

    switch (opcao) {
        case 1:
            std::cout << "Seu saldo é R$ 150\n";
        case 2:
            std::cout << "Quanto quer depositar?\n";
        case 3:
            std::cout << "Até logo!\n";
        default:
            std::cout << "Opção inválida\n";
    }
}
#include <iostream>

int main() {
    int opcao = 2;

    switch (opcao) {
        case 1:
            std::cout << "Seu saldo é R$ 150\n";
            break;
        case 2:
            std::cout << "Quanto quer depositar?\n";
            break;
        case 3:
            std::cout << "Até logo!\n";
            break;
        default:
            std::cout << "Opção inválida\n";
    }
}

3. Quando a queda é útil

Vários valores, a mesma resposta

Rótulos colados, sem nada entre eles, são o jeito de dizer "para este ou este valor":

#include <iostream>

int main() {
    char resposta = 's';

    switch (resposta) {
        case 'S':
        case 's':
            std::cout << "Confirmado\n";
            break;
        case 'N':
        case 'n':
            std::cout << "Cancelado\n";
            break;
        default:
            std::cout << "Responda S ou N\n";
    }
}
$ g++ -std=c++23 -Wall -Wextra grupos.cpp -o grupos
$ ./grupos
Confirmado               « 's' minúsculo caiu no mesmo bloco do 'S'

Aqui não há aviso: um case vazio caindo no próximo é obviamente proposital. Repare nas aspas simples: 'S' é um caractere (char, aula Caracteres), que por dentro é um número, e por isso o switch o aceita.

Cair de propósito: [[fallthrough]] C++17

Às vezes você quer mesmo que um caso com código continue no de baixo. Por exemplo, nos planos de uma empresa de nuvem, cada plano inclui tudo o que os planos menores têm:

#include <iostream>

int main() {
    int plano = 2;

    std::cout << "Seu plano inclui:\n";
    switch (plano) {
        case 3:
            std::cout << "- suporte 24h\n";
            [[fallthrough]];
        case 2:
            std::cout << "- backup diário\n";
            [[fallthrough]];
        case 1:
            std::cout << "- 10 GB de espaço\n";
            break;
        default:
            std::cout << "- nada (plano inválido)\n";
    }
}
$ g++ -std=c++23 -Wall -Wextra proposital.cpp -o proposital
$ ./proposital
Seu plano inclui:
- backup diário          « começou no case 2…
- 10 GB de espaço        « …caiu no case 1 e parou no break
[[fallthrough]];
Um atributo (as instruções entre colchetes duplos são recados para o compilador). Ele diz: "a queda aqui é proposital". O aviso some, e quem lê o código entende que não é esquecimento. Vai sempre logo antes do próximo case, com ;.

Antes do C++17, os programadores escreviam um comentário // fall through no mesmo lugar; o g++ ainda reconhece esse comentário e não avisa. Prefira o atributo, que é padrão e funciona em todo compilador.


4. O que o switch aceita (e o que não aceita)

A variável do switch precisa ser de um tipo inteiro: int, long, char, bool, ou um enum. E os valores dos case precisam ser constantes conhecidas na compilação.

Texto não: switch com std::string

#include <iostream>
#include <string>

int main() {
    std::string fruta = "maçã";

    switch (fruta) {
        case "maçã":
            std::cout << "R$ 4,50 o kg\n";
            break;
    }
}
$ g++ -std=c++23 -Wall -Wextra texto.cpp -o texto
texto.cpp: In function ‘int main()’:
texto.cpp:7:13: error: switch quantity not an integer
    7 |     switch (fruta) {
      |             ^~~~~

"A quantidade do switch não é um inteiro". Diferente de Java, C# ou JavaScript, o switch do C++ não compara textos. Para escolher por texto, use if (fruta == "maçã") … else if …. (Quando você conhecer o std::map, no Nível 2, vai ver um jeito ainda melhor para tabelas de preços.)

Número com vírgula também não

$ g++ -std=c++23 -Wall -Wextra dbl.cpp -o dbl      « double nota = 7.5; switch (nota) { case 7.5: … }
dbl.cpp: In function ‘int main()’:
dbl.cpp:5:13: error: switch quantity not an integer
    5 |     switch (nota) {
      |             ^~~~

Notas, preços e outras faixas são trabalho do else if, com >= e <.

O valor do case precisa ser constante

#include <iostream>

int main() {
    int senha = 1234;
    int digitado = 1234;
    switch (digitado) {
        case senha:
            std::cout << "Entrou\n";
            break;
    }
}
$ g++ -std=c++23 -Wall -Wextra variavelcase.cpp -o variavelcase
variavelcase.cpp: In function ‘int main()’:
variavelcase.cpp:7:14: error: the value of ‘senha’ is not usable in a constant expression
    7 |         case senha:
      |              ^~~~~
variavelcase.cpp:4:9: note: ‘int senha’ is not const
    4 |     int senha = 1234;
      |         ^~~~~
…(a mesma mensagem aparece duas vezes)…

"O valor de senha não pode ser usado numa expressão constante", e a nota explica: "senha não é const". Uma variável comum pode mudar durante o programa, e os rótulos do switch são fixados na compilação. Com const int senha = 1234; (aula Constantes), compila e mostra Entrou. Se o valor só é conhecido ao rodar (digitado pelo usuário, lido de um arquivo), use if.

O switch com enum confere se você esqueceu algum caso

Um enum class (aula Enums) é um tipo com uma lista fechada de valores, como as cores do semáforo. Com ele, o -Wall avisa quando um valor ficou sem case:

#include <iostream>

enum class Cor { Verde, Amarelo, Vermelho };

int main() {
    Cor semaforo = Cor::Amarelo;
    switch (semaforo) {
        case Cor::Verde:
            std::cout << "Siga\n";
            break;
        case Cor::Vermelho:
            std::cout << "Pare\n";
            break;
    }
}
$ g++ -std=c++23 -Wall -Wextra enum.cpp -o enum
enum.cpp: In function ‘int main()’:
enum.cpp:7:12: warning: enumeration value ‘Amarelo’ not handled in switch [-Wswitch]
    7 |     switch (semaforo) {
      |            ^
$ ./enum
                         « Amarelo não tem case: nada aparece

"O valor Amarelo da enumeração não é tratado no switch". Essa conferência é um dos melhores motivos para usar switch com enum: se alguém acrescentar uma cor nova ao enum, o compilador aponta cada switch que precisa ser atualizado.


5. Criar uma variável dentro de um case

Parece natural criar uma variável dentro de um caso. Mas:

#include <iostream>

int main() {
    int opcao = 2;
    switch (opcao) {
        case 1:
            int saldo = 150;
            std::cout << "Saldo: " << saldo << "\n";
            break;
        case 2:
            std::cout << "Depósito\n";
            break;
    }
}
$ g++ -std=c++23 -Wall -Wextra declarar.cpp -o declarar
declarar.cpp: In function ‘int main()’:
declarar.cpp:10:14: error: jump to case label
   10 |         case 2:
      |              ^
declarar.cpp:7:17: note:   crosses initialization of ‘int saldo’
    7 |             int saldo = 150;
      |                 ^~~~~
error: jump to case label
"Salto para um rótulo de case". O problema está no salto para o case 2.
note: crosses initialization of ‘int saldo’
"Atravessa a inicialização de saldo".

O motivo: o corpo do switch é um bloco só, e os case são apenas pontos de entrada nele. A variável saldo existe do ponto em que foi criada até a } do switch, o que inclui o case 2. Quando a opção é 2, o programa pula direto para o case 2, por cima da linha int saldo = 150;: saldo existiria ali, mas sem valor. O C++ proíbe esse salto.

Conserto: coloque o código do case dentro de chaves próprias. Assim, saldo só existe dentro delas:

#include <iostream>

int main() {
    int opcao = 2;
    switch (opcao) {
        case 1: {
            int saldo = 150;
            std::cout << "Saldo: " << saldo << "\n";
            break;
        }
        case 2:
            std::cout << "Depósito\n";
            break;
    }
}
$ g++ -std=c++23 -Wall -Wextra declarar2.cpp -o declarar2
$ ./declarar2
Depósito

No editor do guia, o Clang diz o mesmo com outras palavras: error: cannot jump from switch statement to this case label ("não é possível pular do switch para este rótulo") e note: jump bypasses variable initialization ("o salto passa por cima da inicialização de uma variável").


Formas diferentes + qual usar

SituaçãoUsePor quê
Uma variável inteira, char ou enum comparada com valores exatos (menu, opção, letra)switchMais legível; com enum, o compilador confere se faltou um caso.
Faixas (nota >= 7), doubleelse ifO switch só compara igualdade, e não aceita double.
Textos (std::string)else if com ==O switch não aceita texto.
Valores conhecidos só ao rodarelse ifOs case precisam de constantes.
Condições variadas, com && e ||else ifCada case só testa "é igual a este valor?".
E case 1 ... 5:?

Você pode encontrar na internet case 1 ... 5: (uma faixa de valores). Isso não é C++ padrão: é uma extensão do g++ e do Clang, que não funciona em outros compiladores, como o Visual Studio. Com a opção -Wpedantic (que avisa sobre tudo o que foge do padrão), o g++ denuncia: warning: range expressions in switch statements are non-standard [-Wpedantic] ("faixas em switch não são padrão"). Para faixas, use else if.


Quando dá errado

warning: this statement may fall through [-Wimplicit-fallthrough=]

O que significa: um case com código termina sem break, e o programa vai continuar no case de baixo.

Como consertar: se foi esquecimento (quase sempre), ponha o break;. Se a queda é proposital, escreva [[fallthrough]]; antes do próximo case.

error: switch quantity not an integer

O que significa: a variável do switch é std::string, double ou outro tipo não inteiro.

Como consertar: use if/else if. Se for um texto de uma letra só, use um char.

error: the value of ‘x’ is not usable in a constant expression

O que significa: um case usa uma variável comum.

Como consertar: torne-a const (se o valor é fixo) ou troque o switch por if.

error: duplicate case value
$ g++ -std=c++23 -Wall -Wextra duplicado.cpp -o duplicado
duplicado.cpp: In function ‘int main()’:
duplicado.cpp:9:9: error: duplicate case value
    9 |         case 1:
      |         ^~~~
duplicado.cpp:6:9: note: previously used here
    6 |         case 1:
      |         ^~~~

O que significa: "valor de case duplicado"; a nota mostra onde ele já tinha sido usado ("usado antes aqui"). Normalmente é um erro de copiar e colar.

Como consertar: cada valor só pode aparecer em um case. Corrija o número ou junte os dois blocos.

error: jump to case label + crosses initialization of …

O que significa: uma variável foi criada dentro de um case, sem chaves, e um case abaixo dela "pularia" a criação.

Como consertar: envolva o código do case em { … }, com o break dentro.

warning: enumeration value ‘…’ not handled in switch [-Wswitch]

O que significa: um valor do enum ficou sem case.

Como consertar: acrescente o case que falta. Evite pôr um default só para calar o aviso: você perde a conferência quando o enum ganhar valores novos.


Referência rápida

EscritaO que faz
switch (x) { … }Pula para o case com o valor de x (inteiro, char, bool ou enum)
case 3:Ponto de entrada para o valor 3 (constante, termina com :)
break;Sai do switch
default:Para os valores sem case
case 'S': case 's':Dois valores, mesmo código
[[fallthrough]];Queda proposital para o próximo case (C++17)
case 1: { int v = …; break; }Variável própria do case
switch (int v = f(); v) { … }switch com inicializador (C++17), como o do if

Exercícios

1. Complete o switch

Complete as palavras que faltam:

(dia) { 1: std::cout << "Domingo"; ; : std::cout << "Outro dia"; }

2. Com x = 2, o que mostra: switch (x) { case 1: std::cout << "a"; case 2: std::cout << "b"; case 3: std::cout << "c"; break; default: std::cout << "d"; }?

3. Qual destas variáveis não pode ir num switch?

4. Como consertar o erro jump to case label?

Missão final: a calculadora

Missão: a calculadora lê uma conta como 8 * 5 (a caixa já digita isso para você) e só sabe somar. Acrescente os casos '-', '*' e '/', cada um com seu break. Com 8 * 5, deve aparecer Resultado: 40. Bônus: faça a letra 'x' também multiplicar, juntando dois case.

#include <iostream>

int main() {
    double a = 0, b = 0;
    char op = ' ';
    std::cin >> a >> op >> b;

    switch (op) {
        case '+':
            std::cout << "Resultado: " << a + b << "\n";
            break;
        default:
            std::cout << "Operação inválida: " << op << "\n";
    }
}
#include <iostream>

int main() {
    double a = 0, b = 0;
    char op = ' ';
    std::cin >> a >> op >> b;

    switch (op) {
        case '+':
            std::cout << "Resultado: " << a + b << "\n";
            break;
        case '-':
            std::cout << "Resultado: " << a - b << "\n";
            break;
        case '*':
        case 'x':
            std::cout << "Resultado: " << a * b << "\n";
            break;
        case '/':
            std::cout << "Resultado: " << a / b << "\n";
            break;
        default:
            std::cout << "Operação inválida: " << op << "\n";
    }
}
$ ./calc
8 x 5                    « no terminal, a Ana testou o bônus
Resultado: 40
$ ./calc
9 / 2
Resultado: 4.5
$ ./calc
9 % 2
Operação inválida: %

Resumo

  • switch (x) pula direto para o case com o valor de x; o default pega o resto.
  • O case só marca onde começar. Quem para é o break; sem ele, o programa cai no próximo caso (-Wimplicit-fallthrough, ligado pelo -Wextra no g++).
  • Rótulos colados (case 'S': case 's':) dividem o mesmo código. Queda proposital com código: [[fallthrough]];.
  • Só inteiros, char, bool e enum; os case precisam de constantes. std::string e double dão switch quantity not an integer.
  • Variável dentro de um case: use chaves, ou vem jump to case label.
  • Faixas, textos e condições compostas: else if.