Última actualización:2026-10-12
Punteros y referencias en C++¶
Sobre el autor:¶
Cuenta pública personal:

1. Referencias y punteros¶
Resumen:
| Referencia | Puntero |
|---|---|
| Debe inicializarse | Puede no inicializarse |
| No puede ser nula | Puede ser nulo |
| No puede cambiar de objetivo | Puede cambiar de objetivo |
Una referencia debe inicializarse, mientras que un puntero puede quedar sin inicializar.
Al definir una referencia debemos asignarle obligatoriamente un valor inicial, mientras que un puntero no lo necesita.
Una referencia no puede ser nula, mientras que un puntero sí puede serlo.
Como las referencias no pueden ser nulas, al usarlas no necesitamos comprobar su validez; en cambio, al usar un puntero hay que determinar primero si es un puntero nulo, de lo contrario el programa podría llegar a fallar.
void test_p(int* p)
{
if(p != null_ptr) //对p所指对象赋值时需先判断p是否为空指针
*p = 3;
return;
}
void test_r(int& r)
{
r = 3; //由于引用不能为空,所以此处无需判断r的有效性就可以对r直接赋值
return;
}
Una referencia no puede cambiar de objetivo
Un puntero puede cambiar de apuntado en cualquier momento, pero una referencia solo puede apuntar al objeto al que apuntaba al inicializarse, y eso no se puede cambiar.
int a = 1;
int b = 2;
int &r = a; //初始化引用r指向变量a
int *p = &a; //初始化指针p指向变量a
p = &b; //指针p指向了变量b
r = b; //引用r依然指向a,但a的值变成了b
2. Referencias¶
Referencia a lvalue¶
La referencia convencional, que generalmente representa la identidad de un objeto.
Referencia a rvalue¶
Una referencia a rvalue es una referencia que debe vincularse a un rvalue (un objeto temporal, un objeto que está a punto de destruirse) y que generalmente representa el valor de un objeto.
Las referencias a rvalue permiten implementar semántica de movimiento (Move Sementics) y reenvío exacto (Perfect Forwarding); sus objetivos principales son dos:
- Eliminar las copias innecesarias de objetos cuando dos objetos interactúan, ahorrando recursos de cómputo y almacenamiento y mejorando la eficiencia.
- Permitir definir funciones genéricas de forma más concisa y clara.
Colapso de referencias¶
X& &,X& &&,X&& &se colapsan enX&X&& &&se colapsa enX&&
Las referencias de C++ reducen la libertad del programador, pero a cambio aumentan la seguridad de las operaciones con memoria y la elegancia semántica. Por ejemplo, el hecho de que una referencia exija inicialización nos permite usarla sin tener que comprobar si es nula, haciendo el código más conciso y elegante y evitando la situación de punteros volando por todas partes. Aparte de este escenario, las referencias se usan también en estos dos casos:
Parámetros de tipo referencia
En general usamos parámetros const reference como parámetros formales de solo lectura; en este caso se evita la copia del parámetro y se mantiene además la misma forma de llamada que con parámetros por valor.
void test(const vector<int> &data)
{
//...
}
int main()
{
vector<int> data{1,2,3,4,5,6,7,8};
test(data);
}
Valores de retorno de tipo referencia
C++ ofrece la posibilidad de sobrecargar operadores; al sobrecargar ciertos operadores, usar un valor de retorno de tipo referencia permite obtener la misma sintaxis de llamada que la del operador original, conservando la coherencia de su semántica. Un ejemplo es el operador operator [], que normalmente debe devolver un objeto referencia para poder modificarse correctamente.
vector<int> v(10);
v[5] = 10; //[]操作符返回引用,然后vector对应元素才能被修改
//如果[]操作符不返回引用而是指针的话,赋值语句则需要这样写
*v[5] = 10; //这种书写方式,完全不符合我们对[]调用的认知,容易产生误解
3. Diferencia de rendimiento entre punteros y referencias¶
¿Existe alguna diferencia de rendimiento entre punteros y referencias? Para responder a este tipo de preguntas hay que bajar al nivel del ensamblador. Primero escribimos una función test1 que pasa el parámetro mediante puntero:
El código ensamblador correspondiente a este fragmento es el siguiente:
(gdb) disassemble
Dump of assembler code for function test1(int*):
0x0000000000400886 <+0>: push %rbp
0x0000000000400887 <+1>: mov %rsp,%rbp
0x000000000040088a <+4>: mov %rdi,-0x8(%rbp)
=> 0x000000000040088e <+8>: mov -0x8(%rbp),%rax
0x0000000000400892 <+12>: movl $0x3,(%rax)
0x0000000000400898 <+18>: nop
0x0000000000400899 <+19>: pop %rbp
0x000000000040089a <+20>: retq
End of assembler dump.
En el código anterior, las líneas 1 y 2 son las operaciones de guardado del contexto en la llamada; la línea 3 es el paso de parámetros: el primer argumento de una llamada a función suele colocarse en el registro rdi, y esta línea escribe el valor del registro rdi (el valor del puntero p) en la pila; la línea 4 escribe el valor de p de la pila en el registro rax; la línea 5 escribe el inmediato 3 en la memoria apuntada por el valor del registro rax; aquí conviene prestar atención a los paréntesis alrededor de (%rax): no son en absoluto opcionales, (%rax) y %rax significan dos cosas completamente distintas: (%rax) representa la memoria de la dirección contenida en el registro rax, es decir, equivale a *p en código C++, mientras que %rax representa el registro rax, que equivale al valor de p en código C++; por eso el ensamblador usa aquí (%rax) y no %rax.
Escribamos ahora el fragmento de código C++ test2 que pasa el parámetro mediante referencia:
El código ensamblador correspondiente a este fragmento es el siguiente:
(gdb) disassemble
Dump of assembler code for function test2(int&):
0x000000000040089b <+0>: push %rbp
0x000000000040089c <+1>: mov %rsp,%rbp
0x000000000040089f <+4>: mov %rdi,-0x8(%rbp)
=> 0x00000000004008a3 <+8>: mov -0x8(%rbp),%rax
0x00000000004008a7 <+12>: movl $0x3,(%rax)
0x00000000004008ad <+18>: nop
0x00000000004008ae <+19>: pop %rbp
0x00000000004008af <+20>: retq
End of assembler dump.
Comprobamos que el código ensamblador de test2 es exactamente igual al de test1, lo que indica que el compilador de C++ compila punteros y referencias a código máquina idéntico. Por tanto, la referencia en C++ es simplemente un "azúcar sintáctico" de C++ sobre la operación con punteros; en la implementación de bajo nivel, el compilador de C++ implementa ambas operaciones exactamente de la misma manera.
3. Resumen¶
C++ introdujo las referencias para garantizar, imponiendo más restricciones a su uso, seguridad y comodidad, y para conservar además la elegancia del código. Usando la operación adecuada en cada situación, el empleo de referencias puede evitar en cierta medida la situación de "punteros volando por todas partes", lo cual también tiene un sentido positivo para mejorar la estabilidad del programa. En definitiva, punteros y referencias tienen la misma implementación de bajo nivel, así que no hay que preocuparse por diferencias de rendimiento entre ambos.
Parte de lo anterior está tomado de: http://irootlee.com/juicer_pointer_reference/#
Temas relacionados¶
- Funciones virtuales puras y clases abstractas en C++ | C++那些事
- Aserciones assert en C++ | C++那些事
- Campos de bits en C++ | C++那些事
- Implementar el polimorfismo de C++ en lenguaje C | C++那些事
- const en C++: constantes y modificación | C++那些事
- La familia completa de const en C++ | C++那些事
- El operador de desreferencia * en C++ | C++那些事
- Inicialización y asignación en C++ | C++那些事
- Formas de devolver varios valores en C++ | C++那些事
- La palabra clave mutable en C++ | C++那些事