Introducción: ¿Por qué este tema vuelve locos incluso a desarrolladores experimentados de C++?
\n\nSi alguna vez has abierto código ajeno en C++ y has visto algo como const int * const data, seguramente has sentido el deseo de cerrar el editor e irte a tomar un té. Esta extraña sintaxis con const esparcido a ambos lados del asterisco es una verdadera piedra de tropiezo. La pregunta «What is the difference between const int*, const int * const, and int * const?» en Stack Overflow ha acumulado cientos de miles de visitas. Y no es casualidad.
Entender estas construcciones no es solo un capricho de un pedante. Es la clave para escribir código seguro, autodocumentado y, lo que es más importante, para pasar entrevistas. Hoy vamos a aclarar esta confusión de una vez por todas, usando una regla simple que siempre funciona.
\n\nLos tres pilares: ¿qué vamos a comparar?
\n\nEn C++, los punteros y la constancia pueden combinarse de cuatro maneras (tres de ellas son las más populares). Vamos a nombrarlas:
\n\n- \n
- Opción 1:
const int* ptr(oint const* ptr— es lo mismo). \n - Opción 2:
int * const ptr. \n - Opción 3:
const int * const ptr(combinación de las dos primeras). \n
La idea clave: const siempre se refiere a lo que está a su izquierda. Si no hay nada a la izquierda, se refiere a lo que está a la derecha. Suena simple, pero en la práctica esta regla es salvadora.
\n\nOpción 1: const int* ptr (Puntero a un entero constante)
\n\nLeemos de derecha a izquierda: «ptr es un puntero a un número entero que es constante».
\n\n¿Qué significa esto en la práctica?
\n- \n
- No puedes cambiar el valor de la variable a la que apunta ptr a través de este puntero. \n
- Sí puedes cambiar el puntero en sí mismo (hacer que apunte a otra variable). \n
#include <iostream>\n\nint main() {\n int a = 10;\n int b = 20;\n\n const int* ptr = &a; // ptr — puntero a const int\n\n // *ptr = 15; // ¡ERROR de compilación! No se puede cambiar el valor a través de const int*\n std::cout << "*ptr = " << *ptr << std::endl; // Mostrará 10\n\n ptr = &b; // ¡OK! Cambiamos el puntero en sí\n std::cout << "*ptr = " << *ptr << std::endl; // Mostrará 20\n\n return 0;\n}\n\nEste tipo se usa a menudo para pasar datos a una función cuando quieres garantizar que la función no dañe los datos originales. Por ejemplo, void print(const int* data) es una promesa de «solo lectura».
Opción 2: int * const ptr (Puntero constante a un número entero)
\n\nLeemos de derecha a izquierda: «ptr es un puntero constante a un número entero».
\n\nAquí es al revés:
\n- \n
- Sí puedes cambiar el valor de la variable a la que apunta ptr. \n
- No puedes cambiar el puntero en sí mismo (siempre debe apuntar a la misma dirección de memoria). \n
#include <iostream>\n\nint main() {\n int a = 10;\n int b = 20;\n\n int * const ptr = &a; // ptr — puntero constante a int\n\n *ptr = 15; // ¡OK! Cambiamos el valor a través del puntero\n std::cout << "*ptr = " << *ptr << std::endl; // Mostrará 15\n\n // ptr = &b; // ¡ERROR de compilación! No se puede cambiar el puntero en sí\n\n return 0;\n}\n\nImagina que int * const es un puntero «fijo». Se usa a menudo para trabajar con registros de hardware o en casos donde el puntero a un búfer no debe «escaparse» a otro lugar.
Opción 3: const int * const ptr (Puntero constante a un entero constante)
\n\nLeemos de derecha a izquierda: «ptr es un puntero constante a un número entero, que es constante».