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.
- O que o
=faz, passo a passo, e por quepontos = pontos + 10não é absurdo. - Por que o lado esquerdo tem de ser uma variável (o erro
lvalue required). - Os atalhos
+=,-=,*=,/=e%=, e a armadilha dex *= 2 + 3. - Que a atribuição tem um valor, e a atribuição em cadeia
a = b = 0. - O que acontece ao guardar um
doublenumint. - O
=+trocado, oconstque 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;
- O C calcula primeiro o lado direito: lê o valor atual de
pontos(digamos, 25) e soma 10. O resultado é 35. - Depois guarda o 35 na variável do lado esquerdo, apagando o 25.
- A linha terminou:
pontosvale 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 que | Lê-se |
|---|---|---|
x += 5 | x = x + 5 | "some 5 a x" |
x -= 5 | x = x - 5 | "tire 5 de x" |
x *= 5 | x = x * 5 | "multiplique x por 5" |
x /= 5 | x = x / 5 | "divida x por 5" (com int, divisão inteira) |
x %= 5 | x = 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:
/=comintcorta a parte quebrada (80 / 4 deu 20 exato, mas 81 / 4 também daria 20),%=não aceitadoublee/= 0com 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
- A atribuição agrupa da direita para a esquerda:
vidas = pontos = fase = 0évidas = (pontos = (fase = 0)). fase = 0guarda 0 emfasee vale 0.- Esse 0 vai para
pontos, e a expressão vale 0 de novo; por fim, o 0 vai paravidas.
É 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 comdouble(15.0) e o resultado volta a serintna 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!
copo_a = copo_b;guarda 2 emcopo_a. O 1 que estava lá foi apagado, e não ficou guardado em lugar nenhum.copo_b = copo_a;copia o valor atual decopo_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
| Forma | Exemplo | Quando usar |
|---|---|---|
| Atribuição simples | total = 0; | Para dar um valor novo, sem usar o antigo. |
| Forma longa | total = total + preco; | Funciona sempre. Útil quando a conta é diferente da variável: total = total * 2 + taxa;. |
| Atalho | total += 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 1 | vidas++; / vidas--; | Contadores (aula ++ e --). |
| Em cadeia | a = 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.
=+ 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.
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 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
| Operador | Exemplo | Equivale a |
|---|---|---|
= | x = 5 | x recebe 5 (e a expressão vale 5) |
+= | x += 5 | x = x + 5 |
-= | x -= 5 | x = x - 5 |
*= | x *= a + b | x = x * (a + b) |
/= | x /= 5 | x = x / 5 |
%= | x %= 5 | x = x % 5 (só inteiros) |
&= |= ^= <<= >>= | x |= 4 | x = 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:
2. A forma longa
preco *= 2; é o mesmo que preco = preco ? 2;. Qual é o operador no lugar do ?
3. Quanto vale?
Com int x = 4;, quanto x vale depois de x *= 3 + 1;?
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 parax = 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
doubleemintcorta a parte quebrada, sem aviso no gcc (use-Wconversion). =+é= +: o=vem sempre por último.constnã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.