Subsecções


11 Mais sobre funções: Quando return não é suficiente

Considere o programa abaixo que pede ao usuário dois inteiros, armazena-os em duas variáveis, troca seus valores, e os imprime.

#include <iostream>
using namespace std;

int main()
{
  int a, b, temp;
  
  cout << "Entre dois numeros: ";
  cin >> a >> b;
  
  cout << "Voce entrou com " << a << " e " << b << endl;
  
  /* Troca a com b */
  temp = a;
  a = b;
  b = temp;
  
  cout << "Trocados, eles sao " << a << " e " << b << endl;
}

Aqui está um exemplo de execução do programa:

     Entre dois numeros: 3 5
     Voce entrou 3 e 5
     Trocados, eles sao 5 e 3

O seguinte trecho do programa executa a troca de valores das variáveis a e b:

    temp = a;
    a = b;
    b = temp;

É possível escrever uma função que executa esta operação de troca? Considere a tentativa abaixo de escrever esta função:

#include <iostream>
using namespace std;

void troca(int x, int y)
{
    int temp;

    temp = x;
    x = y;
    y = temp;
}

int main()
{
  int a, b;
  
  cout << "Entre dois numeros: ";
  cin >> a >> b;
  
  cout << "Voce entrou com " << a << " e " << b << endl;
  
  // Troca a com b
  troca(a, b);
  
  cout << "Trocados, eles sao " << a << " e " << b << endl;
}

Se você executar este programa, verá que ele não funciona:

     Entre dois numeros: 3 5
     Voce entrou 3 e 5
     Trocados, eles sao 3 e 5

Como você já viu nas notas anteriores, em C++ os argumentos são passados por valor. Uma vez que somente os valores das variáveis são passados, não é possível para a função troca() alterar os valores de a e b porque troca() não sabe onde na memória estas variáveis estão armazenadas. Além disso, troca() não poderia ser escrito usando a sentença return porque podemos retornar APENAS UM valor (não dois) através da sentença return.

11.1 Usando referência

A solução para o problema acima é ao invés de passar os valores de a e b, passar uma referência às variáveis a e b. Desta forma, troca() saberia que endereço de memória escrever, portanto poderia alterar os valores de a e b.

Lembre-se que em C++ a cada variável está associado: (i) um nome; (ii) um tipo; (iii) um valor; e (iv) um endereço. Assuma que existam as seguintes definições de variáveis.

    int i = 5;
    char c = 'G';

Na memória, eles podem estar armazenados da forma indicada na Figura 11.1:

Figura 3: Variáveis em memória
\includegraphics[scale=0.5]{ptr1}

A variável inteira i está armazenada no endereço 1342. Ela usa dois bytes de memória (quando um objeto usa mais de um byte, seu endereço é onde ele começa - neste caso, 1342 e não 1343). A variável do tipo char c está armazenada no endereço 1346 e usa um byte de memória. O compilador é que controla do local de armazenamento destas variáveis em memória.

11.2 Argumentos por referência em funções

Considere novamente o exemplo da função troca(). Quando a e b são passados como argumentos para troca(), na verdade, somente seus valores são passados. A função não podia alterar os valores de a e b porque ela não conhece os endereços de a e b. Mas se referências para a e b forem passados como argumentos ao invés de a e b, a função troca() seria capaz de alterar seus valores; ela saberia então em que endereço de memória escrever. Na verdade, a função não sabe que os endereços de memória são associados com a e b, mas ela pode modificar o conteúdo destes endereços. Portanto, passando uma variável por referência (ao invés do valor da variável), habilitamos a função a alterar o conteúdo destas variáveis na função chamadora.

A definição da função troca() deve ser alterada, e a lista de parâmetros formais deve ter argumentos não do tipo int, mas referências para int, ou seja, int & . Quando chamamos a função troca(), nós continuamos passando parâmetros reais a e b, mas desta vez, o compilador sabe que o que será passado para a função troca() são as referências a estas variáveis, e não seus valores. Dentro da função troca() não deverá haver mudanças. Uma vez que agora os parâmetros formais são referências, o acesso aos objetos deve ser escrito normalmente, mas deve-se ter em mente que qualquer alteração nos valores dos parâmetros formais da função implica em alterar o valor dos argumentos passados para a função no momento de sua chamada. Assim, a função troca() é capaz de alterar os valores de a e b “remotamente”.

O programa abaixo é a versão correta do problema enunciado para a função troca():

#include <iostream>

using namespace std;

/* função troca(px, py)
 *  ação:        troca os valores inteiros apontados por px e py
 *  entrada:     apontadores px e py
 *  saida:       valor de px e py trocados na origem da chamada da função
 *  suposições:  px e py sao apontadores validos
 *  algoritmo:   primeiro guarda o primeiro valor em um temporario e
 *               troca
 */
void troca(int & px, int & py)
{
    int temp;

    temp = px;
    px = py;
    py = temp;
}

int main()
{
  int a, b;
  
  cout << "Entre dois numeros: ";
  cin >> a >> b;
  
  cout << "Voce entrou com " << a << " e " << b << endl;
  
  // Troca a com b -- passa argumentos por referencia
  troca(a, b);
  
  cout << "Trocados, eles sao " << a << " e " << b << endl;
}

A saída deste programa é:

     Entre dois numeros: 3 5
     Voce entrou com  3 e 5
     Trocados, eles sao 5 e 3

Basicamente, se a função precisa alterar o valor de uma variável no ponto de chamada da função, então passamos o nome da variável como parâmetro real, e escrevemos a função indicando os parâmetros como sendo de SAìDA ou PASSADOS POR REFERÊNCIA.


Créditos: Documento produzido pelo Prof. Armando L.N. Delgado (DINF/ET/UFPR), baseado em revisão sobre material de Prof. Carmem Hara e Prof. Wagner Zola (DINF/ET/UFPR).

Esta obra está licenciada com uma Licença Creative Commons Atribuição-NãoComercial-CompartilhaIgual 4.0 Internacional.  Licença Creative Commons

Armando Luiz Nicolini Delgado
2020-10-20