Saltar a contenido

Última actualización:2026-10-12

const en C++: constantes y modificación

Sobre el autor

Cuenta pública de WeChat:

wechat

1. Significado de const

Un tipo constante es un tipo declarado con el modificador de tipo const; el valor de las variables u objetos de tipo constante no puede actualizarse.

2. Función de const

  • Permite definir constantes

const int a=100;
+ Comprobación de tipos

+ Diferencia entre las constantes const y las constantes definidas con la macro `#define`:
> <strong><s>Las constantes const tienen un tipo, de modo que el compilador puede realizar comprobaciones de seguridad; las constantes definidas con la macro #define no tienen tipo de datos, son un simple reemplazo de cadenas y no permiten comprobaciones de seguridad.</s></strong>Gracias a dos expertos por señalar el problema aquí, ver: [issue](https://github.com/Light-City/CPlusPlusThings/issues/5)

+ Las constantes const admiten todos los tipos.
+ En los demás casos, solo se trata de una variable cualificada con `const`; no hay que confundirla con una constante.
  • Evita modificaciones, ejerce una función de protección y aumenta la robustez del programa

void f(const int i){
    i++; // error!
}
+ Ahorra espacio y evita asignaciones de memoria innecesarias

+ Desde el punto de vista del ensamblador, una constante definida con const solo proporciona la dirección de memoria correspondiente, mientras que `#define` proporciona un inmediato.
+ Una constante definida con const tiene una sola copia durante la ejecución del programa, mientras que las constantes definidas con `#define` tienen varias copias en memoria.

3. Los objetos const son por defecto locales al archivo

Nota: las variables no const son extern por defecto. Para que una variable const pueda utilizarse desde otros archivos, debe especificarse explícitamente como extern en el archivo.

Acceso a variables no modificadas con const desde archivos distintos

// file1.cpp
int ext;
// file2.cpp
#include<iostream>

extern int ext;
int main(){
    std::cout<<(ext+10)<<std::endl;
}

Acceso a constantes const desde archivos distintos

// extern_file1.cpp
extern const int ext=12;
// extern_file2.cpp
#include<iostream>
extern const int ext;
int main(){
    std::cout<<ext<<std::endl;
}

Resumen:
Se puede observar que las variables no modificadas con const no necesitan una declaración explícita de extern, mientras que las constantes const deben declararse explícitamente como extern y deben inicializarse. Como una constante no puede modificarse después de definirse, debe inicializarse en el momento de la definición.

4. Definir constantes

const int b = 10;
b = 0; // error: assignment of read-only variable ‘b’
const string s = "helloworld";
const int i,j=0 // error: uninitialized const ‘i’
Lo anterior contiene dos errores: + b es una constante, ¡no puede modificarse! + i es una constante, ¡debe inicializarse! (Como una constante no puede modificarse después de definirse, debe inicializarse en el momento de la definición.)

5. Punteros y const

Hay cuatro formas de combinar const con punteros:

const char * a; // 指向const对象的指针或者说指向常量的指针。
char const * a; // 同上
char * const a; // 指向类型对象的const指针。或者说常指针、const指针。
const char * const a; // 指向const对象的const指针。

Resumen:
Si const está a la izquierda de *, entonces const modifica la variable apuntada por el puntero, es decir, el punto señalado es constante;
si const está a la derecha de *, const modifica el puntero mismo, es decir, el puntero en sí es constante.

Otra forma de interpretarlo
Leyendo en inglés de derecha a izquierda y usando to como frontera: lo que va antes de to describe las características del puntero y lo que va después de to describe las del objeto apuntado

const char * p; //p is a pointer to const char
char const * p; //同上
char * const p; //p is a const pointer to char
const char * const p; //p is a const pointer to const char
```Cuando el puntero recibe la característica const, el puntero no puede cambiar la dirección a la que apunta<br>
Cuando el objeto apuntado tiene característica char, el contenido puede modificarse a través del puntero, por ejemplo: *char='y';<br>
Cuando el objeto apuntado tiene característica const char, el contenido no puede modificarse a través del puntero<br>

Su uso concreto es el siguiente:

(1) **Puntero a constantes**

```cpp
const int *ptr;
*ptr = 10; // error
ptr es un puntero a un objeto const de tipo int; const califica el tipo int, es decir, el tipo del objeto al que apunta ptr, y no a ptr mismo, por lo que ptr puede no tener valor inicial. Pero no se puede modificar mediante ptr el valor del objeto al que apunta.

Además, tampoco se puede usar un puntero void* para guardar la dirección de un objeto const; debe usarse un puntero de tipo const void* para guardar la dirección de un objeto const.

const int p = 10;
const void * vp = &p;
void *vp = &p; // error
Otro punto importante: está permitido asignar la dirección de un objeto no const a un puntero a objeto const.

