C Operadores de Atribuição

Na aula anterior você fez contas, mas o resultado ia direto para o printf e se perdia. Para o programa lembrar de um resultado (o total da compra, os pontos do jogo, o saldo do cofrinho), ele precisa ser guardado numa variável. Quem guarda é o =, o operador de atribuição. Nesta aula você vai ver como ele funciona por dentro, os atalhos +=, -=, *=, /= e %=, que aparecem em quase todo programa C, e os erros que eles causam quando a gente não presta atenção.

Você vai aprender
  • O que o = faz, passo a passo, e por que pontos = pontos + 10 não é absurdo.
  • Por que o lado esquerdo tem de ser uma variável (o erro lvalue required).
  • Os atalhos +=, -=, *=, /= e %=, e a armadilha de x *= 2 + 3.
  • Que a atribuição tem um valor, e a atribuição em cadeia a = b = 0.
  • O que acontece ao guardar um double num int.
  • O =+ trocado, o const que não deixa mudar e como trocar o valor de duas variáveis.

Todos os programas desta aula foram compilados com o gcc 15.2 na máquina fictícia da Ana (usuária ana, computador notebook), na pasta ~/c, com o comando padrão do guia: gcc -std=c23 -Wall -Wextra arquivo.c -o programa. As saídas dos programas foram iguais no editor do guia; os avisos que mudam estão indicados.


A ideia: a lousa do placar

No campeonato da escola, o placar é uma lousa com o nome do time e um número. Quando o time faz um ponto, o juiz olha o número que está lá, faz a conta de cabeça, apaga o número velho e escreve o novo. A lousa só guarda um número por vez.

Uma variável é essa lousa, e o = é o juiz apagando e escrevendo. Por isso, em C, o = não quer dizer "é igual a" (isso é o ==, aula Operadores de Comparação). Ele quer dizer "recebe": placar = 3 se lê "placar recebe 3".


1. O = por dentro

Você já viu na aula Variáveis que macas = macas + 2; soma 2 ao que estava guardado. Vale a pena olhar a ordem com calma, porque todos os atalhos desta aula seguem a mesma ordem:

pontos = pontos + 10;
  1. O C calcula primeiro o lado direito: lê o valor atual de pontos (digamos, 25) e soma 10. O resultado é 35.
  2. Depois guarda o 35 na variável do lado esquerdo, apagando o 25.
  3. A linha terminou: pontos vale 35, e o 25 não existe mais em lugar nenhum.

Na matemática, "pontos = pontos + 10" seria uma equação sem solução. Em C, é uma ordem: "pontos recebe o valor de pontos mais 10". A informação anda sempre da direita para a esquerda.

O lado esquerdo tem de ser uma variável

Como a informação vai para a esquerda, à esquerda do = precisa haver um lugar para guardá-la. Um número ou uma conta não são lugares:

#include <stdio.h>

int main(void) {
    int pontos = 0;
    10 = pontos;
    printf("%d\n", pontos);
    return 0;
}
$ gcc -std=c23 -Wall -Wextra esquerda.c -o esquerda
esquerda.c: In function ‘main’:
esquerda.c:5:8: error: lvalue required as left operand of assignment
    5 |     10 = pontos;
      |        ^

"É preciso 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": uma variável. O 10 é só um valor. O mesmo erro aparece com a + b = 10;: a soma é um resultado, não uma gaveta. No editor, o Clang diz expression is not assignable ("a expressão não pode receber atribuição"). O conserto é inverter: pontos = 10;.


2. Os atalhos: +=, -=, *=, /=, %=

"Pegue a variável, faça uma conta com ela e guarde nela mesma" é tão comum que o C tem um atalho para cada operador aritmético. cofrinho += 50; quer dizer exatamente cofrinho = cofrinho + 50;. O cofrinho da Ana:

#include <stdio.h>

