C ++ e --
Contar é uma das coisas que um programa mais faz: mais uma pessoa entrou na loja, mais um ponto no jogo, menos uma vida, menos uma vaga. Somar 1 e tirar 1 são tão comuns que o C tem dois operadores só para isso: ++ (incremento) e -- (decremento). O nome da linguagem C++ vem daí: "o C, mais um". Nesta aula você vai usar os dois, entender a diferença entre x++ e ++x e ver por que linhas como i = i++ são proibidas, mesmo compilando.
- Como somar 1 com
++e tirar 1 com--. - A diferença entre o pré (
++x) e o pós (x++), que só aparece quando o resultado é usado numa expressão. - Como usar
++dentro doprintfe comcharedouble. - Por que
i = i++eprintf("%d %d", n++, n++)são comportamento indefinido, e o aviso-Wsequence-point. - Os erros
lvalue required as increment operandeincrement of read-only variable.
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.
A ideia: o contador da porta
Na entrada de um show, o segurança segura um contador de mão: a cada pessoa que passa, ele aperta o botão e o número sobe 1. Ele não precisa fazer conta nenhuma, só apertar. Se alguém sai, ele usa outro botão, que desce 1.
O ++ é o botão "mais um" e o -- é o botão "menos um". pessoas++ faz a mesma coisa que pessoas = pessoas + 1, só que mais curto.
1. Somar 1 e tirar 1
#include <stdio.h>
int main(void) {
int pessoas = 0;
pessoas++;
pessoas++;
pessoas++;
printf("Entraram: %d\n", pessoas);
pessoas--;
printf("Ficaram: %d\n", pessoas);
++pessoas;
--pessoas;
printf("No fim: %d\n", pessoas);
return 0;
}
$ gcc -std=c23 -Wall -Wextra contador.c -o contador $ ./contador Entraram: 3 Ficaram: 2 No fim: 2 « ++pessoas subiu para 3, --pessoas voltou para 2
- pessoas++;
- Soma 1 em
pessoase guarda o resultado na própria variável. É o mesmo quepessoas = pessoas + 1;oupessoas += 1;(aula Operadores de Atribuição). - pessoas--;
- Tira 1. O mesmo que
pessoas = pessoas - 1;. - ++pessoas;
- O sinal também pode vir antes do nome. Numa linha sozinha,
++pessoas;epessoas++;fazem exatamente a mesma coisa. - sem espaço
- Os dois sinais vão grudados:
++. Com espaço,+ +são dois "mais" separados:pessoas+ +;nem compila (error: expected expression before ‘;’ token).
Os nomes: incremento é "aumento" e decremento é "diminuição". Quando o sinal vem antes (++x), chama-se pré-incremento; depois (x++), pós-incremento. Você vai ver o ++ o tempo todo nos laços, como em for (int i = 0; i < 10; i++) (aula for).
Experimente: o placar começa em 0. Acrescente mais linhas gols++; e veja o total mudar. Depois anule um gol com gols--;.
#include <stdio.h>
int main(void) {
int gols = 0;
gols++;
gols++;
printf("Placar: %d gols\n", gols);
return 0;
}
2. Pré × pós: quando o resultado é usado
Até aqui, x++ e ++x pareciam iguais. A diferença aparece quando o ++ está dentro de outra coisa, como uma atribuição ou um printf. Além de mudar a variável, o ++ produz um valor, e esse valor depende do lado do sinal:
x++(pós): entrega o valor antigo, e depois soma.++x(pré): soma primeiro, e entrega o valor novo.
#include <stdio.h>
int main(void) {
int a = 5;
int b = a++;
printf("depois de b = a++: a = %d, b = %d\n", a, b);
int c = 5;
int d = ++c;
printf("depois de d = ++c: c = %d, d = %d\n", c, d);
return 0;
}
$ gcc -std=c23 -Wall -Wextra prepos.c -o prepos $ ./prepos depois de b = a++: a = 6, b = 5 « b recebeu o valor ANTIGO de a depois de d = ++c: c = 6, d = 6 « d recebeu o valor NOVO de c
| Linha | Passo a passo | Depois |
|---|---|---|
int b = a++; | 1) pega o valor de a (5) para entregar; 2) soma 1 em a; 3) b recebe o 5 entregue | a = 6, b = 5 |
int d = ++c; | 1) soma 1 em c (vira 6); 2) entrega o 6; 3) d recebe 6 | c = 6, d = 6 |
Um jeito de lembrar: leia da esquerda para a direita. Em a++, o nome vem primeiro: "usa o a, depois soma". Em ++c, o ++ vem primeiro: "soma, depois usa o c". Nos dois casos, a variável termina com 1 a mais; o que muda é só o valor entregue.
Dentro do printf
Uso típico: a fila de senhas de um banco. O painel mostra a senha atual e já deixa a próxima preparada:
#include <stdio.h>
int main(void) {
int senha = 41;
printf("Chamando a senha %d\n", senha++);
printf("Chamando a senha %d\n", senha++);
printf("Próxima a ser chamada: %d\n", senha);
int vagas = 3;
printf("Restam %d vagas\n", --vagas);
printf("Restam %d vagas\n", --vagas);
return 0;
}
$ gcc -std=c23 -Wall -Wextra senhas.c -o senhas $ ./senhas Chamando a senha 41 « mostrou 41, depois senha virou 42 Chamando a senha 42 « mostrou 42, depois senha virou 43 Próxima a ser chamada: 43 Restam 2 vagas « --vagas: tirou 1 (3 → 2) e depois mostrou Restam 1 vagas
Missão: o painel mostra a mesma senha três vezes. Sem acrescentar linhas, use ++ dentro dos printf para a saída ficar assim (pense: pós ou pré?):
Senha 1 Senha 2 Senha 3
#include <stdio.h>
int main(void) {
int senha = 1;
printf("Senha %d\n", senha);
printf("Senha %d\n", senha);
printf("Senha %d\n", senha);
return 0;
}
#include <stdio.h>
int main(void) {
int senha = 1;
printf("Senha %d\n", senha++);
printf("Senha %d\n", senha++);
printf("Senha %d\n", senha++);
return 0;
}
Com senha começando em 1, o certo é o pós (senha++): mostra o 1 e só depois soma. Com ++senha, a saída começaria em 2.
3. Com char e double
O ++ e o -- funcionam com qualquer tipo de número. Um char é guardado como o código do caractere (aula Caracteres), então somar 1 passa para o próximo caractere da tabela. Um double ganha 1 inteiro, e a parte depois do ponto continua igual:
#include <stdio.h>
int main(void) {
char letra = 'a';
letra++;
double preco = 9.75;
preco++;
printf("letra = %c\n", letra);
printf("preco = %.2f\n", preco);
return 0;
}
$ gcc -std=c23 -Wall -Wextra tipos.c -o tipos $ ./tipos letra = b « 'a' (97) + 1 = 'b' (98) preco = 10.75 « soma 1, não 0.01
Com double funciona, mas é raro: quase sempre você vai usar ++ com inteiros, para contar.
4. i = i++: comportamento indefinido
Agora a parte perigosa. A Ana quis somar 1 nos pontos de um jogo e tirar uma vida, e escreveu "duplicado", achando que ficaria mais garantido:
#include <stdio.h>
int main(void) {
int pontos = 10;
int vidas = 3;
pontos = pontos++;
vidas = --vidas;
printf("Pontos: %d\nVidas: %d\n", pontos, vidas);
return 0;
}
$ gcc -std=c23 -Wall -Wextra jogo.c -o jogo
jogo.c: In function ‘main’:
jogo.c:6:12: warning: operation on ‘pontos’ may be undefined [-Wsequence-point]
6 | pontos = pontos++;
| ~~~~~~~^~~~~~~~~~
jogo.c:7:11: warning: operation on ‘vidas’ may be undefined [-Wsequence-point]
7 | vidas = --vidas;
| ~~~~~~^~~~~~~~~
$ ./jogo
Pontos: 10 « o ++ "sumiu"!
Vidas: 2
O aviso diz: "a operação em pontos pode ser indefinida". O problema é que, na mesma linha, pontos é mudado duas vezes: uma pelo ++ e outra pelo =. As regras do C não dizem qual das duas mudanças acontece primeiro. Nesse caso, o padrão da linguagem diz que o programa tem comportamento indefinido (undefined behavior): o compilador pode fazer qualquer coisa, e cada compilador faz uma.
Aqui o gcc fez assim: guardou o 10 para entregar, somou 1 (11), e depois o = guardou o 10 entregue por cima do 11. O vidas = --vidas deu 2 "por sorte". Nenhum dos dois resultados é garantido.
Dois compiladores, duas respostas
O mesmo problema acontece quando a variável muda duas vezes dentro dos argumentos de uma função:
#include <stdio.h>
int main(void) {
int i = 5;
i = i++;
printf("i = %d\n", i);
int n = 1;
printf("%d %d\n", n++, n++);
return 0;
}
$ gcc -std=c23 -Wall -Wextra indefinido.c -o indefinido
indefinido.c: In function ‘main’:
indefinido.c:5:7: warning: operation on ‘i’ may be undefined [-Wsequence-point]
5 | i = i++;
| ~~^~~~~
indefinido.c:8:29: warning: operation on ‘n’ may be undefined [-Wsequence-point]
8 | printf("%d %d\n", n++, n++);
| ~^~
$ ./indefinido
i = 5
2 1 « o gcc calculou o segundo n++ primeiro
$ clang -std=c23 -Wall -Wextra indefinido.c -o indefinido
indefinido.c:5:10: warning: multiple unsequenced modifications to 'i' [-Wunsequenced]
5 | i = i++;
| ~ ^
indefinido.c:8:24: warning: multiple unsequenced modifications to 'n' [-Wunsequenced]
8 | printf("%d %d\n", n++, n++);
| ^ ~~
2 warnings generated.
$ ./indefinido
i = 5
1 2 « o Clang, na mesma máquina, deu o contrário
O mesmo código, na mesma máquina, mostrou 2 1 com o gcc e 1 2 com o Clang (o compilador do editor do guia também mostra 1 2). O aviso do Clang explica bem: "várias modificações sem ordem definida em n". A vírgula que separa os argumentos de uma função não define ordem: o C não promete calcular o primeiro argumento antes do segundo.
O nome do aviso do gcc, -Wsequence-point, vem de um termo antigo do padrão: ponto de sequência, um ponto do programa em que tudo o que veio antes já terminou (o ; no fim de uma instrução é um deles). Desde o C11, o padrão fala em operações "sequenciadas" e "não sequenciadas", que é a palavra do Clang. O aviso vem com o -Wall. A aula Comportamento Indefinido mostra outros casos.
Numa mesma instrução, não mude uma variável duas vezes, nem mude e leia a mesma variável em outro lugar. Use o ++ sozinho na linha (pontos++;) ou numa expressão em que a variável aparece uma vez só (printf("%d", senha++);). Se o gcc disser may be undefined, reescreva em duas linhas.
Formas diferentes de somar 1
| Forma | O que faz | Quando usar |
|---|---|---|
x = x + 1; | Calcula x + 1 e guarda em x | Para quem está começando, é a mais óbvia. Na prática, pouco usada para somar 1. |
x += 1; | O mesmo, mais curto | Quando o passo pode mudar: x += 2;, x += bonus;. |
x++; | O mesmo, ainda mais curto | Para contar de 1 em 1. É a forma mais comum em C, sobretudo nos laços. |
++x; | O mesmo, numa linha sozinha | Igual a x++;. Em C, escolha um e seja consistente; o guia usa x++. |
y = x++; / y = ++x; | Muda x e entrega o valor antigo / novo | Com moderação, e só quando a variável aparece uma vez na instrução. Duas linhas (y = x; x++;) costumam ser mais claras. |
Quando dá errado
warning: operation on ‘i’ may be undefined [-Wsequence-point]
O que significa: a mesma variável é mudada duas vezes (ou mudada e lida) na mesma instrução, como em i = i++ ou printf("%d %d", n++, n++). O resultado é imprevisível. No Clang: multiple unsequenced modifications to 'i' [-Wunsequenced].
Como consertar: separe em linhas. Para somar 1, basta i++;, sem o i =.
error: lvalue required as increment operand
$ gcc -std=c23 -Wall -Wextra lvalue.c -o lvalue
lvalue.c: In function ‘main’:
lvalue.c:5:12: error: lvalue required as increment operand
5 | (x + 1)++;
| ^~
lvalue.c:6:6: error: lvalue required as increment operand
6 | 5++;
| ^~
O que significa: o ++ precisa de um lugar para guardar o resultado, isto é, uma variável. 5 e (x + 1) são só valores, não têm onde guardar. lvalue é o nome técnico de "algo que pode ficar do lado esquerdo de um =" (left value). No Clang: expression is not assignable ("a expressão não pode receber valor").
Como consertar: aplique o ++ numa variável. Se você queria "x mais 1" sem mudar x, escreva só x + 1.
error: increment of read-only variable ‘dias’
$ gcc -std=c23 -Wall -Wextra constante.c -o constante
constante.c: In function ‘main’:
constante.c:5:9: error: increment of read-only variable ‘dias’
5 | dias++;
| ^~
O que significa: dias foi declarada com const ("somente leitura"; aula Constantes) e não pode mudar. No Clang: cannot assign to variable 'dias' with const-qualified type 'const int'.
Como consertar: se o valor precisa mudar, tire o const; se não precisa, não use ++ nela.
O que significa: você usou o pré onde queria o pós, ou o contrário. printf("%d", ++senha) mostra o valor já somado.
Como consertar: lembre: x++ entrega o antigo, ++x entrega o novo. Na dúvida, faça o ++ numa linha separada, antes ou depois do printf.
Referência
| Expressão | Muda a variável para | Valor entregue | Exemplo (x vale 5) |
|---|---|---|---|
x++ | x + 1 | o antigo | y = x++; → x = 6, y = 5 |
++x | x + 1 | o novo | y = ++x; → x = 6, y = 6 |
x-- | x - 1 | o antigo | y = x--; → x = 4, y = 5 |
--x | x - 1 | o novo | y = --x; → x = 4, y = 4 |
x = x++ | Comportamento indefinido: nunca escreva (-Wsequence-point). | ||
Funciona com variáveis de qualquer tipo de número (int, long, char, double…), e com ponteiros (aula Ponteiros e Arrays). Não funciona com constantes, valores calculados nem variáveis const.
Exercícios
1. Mais um
Complete para somar 1 em visitas:
2. Valor entregue
Depois de int a = 8; int b = a--;, quanto vale b?
3. E agora?
Depois de int a = 8; int b = --a;, quanto vale b?
4. O que este trecho mostra? int n = 3; printf("%d ", n++); printf("%d", n);
5. Por que i = i++; é um erro, mesmo compilando?
6. O que o gcc diz de 10++;?
Missão final: o placar do jogo
Missão: o jogo da Ana deveria dar 1 ponto e tirar 1 vida, mas as linhas 6 e 7 têm comportamento indefinido. Rode, leia os avisos e reescreva essas duas linhas para a saída ficar assim, sem avisos:
Pontos: 11 Vidas: 2
#include <stdio.h>
int main(void) {
int pontos = 10;
int vidas = 3;
pontos = pontos++;
vidas = --vidas;
printf("Pontos: %d\nVidas: %d\n", pontos, vidas);
return 0;
}
#include <stdio.h>
int main(void) {
int pontos = 10;
int vidas = 3;
pontos++;
vidas--;
printf("Pontos: %d\nVidas: %d\n", pontos, vidas);
return 0;
}
Resumo
x++soma 1 emx;x--tira 1. Numa linha sozinha,++xex++são iguais.- Dentro de uma expressão:
x++(pós) entrega o valor antigo e depois soma;++x(pré) soma e entrega o novo. - Funciona com
int,char(próximo caractere),double(soma 1 inteiro) e outros tipos de número. - Mudar a mesma variável duas vezes na mesma instrução (
i = i++,printf("%d %d", n++, n++)) é comportamento indefinido. O gcc avisa com-Wsequence-point, o Clang com-Wunsequenced, e cada um pode dar um resultado. 5++e(x + 1)++:lvalue required as increment operand. Variávelconst:increment of read-only variable.