Aeca

Capítulo 12 — Muitas opções: quando switch ajuda

Série: Do zero ao jogo comercial com C++ e SFML

Capítulo 12 — Muitas opções: quando switch ajuda
aeca

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 case representar uma opção independente, use break explicitamente.

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 1

New Game 2

Load Game 3

Settings 4

Exit 10

Invalid 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 case corresponder?

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:

currentSeason armazena um inteiro.

Mas gostaríamos de algo semanticamente mais forte:

currentSeason representa 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 switch ajuda a representar?

  • O switch substitui if?

  • O que a expressão entre switch (...) representa?

  • Para que serve case?

  • Para que serve break em nossos exemplos?

  • O que é fallthrough?

  • Para que serve default?

  • default é sempre obrigatório?

  • Por que switch funciona bem quando uma expressão possui várias opções discretas conhecidas?

  • Por que playerCoins >= itemPrice continua sendo melhor representado com if?

  • 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:

  • switch avalia uma expressão;

  • case representa valores específicos possíveis;

  • break interrompe nosso caminho e sai do switch;

  • esquecer break pode causar fallthrough;

  • fallthrough pode ser intencional, mas não devemos utilizá-lo acidentalmente;

  • default pode tratar valores sem case correspondente;

  • default não é mecanicamente obrigatório;

  • switch não substitui if;

  • 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 case e o caminho inesperado é importante;

  • números mágicos podem ser reduzidos com constantes;

  • constantes ainda não tornam um int semanticamente 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 usar int;

  • como o operador :: aparece novamente;

  • como enum class trabalha com switch;

  • 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.

Subscreve "Aeca" para receber atualizações diretamente na tua caixa de entrada
aeca

Subscreve aeca para reagir

Subscrever

Comentários

Ainda não há comentários. Sê o primeiro a comentar!

Subscreve Aeca para receber atualizações diretamente na tua caixa de entrada