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 +=.

Você vai aprender
  • 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 =+ 5 compila, 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
  1. 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.
  2. estoque = 7; é uma atribuição: a variável já existe e recebe um valor novo. O 10 se perde.
  3. estoque = estoque + 5;: primeiro o C++ calcula o lado direito com o valor atual (7 + 5 = 12); depois guarda o 12 em estoque.

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 queLeia comoExemplo do dia a dia
x += 5x = x + 5"x aumenta 5"Depositar dinheiro
x -= 5x = x - 5"x diminui 5"Vender do estoque
x *= 5x = x * 5"x fica 5 vezes maior"Pontos em dobro (*= 2)
x /= 5x = x / 5"x é dividido por 5"Dividir a conta
x %= 5x = 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.

Existem mais atalhos

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
  1. A atribuição é feita da direita para a esquerda: a = b = c = 0 é a = (b = (c = 0)).
  2. c = 0 guarda 0 em c e devolve 0.
  3. b = 0 (o valor devolvido) guarda 0 em b e devolve 0.
  4. a = 0. As três ficam com 0.
  5. Na linha de baixo, (x = 8) guarda 8 em x e devolve 8; o + 1 dá 9 para o y.

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:

SinalNomeLeia comoO 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:

  1. estoque = 10 guardou 10 no estoque (o valor verdadeiro, 0, foi perdido).
  2. A atribuição devolveu 10, e qualquer número diferente de zero conta como "verdadeiro" (veja Conversão de Tipos). Então o if sempre 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 longaAtalhoQual usar
Aumentarx = x + n;x += n;O atalho: nome da variável uma vez só.
Aumentar de 1 em 1x = x + 1;x += 1; ou ++x;++x, da aula ++ e --.
Dar um valor novo, sem usar o antigox = 7;(não há)=
Zerar várias variáveisa = 0; b = 0; c = 0;a = b = c = 0;Os dois são claros; escolha um.
Uma conta diferente, como x = 2 * x + 1x = 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;.

A soma não soma: o resultado é o próprio número

O que aconteceu: você escreveu =+ (ou =-) no lugar de += (ou -=). O g++ não avisa.

Como resolver: inverta os sinais: pontos += 5;.


Referência

OperadorExemploEquivale aCom x = 20, x fica
=x = 55
+=x += 5x = x + 525
-=x -= 5x = x - 515
*=x *= 5x = x * 5100
/=x /= 6x = x / 63 (divisão inteira)
%=x %= 6x = x % 62
&= |= ^= <<= >>=x &= 6x = 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):

vidas 1;

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 seja const.
  • 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.5 corta.
  • += também junta textos num std::string.
  • a = b = c = 0; atribui da direita para a esquerda.
  • = muda; == pergunta. if (x = 10) é um engano: o g++ avisa com suggest 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 +=.