int main(void) {
    int cofrinho = 0;
    cofrinho += 50;
    printf("Depositou 50: %d\n", cofrinho);
    cofrinho += 20;
    printf("Depositou 20: %d\n", cofrinho);
    cofrinho -= 30;
    printf("Gastou 30:    %d\n", cofrinho);
    cofrinho *= 2;
    printf("O avô dobrou: %d\n", cofrinho);
    cofrinho /= 4;
    printf("Dividiu com 3 irmãos, ficou com: %d\n", cofrinho);
    cofrinho %= 7;
    printf("Sobra depois de comprar figurinhas de 7: %d\n", cofrinho);
    return 0;
}
$ gcc -std=c23 -Wall -Wextra cofrinho.c -o cofrinho
$ ./cofrinho
Depositou 50: 50         « 0 + 50
Depositou 20: 70         « 50 + 20
Gastou 30:    40         « 70 - 30
O avô dobrou: 80         « 40 * 2
Dividiu com 3 irmãos, ficou com: 20         « 80 / 4 (a Ana e os 3 irmãos)
Sobra depois de comprar figurinhas de 7: 6  « 20 % 7: comprou 2 pacotes (14) e sobraram 6
AtalhoÉ o mesmo queLê-se
x += 5x = x + 5"some 5 a x"
x -= 5x = x - 5"tire 5 de x"
x *= 5x = x * 5"multiplique x por 5"
x /= 5x = x / 5"divida x por 5" (com int, divisão inteira)
x %= 5x = x % 5"fique só com o resto de x por 5" (só inteiros)
+=
Os dois sinais vão grudados e nesta ordem: primeiro a conta, depois o =. O nome é atribuição composta (a conta e a atribuição compostas num operador só).
as regras da conta
São as da aula Operadores Aritméticos: /= com int corta a parte quebrada (80 / 4 deu 20 exato, mas 81 / 4 também daria 20), %= não aceita double e /= 0 com inteiros derruba o programa.
somar 1
Para o caso mais comum de todos, x += 1, existe um atalho ainda menor: x++ (aula ++ e --).

Missão: o caixa da padaria soma os itens da Ana do jeito longo. Reescreva as quatro contas com os atalhos: += para cada item e -= para o cupom de 5 reais. O total continua o mesmo:

Total: 32
#include <stdio.h>

int main(void) {
    int total = 0;
    total = total + 8;     // pão
    total = total + 6;     // leite
    total = total + 23;    // café
    total = total - 5;     // cupom de desconto
    printf("Total: %d\n", total);
    return 0;
}
#include <stdio.h>

int main(void) {
    int total = 0;
    total += 8;     // pão
    total += 6;     // leite
    total += 23;    // café
    total -= 5;     // cupom de desconto
    printf("Total: %d\n", total);
    return 0;
}

3. A armadilha: x *= 2 + 3

O atalho calcula todo o lado direito primeiro e só depois faz a conta com a variável. É como se o lado direito estivesse entre parênteses:

#include <stdio.h>

int main(void) {
    int x = 10;
    x *= 2 + 3;
    printf("x *= 2 + 3 deu %d\n", x);
    int y = 10;
    y = y * 2 + 3;
    printf("y = y * 2 + 3 deu %d\n", y);
    return 0;
}
$ gcc -std=c23 -Wall -Wextra vezes.c -o vezes
$ ./vezes
x *= 2 + 3 deu 50        « x = x * (2 + 3) = 10 * 5
y = y * 2 + 3 deu 23     « y = (y * 2) + 3 = 20 + 3

Parecem a mesma coisa, mas não são. x *= 2 + 3 é x = x * (2 + 3), nunca x = x * 2 + 3. Os operadores de atribuição têm a menor prioridade de quase todos (aula Precedência): tudo à direita deles é calculado antes. Nenhum compilador avisa, porque as duas formas são válidas. Na dúvida, escreva a forma longa, com parênteses.

Experimente: antes de rodar, calcule de cabeça quanto x vale no fim. Depois rode e confira. Troque o *= por -= e por /= e calcule de novo: em todos, o 2 + 3 é feito antes.

#include <stdio.h>

