C++ Escopo
Toda variável tem um território: a parte do código onde ela existe e pode ser usada. Esse território se chama escopo. Até aqui, você já esbarrou nele sem saber: o parâmetro de uma função não existe no main, e a mensagem was not declared in this scope ("não foi declarado neste escopo") apareceu várias vezes. Nesta aula você vai entender onde cada variável nasce e morre, o que são variáveis locais e globais, o perigo de duas variáveis com o mesmo nome, e a variável static, que lembra o valor entre uma chamada e outra.
- O que é uma variável local, e por que ela não existe fora da função.
- Que todo par de chaves
{ }cria um escopo, inclusive o doife o dofor. - O que é uma variável global, e por que o guia recomenda evitá-las.
- O que é sombreamento (uma variável escondendo outra) e o aviso
-Wshadow. - Como uma variável
staticlocal cria um contador que sobrevive entre chamadas.
Todos os programas desta aula foram compilados com o g++ 15 na máquina fictícia da Ana, na pasta ~/cpp, com o comando padrão do guia, g++ -std=c++23 -Wall -Wextra arquivo.cpp -o programa.
A ideia: os cômodos da casa
Na casa da Ana, o que está no quarto fica no quarto: quem está na cozinha não enxerga a escrivaninha dela. Já o que está na sala, que é de todos, qualquer um vê e qualquer um pode mudar de lugar. E dentro do quarto há um armário: o que está lá dentro só existe para quem abriu o armário.
| Na casa | Em C++ |
|---|---|
| O quarto | O corpo de uma função: as variáveis criadas ali são locais |
| O armário dentro do quarto | Um bloco { } dentro da função (o de um if, de um for) |
| A sala, de todos | Fora de qualquer função: variáveis globais |
A regra geral: de dentro, você enxerga o que está fora; de fora, você não enxerga o que está dentro.
1. Variáveis locais: só existem dentro da função
Uma variável criada dentro de uma função é local: nasce na linha em que é criada e morre no } que fecha a função. Fora dela, o nome não existe:
#include <iostream>
void calcularTotal() {
double total = 3 * 10.0;
std::cout << "Total na função: " << total << "\n";
}
int main() {
calcularTotal();
std::cout << "Total no main: " << total << "\n";
}
$ g++ -std=c++23 -Wall -Wextra local.cpp -o local
local.cpp: In function ‘int main()’:
local.cpp:10:39: error: ‘total’ was not declared in this scope
10 | std::cout << "Total no main: " << total << "\n";
| ^~~~~
- In function ‘int main()’
- O problema está no
main. - ‘total’ was not declared in this scope
- "‘total’ não foi declarado neste escopo". O
totalexiste, mas no escopo dacalcularTotal; no escopo domain, não.
Mesmo que a função já tenha sido chamada na linha 9, quando ela termina, o total é destruído. Para levar um valor de dentro de uma função para fora, use o return (aula Retorno): double total = calcularTotal();.
Os parâmetros também são locais. Na função void dobrar(int numero), o numero só existe dentro de dobrar; tentar usá-lo no main dá o mesmo erro, ‘numero’ was not declared in this scope.
O lado bom: nomes iguais não se misturam
Como cada função tem o seu território, duas funções podem ter variáveis com o mesmo nome sem nenhuma confusão:
#include <iostream>
void loja() {
int quantidade = 3;
std::cout << "loja: " << quantidade << "\n";
}
void estoque() {
int quantidade = 50;
std::cout << "estoque: " << quantidade << "\n";
}
int main() {
int quantidade = 1;
loja();
estoque();
std::cout << "main: " << quantidade << "\n";
}
$ g++ -std=c++23 -Wall -Wextra mesmonome.cpp -o mesmonome $ ./mesmonome loja: 3 estoque: 50 main: 1
São três variáveis diferentes, que só têm o nome em comum. É isso que permite escrever uma função sem se preocupar com os nomes usados no resto do programa: quem escreveu a loja não precisa saber que o main também tem uma quantidade.
2. Todo bloco { } cria um escopo
Não são só as funções. Qualquer par de chaves cria um território novo: o do if, o do else, o do while, o do for. Uma variável criada dentro de um bloco morre no } dele:
#include <iostream>
#include <string>
int main() {
double nota = 8.5;
if (nota >= 6) {
std::string mensagem = "aprovada";
std::cout << "Ana foi " << mensagem << "\n";
}
std::cout << mensagem << "\n";
}
$ g++ -std=c++23 -Wall -Wextra bloco.cpp -o bloco
bloco.cpp: In function ‘int main()’:
bloco.cpp:10:18: error: ‘mensagem’ was not declared in this scope
10 | std::cout << mensagem << "\n";
| ^~~~~~~~
A mensagem nasceu na linha 7, dentro do if, e morreu na linha 9. Já a nota, criada no bloco do main, é visível dentro do if: de dentro se enxerga o que está fora. Se você precisa da variável depois do bloco, crie-a antes dele: std::string mensagem; na linha 6 e, dentro do if, só mensagem = "aprovada";.
A variável do for
A variável criada nos parênteses do for pertence ao laço e morre quando ele termina:
#include <iostream>
int main() {
for (int i = 1; i <= 3; i++) {
std::cout << "volta " << i << "\n";
}
std::cout << "terminou na volta " << i << "\n";
}
$ g++ -std=c++23 -Wall -Wextra forvar.cpp -o forvar
forvar.cpp: In function ‘int main()’:
forvar.cpp:7:42: error: ‘i’ was not declared in this scope
7 | std::cout << "terminou na volta " << i << "\n";
| ^
Isso é proposital: o i só serve para contar as voltas, e assim não "vaza" para o resto da função. Se você precisa saber onde o laço parou, crie a variável antes:
#include <iostream>
int main() {
int i = 1;
for (; i <= 3; i++) {
std::cout << "volta " << i << "\n";
}
std::cout << "o laço parou com i = " << i << "\n";
}
$ g++ -std=c++23 -Wall -Wextra forvar2.cpp -o forvar2 $ ./forvar2 volta 1 volta 2 volta 3 o laço parou com i = 4
Dentro de um laço, a variável nasce de novo a cada volta
#include <iostream>
int main() {
for (int volta = 1; volta <= 3; volta++) {
int contador = 0;
contador++;
std::cout << "volta " << volta << ": contador = " << contador << "\n";
}
}
$ g++ -std=c++23 -Wall -Wextra recria.cpp -o recria $ ./recria volta 1: contador = 1 volta 2: contador = 1 volta 3: contador = 1
O contador nunca passa de 1: a cada volta, ele é criado com 0, somado, mostrado e destruído no }. Para contar as voltas, a variável precisa morar fora do laço. É um engano muito comum de quem está começando.
Você pode abrir um bloco sem if nem for, só com { }, para limitar a vida de uma variável. É raro em código do dia a dia, mas mostra a regra na forma mais pura: { int y = 2; } e, depois do }, o y não existe mais.
Missão: a Ana quer somar as quantidades da lista, mas o programa não compila. Leia o erro e mude onde a variável total é criada (ela precisa existir antes do laço e continuar existindo depois dele) para aparecer Total de itens: 6.
#include <iostream>
#include <vector>
int main() {
std::vector<int> quantidades = {1, 2, 3};
for (int q : quantidades) {
int total = 0;
total = total + q;
}
std::cout << "Total de itens: " << total << "\n";
}
#include <iostream>
#include <vector>
int main() {
std::vector<int> quantidades = {1, 2, 3};
int total = 0;
for (int q : quantidades) {
total = total + q;
}
std::cout << "Total de itens: " << total << "\n";
}
3. Variáveis globais (e por que evitar)
Uma variável criada fora de qualquer função, normalmente no topo do arquivo, é global: existe durante o programa inteiro, e todas as funções abaixo dela podem lê-la e mudá-la.
#include <iostream>
int visitas = 0;
void registrarVisita() {
visitas++;
}
void mostrarVisitas() {
std::cout << "Visitas hoje: " << visitas << "\n";
}
int main() {
registrarVisita();
registrarVisita();
registrarVisita();
mostrarVisitas();
}
$ g++ -std=c++23 -Wall -Wextra global.cpp -o global $ ./global Visitas hoje: 3
Parece prático: nada de parâmetros, nada de return. O problema aparece quando o programa cresce. Como qualquer função pode mudar uma global, quando o valor sai errado, você não sabe quem foi o culpado. Veja o que aconteceu com o caixa da Ana:
#include <iostream>
int total = 0;
void somarItem(int preco) {
total = total + preco;
}
void relatorioDoDia() {
for (total = 0; total < 3; total++) {
std::cout << "imprimindo página " << total + 1 << "\n";
}
}
int main() {
somarItem(30);
somarItem(5);
relatorioDoDia();
std::cout << "Total da venda: " << total << "\n";
}
$ g++ -std=c++23 -Wall -Wextra globalbug.cpp -o globalbug $ ./globalbug imprimindo página 1 imprimindo página 2 imprimindo página 3 Total da venda: 3 « deveria ser 35!
Quem escreveu o relatorioDoDia usou o nome total para contar páginas, sem lembrar que já existia uma global com esse nome. O compilador não reclamou: é tudo válido. O valor da venda foi apagado por uma função que não tinha nada a ver com vendas. Num programa de mil linhas, achar esse erro leva horas.
A alternativa é passar o que a função precisa por parâmetro e receber o resultado pelo retorno. Assim, só mexe no total quem recebeu o total:
#include <iostream>
int somarItem(int total, int preco) {
return total + preco;
}
int main() {
int total = 0;
total = somarItem(total, 30);
total = somarItem(total, 5);
std::cout << "Total da venda: " << total << "\n";
}
$ g++ -std=c++23 -Wall -Wextra semglobal.cpp -o semglobal $ ./semglobal Total da venda: 35
| Global | Quando é aceitável |
|---|---|
int visitas = 0; (variável) | Quase nunca. Prefira parâmetros e retorno (ou &, aula Por Valor × Por Referência). |
const double TAXA = 0.05; (constante) | Tudo bem. Ninguém consegue mudá-la, então não há culpado a procurar (aula Constantes). |
Uma global sem valor inicial (int visitas;) começa com 0. Uma variável local sem valor inicial começa com um valor qualquer, o que sobrou na memória, e o -Wall avisa (is used uninitialized; veja Variáveis). De qualquer jeito, a regra do guia vale para as duas: sempre inicialize.
4. Sombreamento: quando um nome esconde outro
Se você cria, num bloco de dentro, uma variável com o mesmo nome de uma de fora, a de dentro "faz sombra" na de fora: dali até o fim do bloco, o nome se refere à de dentro. Isso se chama sombreamento (em inglês, shadowing):
#include <iostream>
int main() {
int preco = 10;
if (preco > 5) {
int preco = 99;
std::cout << "dentro do if: " << preco << "\n";
}
std::cout << "fora do if: " << preco << "\n";
}
$ g++ -std=c++23 -Wall -Wextra sombra.cpp -o sombra « compila sem nenhum aviso $ ./sombra dentro do if: 99 fora do if: 10
Na linha 6, a Ana provavelmente queria mudar o preço (preco = 99;), mas escreveu int na frente e criou outra variável. A de fora continuou 10. Nem -Wall nem -Wextra avisam disso. Existe uma opção específica, -Wshadow ("avisar sombras"):
$ g++ -std=c++23 -Wall -Wextra -Wshadow sombra.cpp -o sombra
sombra.cpp: In function ‘int main()’:
sombra.cpp:6:13: warning: declaration of ‘preco’ shadows a previous local [-Wshadow]
6 | int preco = 99;
| ^~~~~
sombra.cpp:4:9: note: shadowed declaration is here
4 | int preco = 10;
| ^~~~~
- declaration of ‘preco’ shadows a previous local
- "A declaração de ‘preco’ faz sombra a uma variável local anterior".
- note: shadowed declaration is here
- "A declaração que ficou na sombra está aqui": a linha 4.
O mesmo acontece com uma global e uma local de mesmo nome:
#include <iostream>
int visitas = 100;
void mostrar() {
int visitas = 5;
std::cout << "local: " << visitas << "\n";
std::cout << "global: " << ::visitas << "\n";
}
int main() {
mostrar();
}
$ g++ -std=c++23 -Wall -Wextra -Wshadow sombraglobal.cpp -o sombraglobal
sombraglobal.cpp: In function ‘void mostrar()’:
sombraglobal.cpp:6:9: warning: declaration of ‘visitas’ shadows a global declaration [-Wshadow]
6 | int visitas = 5;
| ^~~~~~~
sombraglobal.cpp:3:5: note: shadowed declaration is here
3 | int visitas = 100;
| ^~~~~~~
$ ./sombraglobal
local: 5
global: 100
O ::visitas (dois-pontos duplos sem nada antes) quer dizer "a visitas global". Funciona, mas é um remendo: o certo é dar nomes diferentes. O :: é o mesmo do std::cout, e tem aula própria: Namespaces.
-Wshadow
Muita gente acrescenta -Wshadow ao comando de sempre. Ele às vezes avisa de sombras inofensivas, mas pega exatamente o tipo de erro do sombra.cpp, que nenhum outro aviso pega. A aula Avisos lista outras opções úteis.
Missão: este editor está compilando com -Wshadow. O programa deveria mostrar Preço com desconto: 90, mas mostra 100 e um aviso de sombra (no Clang: declaration shadows a local variable). Conserte a linha do desconto para mudar a variável de fora, sem criar outra.
#include <iostream>
int main() {
double preco = 100;
bool clienteFiel = true;
if (clienteFiel) {
double preco = 90;
}
std::cout << "Preço com desconto: " << preco << "\n";
}
#include <iostream>
int main() {
double preco = 100;
bool clienteFiel = true;
if (clienteFiel) {
preco = 90;
}
std::cout << "Preço com desconto: " << preco << "\n";
}
5. static local: a variável que lembra
A lojinha da Ana tem uma máquina de senhas: cada cliente que chega recebe o próximo número. Com uma variável local comum, isso não funciona, porque ela nasce de novo a cada chamada. Com a palavra static na frente, ela nasce uma vez só e guarda o valor entre as chamadas:
#include <iostream>
void atenderSemStatic() {
int senha = 0;
senha++;
std::cout << "Senha " << senha << "\n";
}
void atender() {
static int senha = 0;
senha++;
std::cout << "Senha " << senha << "\n";
}
int main() {
std::cout << "Sem static:\n";
atenderSemStatic();
atenderSemStatic();
atenderSemStatic();
std::cout << "Com static:\n";
atender();
atender();
atender();
}
$ g++ -std=c++23 -Wall -Wextra contador.cpp -o contador $ ./contador Sem static: Senha 1 Senha 1 Senha 1 Com static: Senha 1 Senha 2 Senha 3
- int senha = 0;
- Variável local comum: criada com 0 a cada chamada e destruída no fim. Sempre mostra 1.
- static int senha = 0;
static("estático", que fica parado): a variável é criada na primeira chamada, e o= 0só acontece nessa vez. Nas chamadas seguintes, a linha é pulada, e a variável continua com o valor que tinha. Ela só é destruída quando o programa termina.
O melhor dos dois mundos: a senha dura como uma global, mas é visível só dentro de atender, como uma local. Ninguém de fora consegue estragá-la:
$ g++ -std=c++23 -Wall -Wextra staticfora.cpp -o staticfora « o main tenta mostrar a senha
staticfora.cpp: In function ‘int main()’:
staticfora.cpp:11:18: error: ‘senha’ was not declared in this scope
11 | std::cout << senha << "\n";
| ^~~~~
| Tipo de variável | Quem enxerga | Quanto tempo dura |
|---|---|---|
Local (int x dentro de função ou bloco) | Só o bloco onde foi criada | Até o } do bloco; recriada a cada vez |
static local (static int x dentro de função) | Só a função | Da primeira chamada até o fim do programa |
| Global (fora das funções) | Todas as funções abaixo dela | O programa inteiro |
static tem outros usos
Numa variável dentro de uma função, static quer dizer "dura o programa todo", como aqui. Em outros lugares ela muda de sentido: fora das funções, deixa a global visível só naquele arquivo (Compilar Vários Arquivos); dentro de uma classe, cria um membro compartilhado (Membros static).
Quando dá errado
error: ‘x’ was not declared in this scope, mas a variável existe
O que significa: a variável existe, mas em outro território: dentro de outra função, dentro de um if/for que já fechou, ou é o parâmetro de outra função.
Como consertar: procure onde a variável foi criada e a que { } ela pertence. Se precisa dela depois do bloco, crie-a antes do bloco. Se o valor vem de outra função, traga-o com return.
O que significa: a variável foi criada dentro do laço ou da função, e nasce de novo, com o valor inicial, a cada volta ou chamada.
Como consertar: num laço, crie o contador antes do laço. Numa função que precisa lembrar entre chamadas, use static.
if, mas fora dela o valor é o antigo
O que significa: provavelmente você escreveu o tipo na frente (int preco = 99;) e criou uma variável nova com o mesmo nome: sombreamento. Sem -Wshadow, não há aviso.
Como consertar: tire o tipo para mudar a variável existente (preco = 99;). Compile com -Wshadow para o g++ apontar casos assim: warning: declaration of ‘preco’ shadows a previous local.
O que significa: alguma função está mexendo nela, talvez sem querer, por usar o mesmo nome.
Como consertar: o melhor é eliminar a global: passe o valor por parâmetro e devolva com return (ou use &). Se precisa mesmo ser global e nunca muda, faça-a const.
Referência rápida
| Escrita | O que é |
|---|---|
void f() { int x = 0; } | x é local de f |
if (…) { int y; } | y só existe dentro do if |
for (int i = 0; …) { } | i só existe dentro do laço |
int total = 0; fora das funções | Global: evite (constantes globais tudo bem) |
static int n = 0; dentro da função | Criada uma vez, lembra o valor entre chamadas |
::nome | A variável global nome, mesmo com uma local de mesmo nome |
-Wshadow | Opção do g++ que avisa quando um nome esconde outro |
Exercícios
1. A palavra que faz lembrar
Complete para que a variável mantenha o valor entre as chamadas da função:
2. O que aparece?
int x = 1; { int x = 2; } std::cout << x;
3. A opção de aviso
Qual opção do g++ avisa quando uma variável esconde outra de mesmo nome?
4. Uma variável criada dentro do bloco de um if…
5. A função void f() { static int n = 0; n++; std::cout << n; } é chamada três vezes. O que aparece?
6. Por que o guia recomenda evitar variáveis globais?
Missão final: os pedidos da lojinha
Missão: complete a função registrarPedido para que cada pedido receba o próximo número, começando em 1. Use uma variável static dentro da função, sem variáveis globais e sem mexer no main. Saída esperada:
Pedido nº 1: caderno Pedido nº 2: caneta Pedido nº 3: mochila
#include <iostream>
#include <string>
void registrarPedido(const std::string& produto) {
// crie aqui o contador de pedidos
std::cout << "Pedido nº " << "?" << ": " << produto << "\n";
}
int main() {
registrarPedido("caderno");
registrarPedido("caneta");
registrarPedido("mochila");
}
#include <iostream>
#include <string>
void registrarPedido(const std::string& produto) {
static int numero = 0;
numero++;
std::cout << "Pedido nº " << numero << ": " << produto << "\n";
}
int main() {
registrarPedido("caderno");
registrarPedido("caneta");
registrarPedido("mochila");
}
Resumo
- Escopo é a parte do código onde uma variável existe. De dentro se enxerga o que está fora; de fora não se enxerga o que está dentro.
- Variáveis locais (e parâmetros) existem só na função. Fora dela:
‘x’ was not declared in this scope. - Todo bloco
{ }cria um escopo: a variável doif, dofore do laço morre no}, e é recriada a cada volta. - Variáveis globais são vistas e mudadas por todos: evite. Constantes globais (
const) são aceitáveis. - Sombreamento: uma variável de dentro com o mesmo nome esconde a de fora. Só o
-Wshadowavisa. staticnuma variável local faz ela ser criada uma vez e lembrar o valor entre chamadas, sem ficar visível fora da função.- Próxima aula: Recursão, uma função que chama a si mesma.