Programar em C++/Referências de dados
Variáveis de referência
editarEm C++ podemos criar variáveis que podem ser uma alternativa para os ponteiros em algumas situações. A vantagem de não usar diretamente o endereço (valor de ponteiro) em situações onde não precisamos lidar diretamente com valores de memória torna a programação mais segura e simplificada. Podemos deixar as operações com ponteiros apenas para quando for estritamente necessário.
Variáveis de referência podem ser criadas para dar um nome diferente para as variáveis que já existem no programa, ou para passar a variável para dentro do corpo de uma função. Observemos, inicialmente, um caso simples:
int a = 10;
int &b = a;
b = 20;
Neste trecho de programa, criamos uma variável de referência b para a variável a, o que significa que criamos outro nome para a variável a. De fato, b é a própria variável a com outro nome, apenas isso. Desta forma, podemos alterar o valor de a usando b.
Passagem de parâmetros
editarNa linguagem "C", durante a chamada de uma função, os argumentos (parâmetros) têm seus valores copiados para a área de processamento da função. Depois que os mesmos foram usados dentro do bloco de processamento da função, eles são descartados. A função retorna o processamento para o bloco que a chamou trazendo apenas o valor de retorno. A única maneira de fazer com que a função modifique o valor de alguma variável definida no bloco de programa que a chamou é passá-la por um ponteiro com o seu endereço.
Vejamos o fragmento de código seguinte:
int f( int x )
{
x--;
return x;
}
int main()
{
int a = 10;
int b;
b = f(a);
...
...
Em "C", a menos que o programador seja bem malicioso e faça manipulações de memória arriscadas, a função f jamais alterará o valor do seu argumento x.
Diferentemente da linguagem "C", a chamada a uma função em C++ pode alterar o valor de uma variável definida antes da chamada da função, mesmo sem esta variável ser explicitamente passada como um ponteiro. Este modo é chamado de passagem por referência. Em termos mais gerais, significa a passagem da variável propriamente dita, para o corpo interno da função com outro nome, aquele definido na lista de parâmetros da função.
Em C++, uma função pode ser chamada na forma acima e alterar o valor das suas variáveis. Para isso basta declará-la como:
int f(int & x)
{
x--;
return x;
}
Temos em C++ o operador & que se comporta analogamente do mesmo em C, porém tendo uma função a mais, a de criar variáveis de referência:
- &x quando usado no código retorna o pointero para o endereço de x;
- &x quando usado na declaração de variável, cria uma referência;
- &x quando usado como parâmetro na declaração de uma função faz com que suas chamadas transfira o argumento/parâmetro passando-o de forma similar a passagem de seu ponteiro. (Passagem por referência).
Em termos semânticos, ao passar a variável para uma função onde o parâmetro é uma referência, o endereço da variável é atribuído ao endereço do parâmetro. Desta forma, o parâmetro é a mesma variável passada, no trecho de código onde a função foi invocada, assumindo um nome diferente dentro da função. Podemos dizer que a variável assume um apelídio dentro da função, sendo a mesma com nome diferente apenas.
Vejamos um exemplo usando a função anterior:
int m = 4;
f(m);
cout << m << endl;
O código anterior imprime na saída padrão o valor 3. Acompanhando o fluxo de execução verificamos o seguinte: Depois que a variável m é incluída na chamada da função o seu nome muda para x e o programa passa a ser executado dentro da função, onde a variável é decrementada. Portanto, quando a execução retorna para o corpo principal a variável estará decrementada.
Exemplo: alterando o valor da variável usando referência
editar #include <iostream>
using namespace std;
int main()
{
int val = 1;
int &ref = val;
cout << "val is " << val << endl;
cout << "ref is " << ref << endl;
cout << "Setting val to 2" << endl;
val = 2;
cout << "val is " << val << endl;
cout << "ref is " << ref << endl;
cout << "Setting ref to 3" << endl;
ref = 3;
cout << "val is " << val << endl;
cout << "ref is " << ref << endl;
cout<<"Digite enter para continuar..."<<endl;
cin.get();
return 0;
}
Como se viu conseguimos alterar o valor de val alterando o valor de ref.
Existe apenas umas restrições para o seu uso:
- Teremos de inicializar e no momento da declaração teremos de atribuir de imediato o valor (se não fizermos isso gerará um erro de compilação)
- As referência não podem ser reatribuídas, ou seja no exemplo anterior tinha
int &ref = val; se mais tarde no código tentar-se fazer int &ref=m; (sendo m uma variável já declarada e iniciada por hipótese) o que acontece é que a 2ª instrução é completamente ignorada e ficamos sempre com a primeira.
A vantagem real das referências é que quando elas são usadas para passar valores para as funções elas providenciam uma maneira de retornar valores das funções.
Vejamos o exemplo
#include <iostream>
using namespace std;
int main()
{
int val1 = 10;
int val2 = 20;
int &ref = val1;
cout << "val1 is " << val1 << endl;
cout << "val2 is " << val2 << endl;
cout << "ref is " << ref << endl;
ref = val2; //What does this do?
cout << endl << "ref = val2" << endl;
cout << "val1 is " << val1 << endl;
cout << "val2 is " << val2 << endl;
cout << "ref is " << ref << endl;
val2 = 30;
cout << endl << "Setting val2 = 30" << endl;
cout << "val1 is " << val1 << endl;
cout << "val2 is " << val2 << endl;
cout << "ref is " << ref << endl;
cout<<"Digite enter para continuar..."<<endl;
cin.get();
return 0;
}
Exemplo: Swap
editarO exemplo abaixo mostra uma forma muito comum de usar referências. A instrução "swap", que tem por objetivo trocar os valores de duas variáveis, é mais naturalmente chamada como Swap(a, b) do que Swap(&a, &b); assim, é mais simples declarar a função usando referência:
#include <iostream>
using namespace std;
void Swap (int &i,int &j)
{
int t=i;
i=j;
j=t;
}
int main ()
{
int a,b;
a=5;
b=10;
cout<<a<<"\t"<<b;
Swap (a,b);
cout<<a<<"\t"<<b;
cout<<"Digite enter para continuar..."<<endl;
cin.get();
return 0;
}
Comparação entre passagem por referência e ponteiros
editarPara exercitar vamos criar um novo problema: Criar um função que duplique qualquer valor colocado pelo utilizador:
1º PROGRAMA-via referência | 2º PROGRAMA – via ponteiros - endereços |
#include <iostream>
using namespace std;
void doubleIt(int&);//prototype com endereço de variavel
int main ()
{
int num;
cout << "Enter number: ";
cin >> num;
cin.get();
doubleIt(num); //chamo função, passando parametro num
cout << "The number doubled in main is " << num << endl;
cout<<"Digite enter para continuar..."<<endl;
cin.get();
return 0;
}
void doubleIt (int& x)
{
cout << "The number to be doubled is " << x << endl;
x *= 2;
cout << "The number doubled in doubleIt is " << x << endl;
}
|
#include <iostream>
using namespace std;
void doubleIt(int*); //parametro por endereço
int main ()
{
int num;
cout << "Enter number: ";
cin >> num;
cin.get();
doubleIt(&num);//passei parametro como endereço
cout << "The number doubled in main is " << num << endl;
cout<<"Digite enter para continuar..."<<endl;
cin.get();
return 0;
}
void doubleIt (int* x)
{
cout << "The number to be doubled is " << *x << endl;
*x *= 2;
cout << "The number doubled in doubleIt is " << *x << endl;
}
|
Ou seja, nestes dois códigos temos uma passagem por referência e outro por endereço. Com diferenças:
- Na chamada da função (dentro do main() )
doubleIt(num); // por referência doubleIt(&num); // por endereço
- No protótipo da função (confirmar o ponto e virgula)
void doubleIt(int&); // por referência void doubleIt(int*); // por endereço
- No "header" da função
void doubleIt (int& x) // por referência void doubleIt (int* x) // por endereço
- dentro do corpo da função (dentro da própria função)
x *x
Podemos pensar que passando por referência parece ser muito mais simples do que passado por endereço.
Na verdade existem certas funções da biblioteca que só permitem a passagem por endereço.