Capítulo 12 — Muitas opções: quando switch ajuda
Série: Do zero ao jogo comercial com C++ e SFML
Quando uma única informação pode assumir várias opções conhecidas, precisamos escolher uma estrutura que represente isso com clareza
No capítulo anterior nosso programa evoluiu de uma decisão simples:
if (playerCoins >= itemPrice)
{
playerCoins -= itemPrice;
}
para uma decisão com dois caminhos:
if (playerCoins >= itemPrice)
{
playerCoins -= itemPrice;
std::cout << "Item purchased." << std::endl;
}
else
{
std::cout << "Not enough coins." << std::endl;
}
Agora conseguimos representar:
CONDIÇÃO
│
├── true → caminho A
│
└── false → caminho B
Isso funciona muito bem quando temos duas alternativas.
Mas jogos frequentemente apresentam situações diferentes.
Imagine um menu:
1 → New Game
2 → Load Game
3 → Settings
4 → Exit
Ou uma direção:
1 → North
2 → South
3 → East
4 → West
Ou uma estação:
1 → Spring
2 → Summer
3 → Autumn
4 → Winter
Agora não estamos mais perguntando apenas:
verdadeiro ou falso?
Estamos perguntando:
qual entre várias opções conhecidas corresponde ao valor atual?
Poderíamos continuar utilizando if e else.
Mas quando uma única expressão precisa ser comparada com vários valores discretos conhecidos, C++ possui outra estrutura que pode representar essa intenção de maneira bastante clara:
switch
Neste capítulo vamos descobrir qual problema o switch resolve, como ele funciona e, principalmente, quando ele realmente ajuda.
1. Começando pelo problema
Imagine que nosso programa possua um menu extremamente simples:
=== GAME MENU ===
1 - New Game
2 - Load Game
3 - Settings
4 - Exit
Por enquanto não precisamos permitir entrada pelo teclado.
Ainda chegaremos a isso.
Podemos simplesmente representar a opção selecionada:
int selectedOption = 2;
Agora precisamos decidir o que fazer.
Com o conhecimento atual, poderíamos escrever:
if (selectedOption == 1)
{
std::cout << "Starting new game." << std::endl;
}
else
{
if (selectedOption == 2)
{
std::cout << "Loading game." << std::endl;
}
}
Mas ainda faltam:
3
4
E talvez também precisemos tratar valores inválidos.
Nosso código começará a crescer.
2. Poderíamos continuar usando if
Uma possibilidade seria:
if (selectedOption == 1)
{
std::cout << "Starting new game." << std::endl;
}
else if (selectedOption == 2)
{
std::cout << "Loading game." << std::endl;
}
else if (selectedOption == 3)
{
std::cout << "Opening settings." << std::endl;
}
else if (selectedOption == 4)
{
std::cout << "Exiting game." << std::endl;
}
else
{
std::cout << "Invalid option." << std::endl;
}
Essa estrutura funciona.
Não existe nada fundamentalmente errado nela.
Inclusive acabamos de encontrar uma construção que ainda não havíamos explorado:
else if
Ela permite testar uma nova condição quando a anterior foi falsa.
3. Entendendo rapidamente else if
Podemos ler:
if (conditionA)
{
}
else if (conditionB)
{
}
else
{
}
como:
SE conditionA
faça A
SENÃO, SE conditionB
faça B
SENÃO
faça C
Por exemplo:
if (selectedOption == 1)
{
std::cout << "New Game" << std::endl;
}
else if (selectedOption == 2)
{
std::cout << "Load Game" << std::endl;
}
else
{
std::cout << "Unknown Option" << std::endl;
}
Essa estrutura será muito útil durante toda a nossa jornada.
Mas observe o padrão que apareceu no menu completo:
selectedOption == 1
selectedOption == 2
selectedOption == 3
selectedOption == 4
Estamos repetidamente comparando a mesma expressão contra diferentes valores conhecidos.
É exatamente aqui que vale conhecer switch.
4. O problema não é que if deixou de funcionar
Esse ponto é essencial.
Não estamos aprendendo switch porque:
if é ruim
ou porque:
switch é melhor que if
Isso seria uma conclusão errada.
Estamos aprendendo porque estruturas diferentes expressam melhor problemas diferentes.
No nosso menu temos:
selectedOption
e queremos descobrir se seu valor corresponde a:
1
2
3
4
Podemos pensar:
selectedOption
│
├── 1 → New Game
├── 2 → Load Game
├── 3 → Settings
├── 4 → Exit
└── outro → Invalid Option
Essa estrutura se aproxima bastante da ideia de switch.
5. Conhecendo switch
Uma forma básica é:
switch (expression)
{
case valueA:
// ação A
break;
case valueB:
// ação B
break;
default:
// ação padrão
break;
}
Existem várias partes novas aqui:
switch
case
:
break
default
Não precisamos decorar tudo imediatamente.
Vamos construir passo a passo.
6. Nosso primeiro switch
Comece:
#include <iostream>
int main()
{
int selectedOption = 1;
switch (selectedOption)
{
case 1:
std::cout << "New Game" << std::endl;
break;
}
return 0;
}
Execute.
Resultado:
New Game
Agora precisamos entender o que aconteceu.
7. O valor do switch
Temos:
switch (selectedOption)
O switch avalia a expressão entre parênteses.
Neste exemplo:
selectedOption
possui:
1
Então o programa procura um case correspondente.
Temos:
case 1:
Encontramos uma correspondência.
Portanto as instruções daquele caminho começam a ser executadas.
8. O que significa case
Podemos pensar:
case 1:
como:
caso o valor corresponda a
1, comece por aqui.
Outro exemplo:
case 2:
significa:
caso corresponda a
2, comece por aqui.
Então podemos expandir:
switch (selectedOption)
{
case 1:
std::cout << "New Game" << std::endl;
break;
case 2:
std::cout << "Load Game" << std::endl;
break;
}
Agora:
selectedOption = 1
→ New Game
selectedOption = 2
→ Load Game
9. Adicionando todas as opções
#include <iostream>
int main()
{
int selectedOption = 3;
switch (selectedOption)
{
case 1:
std::cout << "New Game" << std::endl;
break;
case 2:
std::cout << "Load Game" << std::endl;
break;
case 3:
std::cout << "Settings" << std::endl;
break;
case 4:
std::cout << "Exit" << std::endl;
break;
}
return 0;
}
Como:
selectedOption = 3
o resultado será:
Settings
Temos agora:
selectedOption
│
├── case 1 → New Game
├── case 2 → Load Game
├── case 3 → Settings
└── case 4 → Exit
10. E se nenhuma opção corresponder?
Agora faça:
int selectedOption = 8;
Não temos:
case 8:
Então nenhum dos nossos casos corresponde.
Talvez quiséssemos mostrar:
Invalid option.
Para isso existe:
default
11. Conhecendo default
Podemos escrever:
switch (selectedOption)
{
case 1:
std::cout << "New Game" << std::endl;
break;
case 2:
std::cout << "Load Game" << std::endl;
break;
case 3:
std::cout << "Settings" << std::endl;
break;
case 4:
std::cout << "Exit" << std::endl;
break;
default:
std::cout << "Invalid option." << std::endl;
break;
}
Agora:
selectedOption = 8
produzirá:
Invalid option.
12. default lembra um pouco o else
Compare:
if (condition)
{
}
else
{
}
com:
switch (value)
{
case 1:
break;
case 2:
break;
default:
break;
}
Existe uma semelhança conceitual.
O default representa o caminho utilizado quando nenhum case correspondente é encontrado.
Mas não devemos pensar que:
default == else
em todos os aspectos.
São construções pertencentes a estruturas diferentes.
A comparação serve apenas para facilitar nossa compreensão inicial.
13. Agora precisamos entender break
Até aqui colocamos:
break;
em cada caso.
Mas ainda não explicamos por quê.
Isso é proposital.
Vamos remover um deles e observar o comportamento.
Comece:
int selectedOption = 2;
e:
switch (selectedOption)
{
case 1:
std::cout << "New Game" << std::endl;
break;
case 2:
std::cout << "Load Game" << std::endl;
case 3:
std::cout << "Settings" << std::endl;
break;
case 4:
std::cout << "Exit" << std::endl;
break;
}
Observe que removemos:
break;
do:
case 2:
Execute.
14. Um resultado inesperado
Você provavelmente verá:
Load Game
Settings
Mas selecionamos apenas:
2
Por que o case 3 também executou?
Porque encontrar um case determina onde a execução começa dentro do switch.
Sem um break, ela pode continuar pelas instruções do caso seguinte.
Esse comportamento é conhecido como:
fallthrough.
15. Visualizando o fallthrough
Temos:
selectedOption = 2
↓
case 2
↓
Load Game
↓
sem break
↓
case 3
↓
Settings
↓
break
↓
sair do switch
Por isso recebemos:
Load Game
Settings
16. Restaurando break
Corrija:
case 2:
std::cout << "Load Game" << std::endl;
break;
Agora:
selectedOption = 2
produz apenas:
Load Game
Podemos pensar no break deste exemplo como:
termine este caminho e saia do
switch.
17. break não significa simplesmente "fim do case"
Existe uma nuance importante.
O case não cria automaticamente um bloco independente que termina antes do próximo.
É justamente por isso que o fallthrough existe.
Para nosso estágio atual, adotaremos a regra:
quando cada
caserepresentar uma opção independente, usebreakexplicitamente.
Mais tarde poderemos encontrar situações em que o fallthrough é intencional.
Mas não precisamos utilizá-lo agora.
18. Erro proposital — esquecer break
Faça este experimento:
#include <iostream>
int main()
{
int selectedOption = 1;
switch (selectedOption)
{
case 1:
std::cout << "New Game" << std::endl;
case 2:
std::cout << "Load Game" << std::endl;
case 3:
std::cout << "Settings" << std::endl;
case 4:
std::cout << "Exit" << std::endl;
}
return 0;
}
Qual resultado você espera?
Execute.
Você poderá obter:
New Game
Load Game
Settings
Exit
Agora investigue:
Qual case foi encontrado primeiro?
Onde deveria existir break?
Por que a execução continuou?
Depois restaure todos os break.
19. Nem todo comportamento estranho é erro de compilação
Assim como já vimos em expressões e decisões, este código:
case 1:
std::cout << "New Game" << std::endl;
sem break pode ser perfeitamente válido para C++.
O compilador não conhece nossa intenção.
Talvez quiséssemos fallthrough.
Talvez tenhamos esquecido break.
Portanto:
compilou
≠
comportamento correto
Essa ideia continua aparecendo porque é fundamental em Engenharia de Software.
20. Comparando if/else if com switch
Versão usando condições:
if (selectedOption == 1)
{
std::cout << "New Game" << std::endl;
}
else if (selectedOption == 2)
{
std::cout << "Load Game" << std::endl;
}
else if (selectedOption == 3)
{
std::cout << "Settings" << std::endl;
}
else if (selectedOption == 4)
{
std::cout << "Exit" << std::endl;
}
else
{
std::cout << "Invalid option." << std::endl;
}
Versão com switch:
switch (selectedOption)
{
case 1:
std::cout << "New Game" << std::endl;
break;
case 2:
std::cout << "Load Game" << std::endl;
break;
case 3:
std::cout << "Settings" << std::endl;
break;
case 4:
std::cout << "Exit" << std::endl;
break;
default:
std::cout << "Invalid option." << std::endl;
break;
}
As duas podem resolver o problema.
Então a pergunta correta não é:
qual é melhor?
A pergunta é:
qual estrutura comunica melhor a decisão que estamos modelando?
21. Quando switch começa a fazer sentido?
Nosso exemplo possui características importantes.
Estamos avaliando:
selectedOption
contra valores discretos conhecidos:
1
2
3
4
Não estamos perguntando:
selectedOption > 2
ou:
selectedOption <= 10
Estamos procurando correspondência com valores específicos.
Esse é um cenário natural para switch.
22. Quando if continua sendo mais adequado?
Considere:
if (playerHealth <= 0)
{
std::cout << "Player defeated." << std::endl;
}
Estamos avaliando uma relação:
playerHealth <= 0
Não estamos comparando playerHealth com uma lista de valores exatos conhecidos.
Outro exemplo:
if (playerCoins >= itemPrice)
{
std::cout << "Item purchased." << std::endl;
}
Aqui temos uma comparação entre duas variáveis.
switch não é uma substituição natural para isso.
23. Não tente transformar tudo em switch
Depois de aprender uma nova estrutura é comum querer utilizá-la em todos os lugares.
Evite isso.
Não pense:
aprendi switch
↓
agora todos os if devem desaparecer
Nosso princípio continua:
PROBLEMA
↓
CARACTERÍSTICAS DA DECISÃO
↓
ESTRUTURA MAIS CLARA
Ferramentas servem ao problema.
Não o contrário.
24. Um exemplo relacionado ao nosso futuro jogo
Nossa direção de jogo inclui um sistema de estações.
No futuro poderemos ter:
Spring
Summer
Autumn
Winter
Ainda não construiremos o sistema real de estações.
Mas podemos utilizar a ideia como exercício.
Por enquanto:
int currentSeason = 2;
Podemos definir:
1 → Spring
2 → Summer
3 → Autumn
4 → Winter
Então:
switch (currentSeason)
{
case 1:
std::cout << "Spring" << std::endl;
break;
case 2:
std::cout << "Summer" << std::endl;
break;
case 3:
std::cout << "Autumn" << std::endl;
break;
case 4:
std::cout << "Winter" << std::endl;
break;
default:
std::cout << "Unknown season." << std::endl;
break;
}
25. O problema dos números mágicos reaparece
Observe:
case 1:
case 2:
case 3:
case 4:
Precisamos lembrar:
1 = Spring
2 = Summer
3 = Autumn
4 = Winter
Se alguém encontrar:
currentSeason = 3;
precisa saber que:
3
significa:
Autumn
Isso não é ideal.
Encontramos novamente o problema dos magic numbers.
26. Já conhecemos uma direção melhor, mas ainda não chegamos lá
Existe uma estrutura muito adequada para representar conjuntos de estados nomeados.
Em vez de:
1
2
3
4
poderíamos ter algo conceitualmente semelhante a:
SPRING
SUMMER
AUTUMN
WINTER
Isso tornaria nosso código muito mais expressivo.
E não é coincidência que o próximo capítulo da nossa sequência esteja se aproximando desse assunto.
Mas ainda existe um passo antes.
Precisamos entender melhor quando muitas opções realmente representam estados conhecidos e nomeados.
27. Por enquanto, mantenha o exemplo simples
Para este capítulo, podemos continuar com:
int currentSeason = 2;
porque nosso objetivo é compreender:
switch
case
break
default
Não vamos misturar dois conceitos novos importantes na mesma implementação principal.
Quando introduzirmos estados nomeados, poderemos voltar e refatorar esse código.
Esse é exatamente o tipo de evolução que queremos observar na série:
solução simples
↓
problema percebido
↓
novo conhecimento
↓
refatoração
28. Outro exemplo: ferramenta selecionada
Nosso futuro farming game provavelmente possuirá ferramentas como:
1 → Axe
2 → Pickaxe
3 → Hoe
4 → Watering Can
Podemos representar temporariamente:
int selectedTool = 3;
Então:
switch (selectedTool)
{
case 1:
std::cout << "Axe selected." << std::endl;
break;
case 2:
std::cout << "Pickaxe selected." << std::endl;
break;
case 3:
std::cout << "Hoe selected." << std::endl;
break;
case 4:
std::cout << "Watering Can selected." << std::endl;
break;
default:
std::cout << "Unknown tool." << std::endl;
break;
}
Resultado com:
selectedTool = 3
será:
Hoe selected.
29. Mas isso ainda não é nosso sistema de ferramentas
Não confunda o exemplo com implementação arquitetural.
Ainda não temos:
Tool
Inventory
Item
Player
Farm
Crop
Interaction
Estamos apenas utilizando conceitos do futuro jogo para tornar o aprendizado contextual.
Nosso projeto continua mínimo.
30. case trabalha com valores conhecidos
Considere:
switch (selectedOption)
e:
case 1:
O case representa um valor conhecido para comparação.
Não podemos simplesmente escrever uma condição arbitrária como:
case selectedOption > 2:
Isso não é a função de case.
Se precisamos avaliar condições como:
Health < 30
Coins >= Price
Experience > Required
Temperature <= Minimum
estamos novamente em território natural de:
if
31. Um contraste importante
Correspondência com opções conhecidas
currentSeason
│
├── Spring
├── Summer
├── Autumn
└── Winter
Pode ser um bom candidato a:
switch
Condições e intervalos
playerHealth > 70
playerHealth > 30
playerHealth > 0
São mais naturalmente expressos com:
if
else if
else
A estrutura deve refletir a natureza da regra.
32. switch também não é obrigatório para poucas opções
Imagine:
int gameMode = 1;
com apenas:
1 → Single Player
2 → Multiplayer
Poderíamos usar:
switch (gameMode)
Mas também poderíamos escrever:
if (gameMode == 1)
{
}
else
{
}
Dependendo do contexto, o if/else pode ser mais simples.
Não existe uma regra:
duas opções = if
três opções = switch
A decisão envolve clareza e natureza da comparação.
33. Não conte case; entenda o problema
Esse princípio merece destaque.
Não escolha switch porque:
existem 4 opções
Escolha porque:
uma mesma expressão
↓
precisa ser comparada
↓
com vários valores discretos conhecidos
Essa compreensão é muito mais útil do que decorar uma quantidade arbitrária.
34. Podemos agrupar casos?
Existe uma possibilidade interessante.
Imagine que várias opções devam produzir o mesmo comportamento.
Por exemplo:
1 → move horizontally
2 → move horizontally
Poderíamos encontrar estruturas como:
switch (direction)
{
case 1:
case 2:
std::cout << "Horizontal movement." << std::endl;
break;
}
Aqui o primeiro case não possui break.
Isso é um fallthrough intencional.
Mas precisamos ter cuidado.
35. Fallthrough intencional deve ser óbvio
Compare:
case 1:
case 2:
std::cout << "Horizontal movement." << std::endl;
break;
Aqui é relativamente claro que os dois valores compartilham o mesmo comportamento.
Já:
case 1:
DoSomething();
case 2:
DoSomethingElse();
break;
pode gerar dúvida:
fallthrough foi intencional?
ou esqueceram break?
Clareza continua sendo prioridade.
Neste estágio, nossos casos independentes continuarão utilizando break.
36. Não precisamos explorar todas as regras de switch agora
C++ possui detalhes adicionais relacionados a:
tipos aceitos;
escopo;
inicialização dentro de casos;
atributos como
[[fallthrough]];enums;
conversões;
integral promotions;
declarações dentro de
case;comportamento de controle de fluxo.
Não precisamos despejar tudo isso neste capítulo.
Nosso objetivo atual é dominar o modelo fundamental.
Outros detalhes aparecerão quando tivermos um problema que os exija.
37. Um pequeno fluxo mental para switch
Quando encontrar uma decisão, pergunte:
1. Estou avaliando uma única expressão?
2. Quero compará-la com valores específicos conhecidos?
3. Cada valor representa uma opção discreta?
4. Os cases tornam a intenção mais clara?
5. Preciso de um caminho para valores não reconhecidos?
Se essas respostas apontarem na direção certa, switch pode ser uma boa opção.
38. Microdesafio — menu
Comece:
int selectedOption = 2;
Crie:
1 → New Game
2 → Load Game
3 → Settings
4 → Exit
Resultado esperado:
Load Game
Depois teste:
selectedOption = 4;
Esperado:
Exit
Finalmente:
selectedOption = 10;
Esperado:
Invalid option.
39. Uma possível solução
switch (selectedOption)
{
case 1:
std::cout << "New Game" << std::endl;
break;
case 2:
std::cout << "Load Game" << std::endl;
break;
case 3:
std::cout << "Settings" << std::endl;
break;
case 4:
std::cout << "Exit" << std::endl;
break;
default:
std::cout << "Invalid option." << std::endl;
break;
}
Teste todos os casos.
Não teste apenas o valor que você escolheu inicialmente.
40. Microdesafio — estações
Utilize:
int currentSeason = 1;
Mapeie:
1 → Spring
2 → Summer
3 → Autumn
4 → Winter
Depois teste:
0
1
2
3
4
5
Resultados esperados:
0 → Unknown season
1 → Spring
2 → Summer
3 → Autumn
4 → Winter
5 → Unknown season
41. Microdesafio — ferramentas
Utilize:
int selectedTool = 4;
Crie:
1 → Axe
2 → Pickaxe
3 → Hoe
4 → Watering Can
Resultado:
Watering Can selected.
Depois remova propositalmente o break do:
case 3:
e execute com:
selectedTool = 3;
Observe o resultado.
Explique por que aconteceu antes de corrigir.
42. Testar cada case é importante
Se criamos:
case 1
case 2
case 3
case 4
default
precisamos validar:
1
2
3
4
valor não reconhecido
Isso nos dá pelo menos cinco cenários.
Podemos organizar:
Entrada Resultado
esperado
1New Game
2Load Game
3Settings
4Exit
10Invalid option
Essa tabela simples já funciona como uma pequena especificação de comportamento.
43. Estamos começando a pensar como QA também
Observe o que fizemos.
Primeiro definimos:
entrada
Depois:
resultado esperado
Depois executamos:
programa
E comparamos:
esperado
versus
obtido
Isso é uma mentalidade de teste.
Ainda não temos:
Test Framework
Assertions
Fixtures
Mocks
Test Runner
E não precisamos deles agora.
A disciplina começa antes da ferramenta.
44. Erros comuns com switch
Alguns problemas merecem atenção.
Esquecer break
case 1:
std::cout << "New Game" << std::endl;
pode permitir fallthrough.
Esquecer :
Errado:
case 1
Correto:
case 1:
Esperar que case aceite qualquer condição
Isto não representa o propósito do case:
case playerHealth > 0:
Usar switch onde uma condição é mais clara
Não force switch para substituir:
if (playerCoins >= itemPrice)
Não tratar valores inesperados
Dependendo da regra, um:
default:
pode ser importante.
45. Erro proposital — remover :
Pegue:
case 1:
e altere para:
case 1
Faça Build.
Leia a mensagem do compilador.
Não tente adivinhar apenas pela linha vermelha.
Pergunte:
Qual estrutura o compilador esperava?
O que existe nos outros cases?
Qual símbolo desapareceu?
Depois restaure:
case 1:
46. Erro proposital — duplicar um case
Experimente:
switch (selectedOption)
{
case 1:
std::cout << "New Game" << std::endl;
break;
case 1:
std::cout << "Load Game" << std::endl;
break;
}
Temos dois:
case 1
Mas se:
selectedOption = 1
qual deveria ser escolhido?
Essa ambiguidade não é permitida dessa maneira.
Compile e observe o diagnóstico.
Depois corrija:
case 2:
no segundo caminho.
47. default é sempre obrigatório?
Não.
Um switch pode existir sem:
default:
como já vimos.
Mas precisamos perguntar:
o que deveria acontecer se nenhum
casecorresponder?
Talvez:
nada
seja realmente aceitável.
Talvez seja necessário:
registrar um erro
usar uma opção padrão
rejeitar o valor
A decisão depende da regra.
Não adicionamos default mecanicamente.
48. Neste exercício, default faz sentido
Para nosso menu:
1
2
3
4
um valor:
10
é inválido.
Então:
default:
std::cout << "Invalid option." << std::endl;
break;
comunica claramente o comportamento esperado.
No futuro, quando tivermos nosso sistema de logging, uma situação inesperada também poderá produzir um log apropriado.
Mas ainda não precisamos desse sistema.
49. Código final do capítulo
Vamos manter um exemplo relacionado ao futuro jogo, mas ainda simples o suficiente para representar apenas o conceito atual.
#include <iostream>
int main()
{
const int SPRING = 1;
const int SUMMER = 2;
const int AUTUMN = 3;
const int WINTER = 4;
int currentSeason = SUMMER;
std::cout << "=== CURRENT SEASON ===" << std::endl;
switch (currentSeason)
{
case SPRING:
std::cout << "Spring" << std::endl;
std::cout << "Flowers begin to bloom." << std::endl;
break;
case SUMMER:
std::cout << "Summer" << std::endl;
std::cout << "Days are longer and warmer." << std::endl;
break;
case AUTUMN:
std::cout << "Autumn" << std::endl;
std::cout << "Leaves begin to fall." << std::endl;
break;
case WINTER:
std::cout << "Winter" << std::endl;
std::cout << "Cold weather reaches the farm." << std::endl;
break;
default:
std::cout << "Unknown season." << std::endl;
break;
}
return 0;
}
Resultado:
=== CURRENT SEASON ===
Summer
Days are longer and warmer.
50. Por que usamos constantes?
Poderíamos escrever:
case 1:
case 2:
case 3:
case 4:
Mas já aprendemos constantes.
Então utilizamos:
const int SPRING = 1;
const int SUMMER = 2;
const int AUTUMN = 3;
const int WINTER = 4;
Agora:
int currentSeason = SUMMER;
é muito mais compreensível que:
int currentSeason = 2;
Estamos reutilizando conhecimento anterior em vez de descartá-lo.
51. Mas essa solução ainda possui uma limitação
Temos:
const int SPRING = 1;
const int SUMMER = 2;
const int AUTUMN = 3;
const int WINTER = 4;
Isso melhorou bastante.
Mas currentSeason continua sendo:
int
Então isto ainda compila:
int currentSeason = 500;
Tecnicamente:
500
é um int.
Mas conceitualmente:
500
não é uma estação válida.
Encontramos um novo problema.
52. O tipo ainda é genérico demais
Observe:
int currentSeason
O tipo nos diz:
currentSeasonarmazena um inteiro.
Mas gostaríamos de algo semanticamente mais forte:
currentSeasonrepresenta uma estação.
Queremos restringir conceitualmente as opções para algo como:
Spring
Summer
Autumn
Winter
Em vez de qualquer inteiro possível.
Essa necessidade nos levará a uma ferramenta muito importante.
53. Ainda não vamos resolver esse problema aqui
Poderíamos introduzir imediatamente uma enumeração.
Mas isso transformaria este capítulo em:
switch
+
enum
+
type safety
+
scoped enumerations
+
underlying values
+
conversions
Não precisamos fazer isso.
Primeiro dominamos:
muitas opções
↓
switch
Agora percebemos uma limitação real:
usar int para representar estados nomeados
Essa limitação prepara naturalmente o próximo passo.
54. Validando o código final
Salve:
Ctrl + S
Faça Build:
Ctrl + Shift + B
Confirme:
Build succeeded
Execute:
Ctrl + F5
Com:
int currentSeason = SUMMER;
espere:
=== CURRENT SEASON ===
Summer
Days are longer and warmer.
55. Teste todas as estações
Agora altere:
int currentSeason = SPRING;
Depois:
int currentSeason = SUMMER;
Depois:
int currentSeason = AUTUMN;
Depois:
int currentSeason = WINTER;
Finalmente:
int currentSeason = 999;
Confirme que o último produz:
Unknown season.
Não considere a implementação validada depois de testar apenas um case.
56. Teste também o fallthrough
Depois da validação normal, faça um experimento.
Remova temporariamente:
break;
de:
case SUMMER:
Execute:
int currentSeason = SUMMER;
Observe se o comportamento continua no próximo case.
Depois restaure imediatamente o break.
Esse experimento existe para transformar uma regra abstrata em comportamento observado.
57. Checklist de compreensão
Antes de avançar, tente responder:
Qual problema o
switchajuda a representar?O
switchsubstituiif?O que a expressão entre
switch (...)representa?Para que serve
case?Para que serve
breakem nossos exemplos?O que é fallthrough?
Para que serve
default?defaulté sempre obrigatório?Por que
switchfunciona bem quando uma expressão possui várias opções discretas conhecidas?Por que
playerCoins >= itemPricecontinua sendo melhor representado comif?Por que
int currentSeason = 500;revela uma limitação do nosso modelo atual?Por que constantes melhoraram nosso código sem resolver completamente o problema das estações?
Se conseguir explicar essas respostas com suas próprias palavras, estamos prontos para continuar.
58. Estado atual do projeto
Nossa estrutura continua:
CppGameEngine/
└── main.cpp
Ainda não precisamos criar:
SeasonManager
StateManager
MenuManager
ToolManager
Nem qualquer outra abstração.
Nosso programa está evoluindo em conhecimento e comportamento.
A arquitetura continuará surgindo somente quando problemas concretos justificarem novas responsabilidades.
59. Git
Depois de validar:
git status
Adicione:
git add CppGameEngine/main.cpp
Ajuste o caminho caso necessário.
Commit sugerido:
git commit -m "feat: add multi-option decisions with switch"
Esse commit representa uma nova capacidade:
nosso programa agora consegue selecionar comportamentos entre várias opções discretas conhecidas.
60. Atualizando o CHANGELOG
Em [Unreleased]:
### Added
- Added multi-option decision handling using `switch`.
- Added `case`, `break`, and `default` examples.
- Added intentional fallthrough validation for learning purposes.
- Added season-based gameplay example using named constants.
- Added manual validation scenarios for every switch case and unknown values.
Depois:
git add CHANGELOG.md
git commit -m "docs: update changelog with switch decisions"
Não precisamos alterar o ROADMAP técnico neste capítulo.
61. O que aprendemos?
Nos capítulos anteriores evoluímos de:
if
para:
if / else
Agora encontramos situações em que uma única informação pode corresponder a várias opções conhecidas.
Conhecemos:
switch
e sua estrutura:
switch (value)
{
case VALUE_A:
break;
case VALUE_B:
break;
default:
break;
}
Aprendemos que:
switchavalia uma expressão;caserepresenta valores específicos possíveis;breakinterrompe nosso caminho e sai doswitch;esquecer
breakpode causar fallthrough;fallthrough pode ser intencional, mas não devemos utilizá-lo acidentalmente;
defaultpode tratar valores semcasecorrespondente;defaultnão é mecanicamente obrigatório;switchnão substituiif;condições envolvendo intervalos e relações continuam naturalmente representadas com
if;uma sequência de comparações contra valores discretos pode ficar mais expressiva com
switch;testar todos os
casee o caminho inesperado é importante;números mágicos podem ser reduzidos com constantes;
constantes ainda não tornam um
intsemanticamente equivalente a uma estação.
E foi justamente essa última limitação que abriu nosso próximo problema.
Conclusão
Nosso programa começou apenas executando instruções.
Depois aprendeu a armazenar dados:
variáveis
Aprendeu que informações possuem naturezas diferentes:
tipos
Protegemos valores que não deveriam mudar:
constantes
Transformamos dados:
operadores
Combinamos operações:
expressões
Começamos a decidir:
if
Criamos caminhos alternativos:
if / else
E agora conseguimos organizar decisões em torno de várias opções conhecidas:
switch
Nossa evolução pode ser visualizada assim:
DADOS
↓
EXPRESSÕES
↓
CONDIÇÕES
↓
DECISÕES
↓
ALTERNATIVAS
↓
MÚLTIPLAS OPÇÕES
Mas o exercício das estações revelou algo importante.
Hoje temos:
const int SPRING = 1;
const int SUMMER = 2;
const int AUTUMN = 3;
const int WINTER = 4;
int currentSeason = SUMMER;
Melhoramos os nomes.
Mas ainda podemos escrever:
currentSeason = 742;
e C++ continuará vendo apenas:
int recebe int
Nosso compilador ainda não entende o conceito:
Season
Ele entende apenas:
integer
Precisamos de uma maneira melhor de representar um conjunto fechado de estados nomeados.
Algo que diga:
isto não é simplesmente um número
isto representa um estado específico do nosso domínio
E essa necessidade nos leva diretamente ao próximo capítulo.
Próximo capítulo
Capítulo 13 — Estados mais seguros com enum class
Série: Do zero ao jogo comercial com C++ e SFML
Partiremos exatamente deste código:
const int SPRING = 1;
const int SUMMER = 2;
const int AUTUMN = 3;
const int WINTER = 4;
int currentSeason = SUMMER;
e deste problema:
currentSeason = 999;
O código aceita.
Mas nosso modelo conceitual não deveria aceitar qualquer número como uma estação.
Queremos chegar a algo muito mais expressivo:
enum class Season
{
Spring,
Summer,
Autumn,
Winter
};
e então:
Season currentSeason = Season::Summer;
Nosso switch também começará a ficar muito mais significativo:
switch (currentSeason)
{
case Season::Spring:
break;
case Season::Summer:
break;
case Season::Autumn:
break;
case Season::Winter:
break;
}
Mas não vamos apenas substituir constantes por uma sintaxe nova.
Vamos entender:
qual problema uma enumeração resolve;
por que estados nomeados são melhores que números mágicos;
o que significa criar um novo tipo;
por que
enum classé diferente de simplesmente usarint;como o operador
::aparece novamente;como
enum classtrabalha comswitch;como estados inválidos podem ser reduzidos pelo próprio design do código;
e por que tornar estados inválidos mais difíceis de representar é uma poderosa ideia de Engenharia de Software.
Nosso programa já sabe escolher entre várias opções.
No próximo capítulo começaremos a fazer com que essas opções sejam mais seguras, mais expressivas e mais próximas do domínio real do jogo.
Comentários
Ainda não há comentários. Sê o primeiro a comentar!