C switch
A lanchonete da escola tem um cardápio numerado: 1 é o X-salada, 2 é o pastel, 3 é o suco. Com o que você já sabe, daria para escrever isso com else if: "se for 1…, senão, se for 2…, senão, se for 3…". Funciona, mas repete a mesma pergunta várias vezes. O switch ("chave", como a chave seletora de um aparelho) foi feito para esse caso: uma variável comparada com vários valores fixos. Nesta aula você vai aprender o switch, o case, o default e, principalmente, o break, cujo esquecimento é um dos erros mais clássicos do C.
- Como escrever um
switchcomcase,breakedefault. - O que acontece quando falta o
break, e o aviso-Wimplicit-fallthrough. - Como juntar vários
caseno mesmo bloco ('s'e'S'; os meses de 31 dias). - O que o
switchnão aceita: faixas,double, textos, variáveis nocaseecaserepetido, com os erros reais. - Como declarar variáveis dentro de um
caseC23. - Quando usar
switche quando usarelse if.
Todos os programas desta aula foram compilados com o gcc 15.2 na máquina fictícia da Ana (usuária ana, computador notebook), na pasta ~/c, com o comando padrão do guia: gcc -std=c23 -Wall -Wextra arquivo.c -o programa. As saídas dos programas foram iguais no editor do guia; os avisos e erros do Clang, que mudam de palavras, estão indicados.
A ideia: o painel do elevador
No elevador, você aperta o botão do 3º andar e ele vai direto para o 3º andar. Ele não pergunta "é o térreo? não. É o 1º? não. É o 2º? não. É o 3º? sim". Cada botão leva a um lugar.
O switch funciona assim. Ele olha o valor da variável uma vez e pula direto para o case (o "caso", o botão) com aquele valor. Se nenhum botão combina, ele vai para o default ("padrão"), o "nenhuma das anteriores". E o break ("interromper") é o "chegamos, pode sair do elevador".
1. O primeiro switch
#include <stdio.h>
int main(void) {
int opcao = 0;
printf("1 - X-salada\n");
printf("2 - Pastel\n");
printf("3 - Suco\n");
printf("Escolha: ");
if (scanf("%d", &opcao) != 1) {
return 1;
}
switch (opcao) {
case 1:
printf("X-salada: R$ 18.00\n");
break;
case 2:
printf("Pastel: R$ 9.00\n");
break;
case 3:
printf("Suco: R$ 7.00\n");
break;
default:
printf("Opção %d não existe.\n", opcao);
break;
}
printf("Obrigada pela preferência!\n");
return 0;
}
$ gcc -std=c23 -Wall -Wextra lanche.c -o lanche $ ./lanche 1 - X-salada 2 - Pastel 3 - Suco Escolha: 2 Pastel: R$ 9.00 « pulou direto para o case 2 Obrigada pela preferência! « depois do break, o programa continua depois do switch $ ./lanche 1 - X-salada 2 - Pastel 3 - Suco Escolha: 7 Opção 7 não existe. « nenhum case é 7: foi para o default Obrigada pela preferência!
- switch (opcao)
- "Conforme o valor de
opcao…". Entre os parênteses vai a expressão que será comparada. Ela precisa ser um inteiro:int,char,long, um enum… - { … }
- As chaves do
switchguardam todos os casos. Elas são obrigatórias. - case 1:
- "No caso de ser 1, comece aqui". Depois do
casevem um valor fixo (um número ou um caractere como's') e dois-pontos, não ponto e vírgula. Ocaseé só um rótulo, um ponto de entrada: ele não tem chaves próprias. - break;
- "Terminei este caso: saia do
switch". O programa continua na primeira linha depois da chave que fecha oswitch. A seção 2 mostra o que acontece sem ele. - default:
- "Se não for nenhum dos anteriores". É opcional, mas quase sempre vale a pena: aqui ele avisa a pessoa que digitou uma opção que não existe. O
breakdo último caso não é obrigatório, mas o guia sempre coloca: se alguém acrescentar outrocaseembaixo, ele já está lá. - o recuo
- Os
caseficam 4 espaços para dentro doswitch, e o código de cada caso, mais 4. O compilador ignora; é para você enxergar onde cada caso começa. (Alguns projetos, como o Linux, deixam ocasena mesma coluna doswitch. Os dois estilos são comuns.)
Experimente: clique em Rodar e digite uma opção no terminal da caixa. Teste 1, 2, 3 e um número que não está no cardápio. Depois acrescente a opção 4 - Água, que custa R$ 4.00: uma linha a mais no cardápio e um case novo, com o seu break.
#include <stdio.h>
int main(void) {
int opcao = 0;
printf("1 - X-salada\n");
printf("2 - Pastel\n");
printf("3 - Suco\n");
printf("Escolha: ");
if (scanf("%d", &opcao) != 1) {
return 1;
}
switch (opcao) {
case 1:
printf("X-salada: R$ 18.00\n");
break;
case 2:
printf("Pastel: R$ 9.00\n");
break;
case 3:
printf("Suco: R$ 7.00\n");
break;
default:
printf("Opção %d não existe.\n", opcao);
break;
}
printf("Obrigada pela preferência!\n");
return 0;
}
2. O break esquecido
O case diz onde entrar, mas não onde sair. Sem break, o programa continua descendo e roda o código dos casos de baixo também, sem conferir o valor deles. Isso se chama queda (em inglês, fall through, "cair através"):
#include <stdio.h>
int main(void) {
int opcao = 2;
switch (opcao) {
case 1:
printf("X-salada: R$ 18.00\n");
break;
case 2:
printf("Pastel: R$ 9.00\n");
case 3:
printf("Suco: R$ 7.00\n");
default:
printf("Opção %d não existe.\n", opcao);
}
return 0;
}
$ gcc -std=c23 -Wall -Wextra sembreak.c -o sembreak
sembreak.c: In function ‘main’:
sembreak.c:10:13: warning: this statement may fall through [-Wimplicit-fallthrough=]
10 | printf("Pastel: R$ 9.00\n");
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~
sembreak.c:11:9: note: here
11 | case 3:
| ^~~~
sembreak.c:12:13: warning: this statement may fall through [-Wimplicit-fallthrough=]
12 | printf("Suco: R$ 7.00\n");
| ^~~~~~~~~~~~~~~~~~~~~~~~~
sembreak.c:13:9: note: here
13 | default:
| ^~~~~~~
$ ./sembreak
Pastel: R$ 9.00 « entrou no case 2…
Suco: R$ 7.00 « …caiu no case 3…
Opção 2 não existe. « …e no default. A Ana pediu só um pastel!
- O
switchpulou para ocase 2e mostrou o pastel. - Não havia
break. Ocase 3:é só um rótulo, então o programa passou por ele e rodou oprintfdo suco. - Também não havia
breakdepois do suco, e o programa caiu nodefault.
O aviso diz: "esta instrução pode cair (no próximo caso)", e a nota here ("aqui") mostra onde. Ele vem do -Wextra: só com -Wall, o gcc compila calado. No editor do guia, o Clang não dá esse aviso nem com -Wall -Wextra; é preciso pedir -Wimplicit-fallthrough, e aí ele diz unannotated fall-through between switch labels ("queda sem anotação entre rótulos do switch") e sugere o conserto: insert 'break;' to avoid fall-through.
Missão: esta caixa compila com -Wimplicit-fallthrough, então o Clang avisa sobre as quedas. Ponha os break que faltam para a Ana receber só o pastel, sem nenhum aviso. A saída inteira deve ser:
Pastel: R$ 9.00
#include <stdio.h>
int main(void) {
int opcao = 2;
switch (opcao) {
case 1:
printf("X-salada: R$ 18.00\n");
break;
case 2:
printf("Pastel: R$ 9.00\n");
case 3:
printf("Suco: R$ 7.00\n");
default:
printf("Opção %d não existe.\n", opcao);
}
return 0;
}
#include <stdio.h>
int main(void) {
int opcao = 2;
switch (opcao) {
case 1:
printf("X-salada: R$ 18.00\n");
break;
case 2:
printf("Pastel: R$ 9.00\n");
break;
case 3:
printf("Suco: R$ 7.00\n");
break;
default:
printf("Opção %d não existe.\n", opcao);
break;
}
return 0;
}
Às vezes cair no caso de baixo é exatamente o que se quer (quem chega ao nível 3 de um jogo ganha os prêmios dos níveis 3, 2 e 1). Desde o C23, você marca isso com [[fallthrough]]; no fim do caso, e o compilador para de avisar. A aula Atributos mostra o exemplo completo. Sem a marca, quem ler o código vai achar que você esqueceu o break.
3. Vários case para o mesmo bloco
Há um tipo de queda que é comum e não gera aviso: vários case seguidos, sem nada entre eles. Todos entram no mesmo bloco. Serve para aceitar a resposta em minúscula e em maiúscula:
#include <stdio.h>
int main(void) {
char resposta = '?';
printf("Quer o recibo? (s/n) ");
if (scanf(" %c", &resposta) != 1) {
return 1;
}
switch (resposta) {
case 's':
case 'S':
printf("Imprimindo o recibo...\n");
break;
case 'n':
case 'N':
printf("Tudo bem, sem recibo.\n");
break;
default:
printf("Não entendi '%c'.\n", resposta);
break;
}
return 0;
}
$ gcc -std=c23 -Wall -Wextra resposta.c -o resposta « nenhum aviso $ ./resposta Quer o recibo? (s/n) S Imprimindo o recibo... « 'S' entrou pelo case 'S' $ ./resposta Quer o recibo? (s/n) n Tudo bem, sem recibo. $ ./resposta Quer o recibo? (s/n) talvez Não entendi 't'. « o %c leu só a primeira letra
- case 's':
- Um caractere entre aspas simples é um número para o C (o código da letra; aula Caracteres), então pode ser um
case.'s'e'S'são valores diferentes (115 e 83). - case 's': case 'S':
- Dois rótulos para o mesmo lugar. Se a resposta for
's', o programa entra no primeiro, passa pelo segundo (que não tem código) e roda oprintf. O gcc não avisa, porque não há nenhuma instrução "caindo". - " %c"
- O espaço antes do
%cpula Enters e espaços que sobraram (aula Entrada com scanf).
Os rótulos podem ficar na mesma linha: case 4: case 6: case 9: case 11:. É assim que a missão final desta aula vai separar os meses de 30 e de 31 dias.
4. Sem default
O default é opcional. Sem ele, se nenhum case combina, o switch simplesmente não faz nada, e o programa segue:
#include <stdio.h>
int main(void) {
int opcao = 7;
switch (opcao) {
case 1:
printf("X-salada: R$ 18.00\n");
break;
case 2:
printf("Pastel: R$ 9.00\n");
break;
}
printf("Obrigada pela preferência!\n");
return 0;
}
$ gcc -std=c23 -Wall -Wextra lanche2.c -o lanche2 $ ./lanche2 Obrigada pela preferência! « o 7 não combinou com nada, e ninguém foi avisado
Às vezes é isso mesmo que você quer. Mas, quando o valor vem de quem usa o programa, um default que avisa "opção inválida" evita que um erro de digitação passe calado. Com um enum, o compilador consegue até avisar quando falta um caso (-Wswitch), como mostra aquela aula.
5. O que o switch não aceita
O switch é rápido e organizado porque só compara igualdade com valores inteiros fixos, conhecidos na hora de compilar. Tudo o que foge disso dá erro de compilação.
Faixas de valores
$ cat faixa.c
#include <stdio.h>
int main(void) {
int nota = 8;
switch (nota) {
case nota >= 7:
printf("Aprovada\n");
break;
}
return 0;
}
$ gcc -std=c23 -Wall -Wextra faixa.c -o faixa
faixa.c: In function ‘main’:
faixa.c:6:9: error: case label does not reduce to an integer constant
6 | case nota >= 7:
| ^~~~
"O rótulo do case não se reduz a uma constante inteira." Depois do case só pode vir um valor fixo, e nota >= 7 depende da variável. Perguntas como "maior que", "entre 7 e 10" são trabalho do else if. (No Clang: expression is not an integer constant expression.)
case 0 ... 11:
O gcc e o Clang aceitam uma extensão (um recurso que não faz parte do C padrão): case 0 ... 11:, com espaços em volta dos três pontos, vale para todos os números de 0 a 11. Com -Wpedantic, que avisa sobre o que não é padrão, o gcc 15 diz ISO C does not support range expressions in switch statements before C2Y: o recurso entrou no C2y, a próxima versão do C. Por enquanto, se o seu código precisa compilar em qualquer compilador, use else if.
Números com vírgula e textos
$ gcc -std=c23 -Wall -Wextra preco.c -o preco
preco.c: In function ‘main’:
preco.c:5:13: error: switch quantity not an integer
5 | switch (preco) {
| ^~~~~
preco.c:6:9: error: case label does not reduce to an integer constant
6 | case 9.0:
| ^~~~
$ gcc -std=c23 -Wall -Wextra texto.c -o texto
texto.c: In function ‘main’:
texto.c:5:13: error: switch quantity not an integer
5 | switch (nome) {
| ^~~~
texto.c:6:9: error: pointers are not permitted as case values
6 | case "pastel":
| ^~~~
No preco.c, preco é um double; no texto.c, nome é um texto (char nome[] = "pastel";). "A quantidade do switch não é um inteiro." No segundo, o case "pastel": também é recusado: "ponteiros não são permitidos como valores de case" (um texto entre aspas duplas é, por baixo, um endereço; aula Strings). Para textos, compare com strcmp num else if (aula Funções de String). No Clang: statement requires expression of integer type ('double' invalid).
Variáveis no case e case repetido
$ gcc -std=c23 -Wall -Wextra variavel.c -o variavel
variavel.c: In function ‘main’:
variavel.c:7:9: error: case label does not reduce to an integer constant
7 | case promocao:
| ^~~~
$ gcc -std=c23 -Wall -Wextra duplo.c -o duplo
duplo.c: In function ‘main’:
duplo.c:9:9: error: duplicate case value
9 | case 1:
| ^~~~
duplo.c:6:9: note: previously used here
6 | case 1:
| ^~~~
No primeiro, promocao é uma variável comum (int promocao = 3;): o mesmo erro da faixa. O valor do case precisa ser conhecido na hora de compilar: um número, um caractere, um #define, um valor de enum ou uma constante constexpr C23 (aula Constantes). No segundo, o case 1 aparece duas vezes: "valor de case repetido", e a nota mostra onde ele foi usado antes. O elevador não pode ter dois botões para o mesmo andar.
6. Variáveis dentro de um case
Um caso pode precisar de uma variável só dele:
#include <stdio.h>
int main(void) {
int opcao = 1;
switch (opcao) {
case 1:
int quantidade = 2;
printf("%d X-saladas: R$ %d.00\n", quantidade, quantidade * 18);
break;
case 2:
printf("Pastel\n");
break;
}
return 0;
}
$ gcc -std=c23 -Wall -Wextra declara.c -o declara
$ ./declara
2 X-saladas: R$ 36.00
$ gcc -std=c17 -Wall -Wextra -Wpedantic declara.c -o declara
declara.c: In function ‘main’:
declara.c:7:13: warning: a label can only be part of a statement and a declaration is not a statement [-Wfree-labels]
7 | int quantidade = 2;
| ^~~
No C23, uma declaração pode vir logo depois de um rótulo como case 1:. Até o C17, não podia: "um rótulo só pode fazer parte de uma instrução, e uma declaração não é uma instrução". Compiladores mais antigos dão erro nesse ponto, e você vai encontrar essa mensagem em muitos fóruns. No editor, com -std=c17, o Clang avisa label followed by a declaration is a C23 extension.
Mesmo no C23, há uma pegadinha: o switch inteiro é um bloco só, então quantidade continua existindo nos casos de baixo, mas sem valor, porque o programa pulou a linha que a inicializa. Neste pula.c, o case 2 usa a variável do case 1:
$ cat pula.c
#include <stdio.h>
int main(void) {
int opcao = 2;
switch (opcao) {
case 1:
int quantidade = 2;
printf("%d\n", quantidade);
break;
case 2:
printf("%d\n", quantidade);
break;
}
return 0;
}
$ gcc -std=c23 -Wall -Wextra pula.c -o pula
pula.c: In function ‘main’:
pula.c:11:13: warning: ‘quantidade’ is used uninitialized [-Wuninitialized]
11 | printf("%d\n", quantidade);
| ^~~~~~~~~~~~~~~~~~~~~~~~~~
pula.c:7:17: note: ‘quantidade’ was declared here
7 | int quantidade = 2;
| ^~~~~~~~~~
$ ./pula
0 « um valor qualquer: o "= 2" nunca rodou
"quantidade é usada sem valor inicial." Desta vez saiu 0, mas poderia ser qualquer número (aula Variáveis). O jeito que funciona em qualquer versão, e o que o guia recomenda, é dar chaves ao caso:
case 1: {
int quantidade = 2;
printf("%d X-saladas: R$ %d.00\n", quantidade, quantidade * 18);
break;
}
Com as chaves, quantidade só existe dentro do case 1 (aula Escopo). Se outro caso tentar usá-la, o erro aparece na compilação (‘quantidade’ undeclared), em vez de um valor qualquer na execução.
switch ou else if?
O mesmo cardápio com else if:
#include <stdio.h>
int main(void) {
int opcao = 2;
if (opcao == 1) {
printf("X-salada: R$ 18.00\n");
} else if (opcao == 2) {
printf("Pastel: R$ 9.00\n");
} else if (opcao == 3) {
printf("Suco: R$ 7.00\n");
} else {
printf("Opção %d não existe.\n", opcao);
}
return 0;
}
$ gcc -std=c23 -Wall -Wextra lanche3.c -o lanche3 $ ./lanche3 Pastel: R$ 9.00
| Situação | Use | Por quê |
|---|---|---|
Uma variável inteira (ou char, ou enum) comparada com vários valores fixos: menus, letras, códigos | switch | A variável aparece uma vez só, cada caso fica bem separado, e com enum o compilador avisa se faltar um caso. |
Faixas: nota >= 9, idade < 12 | else if | O switch só compara igualdade. |
| Números com vírgula ou textos | else if (com strcmp para textos) | O switch só aceita inteiros. |
Condições com variáveis diferentes: saldo > 0 && idade >= 18 | else if | O switch olha uma expressão só. |
| Dois caminhos | if / else | Um switch para dois casos é exagero. |
Quando dá errado
warning: this statement may fall through [-Wimplicit-fallthrough=]
O que significa: um caso termina sem break, e o programa vai cair no caso de baixo. Vem do -Wextra. No Clang, só com -Wimplicit-fallthrough: unannotated fall-through between switch labels.
Como consertar: ponha break; no fim do caso. Se a queda é de propósito, marque com [[fallthrough]];.
O que significa: o mesmo problema do aviso acima, num programa compilado sem -Wextra (ou no editor, sem -Wimplicit-fallthrough).
Como consertar: confira se todo caso termina com break, e compile sempre com -Wall -Wextra.
error: case label does not reduce to an integer constant
O que significa: depois do case há algo que não é um valor inteiro fixo: uma comparação (case nota >= 7:), uma variável (case promocao:) ou um número com vírgula (case 9.0:). No Clang: expression is not an integer constant expression.
Como consertar: use um número, um caractere, um #define, um enum ou um constexpr. Para faixas e variáveis, troque por else if.
error: switch quantity not an integer
O que significa: a expressão do switch (…) é um double, um texto ou outra coisa que não é inteira. No Clang: statement requires expression of integer type.
Como consertar: use else if, ou guarde o valor num inteiro se ele for mesmo inteiro (centavos em vez de reais, por exemplo).
error: duplicate case value
O que significa: dois case com o mesmo valor. Pode ser um erro de digitação, ou dois nomes de #define/enum que valem o mesmo número. No Clang: duplicate case value '1'.
Como consertar: apague um deles, ou junte os dois blocos num só.
a label can only be part of a statement and a declaration is not a statement
O que significa: uma variável foi declarada logo depois de um case, num compilador ou padrão anterior ao C23 (no gcc 15 com -std=c17 -Wpedantic, é um aviso; em compiladores mais antigos, um erro).
Como consertar: dê chaves ao caso: case 1: { int quantidade = 2; … break; }.
Referência rápida
| Parte | O que faz |
|---|---|
switch (expr) { … } | Calcula expr (um inteiro) uma vez e pula para o case com esse valor. |
case 3: / case 'a': | Ponto de entrada para aquele valor fixo. Dois-pontos no fim. |
case 'a': case 'A': | Vários valores para o mesmo bloco. |
break; | Sai do switch. Sem ele, o programa cai no caso de baixo. |
default: | Para qualquer valor sem case. Opcional. |
[[fallthrough]]; | Marca uma queda proposital C23 (aula Atributos). |
case 1: { … } | Chaves para declarar variáveis só daquele caso. |
| não aceita | faixas (>=), double, textos, variáveis no case, case repetido. |
Exercícios
1. Sair do caso
Complete com a palavra que encerra o caso e sai do switch:
2. Nenhuma das anteriores
Complete com a palavra do caso que pega todos os outros valores:
3. A pontuação
Complete o rótulo do caso da letra q minúscula (lembre: caractere entre aspas simples, e o sinal do fim):
4. Com int n = 1;, o que mostra switch (n) { case 1: printf("A"); case 2: printf("B"); break; case 3: printf("C"); }?
5. Qual destes não pode ser usado num switch?
6. Para dar o conceito A para notas de 9 a 10, B de 7 a 8,9 e assim por diante, o melhor é:
Missão final: os dias do mês
Missão: o programa lê o número de um mês (a entrada preparada é 4, abril) e deve mostrar quantos dias ele tem. Escreva um switch (mes) que guarda em dias: 31 para os meses 1, 3, 5, 7, 8, 10 e 12; 30 para 4, 6, 9 e 11; e 28 para o 2 (vamos ignorar os anos bissextos). No default, mostre Mês inválido. e termine com return 1;. Junte os meses com vários case seguidos. Sem avisos. A saída deve terminar com:
O mês 4 tem 30 dias.
#include <stdio.h>
int main(void) {
int mes = 0;
printf("Mês (1 a 12): ");
if (scanf("%d", &mes) != 1) {
return 1;
}
int dias = 0;
// escreva o switch aqui
printf("O mês %d tem %d dias.\n", mes, dias);
return 0;
}
#include <stdio.h>
int main(void) {
int mes = 0;
printf("Mês (1 a 12): ");
if (scanf("%d", &mes) != 1) {
return 1;
}
int dias = 0;
switch (mes) {
case 1: case 3: case 5: case 7: case 8: case 10: case 12:
dias = 31;
break;
case 4: case 6: case 9: case 11:
dias = 30;
break;
case 2:
dias = 28;
break;
default:
printf("Mês inválido.\n");
return 1;
}
printf("O mês %d tem %d dias.\n", mes, dias);
return 0;
}
Troque a entrada da caixa para 2, 12 e 13 e confira as três respostas. No laboratório da Ana, a solução deu:
$ ./meses <<< 2 Mês (1 a 12): O mês 2 tem 28 dias. $ ./meses <<< 12 Mês (1 a 12): O mês 12 tem 31 dias. $ ./meses <<< 13 Mês (1 a 12): Mês inválido.
(O <<< do terminal entrega o texto ao programa como se fosse digitado, mas sem mostrá-lo na tela; por isso a resposta sai grudada na pergunta.) Repare no default: em vez de break, ele termina com return 1;, que sai do main inteiro. Assim o programa não chega a mostrar "O mês 13 tem 0 dias.".
Resumo
switch (x) { case V: … break; default: … }compara uma expressão inteira com vários valores fixos e pula direto para o caso certo.- O
caseé só um ponto de entrada. Sembreak, o programa cai nos casos de baixo; o gcc avisa com-Wextra(this statement may fall through), o Clang só com-Wimplicit-fallthrough. - Vários
caseseguidos, sem código entre eles, dividem o mesmo bloco:case 's': case 'S':. Queda proposital com código:[[fallthrough]];C23. defaultpega o resto. É opcional, mas avisa quando a pessoa digita algo inesperado.- O
switchnão aceita faixas,double, textos, variáveis nocasenemcaserepetido: para isso,else if. - Para declarar uma variável num caso, dê chaves a ele:
case 1: { … }. - Próximo passo: os laços, a começar pelo while, que repete um bloco. Juntos, um laço e um
switchfazem o menu que volta depois de cada escolha.