Resumo
Esta aula estuda por quais caminhos os dados entram em uma função e saem os seus resultados. Por padrão, C++ passa os argumentos por valor: o parâmetro é uma cópia, e nada do que a função lhe faça alcança o original. Uma referência, por outro lado, é um segundo nome para uma posição que já existe, e permite que a função modifique a variável de quem a chama; a referência constante permite ler um dado grande sem copiá-lo e sem poder alterá-lo. Para devolver vários resultados, a biblioteca oferece std::pair e std::tuple, que se recebem com vinculações estruturadas. Seguem-se o escopo de um nome (onde ele é visto) e a duração de uma variável (quando ela existe), com o sombreamento de um nome e as variáveis static locais, e fecha a aula a referência que sobrevive à sua variável. O conceito que unifica tudo é o fluxo de dados entre as partes de um programa: copiar ou compartilhar, e a visibilidade dos nomes como mecanismo de isolamento.
Objetivos de aprendizagem
- Prever o efeito de uma função sobre os seus argumentos conforme sejam passados por valor ou por referência.
- Escolher o modo de passagem de cada parâmetro (valor para tipos pequenos, referência constante para dados grandes, referência para o que se modifica) e justificar a escolha.
- Devolver vários resultados com
std::pairoustd::tuplee recebê-los com vinculações estruturadas. - Determinar o escopo e a duração de cada variável de um programa, incluídas as variáveis
staticlocais e as que ficam sombreadas por outra. - Reconhecer a referência a uma variável local ou a um valor temporário que já não existe, com o aviso do compilador que a anuncia, e corrigi-la.
Passagem por valor
O parâmetro é uma cópia
A aula Funções mostrou que cada chamada cria um quadro na pilha, com os parâmetros e as variáveis locais da função, e que o quadro desaparece ao retornar. Falta precisar que relação um parâmetro guarda com o argumento que o inicializa. A resposta padrão de C++ é a mais simples: o parâmetro é uma variável local nova, que se inicializa com uma cópia do valor do argumento. Se isso é assim, uma função que modifica os seus parâmetros modifica as suas cópias, e as variáveis de quem a chama não mudam. O programa a seguir põe isso à prova com uma função que tenta trocar dois inteiros.
#include <print>// Tenta trocar a e b; só troca as suas cópias.void swapValues(int a, int b){ const int saved{a}; a = b; b = saved; std::println("Dentro: a = {}, b = {}", a, b);}int main(){ int x{1}; int y{2}; swapValues(x, y); std::println("Depois: x = {}, y = {}", x, y);}Dentro: a = 2, b = 1Depois: x = 1, y = 2Dentro da função, a troca é feita corretamente; depois da chamada, x e y conservam os seus valores. O compilador não avisa, porque não há nada a avisar: o código troca duas variáveis locais que desaparecem ao terminar. A figura acompanha os quadros da pilha durante a chamada.
O quadro de main contém x e y; a chamada cria por cima o quadro de swapValues, com posições próprias.
Demonstração
Antes da chamada, o quadro de
maintem duas posições:xcom 1 eycom 2.A chamada cria o quadro de
swapValues. Os seus parâmetrosaebsão posições novas, e nelas se copiam os valores dos argumentos: 1 e 2. A partir deste momento não resta nenhum vínculo entreaex, exceto que começaram com o mesmo valor.A variável local
savedé criada no mesmo quadro e guarda uma cópia dea.As duas atribuições trocam
aeb. As posições que mudam estão todas no quadro de cima; as demainnão são tocadas.Ao retornar, o quadro de
swapValuesdesaparece inteiro, com a troca que continha.xeycontinuam valendo 1 e 2.
O que a cópia protege
A passagem por valor não é um defeito, mas a regra que torna as funções razoáveis. Primeiro, o argumento pode ser qualquer expressão, não só uma variável: swapValues(x + 1, 7) é válida, porque o parâmetro se inicializa com um valor, venha de onde vier. Segundo, quem chama sabe pela declaração, sem ler o corpo, que as suas variáveis não mudarão. Terceiro, dentro da função o parâmetro é mais uma variável local, que pode servir de variável de trabalho sem consequências fora.
#include <print>// Quantidade de algarismos decimais de n.// Pré-condição: n >= 0.// Pós-condição: devolve o número de algarismos de n (1 para o 0).int digitCount(int n){ int count{1}; while (n >= 10) { n = n / 10; ++count; } return count;}int main(){ const int population{48'210}; std::println("{} tem {} algarismos", population, digitCount(population));}48210 tem 5 algarismosA função destrói o seu parâmetro dividindo-o por 10 até que tenha um só algarismo; como é uma cópia, population fica intacta, e pode ser const. Disso decorre uma regra de leitura que vale para todo o curso: se um parâmetro não tem o sinal & que se estuda na seção seguinte, a função não pode modificar a variável de quem a chama, faça o que fizer com ele.
Carregando o conteúdo…