Funções: parâmetros e escopo
Na etapa passada, você aprendeu a definir e chamar funções, com parâmetros e valores de retorno. Mas ficou uma pergunta em aberto: quando você passa um valor pra um parâmetro, o que exatamente acontece ali? E onde, dentro do programa, uma variável realmente existe? Esta etapa responde as duas perguntas: você vai formalizar a passagem por valor, que já vinha usando sem esse nome, conhecer a passagem por referência, e entender o escopo de uma variável, local ou global.
Base bibliográfica: este material segue a seção 4.1 (Parâmetros) de SAVITCH, C++ Absoluto (Pearson Addison Wesley, 2004), cobrindo passagem por valor e passagem por referência, e a seção 3.3 (Regras de Escopo) do mesmo livro, cobrindo escopo de variáveis.
1. Passagem por valor
Toda função que você escreveu na etapa passada já usava um mecanismo específico pra receber seus parâmetros, mesmo sem a gente dar um nome a ele: o valor do argumento era copiado pro parâmetro, e usado dentro da função. Isso é a passagem por valor, e é o comportamento padrão em C++: se você não fizer nada diferente na hora de declarar um parâmetro, é assim que ele se comporta.
Num parâmetro passado por valor, o parâmetro é, na prática, uma variável local, criada do zero a cada chamada: o valor do argumento é calculado primeiro, e essa variável local é inicializada com esse valor. A partir daí, o parâmetro e o argumento passam a ser duas variáveis completamente separadas, mesmo que, no instante da chamada, tenham o mesmo valor.
void dobrarValor(int numero) {
numero = numero * 2;
cout << "Dentro da funcao: " << numero << endl;
}
int main() {
int valor = 5;
dobrarValor(valor);
cout << "Fora da funcao: " << valor << endl;
return 0;
}
Dentro da função, numero vira 10. Fora dela, valor continua 5. Isso acontece porque numero e valor são duas variáveis diferentes, guardadas em posições de memória diferentes: numero só recebeu uma cópia do valor de valor, no momento da chamada. É como fotocopiar um documento: rabiscar a cópia não muda uma letra sequer do original.
Esse comportamento não é uma limitação: é útil. Como o parâmetro é uma variável local de verdade, você pode usar ele como espaço de trabalho dentro da função, alterando o valor à vontade, sem medo de estragar nada de fora:
double calcularPrecoComImposto(double preco) {
preco = preco * 1.10;
return preco;
}
Mas fica uma limitação real: e se a função precisar, de verdade, alterar a variável original, lá de fora, e não só uma cópia dela? Com passagem por valor, isso não é possível, por design.
🎥 Vídeo 1 de 3: Passagem por valor (~5 min)
2. Passagem por referência
A passagem por referência resolve exatamente o problema deixado em aberto na seção anterior, permitindo que uma função altere, de verdade, a variável que foi passada na chamada. Nela, o parâmetro deixa de ser uma cópia e passa a ser um apelido para a própria variável do argumento: os dois nomes passam a apontar para o mesmo espaço de memória. Para declarar um parâmetro assim, basta colocar um & logo depois do tipo, no cabeçalho da função:
void dobrarValor(int &numero) {
numero = numero * 2;
}
int main() {
int valor = 5;
dobrarValor(valor);
cout << valor << endl;
return 0;
}
Dessa vez, valor vira 10 depois da chamada: o & fez de numero um apelido para valor, então alterar numero dentro da função alterou valor diretamente. O & aparece só uma vez, no cabeçalho da função: na chamada dobrarValor(valor), não se escreve & nenhum, ela é idêntica a uma chamada por valor.
A passagem por referência resolve problemas que a passagem por valor nunca conseguiria, como trocar o conteúdo de duas variáveis:
void trocar(int &a, int &b) {
int temp = a;
a = b;
b = temp;
}
Chamando trocar(x, y) com x = 3 e y = 7, depois da chamada x vale 7 e y vale 3, de verdade. Se a e b fossem cópias, a troca aconteceria só dentro da função, sem efeito nenhum lá fora.
Uma mesma função pode misturar parâmetros por valor e por referência ao mesmo tempo, cada um decidindo isso individualmente:
void ajustarPreco(double &preco, double percentualDesconto) {
preco = preco - (preco * percentualDesconto / 100);
}
Aqui, preco é por referência, porque a função precisa alterar o preço original; percentualDesconto é por valor, porque só é usado no cálculo. Como regra geral: use por valor quando a função só precisa ler o argumento, e use por referência só quando ela realmente precisa alterar a variável original.
🎥 Vídeo 2 de 3: Passagem por referência (~5 min)
3. Escopo de variáveis: local e global
Escopo é a região do programa onde uma variável existe e pode ser usada, decidida pelo lugar exato onde ela foi declarada. Toda variável declarada dentro de uma função, incluindo os próprios parâmetros, tem escopo local: só existe dentro daquela função, criada quando ela é chamada e destruída quando ela termina.
void mostrarQuadrado(int n) {
int quadrado = n * n;
cout << quadrado << endl;
}
Aqui, quadrado só existe dentro de mostrarQuadrado; usar quadrado no main, ou em qualquer outra função, resulta em erro de compilação, porque, fora dali, essa variável simplesmente não existe.
Uma consequência disso: uma variável local não guarda valor de uma chamada para a outra, ela é criada do zero a cada vez:
void contarChamada() {
int contador = 0;
contador = contador + 1;
cout << contador << endl;
}
Não importa quantas vezes contarChamada seja chamada, contador sempre imprime 1: cada chamada cria a sua própria variável, sem nenhuma ligação com a anterior.
Já uma variável declarada fora de qualquer função, geralmente no topo do arquivo, tem escopo global: existe durante toda a execução do programa, e pode ser lida e alterada por qualquer função.
int contadorGlobal = 0;
void incrementarContador() {
contadorGlobal = contadorGlobal + 1;
}
Diferente do exemplo anterior, contadorGlobal mantém seu valor entre chamadas: cada chamada de incrementarContador soma 1 no mesmo contadorGlobal.
Apesar disso, variáveis globais costumam trazer mais problemas do que soluções: como qualquer função pode alterá-las sem aviso, fica difícil rastrear onde, exatamente, um valor mudou, quando algo dá errado. Prefira sempre passar informação por parâmetro e devolver resultado por retorno; reserve variáveis globais para casos raros e bem justificados.
🎥 Vídeo 3 de 3: Escopo de variáveis: local vs global (~5 min)
Leitura da Etapa 8 - Base: Savitch, C++ Absoluto, seções 4.1 e 3.3. Faça a lista de exercícios e traga suas dúvidas para o encontro síncrono.