int main(void) {
    int x = 10;
    x *= 2 + 3;
    printf("x = %d\n", x);
    x -= 8 - 3;
    printf("x = %d\n", x);
    return 0;
}

4. A atribuição tem um valor

Em C, x = 42 não só guarda: a expressão inteira vale 42, o valor que foi guardado. Isso permite zerar várias variáveis numa linha:

#include <stdio.h>

int main(void) {
    int vidas;
    int pontos;
    int fase;
    vidas = pontos = fase = 0;
    printf("%d %d %d\n", vidas, pontos, fase);
    int x = 0;
    printf("A atribuição vale %d\n", x = 42);
    printf("e x agora é %d\n", x);
    return 0;
}
$ gcc -std=c23 -Wall -Wextra cadeia.c -o cadeia
$ ./cadeia
0 0 0
A atribuição vale 42     « o printf recebeu o valor da expressão x = 42
e x agora é 42           « e o x foi mudado de verdade
  1. A atribuição agrupa da direita para a esquerda: vidas = pontos = fase = 0 é vidas = (pontos = (fase = 0)).
  2. fase = 0 guarda 0 em fase e vale 0.
  3. Esse 0 vai para pontos, e a expressão vale 0 de novo; por fim, o 0 vai para vidas.

É também por causa desse valor que if (estoque = 5) compila: a atribuição vale 5, e 5 é "verdadeiro". Esse é o erro mais famoso do C, explicado em Operadores de Comparação. Use o valor da atribuição com moderação: printf("%d", x = 42) funciona, mas esconde uma mudança de variável no meio de outra instrução. Uma atribuição por linha é mais fácil de ler. A forma em cadeia só serve para dar o mesmo valor a várias variáveis; para declarar, prefira uma por linha, cada uma com o seu valor: int vidas = 0;.


5. Guardar um double num int

A variável não muda de tipo para caber o valor: é o valor que é convertido para o tipo da variável. Num int, a parte depois da vírgula é cortada (não arredondada):

#include <stdio.h>

int main(void) {
    int estoque = 10;
    estoque *= 1.5;
    printf("Estoque: %d\n", estoque);
    int notas = 9.99;
    printf("Nota: %d\n", notas);
    double media = 7 / 2;
    printf("Média: %.1f\n", media);
    return 0;
}
$ gcc -std=c23 -Wall -Wextra corta.c -o corta       « nenhum aviso
$ ./corta
Estoque: 15      « 10 * 1.5 = 15.0, que cabe certinho num int
Nota: 9          « 9.99 virou 9: o ,99 foi cortado
Média: 3.0       « 7 / 2 é divisão inteira (3); só depois o 3 virou 3.0
estoque *= 1.5
É estoque = estoque * 1.5. A conta é feita com double (15.0) e o resultado volta a ser int na hora de guardar. Com 11 no estoque, o resultado 16.5 seria guardado como 16.
int notas = 9.99;
O 9.99 é cortado para 9. O gcc não avisa com -Wall -Wextra. No editor do guia, o Clang avisa sozinho: implicit conversion from 'double' to 'int' changes value from 9.99 to 9 [-Wliteral-conversion].
double media = 7 / 2;
O contrário não salva a conta: o tipo da variável que recebe não muda a conta do lado direito, que é feita antes (aula Conversão de Tipos).

Para o gcc avisar nesses casos, acrescente -Wconversion:

$ gcc -std=c23 -Wall -Wextra -Wconversion corta.c -o corta
corta.c: In function ‘main’:
corta.c:5:16: warning: conversion from ‘double’ to ‘int’ may change value [-Wfloat-conversion]
    5 |     estoque *= 1.5;
      |                ^~~
corta.c:7:17: warning: conversion from ‘double’ to ‘int’ changes value from ‘9.9900000000000002e+0’ to ‘9’ [-Wfloat-conversion]
    7 |     int notas = 9.99;
      |                 ^~~~