Asignar la dirección de un objeto no const a un puntero a objeto const:

const int *ptr;
int val = 3;
ptr = &val; // ok
No podemos modificar mediante el puntero ptr el valor de val, ¡aunque apunte a un objeto no const!

No podemos usar un puntero a objeto const para modificar el objeto subyacente; sin embargo, si ese puntero apunta a un objeto no const, el objeto apuntado puede modificarse por otros medios. El valor apuntado por un puntero const puede modificarse, ¡solo que no a través del puntero a objeto const! Modificación como sigue:

int *ptr1 = &val;
*ptr1=4;
cout<<*ptr<<endl;

Resumen:
1. Con un puntero a constantes no se puede modificar mediante el puntero el valor del objeto.
2. No se puede usar un puntero void* para guardar la dirección de un objeto const; debe usarse un puntero de tipo const void* para guardar la dirección de un objeto const.
3. Está permitido asignar la dirección de un objeto no const a un puntero a objeto const; si se desea modificar el valor del objeto apuntado, debe hacerse por otros medios, no directamente a través del puntero actual.

(2) Puntero constante

Un puntero const debe inicializarse; el valor apuntado por un puntero const puede modificarse, pero no puede cambiar el objeto al que apunta.

#include<iostream>
using namespace std;
int main(){
    int num=0, num1=1;
    int * const ptr=&num; // const指针必须初始化!且const指针的指向不能修改
    ptr = &num1; // error! const指针不能修改指向!
    cout<<*ptr<<endl;
}
El código produce un error de compilación: un puntero const no puede modificar el objeto al que apunta.

#include<iostream>
using namespace std;
int main(){
    int num=0, num1=1;
    int * const ptr=&num; // const指针必须初始化!且const指针的指向不能修改
    *ptr = 1;
    cout<<*ptr<<endl;
}
El código no produce nada raro y muestra 1 con normalidad.

Por último, cuando se asigna a ptr la dirección de una constante const, como ptr apunta a una variable y no a una constante const, se produce un error: ¡error de const int* -> int *!

#include<iostream>
using namespace std;
int main(){
    const int num=0;
    int * const ptr=&num; // error! const int* -> int*
    cout<<*ptr<<endl;
}
Si lo anterior se cambia a const int *ptr o a const int *const ptr, ¡ambas versiones funcionan con normalidad!

(3) Puntero constante a constantes

Entendidos los dos casos anteriores, este caso resulta más fácil de entender:

const int p = 3;
const int * const ptr = &p; 
ptr es un puntero const que apunta a un objeto const de tipo int.

6. Uso de const en funciones

const modifica el valor de retorno de la función

Esto tiene básicamente el mismo significado que cuando const modifica variables ordinarias y punteros:

(1) const int

const int func1();
Esto en sí carece de sentido, ¡porque el valor devuelto ya se asigna a otras variables!

(2) const int*

const int* func2();
El contenido apuntado por el puntero no cambia.

(3) int *const

int *const func2();
El puntero mismo no puede cambiarse.

const modifica los parámetros de la función

(1) El parámetro recibido y el puntero mismo no pueden modificarse dentro de la función, ¡carece de sentido!

void func(const int var); // 传递过来的参数不可变
void func(int *const var); // 指针本身不可变
Indica que el parámetro no puede modificarse dentro del cuerpo de la función, pero aquí no tiene ningún sentido: var es en sí un parámetro formal y no cambia dentro de la función. Lo mismo ocurre cuando el parámetro formal recibido es un puntero.

Cuando el parámetro de entrada se pasa "por valor", como la función genera automáticamente una variable temporal para copiarlo, dicho parámetro de entrada no necesita protección, así que no hace falta añadir const.

(2) El contenido apuntado por el parámetro puntero es constante e inmutable

void StringCopy(char *dst, const char *src);
Aquí src es un parámetro de entrada y dst es un parámetro de salida. Al añadir const a src, si alguna instrucción del cuerpo de la función intenta modificar el contenido de src, el compilador señalará el error. Este es uno de los efectos de añadir const.

(3) El parámetro es una referencia, para ganar eficiencia y a la vez evitar modificaciones.

void func(const A &a)
Para los parámetros de tipos de datos no internos, una función declarada como void func(A a) está condenada a ser poco eficiente, porque en el cuerpo de la función se generará un objeto temporal de tipo A para copiar el parámetro a, y la construcción, copia y destrucción del objeto temporal consumen tiempo.

Para mejorar la eficiencia, la función puede declararse como void func(A &a), porque el "paso por referencia" solo toma prestado el alias del parámetro y no necesita generar un objeto temporal.

Pero la función void func(A &a) tiene un inconveniente:

