Saltar para o conteúdo
Topos Uranos

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

  1. Prever o efeito de uma função sobre os seus argumentos conforme sejam passados por valor ou por referência.
  2. 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.
  3. Devolver vários resultados com std::pair ou std::tuple e recebê-los com vinculações estruturadas.
  4. Determinar o escopo e a duração de cada variável de um programa, incluídas as variáveis static locais e as que ficam sombreadas por outra.
  5. 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.

Programa em C++
#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);}
Saída
Dentro: a = 2, b = 1Depois: x = 1, y = 2

Dentro 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.

ExemploDois quadros, quatro posições

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

  1. Antes da chamada, o quadro de main tem duas posições: x com 1 e y com 2.

  2. A chamada cria o quadro de swapValues. Os seus parâmetros a e b sã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 entre a e x, exceto que começaram com o mesmo valor.

  3. A variável local saved é criada no mesmo quadro e guarda uma cópia de a.

  4. As duas atribuições trocam a e b. As posições que mudam estão todas no quadro de cima; as de main não são tocadas.

  5. Ao retornar, o quadro de swapValues desaparece inteiro, com a troca que continha. x e y continuam 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.

Programa em C++
#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));}
Saída
48210 tem 5 algarismos

A 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.