"A conversão de double para int pode mudar o valor" e "muda o valor de 9.99… para 9". (O 9.9900000000000002e+0 é o 9.99 como ele fica guardado de verdade; a aula float e double explica por que não é exato.)


6. =+ não é +=

Os dois sinais trocados de lugar formam outra coisa, e compilam:

#include <stdio.h>

int main(void) {
    int saldo = 100;
    saldo =+ 50;
    printf("Saldo: %d\n", saldo);
    saldo =- 20;
    printf("Saldo: %d\n", saldo);
    return 0;
}
$ gcc -std=c23 -Wall -Wextra maismais.c -o maismais      « nenhum aviso!
$ ./maismais
Saldo: 50        « era para ser 150
Saldo: -20       « era para ser 130

O C lê saldo =+ 50 como saldo = +50: "saldo recebe mais cinquenta", com o + unário (o sinal do número, como em -20). Os 100 que estavam lá foram apagados. E saldo =- 20 é saldo = -20. O gcc não avisa nada. O Clang do editor desconfia: use of unary operator that may be intended as compound assignment (+=) ("uso de operador unário que talvez devesse ser a atribuição composta +="). A regra para lembrar: o = vem sempre por último, como em +=, -=, ==, !=, <=, >=.


7. Trocar o valor de duas variáveis

Um problema clássico: o copo A tem suco, o copo B tem leite, e a Ana quer trocar. Despejar um no outro direto não dá certo, e com variáveis também não:

#include <stdio.h>

int main(void) {
    int copo_a = 1;
    int copo_b = 2;
    copo_a = copo_b;
    copo_b = copo_a;
    printf("A = %d, B = %d\n", copo_a, copo_b);
    return 0;
}
$ gcc -std=c23 -Wall -Wextra troca.c -o troca
$ ./troca
A = 2, B = 2      « o 1 sumiu!
  1. copo_a = copo_b; guarda 2 em copo_a. O 1 que estava lá foi apagado, e não ficou guardado em lugar nenhum.
  2. copo_b = copo_a; copia o valor atual de copo_a, que já é 2.

Na cozinha, a solução é usar um terceiro copo, vazio: despeje o A nele, depois o B no A, e por fim o terceiro copo no B. Em C, o terceiro copo é uma variável a mais, normalmente chamada temp (temporária). Essa é a sua missão final.


Formas diferentes de mudar uma variável

FormaExemploQuando usar
Atribuição simplestotal = 0;Para dar um valor novo, sem usar o antigo.
Forma longatotal = total + preco;Funciona sempre. Útil quando a conta é diferente da variável: total = total * 2 + taxa;.
Atalhototal += preco;O jeito mais comum em C para "acumular" ou "descontar". Mais curto e o nome da variável aparece uma vez só (menos chance de errar a digitação).
Somar ou tirar 1vidas++; / vidas--;Contadores (aula ++ e --).
Em cadeiaa = b = 0;Para zerar variáveis já declaradas. Para declarar, uma por linha.
Atalhos de bits&=, |=, ^=, <<=, >>=Existem também, para os operadores de bits (aula Operadores de Bits).

Quando dá errado

error: lvalue required as left operand of assignment

O que significa: o lado esquerdo do = não é uma variável: é um número (10 = x), uma conta (a + b = 10) ou outra coisa que não tem lugar na memória. No Clang: expression is not assignable.

Como consertar: a variável que recebe fica à esquerda: x = 10;. Para "a + b deve dar 10", você quer uma comparação: a + b == 10.

error: assignment of read-only variable ‘VIDAS_INICIAIS’
$ gcc -std=c23 -Wall -Wextra constante.c -o constante
constante.c: In function ‘main’:
constante.c:5:20: error: assignment of read-only variable ‘VIDAS_INICIAIS’
    5 |     VIDAS_INICIAIS += 1;
      |                    ^~

O que significa: "atribuição a uma variável só de leitura". A variável foi declarada com const (const int VIDAS_INICIAIS = 3;), e nenhum operador de atribuição pode mudá-la, nem os atalhos. No Clang: cannot assign to variable 'VIDAS_INICIAIS' with const-qualified type 'const int'.

