Referencias
Una referencia (&) es como un puntero constante que se destruye automáticamente. Normalmente se utiliza en la lista de argumentos y en el valor de retorno de una función. Pero también se puede hacer una referencia que apunte a algo que no ha sido asignado. Por ejemplo:
//: C11: Free Standing References.cpp
- include <iostream>
using namespace std;
// Ordinary free-standing reference: int y; int& r = y; // When a reference is created, it must // be initialized to a live object. // However, you can also say: const int& q = 12; // (1) // References are tied to someone else’s storage: int x = 0; // (2) int& a = x; // (3) int main() {
cout << “x = “ << x << “, a = “ << a << endl; a++; cout << “x = “ << x << “, a = “ << a << endl;
}
En la linea (1) el compilador asigna la cantidad necesaria de memoria, la inicializa con el valor 12, y liga la referencia
Hay que seguir unas determinadas reglas cuando se utilizan referencias:
Cuando se crea una referencia, se ha de inicializar. (Los punteros pueden inicializarse en cualquier momento.)
Una vez que se inicializa una referencia, ligándola a un objeto, no se puede ligar a otro objeto. (Los punteros pueden apuntar a otro objeto en cualquier momento.)
No se pueden tener referencias con valor nulo. Siempre ha de suponer que una referencia está conectada a una trozo de memoria ya asignada.
2.1. Referencias en las funciones El lugar más común en el que verá referencias es en los argumentos y valor de retorno de las funciones. Cuando se utiliza una referencia como un argumento de una función, cualquier cambio realizado en la referencia dentro de la función se realizará realmente sobre en el argumento fuera de la función. Por supuesto que podría hacer lo mismo pasando un puntero como argumento, pero una referencia es sintácticamente más clara. (Si lo desea, puede pensar que una referencia es, nada más y nada menos, sintácticamente más conveniente.)
Si una función retorna una referencia, ha de tener el mismo cuidado que si la función retornara un puntero. La referencia que se devuelva debe estar ligada a algo que no sea liberado cuando la función retorne. Si no, la referencia se referirá a un trozo de memoria sobre el que ya no tiene control.
He aquí un ejemplo:
//: C11:Reference.cpp // Simple C++ references
int* f(int* x) {
(*x)++; return x; // Safe, x is outside this scope
}
int& g(int& x) {
x++; // Same effect as in f() return x; // Safe, outside this scope
}
int& h() {
int q;
//! return q; // Error
static int x; return x; // Safe, x lives outside this scope
}
int main() {
int a = 0; f(&a); // Ugly (but explicit) g(a); // Clean (but hidden)
}
La llamada a f() no tiene la ventaja ni la claridad que la utilización de referencias, pero está claro que se está pasando una dirección mediante un puntero. En la llamada a g(), también se pasa una dirección (mediante una referencia), pero no se ve.
2.1.1. Referencias constantes El argumento referencia en Reference.cpp funciona solamente en caso de que el argumento no sea un objeto constante (es decir, no sea const). Si fuera un objeto constante, la función g() no aceptaría el argumento, lo cual es positivo porque la función modificaría el argumento que está fuera del ámbito de la función. Si sabe que la función respetará el valor “constante” de un objeto, el hecho de que el argumento sea una referencia constante permitirá que la función se pueda utilizar en cualquier situación. Esto significa que para tipos predefinidos, la función no modificará el argumento, y para tipos definidos por el usuario, la función llamará solamente a métodos constantes, y no modificara ningún atributo público.
La utilización de referencias constantes en argumentos de funciones es especialmente importante porque una función puede recibir un objeto temporal. Éste podría haber sido creado como valor de retorno de otra función o explícitamente por el usuario de la función. Los objetos temporales son siempre constantes. Así, si no utiliza una referencia constante, el compilador se quejará. Como ejemplo muy simple:
//: C11:Const Reference Arguments?.cpp // Passing references as const
void f(int&) {} void g(const int&) {}
int main() { //! f(1); // Error
g(1);
}
La llamada f(1) provoca un error en tiempo de compilación porque el compilador debe primero crear una referencia. Lo hace asignando memoria para un int, iniciánlizándolo a uno y generando la dirección de memoria para ligarla a la referencia. La memoria debe ser constante porque no tendría sentido cambiarlo: no puede cambiarse de nuevo. Puede hacer la misma suposición para todos los objetos temporales: son FIXME:inaccesibles. Es importante que el compilador le diga cuándo está intentando cambiar algo de este estilo porque podría perder información. 2.1.2. Referencias a puntero En C, si desea modificar el contenido del puntero en sí en vez de modificar a lo que apunta, la declaración de la función sería:
void f(int**);
y tendría que coger la dirección del puntero cuando se llamara a la función:
int i = 47; int* ip = &i; f(&ip);
La sintaxis es más clara con las referencias en C++. El argumento de la función pasa a ser de una referencia a un puntero, y así no ha de manejar la dirección del puntero. Así,
//: C11:Reference to Pointer?.cpp
- include <iostream>
using namespace std;
void increment(int*& i) { i++; }
int main() {
int* i = 0; cout << “i = “ << i << endl; increment(i); cout << “i = “ << i << endl;
}
Al ejecutar este programa se observa que el puntero se incrementa en vez de incrementar a lo que apunta.