C++ Operadores de Atribuição
Atribuir é colocar um valor numa variável. Você faz isso desde a aula de Variáveis, com o sinal =. Mas programas mudam variáveis o tempo todo: o saldo aumenta, o estoque diminui, os pontos do jogo dobram. Para essas mudanças, o C++ tem atalhos: +=, -=, *=, /= e %=. Nesta aula você vai usar todos eles e conhecer dois enganos famosos: trocar = por ==, e escrever =+ no lugar de +=.
- Por que o
=do C++ quer dizer "recebe", e não "é igual a". - Os atalhos
+=,-=,*=,/=e%=, e a conta exata que cada um faz. - Como dar o mesmo valor a várias variáveis de uma vez:
a = b = c = 0;. - A diferença entre
=(atribuição) e==(comparação), e o aviso que o g++ dá quando você troca um pelo outro. - Por que
x =+ 5compila, mas não soma nada.
Todos os programas desta aula foram compilados com o g++ 15.2 na máquina fictícia da Ana (usuária ana, computador notebook), na pasta ~/cpp, com o comando padrão do guia: g++ -std=c++23 -Wall -Wextra arquivo.cpp -o programa.
A ideia: o placar do jogo
Pense no placar de um jogo de futebol. Quando sai um gol, o operador do placar não escreve uma frase nova do zero: ele olha o número que está lá (2), soma um, apaga e escreve o novo número (3). O placar é a variável; "olhar, calcular, apagar e escrever" é a atribuição.
Por isso, em C++, gols = gols + 1; faz todo sentido, mesmo parecendo absurdo na matemática ("um número igual a ele mais um?"). O = não afirma que os dois lados são iguais; ele é uma ordem: "calcule o lado direito e guarde na variável da esquerda". Leia sempre como "recebe": gols recebe gols + 1.
1. O =: atribuição simples
#include <iostream>
int main() {
int estoque = 10; // criar a variável já com um valor
estoque = 7; // atribuição: o 10 é jogado fora
estoque = estoque + 5; // usa o valor atual (7) para calcular o novo
std::cout << estoque << '\n';
}
$ g++ -std=c++23 -Wall -Wextra estoque.cpp -o estoque $ ./estoque 12
int estoque = 10;cria a variável e já põe 10 nela. Tecnicamente isso é uma inicialização (o valor de nascença), não uma atribuição, mas usa o mesmo sinal.estoque = 7;é uma atribuição: a variável já existe e recebe um valor novo. O 10 se perde.estoque = estoque + 5;: primeiro o C++ calcula o lado direito com o valor atual (7 + 5 = 12); depois guarda o 12 emestoque.
O lado esquerdo precisa ser um lugar
Do lado esquerdo do = vai onde guardar: uma variável. Um número, uma conta ou uma constante não são lugares onde se guarda alguma coisa:
$ g++ -std=c++23 -Wall -Wextra esquerda.cpp -o esquerda
esquerda.cpp: In function ‘int main()’:
esquerda.cpp:3:5: error: lvalue required as left operand of assignment
3 | 10 = estoque;
| ^~
"É necessário um lvalue como operando esquerdo da atribuição." lvalue (de left value, "valor da esquerda") é o nome técnico de "algo que tem um lugar na memória e pode receber um valor", como uma variável. O 10 é só um valor, não tem lugar. O mesmo vale para uma variável const (aula Constantes): ela tem lugar, mas está trancada.
$ g++ -std=c++23 -Wall -Wextra constante.cpp -o constante
constante.cpp: In function ‘int main()’:
constante.cpp:3:12: error: assignment of read-only variable ‘limite’
3 | limite += 5;
| ~~~~~~~^~~~
"Atribuição a uma variável somente leitura, limite." O limite foi criado com const int limite = 10;, e nenhum operador de atribuição pode mudá-lo.
2. Os atalhos: +=, -=, *=, /=, %=
Mudar uma variável a partir do valor dela mesma é tão comum que existe um atalho para cada operação aritmética. saldo += 50; quer dizer exatamente saldo = saldo + 50;, só que escrito uma vez só. São os operadores de atribuição composta (compostos de uma conta e uma atribuição).
#include <iostream>
int main() {
int saldo = 100;
std::cout << "Início: " << saldo << '\n';
saldo += 50;
std::cout << "Depois de += 50: " << saldo << '\n';
saldo -= 30;
std::cout << "Depois de -= 30: " << saldo << '\n';
saldo *= 2;
std::cout << "Depois de *= 2: " << saldo << '\n';
saldo /= 3;
std::cout << "Depois de /= 3: " << saldo << '\n';
saldo %= 7;
std::cout << "Depois de %= 7: " << saldo << '\n';
}
$ g++ -std=c++23 -Wall -Wextra carteira.cpp -o carteira $ ./carteira Início: 100 Depois de += 50: 150 « 100 + 50 Depois de -= 30: 120 « 150 - 30 Depois de *= 2: 240 « 120 * 2 Depois de /= 3: 80 « 240 / 3 Depois de %= 7: 3 « 80 % 7: 7 × 11 = 77, sobram 3
| Atalho | É o mesmo que | Leia como | Exemplo do dia a dia |
|---|---|---|---|
x += 5 | x = x + 5 | "x aumenta 5" | Depositar dinheiro |
x -= 5 | x = x - 5 | "x diminui 5" | Vender do estoque |
x *= 5 | x = x * 5 | "x fica 5 vezes maior" | Pontos em dobro (*= 2) |
x /= 5 | x = x / 5 | "x é dividido por 5" | Dividir a conta |
x %= 5 | x = x % 5 | "x vira o resto da divisão por 5" | Dar a volta no relógio (hora %= 24) |
O sinal da conta vem antes do =, e os dois ficam grudados: +=, nunca + = nem =+ (este último tem uma armadilha própria, na seção 5).
Por que usar o atalho? Ele é mais curto, e você escreve o nome da variável uma vez só, o que evita enganos como saldo = sado + 50. Com nomes compridos (pontos_do_jogador_um), a diferença aparece.
Experimente: a Ana tem 12 laranjas. Acrescente linhas para ela comprar mais 8 (+=), dar 5 para a vizinha (-=) e mostre o resultado após cada mudança.
#include <iostream>
int main() {
int laranjas = 12;
std::cout << "Laranjas: " << laranjas << '\n';
laranjas += 3;
std::cout << "Depois da feira: " << laranjas << '\n';
}
O lado direito é calculado inteiro antes
Quando o lado direito do atalho é uma conta, ela é feita toda, e só então combinada com a variável. x *= 2 + 3 é x = x * (2 + 3), e não x = x * 2 + 3:
#include <iostream>
int main() {
int x = 4;
x *= 2 + 3;
std::cout << x << '\n';
int y = 4;
y = y * 2 + 3;
std::cout << y << '\n';
}
$ g++ -std=c++23 -Wall -Wextra vezes.cpp -o vezes $ ./vezes 20 « 4 * (2 + 3) 11 « (4 * 2) + 3
Os atalhos respeitam o tipo da variável
O resultado do atalho é guardado na própria variável, com o tipo dela. Com um int, isso quer dizer que /= faz divisão inteira, e que um *= 1.5 é cortado sem aviso:
#include <iostream>
int main() {
int itens = 10;
itens *= 1.5;
std::cout << itens << '\n';
itens /= 4;
std::cout << itens << '\n';
double preco = 10;
preco /= 4;
std::cout << preco << '\n';
}
$ g++ -std=c++23 -Wall -Wextra mistura.cpp -o mistura
$ ./mistura
15 « 10 * 1.5 = 15.0, guardado como int 15 (aqui não perdeu nada, por sorte)
3 « 15 / 4 = 3,75: divisão inteira, ficou 3
2.5 « com double, a divisão é real
$ g++ -std=c++23 -Wall -Wextra -Wconversion mistura.cpp -o mistura
mistura.cpp: In function ‘int main()’:
mistura.cpp:5:11: warning: conversion from ‘double’ to ‘int’ may change value [-Wfloat-conversion]
5 | itens *= 1.5;
| ~~~~~~^~~~~~
Com -Wconversion, o g++ avisa que itens *= 1.5 faz uma conta com double e guarda num int. Se itens valesse 11, o resultado 16.5 viraria 16. Veja Conversão de Tipos. E, como na aula de Operadores Aritméticos, o %= só funciona com inteiros, e /= 0 com inteiro quebra o programa.
+= também junta textos
Com um std::string, o += acrescenta texto no fim. É muito usado para montar uma frase aos poucos:
#include <iostream>
#include <string>
int main() {
std::string lista = "arroz";
lista += ", feijão";
lista += ", café";
std::cout << lista << '\n';
}
$ g++ -std=c++23 -Wall -Wextra frase.cpp -o frase $ ./frase arroz, feijão, café
Os detalhes ficam para a aula Juntar Strings. Só o += funciona com texto; -=, *= e companhia, não.
O C++ ainda tem &=, |=, ^=, <<= e >>=, que combinam a atribuição com operadores de bits. Eles seguem a mesma regra (x &= y é x = x & y) e quase não aparecem em programas de iniciante.
3. Atribuição encadeada
Em C++, uma atribuição não só guarda o valor: ela também devolve o valor guardado, que pode ser usado de novo. Isso permite dar o mesmo valor a várias variáveis numa linha:
#include <iostream>
int main() {
int a, b, c;
a = b = c = 0;
std::cout << a << ' ' << b << ' ' << c << '\n';
int x = 5;
int y = (x = 8) + 1;
std::cout << x << ' ' << y << '\n';
}
$ g++ -std=c++23 -Wall -Wextra encadeada.cpp -o encadeada $ ./encadeada 0 0 0 8 9
- A atribuição é feita da direita para a esquerda:
a = b = c = 0éa = (b = (c = 0)). c = 0guarda 0 emce devolve 0.b = 0(o valor devolvido) guarda 0 embe devolve 0.a = 0. As três ficam com 0.- Na linha de baixo,
(x = 8)guarda 8 emxe devolve 8; o+ 1dá 9 para oy.
A primeira forma (zerar várias variáveis) é comum e clara. A segunda (atribuir no meio de uma conta) funciona, mas deixa o código difícil de ler: prefira duas linhas, x = 8; e int y = x + 1;. E repare que int a, b, c; cria as variáveis sem valor; aqui não tem problema porque a linha seguinte já dá um valor a elas, mas o hábito de inicializar (int a = 0;) é mais seguro.
4. = × ==: atribuir não é comparar
Este é um dos enganos mais famosos da programação. Os dois sinais são parecidos, mas fazem coisas completamente diferentes:
| Sinal | Nome | Leia como | O que faz |
|---|---|---|---|
= | Atribuição | "recebe" | Muda a variável da esquerda. |
== | Igualdade | "é igual a?" | Pergunta se os dois lados são iguais e responde verdadeiro ou falso, sem mudar nada (aula Operadores de Comparação). |
O problema aparece quando você quer perguntar e escreve um = só. O programa abaixo usa um if ("se"), que você vai estudar em if e else: o bloco entre chaves só roda se a condição entre parênteses for verdadeira. A intenção era perguntar "o estoque é 10?":
#include <iostream>
int main() {
int estoque = 0;
if (estoque = 10) {
std::cout << "Estoque é 10\n";
}
std::cout << "estoque agora vale " << estoque << '\n';
}
$ g++ -std=c++23 -Wall -Wextra igual.cpp -o igual
igual.cpp: In function ‘int main()’:
igual.cpp:5:17: warning: suggest parentheses around assignment used as truth value [-Wparentheses]
5 | if (estoque = 10) {
| ~~~~~~~~^~~~
$ ./igual
Estoque é 10 « mas o estoque era 0!
estoque agora vale 10 « e o "teste" mudou o estoque
Dois estragos de uma vez:
estoque = 10guardou 10 no estoque (o valor verdadeiro, 0, foi perdido).- A atribuição devolveu 10, e qualquer número diferente de zero conta como "verdadeiro" (veja Conversão de Tipos). Então o
ifsempre entra.
O aviso diz: "sugiro parênteses em volta da atribuição usada como valor verdade". O g++ desconfia que você quis comparar e escreveu atribuição. (Os parênteses extras, if ((estoque = 10)), servem para dizer "eu quero mesmo atribuir aqui", e calam o aviso; não faça isso sem motivo.) No editor do guia, o Clang diz a mesma coisa com outras palavras: using the result of an assignment as a condition without parentheses, e sugere: use '==' to turn this assignment into an equality comparison.
O engano contrário também existe: escrever == querendo atribuir. A linha não muda nada, e o g++ avisa que ela é inútil:
$ g++ -std=c++23 -Wall -Wextra igual2.cpp -o igual2
igual2.cpp: In function ‘int main()’:
igual2.cpp:5:13: warning: statement has no effect [-Wunused-value]
5 | estoque == 10;
| ~~~~~~~~^~~~~
"A instrução não tem efeito": comparar e jogar a resposta fora não serve para nada. Os dois avisos só aparecem porque você compilou com -Wall. Mais um motivo para nunca compilar sem ele.
5. A armadilha do =+
Inverter a ordem dos sinais é fácil de fazer e difícil de enxergar:
#include <iostream>
int main() {
int pontos = 10;
pontos =+ 5;
std::cout << pontos << '\n';
pontos =- 3;
std::cout << pontos << '\n';
}
$ g++ -std=c++23 -Wall -Wextra invertido.cpp -o invertido $ ./invertido 5 « deveria ser 15 -3 « deveria ser 2
O g++ compilou sem nenhum aviso. O C++ leu pontos =+ 5 como pontos = +5: atribuição de "mais cinco" (o + unário, que não muda nada). E pontos =- 3 é pontos = -3. Os espaços enganam o olho, mas o compilador não liga para eles. No editor do guia, o Clang percebe: use of unary operator that may be intended as compound assignment (+=), "uso de operador unário que talvez devesse ser atribuição composta (+=)".
Regra para não errar: o sinal da conta vem primeiro, o = vem por último. += se lê "mais, recebe".
6. Somar em uma variável sem valor
Os atalhos leem o valor atual da variável. Se ela nunca recebeu um valor, a leitura pega lixo:
$ cat naoinit.cpp
#include <iostream>
int main() {
int total;
total += 5;
std::cout << total << '\n';
}
$ g++ -std=c++23 -Wall -Wextra naoinit.cpp -o naoinit
naoinit.cpp: In function ‘int main()’:
naoinit.cpp:5:11: warning: ‘total’ is used uninitialized [-Wuninitialized]
5 | total += 5;
| ~~~~~~^~~~
naoinit.cpp:4:9: note: ‘total’ was declared here
4 | int total;
| ^~~~~
$ ./naoinit
5 « deu 5 por sorte: o lixo na memória era 0 desta vez
"total é usada sem ter sido inicializada." O resultado 5 é sorte: o que havia naquele pedaço de memória por acaso era 0. Em outro programa, em outro computador ou com outras opções de compilação, pode sair qualquer número. Isso é comportamento indefinido. A solução é simples: sempre inicialize os acumuladores, int total = 0;.
Formas diferentes: qual usar
| Para… | Forma longa | Atalho | Qual usar |
|---|---|---|---|
| Aumentar | x = x + n; | x += n; | O atalho: nome da variável uma vez só. |
| Aumentar de 1 em 1 | x = x + 1; | x += 1; ou ++x; | ++x, da aula ++ e --. |
| Dar um valor novo, sem usar o antigo | x = 7; | (não há) | = |
| Zerar várias variáveis | a = 0; b = 0; c = 0; | a = b = c = 0; | Os dois são claros; escolha um. |
Uma conta diferente, como x = 2 * x + 1 | x = 2 * x + 1; | (não há) | A forma longa. Não force um atalho. |
Quando dá errado
warning: suggest parentheses around assignment used as truth value
O que aconteceu: há um = (atribuição) onde o C++ esperava uma condição, como dentro de um if. Quase sempre você quis escrever ==.
Como resolver: troque por ==. Não ignore esse aviso: o programa compila, mas a variável é alterada e a condição dá sempre o mesmo resultado.
warning: statement has no effect [-Wunused-value]
O que aconteceu: a linha calcula algo e joga fora, como estoque == 10;. Provavelmente você quis atribuir.
Como resolver: estoque = 10;.
error: lvalue required as left operand of assignment
O que aconteceu: do lado esquerdo do = (ou do += etc.) há algo que não é uma variável: um número, uma conta (a + b = 5) ou, de novo, um = que deveria ser == numa expressão maior.
Como resolver: deixe só a variável à esquerda: estoque = 10;, nunca 10 = estoque;.
error: assignment of read-only variable ‘limite’
O que aconteceu: você tentou mudar uma variável const.
Como resolver: se o valor precisa mudar, tire o const. Se não precisa, crie outra variável para o valor novo: int novo_limite = limite + 5;.
O que aconteceu: você escreveu =+ (ou =-) no lugar de += (ou -=). O g++ não avisa.
Como resolver: inverta os sinais: pontos += 5;.
Referência
| Operador | Exemplo | Equivale a | Com x = 20, x fica |
|---|---|---|---|
= | x = 5 | 5 | |
+= | x += 5 | x = x + 5 | 25 |
-= | x -= 5 | x = x - 5 | 15 |
*= | x *= 5 | x = x * 5 | 100 |
/= | x /= 6 | x = x / 6 | 3 (divisão inteira) |
%= | x %= 6 | x = x % 6 | 2 |
&= |= ^= <<= >>= | x &= 6 | x = x & 6 | (operadores de bits) |
Exercícios
1. O atalho
Reescreva vidas = vidas - 1; usando um operador de atribuição composta (complete com o operador):
2. Quanto vale?
int x = 3; e depois x *= 4 + 1;. Quanto vale x?
3. Divisão no atalho
int fatias = 17; e depois fatias /= 2;. Quanto vale fatias?
4. int nivel = 4; e depois nivel =+ 2;. Quanto vale nivel?
5. Qual linha pergunta se senha é igual a 1234, sem mudar senha?
6. Depois de int a = 1, b = 2, c = 3; e a = b = c;, quanto valem a, b e c?
Missão final: o placar do jogo
Missão: a Ana começa o jogo com 100 pontos. Usando os atalhos, faça: ela ganha 50 pontos (+=), perde 30 numa armadilha (-=) e pega um bônus que dobra os pontos (*=). A saída final deve ser:
Pontos: 240
#include <iostream>
int main() {
int pontos = 100;
// ganha 50
// perde 30
// dobra
std::cout << "Pontos: " << pontos << '\n';
}
#include <iostream>
int main() {
int pontos = 100;
// ganha 50
pontos += 50;
// perde 30
pontos -= 30;
// dobra
pontos *= 2;
std::cout << "Pontos: " << pontos << '\n';
}
Resumo
=é atribuição: "recebe". O lado direito é calculado primeiro, depois guardado na variável da esquerda. À esquerda só vai uma variável (um lvalue) que não sejaconst.x += néx = x + n. O mesmo vale para-=,*=,/=e%=. O lado direito é calculado inteiro antes:x *= 2 + 3éx = x * 5.- O resultado fica com o tipo da variável: com
int,/=é divisão inteira e*= 1.5corta. +=também junta textos numstd::string.a = b = c = 0;atribui da direita para a esquerda.=muda;==pergunta.if (x = 10)é um engano: o g++ avisa comsuggest parentheses around assignment used as truth value.x =+ 5éx = +5, não soma. O sinal da conta vem antes do=.- Inicialize os acumuladores (
int total = 0;) antes de usar+=.