Como consertar: se o valor precisa mudar, ele não é uma constante: use outra variável, sem const (int vidas = VIDAS_INICIAIS;), e mude essa. Mais em Constantes.

O valor antigo sumiu (=+ no lugar de +=)

O que significa: x =+ 5 é x = +5: guarda 5 e apaga o que estava lá. O gcc não avisa; o Clang avisa may be intended as compound assignment.

Como consertar: o = por último: x += 5.

A conta com atalho deu um valor diferente da forma longa

O que significa: o atalho calcula todo o lado direito antes: x *= a + b é x = x * (a + b).

Como consertar: se você queria x * a + b, escreva a forma longa: x = x * a + b;.

O valor guardado perdeu as casas depois da vírgula

O que significa: a variável é int, e a atribuição cortou a parte quebrada (int n = 9.99; guarda 9; n *= 1.5 também corta).

Como consertar: se o valor pode ter vírgula, declare a variável como double. Compile com -Wconversion para o gcc mostrar todos os cortes.


Referência rápida

OperadorExemploEquivale a
=x = 5x recebe 5 (e a expressão vale 5)
+=x += 5x = x + 5
-=x -= 5x = x - 5
*=x *= a + bx = x * (a + b)
/=x /= 5x = x / 5
%=x %= 5x = x % 5 (só inteiros)
&= |= ^= <<= >>=x |= 4x = x | 4 (bits)
Agrupam da direita para a esquerda (a = b = 0) e têm prioridade menor que as contas, as comparações e os lógicos.

Exercícios

1. O atalho

Complete com o atalho que tira 3 das vidas:

vidas 3;

2. A forma longa

preco *= 2; é o mesmo que preco = preco ? 2;. Qual é o operador no lugar do ?

preco = preco 2;

3. Quanto vale?

Com int x = 4;, quanto x vale depois de x *= 3 + 1;?

x =

4. Com int pontos = 10;, quanto vale pontos depois de pontos =+ 5;?

5. Qual linha não compila?

6. Com int total = 7;, quanto vale total depois de total /= 2;?

Missão final: trocar os copos

Missão: conserte a troca da seção 7 usando um terceiro copo: crie uma variável int temp que guarda o valor de um dos copos antes de ele ser apagado, e use-a para terminar a troca. A saída deve ser:

A = 2, B = 1
#include <stdio.h>

int main(void) {
    int copo_a = 1;
    int copo_b = 2;
    copo_a = copo_b;
    copo_b = copo_a;
    printf("A = %d, B = %d\n", copo_a, copo_b);
    return 0;
}
#include <stdio.h>

int main(void) {
    int copo_a = 1;
    int copo_b = 2;
    int temp = copo_a;
    copo_a = copo_b;
    copo_b = temp;
    printf("A = %d, B = %d\n", copo_a, copo_b);
    return 0;
}

Depois de acertar, troque os valores iniciais (por exemplo, 30 e 75) e confira que a troca continua funcionando. Você vai usar essa troca de três passos para ordenar listas de números, mais adiante no guia.


Resumo

  • = é "recebe": calcula o lado direito e guarda o resultado na variável da esquerda, apagando o valor antigo. Não é comparação (essa é o ==).
  • O lado esquerdo precisa ser uma variável; senão, lvalue required as left operand of assignment.
  • +=, -=, *=, /= e %= são atalhos para x = x ? valor. O lado direito inteiro é calculado antes: x *= 2 + 3 é x = x * 5.
  • A atribuição vale o que foi guardado, e agrupa da direita para a esquerda: a = b = 0.
  • Guardar double em int corta a parte quebrada, sem aviso no gcc (use -Wconversion).
  • =+ é = +: o = vem sempre por último. const não aceita nenhuma atribuição.
  • Para trocar duas variáveis, use uma terceira: temp = a; a = b; b = temp;.
  • Próximo passo: Operadores de Comparação.