Capítulo 08 — Operadores: transformando dados
Série: Do zero ao jogo comercial com C++ e SFML
Nosso estado já existe. Agora precisamos calcular, modificar e combinar seus valores.
Nos capítulos anteriores, nosso pequeno programa começou a armazenar diferentes tipos de informações e a separar estado mutável de regras constantes.
Agora surge um novo problema: um jogo não pode apenas armazenar estado. Ele precisa transformá-lo.
Quando o jogador sofre dano:
Health: 100
↓
Health: 80
Quando recebe moedas:
Coins: 25
↓
Coins: 40
Quando recebe experiência:
Experience: 40
↓
Experience: 100
Para realizar essas transformações precisamos executar operações sobre os dados.
É aqui que entram os operadores.
1. Começando pelo problema
Imagine este estado:
int playerHealth = 100;
int playerCoins = 25;
int playerExperience = 40;
Agora três acontecimentos ocorrem:
Player sofre 20 de dano
Player recebe 15 moedas
Player recebe 60 pontos de experiência
Queremos chegar a:
Health: 80
Coins: 40
Experience: 100
Já utilizamos algumas operações nos capítulos anteriores:
playerHealth = playerHealth - 20;
playerCoins = playerCoins + 10;
Agora vamos entender formalmente o que está acontecendo.
2. O que é um operador?
Um operador é um símbolo da linguagem que representa uma operação realizada sobre um ou mais operandos.
Observe:
playerHealth - 20
Temos dois operandos, playerHealth e 20, ligados pelo operador -.
Se playerHealth vale 100, a expressão produz 80.
Mas calcular 80 não significa automaticamente atualizar a variável.
3. Calcular não é atualizar o estado
Isto:
playerHealth - 20;
calcula um resultado, mas não o armazena novamente em playerHealth.
Para atualizar o estado:
playerHealth = playerHealth - 20;
Conceitualmente:
playerHealth = 100
↓
100 - 20
↓
80
↓
playerHealth = 80
Primeiro calculamos. Depois atribuímos.
4. O operador de atribuição =
Em:
playerHealth = 80;
podemos ler:
atribua o valor 80 a playerHealth.
Não devemos confundir esse = com a igualdade matemática tradicional.
5. Adição com +
int playerCoins = 25;
playerCoins = playerCoins + 15;
Resultado:
25 + 15 = 40
No contexto do jogo, adição pode representar recompensa, coleta ou ganho.
6. Subtração com -
int playerHealth = 100;
playerHealth = playerHealth - 20;
Resultado:
100 - 20 = 80
Subtração pode representar dano, gasto, consumo ou perda.
7. Multiplicação com *
Imagine um ataque crítico:
int damage = 20;
int criticalDamage = damage * 2;
Resultado:
Critical Damage: 40
Ainda não temos um sistema de combate. O exemplo existe apenas para dar contexto ao operador.
8. Divisão com /
int totalExperience = 200;
int experiencePerLevel = 100;
int completedLevels = totalExperience / experiencePerLevel;
Resultado:
Completed Levels: 2
Mas existe um detalhe importante: divisão inteira.
9. Divisão inteira
int result = 5 / 2;
O resultado será:
2
e não 2.5.
Agora compare:
float result = 5.0f / 2.0f;
Resultado:
2.5
Tipos e operadores trabalham juntos.
E atenção:
float result = 5 / 2;
continua realizando primeiro uma divisão entre inteiros. O resultado 2 é então armazenado como ponto flutuante.
Esse comportamento será ainda mais importante no próximo capítulo, quando estudarmos expressões.
10. Divisão por zero
Uma expressão como:
int result = 10 / 0;
não representa uma operação válida para nosso programa; divisão inteira por zero leva a comportamento indefinido.
Quando estudarmos decisões, poderemos verificar condições antes de dividir.
Por enquanto, guarde a regra:
o divisor precisa ser válido antes da divisão.
11. Resto da divisão com %
O operador % obtém o resto de uma divisão inteira.
5 % 2 = 1
4 % 2 = 0
Exemplo:
int totalExperience = 250;
int experiencePerLevel = 100;
int completedLevels = totalExperience / experiencePerLevel;
int remainingExperience = totalExperience % experiencePerLevel;
Resultado:
Completed Levels: 2
Remaining Experience: 50
12. Os operadores aritméticos deste capítulo
| Operador | Operação | Exemplo |
|-----|--------------------------|-------------------------|
| + | Adição | playerCoins + 10 |
| - | Subtração | playerHealth - 20 |
| `*` | Multiplicação | `damage * 2` |
| `/` | Divisão | `totalExperience / 100` |
| `%` | Resto da divisão inteira | `totalExperience % 100` |
Não precisamos estudar todos os operadores C++ de uma vez. Estamos aprendendo os que resolvem nossos problemas atuais.
13. Atribuição composta
Compare:
playerCoins = playerCoins + 15;
com:
playerCoins += 15;
Para essa atualização simples, a segunda forma comunica diretamente:
- adicione 15 ao valor atual de playerCoins.
Também podemos escrever:
playerHealth -= 20;
damage *= 2;
experience /= 2;
Os principais operadores de atribuição composta vistos aqui são:
+=
-=
*=
/=
%=
14. Incremento com ++
Para subir exatamente um nível:
playerLevel = playerLevel + 1;
ou:
playerLevel += 1;
C++ também oferece:
playerLevel++;
Se playerLevel vale 1, depois passa a valer 2.
15. Decremento com --
itemCount--;
Se itemCount vale 5, depois passa a valer 4.
Temos:
++ → incrementa uma unidade
-- → decrementa uma unidade
Existem formas prefixa e pós-fixa:
++playerLevel;
playerLevel++;
A diferença entre elas se torna importante quando o valor da própria expressão é utilizado. Como o próximo capítulo é sobre expressões, deixaremos essa análise para o momento adequado.
16. Não compacte código apenas porque consegue
Código menor não é automaticamente melhor.
Nossa prioridade continuará sendo:
clareza
↓
intenção
↓
correção
↓
manutenção
Operadores são ferramentas, não uma competição para escrever a menor quantidade possível de caracteres.
17. Tipos influenciam operações
Já vimos:
5 / 2 → 2
5.0f / 2.0f → 2.5
Isso mostra que o significado e o resultado de uma operação dependem dos tipos envolvidos.
Até o + pode aparecer em outros contextos. Com std::string, por exemplo, podemos combinar textos:
std::string playerName = "Alex";
std::string message = "Player: " + playerName;
Resultado:
Player: Alex
Não aprofundaremos concatenação agora. O ponto importante é perceber a relação entre tipo e operação.
18. E o << que usamos desde o começo?
Também é um operador:
std::cout << "Health: " << playerHealth << std::endl;
No contexto de streams, estamos utilizando << para inserção no fluxo de saída.
Continuaremos aprendendo-os conforme surgirem problemas reais.
19. Ordem das operações começa a importar
Considere:
int result = 10 + 5 * 2;
O resultado é:
20
porque a multiplicação possui precedência maior:
10 + (5 * 2)
10 + 10
20
Agora:
int result = (10 + 5) * 2;
produz:
30
Parênteses alteram o agrupamento e também podem tornar a intenção mais explícita.
Não precisamos decorar agora uma tabela gigantesca de precedência. Esse assunto será aprofundado no próximo capítulo.
20. Aplicando operadores ao estado do jogador
Vamos reunir os conceitos:
const int MAX_PLAYER_HEALTH = 100;
int playerHealth = MAX_PLAYER_HEALTH;
int playerCoins = 25;
int playerExperience = 40;
int playerLevel = 1;
playerHealth -= 20;
playerCoins += 15;
playerExperience += 60;
playerLevel++;
Resultado:
Health: 80
Coins: 40
Experience: 100
Level: 2
Nosso programa agora está realizando uma pequena transformação de estado.
21. Implementação completa
#include <iostream>
int main()
{
const int MAX_PLAYER_HEALTH = 100;
int playerHealth = MAX_PLAYER_HEALTH;
int playerCoins = 25;
int playerExperience = 40;
int playerLevel = 1;
std::cout << "=== INITIAL STATE ===" << std::endl;
std::cout << "Health: " << playerHealth << std::endl;
std::cout << "Coins: " << playerCoins << std::endl;
std::cout << "Experience: " << playerExperience << std::endl;
std::cout << "Level: " << playerLevel << std::endl;
playerHealth -= 20;
playerCoins += 15;
playerExperience += 60;
playerLevel++;
std::cout << std::endl;
std::cout << "=== UPDATED STATE ===" << std::endl;
std::cout << "Health: " << playerHealth << std::endl;
std::cout << "Coins: " << playerCoins << std::endl;
std::cout << "Experience: " << playerExperience << std::endl;
std::cout << "Level: " << playerLevel << std::endl;
return 0;
}
Resultado:
=== INITIAL STATE ===
Health: 100
Coins: 25
Experience: 40
Level: 1
=== UPDATED STATE ===
Health: 80
Coins: 40
Experience: 100
Level: 2
22. Operação válida não significa regra válida
Podemos escrever:
playerHealth -= 200;
e acabar com:
Health: -100
Também podemos escrever:
playerCoins -= 1000;
e produzir moedas negativas.
Isso nos ensina uma diferença fundamental:
operação válida em C++
≠
regra válida para o jogo
O compilador conhece tipos e operações. Ele não conhece automaticamente nossas regras de gameplay.
Quando estudarmos decisões, começaremos a proteger essas regras.
23. Não precisamos de DamageSystem
Temos apenas:
playerHealth -= 20;
Ainda não precisamos criar:
DamageSystem
CombatManager
HealthComponent
EventBus
Nossa evolução continua seguindo:
problema
↓
solução simples
↓
uso
↓
limitação
↓
novo conhecimento
↓
refatoração quando necessária
A estrutura permanece:
CppGameEngine/
└── main.cpp
24. Erro proposital — esquecendo a atribuição
Teste:
int playerHealth = 100;
playerHealth - 20;
std::cout << playerHealth << std::endl;
Resultado:
100
A expressão calculou um valor, mas playerHealth não foi atualizado.
Corrija para:
playerHealth -= 20;
Agora:
80
Esse experimento ajuda a separar calcular de armazenar o resultado.
25. Erro proposital — divisão inteira inesperada
Teste:
float result = 5 / 2;
std::cout << result << std::endl;
Depois altere para:
float result = 5.0f / 2.0f;
Compare os resultados.
A pergunta importante não é apenas “por que deu diferente?”, mas:
- quais eram os tipos dos operandos quando a divisão aconteceu?
26. Microdesafio — dano e recompensa
Comece com:
int playerHealth = 100;
int playerCoins = 25;
Aplique:
30 de dano
20 moedas de recompensa
Resultado esperado:
Health: 70
Coins: 45
Uma possível solução:
playerHealth -= 30;
playerCoins += 20;
27. Microdesafio — ataque crítico
Comece com:
int baseDamage = 15;
int criticalMultiplier = 2;
Calcule:
Critical Damage: 30
Uma possível solução:
int criticalDamage = baseDamage * criticalMultiplier;
Observe que preservamos baseDamage e armazenamos o resultado separadamente.
Nem toda operação precisa modificar o operando original.
28. Microdesafio — experiência
Comece com:
int totalExperience = 250;
int experiencePerLevel = 100;
Calcule:
Completed Levels: 2
Remaining Experience: 50
Uma possível solução:
int completedLevels = totalExperience / experiencePerLevel;
int remainingExperience = totalExperience % experiencePerLevel;
Aqui / e % trabalham juntos para resolver um pequeno problema.
29. Código final do capítulo
#include <iostream>
int main()
{
const int MAX_PLAYER_HEALTH = 100;
const int EXPERIENCE_PER_LEVEL = 100;
int playerHealth = MAX_PLAYER_HEALTH;
int playerCoins = 25;
int playerExperience = 40;
int playerLevel = 1;
int baseDamage = 15;
int criticalMultiplier = 2;
std::cout << "=== INITIAL STATE ===" << std::endl;
std::cout << "Health: " << playerHealth << std::endl;
std::cout << "Coins: " << playerCoins << std::endl;
std::cout << "Experience: " << playerExperience << std::endl;
std::cout << "Level: " << playerLevel << std::endl;
int criticalDamage = baseDamage * criticalMultiplier;
playerHealth -= criticalDamage;
playerCoins += 20;
playerExperience += 160;
int completedLevels = playerExperience / EXPERIENCE_PER_LEVEL;
int remainingExperience = playerExperience % EXPERIENCE_PER_LEVEL;
std::cout << std::endl;
std::cout << "=== AFTER GAMEPLAY EVENTS ===" << std::endl;
std::cout << "Critical Damage: " << criticalDamage << std::endl;
std::cout << "Health: " << playerHealth << std::endl;
std::cout << "Coins: " << playerCoins << std::endl;
std::cout << "Total Experience: " << playerExperience << std::endl;
std::cout << "Completed Level Units: " << completedLevels << std::endl;
std::cout << "Remaining Experience: " << remainingExperience << std::endl;
return 0;
}
Resultado esperado:
=== INITIAL STATE ===
Health: 100
Coins: 25
Experience: 40
Level: 1
=== AFTER GAMEPLAY EVENTS ===
Critical Damage: 30
Health: 70
Coins: 45
Total Experience: 200
Completed Level Units: 2
Remaining Experience: 0
Não atualizamos automaticamente playerLevel a partir de completedLevels, porque isso exigiria definir uma regra concreta de progressão. Nosso objetivo atual é entender operadores, não antecipar todo o sistema de níveis.
30. Validação
Antes do commit:
1. Save
2. Build
3. Run
4. Compare os resultados
5. Faça os erros propositalmente
6. Corrija
7. Execute novamente
No Visual Studio:
Save → Ctrl + S
Build → Ctrl + Shift + B
Run → Ctrl + F5
Confirme que os valores exibidos correspondem às operações realizadas.
31. Git
Verifique:
git status
Adicione o arquivo real:
git add CppGameEngine/main.cpp
Ajuste o caminho se sua estrutura local for diferente.
Commit sugerido:
git commit -m "feat: add player state arithmetic operations"
32. Atualizando o CHANGELOG
Em [Unreleased]:
### Added
- Added arithmetic operations for player state updates.
- Added compound assignment examples for health, coins, and experience.
- Added critical damage calculation using multiplication.
- Added integer division and remainder examples for experience progression.
Commit:
git add CHANGELOG.md
git commit -m "docs: update changelog with arithmetic operations"
O ROADMAP técnico não precisa ser alterado a cada fundamento individual.
33. O que aprendemos?
Conhecemos:
+ adição
- subtração
* multiplicação
/ divisão
% resto da divisão inteira
Também utilizamos:
+=
-=
*=
/=
%=
++
--
E descobrimos que:
- calcular não é o mesmo que armazenar o resultado;
- tipos influenciam operações;
- divisão inteira descarta a parte fracionária;
- mudar apenas o tipo da variável de destino não muda a operação que já aconteceu;
- divisão por zero precisa ser evitada;
- % permite obter o resto de uma divisão inteira;
- precedência começa a importar quando combinamos operadores;
- parênteses podem alterar o agrupamento;
- uma operação válida para C++ não é necessariamente uma regra válida para nosso jogo.
Conclusão
Nosso programa já sabia lembrar informações.
Agora começou a transformá-las:
Health
100 - 20 → 80
Coins
25 + 15 → 40
Damage
15 * 2 → 30
Experience
250 / 100 → 2
Remaining Experience
250 % 100 → 50
Jogos são sistemas em constante transformação. Input, tempo, movimento, combate, economia, inventário, NPCs e progressão alterarão continuamente o estado.
Ainda estamos no console, mas a lógica que futuramente ficará por trás de sprites, animações e interfaces já começou a nascer.
E nossas operações estão começando a ficar maiores:
int finalDamage = baseDamage + bonusDamage * multiplier;
O que acontece primeiro?
E por que isto:
int finalDamage = (baseDamage + bonusDamage) * multiplier;
pode produzir outro resultado?
Para responder corretamente, precisamos compreender uma ideia maior: expressões.
Próximo capítulo
Capítulo 09 — Expressões: combinando valores e operações
Série: Do zero ao jogo comercial com C++ e SFML
No próximo artigo vamos partir de operações que já conhecemos:
baseDamage + bonusDamage
baseDamage * multiplier
playerHealth - damage
e começar a combiná-las:
(baseDamage + bonusDamage) * multiplier
Vamos compreender progressivamente operandos, operadores, expressões, subexpressões, precedência, associatividade, parênteses e tipo do resultado, sem transformar o capítulo em uma tabela gigantesca de regras.
Até aqui transformamos dados com operações relativamente simples.
No próximo capítulo começaremos a entender como C++ combina essas operações para produzir resultados mais complexos.
Comentários
Ainda não há comentários. Sê o primeiro a comentar!