el "paso por referencia" podría modificar el parámetro a, y eso no es lo que deseamos. La solución es muy sencilla: añadir el modificador const, de modo que la función finalmente queda como void func(const A &a).

Por analogía, ¿debería reescribirse void func(int x) como void func(const int &x) para mejorar la eficiencia? No hay ninguna necesidad, porque los parámetros de tipos de datos internos no pasan por procesos de construcción y destrucción, y copiarlos es muy rápido: la eficiencia del "paso por valor" y del "paso por referencia" es prácticamente la misma.

Resumen:
1. Para los parámetros de entrada de tipos de datos no internos, conviene cambiar el "paso por valor" por un "paso por const referencia", con el objetivo de mejorar la eficiencia. Por ejemplo, cambiar void func(A a) por void func(const A &a).

2. Para los parámetros de entrada de tipos de datos internos, no hay que cambiar el "paso por valor" por un "paso por const referencia". De lo contrario, no solo no se gana eficiencia, sino que se reduce la claridad de la función. Por ejemplo, void func(int x) no debería cambiarse por void func(const int &x).

Con lo anterior se resuelven dos preguntas de entrevista:

  • Si una función necesita recibir un puntero, ¿hay que añadir const a ese puntero y qué diferencia hay según la posición en la que se coloque const;
  • Si la función que escribes necesita recibir como parámetro una instancia de un tipo complejo, qué diferencia hay entre pasar el parámetro por valor o por referencia, y cuándo hay que añadir const al parámetro de referencia recibido.

7. Uso de const en clases

En una clase, toda función que no modifique los miembros de datos debería declararse de tipo const. Si al escribir una función miembro const modificamos por descuido un miembro de datos, o llamamos a otra función miembro no const, el compilador señalará el error, lo que sin duda mejora la robustez del programa.

Las funciones miembro declaradas con la palabra clave const se denominan funciones miembro constantes. Solo las funciones miembro constantes pueden operar con constantes o con objetos constantes; las funciones miembro declaradas sin la palabra clave const no pueden utilizarse para operar con objetos constantes.

Las variables miembro const de una clase deben inicializarse mediante la lista de inicialización, como se muestra a continuación:

class Apple{
private:
    int people[100];
public:
    Apple(int i); 
    const int apple_number;
};

Apple::Apple(int i):apple_number(i)
{

}
Un objeto const solo puede acceder a funciones miembro const, mientras que un objeto no const puede acceder a cualquier función miembro, incluidas las funciones miembro const.

Por ejemplo:

// apple.cpp
class Apple
{
private:
    int people[100];
public:
    Apple(int i); 
    const int apple_number;
    void take(int num) const;
    int add();
    int add(int num) const;
    int getCount() const;

};
// apple.cpp
Apple::Apple(int i) : apple_number(i)
{
}
int Apple::add(int num)
{
    take(num);
    return 0;
}
int Apple::add(int num) const
{
    take(num);
    return 0;
}
void Apple::take(int num) const
{
    std::cout << "take func " << num << std::endl;
}
int Apple::getCount() const
{
    take(1);
    //    add(); // error
    return apple_number;
}
int main()
{
    Apple a(2);
    cout << a.getCount() << endl;
    a.add(10);
    const Apple b(3);
    b.add(100);
    return 0;
}
// main.cpp
```> Compilación: bazel run basic_content/const/class_const/first_example:main<br>

En este momento se produce un error: en el método getCount() anterior se llama a un método add, y como el método add no está modificado con const, la ejecución da error. Es decir, una función miembro const solo puede acceder a funciones miembro const.

Cuando la llamada se cambia a:
const Apple b(3); b.add(); // error ```En este punto queda demostrado que un objeto const solo puede acceder a funciones miembro const.

Además de inicializar las constantes const con la lista de inicialización como se ha visto, también podemos hacerlo de las siguientes maneras:

Primera: combinar la definición de la constante con static, es decir:

static const int apple_number
Segunda: inicializarla fuera:

const int Apple::apple_number=10;
Por supuesto, si compilas con C++11, puedes inicializar directamente en la definición y escribir directamente:

static const int apple_number=10;
// 或者
const int apple_number=10;
¡Ambas formas están soportadas en C++11!

¡Basta con añadir -std=c++11 al compilar!

Aquí se ha mencionado static; a continuación, brevemente:

En C++, las variables miembro estáticas static no pueden inicializarse dentro de la clase. Dentro de la clase solo hay una declaración; la definición debe estar fuera del cuerpo de la definición de la clase, normalmente en el archivo de implementación de la clase.

Declaración en la clase:

static int ap;
Uso en el archivo de implementación de la clase:

int Apple::ap=666
En este punto, C++11 no permite declarar e inicializar a la vez, es decir, el uso descrito anteriormente.

Temas relacionados

Comentarios