Последнее обновление:2026-10-12
C++ константы const и его модификация¶
Об авторе¶
Официальный аккаунт в WeChat:

1.Смысл const¶
Константный тип — это тип, описанный с помощью модификатора типа const; значение переменных или объектов константного типа не может быть изменено.
2.Роль const¶
- Позволяет определять константы
-
Проверка типов
-
Различие между const-константами и константами, определёнными макросом
#define:const-константы имеют тип, и компилятор может выполнять проверку безопасности; у макроопределений #define нет типа данных — это лишь простая замена строк, поэтому проверка безопасности невозможна.Благодарим двух знатоков, указавших на проблему здесь, см.: issue -
const-константы поддерживают все типы.
- В остальных случаях это просто переменная с квалификатором
const; не путайте её с константой.
-
-
Предотвращает изменение, играет защитную роль и повышает надёжность программы
-
Позволяет экономить память и избегать ненужных выделений памяти
- С точки зрения ассемблера, определение константы через const даёт лишь соответствующий адрес в памяти, а не непосредственное значение, как в случае с
#define. - Константа, определённая через const, имеет во время работы программы только одну копию, тогда как константа, определённая через
#define, имеет в памяти несколько копий.
- С точки зрения ассемблера, определение константы через const даёт лишь соответствующий адрес в памяти, а не непосредственное значение, как в случае с
3.const-объекты по умолчанию являются локальными для файла¶
Внимание: неконстантные переменные по умолчанию являются extern. Чтобы const-переменная была доступна в других файлах, необходимо явно указать в файле, что она extern.
Доступ к переменной, не изменённой const, из разных файлов
// file1.cpp
int ext;
// file2.cpp
#include<iostream>
extern int ext;
int main(){
std::cout<<(ext+10)<<std::endl;
}
Доступ к const-константе из разных файлов
// extern_file1.cpp
extern const int ext=12;
// extern_file2.cpp
#include<iostream>
extern const int ext;
int main(){
std::cout<<ext<<std::endl;
}
Резюме:
Как видно, для переменной, не изменённой const, явное объявление extern не требуется! А const-константа требует явного объявления extern и должна быть инициализирована! Поскольку константа после определения не может быть изменена, её необходимо инициализировать при определении.
4.Определение констант¶
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’
В приведённом коде две ошибки: + b — константа, её нельзя изменять! + i — константа, она должна быть инициализирована! (Поскольку константа после определения не может быть изменена, её необходимо инициализировать при определении.)
5.Указатели и const¶
Существует четыре варианта const, связанных с указателями:
const char * a; // 指向const对象的指针或者说指向常量的指针。
char const * a; // 同上
char * const a; // 指向类型对象的const指针。或者说常指针、const指针。
const char * const a; // 指向const对象的const指针。
Резюме:
Если const находится слева от*, то const модифицирует переменную, на которую указывает указатель, то есть указуемый объект является константой;
если const находится справа от*, то const модифицирует сам указатель, то есть сам указатель является константой.
Другой способ толкования
Читаем по-английски справа налево, используя to как разделитель: до to описывается свойство указателя, после to — свойство цели
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
Когда свойство цели — char, содержимое может быть изменено через указатель, например: *char='y';
Когда свойство цели — const char, содержимое не может быть изменено через указатель
Конкретные примеры использования:
(1) Указатель на константу
ptr — это указатель на const-объект типа int; const определяет тип int, то есть тип объекта, на который указывает ptr, а не сам ptr, поэтому ptr может не получать начального значения. Однако нельзя изменять через ptr значение указуемого объекта.
Кроме того, нельзя сохранять адрес const-объекта в указателе типа void*; необходимо использовать указатель типа const void*, чтобы сохранить адрес const-объекта.
Ещё один важный момент: разрешается присваивать указателю на const-объект адрес неконстантного объекта.
Присваивание указателю на const-объект адреса неконстантного объекта:
Мы не можем изменять значение val через указатель ptr, даже если он указывает на неконстантный объект!
Мы не можем изменять базовый объект через указатель на const-объект, однако если такой указатель указывает на неконстантный объект, сам объект можно изменить другими способами. Значение, на которое указывает const-указатель, изменять можно — только не через указатель на const-объект! Например, так:
Резюме:
1. Через указатель на константу нельзя изменять значение объекта.
2. Нельзя сохранять адрес const-объекта в указателе типа void*; необходимо использовать указатель типа const void*, чтобы сохранить адрес const-объекта.
3. Разрешается присваивать указателю на const-объект адрес неконстантного объекта; если нужно изменить значение объекта, на который указывает указатель, это следует делать другими способами, а не напрямую через текущий указатель.
(2) Константный указатель
const-указатель должен быть инициализирован; значение, на которое указывает const-указатель, изменять можно, а направление указателя — нельзя.
#include<iostream>
using namespace std;
int main(){
int num=0, num1=1;
int * const ptr=# // const指针必须初始化!且const指针的指向不能修改
ptr = &num1; // error! const指针不能修改指向!
cout<<*ptr<<endl;
}
Код вызовет ошибку компиляции: const-указатель не может изменять своё направление.
#include<iostream>
using namespace std;
int main(){
int num=0, num1=1;
int * const ptr=# // const指针必须初始化!且const指针的指向不能修改
*ptr = 1;
cout<<*ptr<<endl;
}
Код отработает без происшествий и нормально выведет 1.
Наконец, когда адресу const-константы присваивается значение через ptr, поскольку ptr указывает на переменную, а не на const-константу, возникнет ошибка вида: const int* -> int *!
#include<iostream>
using namespace std;
int main(){
const int num=0;
int * const ptr=# // error! const int* -> int*
cout<<*ptr<<endl;
}
Если в приведённом выше коде заменить на const int *ptr или на const int *const ptr, всё будет работать нормально!
(3) Константный указатель на константу
Поняв два предыдущих случая, следующий понять гораздо проще:
ptr — это const-указатель, который указывает на const-объект типа int.
6.Использование const в функциях¶
const модифицирует возвращаемое значение функции
Смысл в основном тот же, что и при модификации const обычных переменных и указателей:
(1)const int
Само по себе это бессмысленно, поскольку возвращаемое значение и так присваивается другой переменной!
(2)const int*
Содержимое, на которое указывает указатель, неизменно.
(3)int *const
Сам указатель неизменяем.
const модифицирует параметры функции
(1)Переданный параметр и сам указатель неизменяемы внутри функции — бессмысленно!
Это означает, что параметр не может быть изменён в теле функции, но здесь в этом нет никакого смысла: var сам по себе является формальным параметром и внутри функции не меняется. То же относится и к случаю, когда передаваемый формальный параметр — указатель.
Если входной параметр передаётся «по значению», то функция автоматически создаёт временную переменную для копирования этого параметра, поэтому такой входной параметр изначально не нуждается в защите, и не следует добавлять модификатор const.
(2)Содержимое, на которое указывает параметр-указатель, является константой и не изменяется
Здесь src — входной параметр, dst — выходной. После добавления модификатора const к src, если какой-либо оператор в теле функции попытается изменить содержимое src, компилятор укажет на ошибку. Это одно из применений const.
(3)Параметр является ссылкой — для повышения эффективности и одновременно предотвращения изменения.
Для параметров не внутренних типов данных функция, объявленная как void func(A a), неизбежно будет сравнительно неэффективной. Ведь в теле функции будет создан временный объект типа A для копирования параметра a, а конструирование, копирование и разрушение временного объекта отнимают время.
Чтобы повысить эффективность, можно изменить объявление функции на void func(A &a), поскольку «передача по ссылке» лишь заимствует псевдоним параметра и не требует создания временного объекта.
Однако у функции void func(A &a) есть недостаток:
«Передача по ссылке» может изменить параметр a, чего мы не хотим. Эта проблема легко решается добавлением модификатора const, поэтому функция в итоге приобретает вид void func(const A &a).
По аналогии: следует ли переписать void func(int x) в void func(const int &x) ради повышения эффективности? В этом совершенно нет необходимости, поскольку для параметров внутренних типов данных не существует процессов конструирования и разрушения, а копирование происходит очень быстро — эффективность «передачи по значению» и «передачи по ссылке» практически одинакова.
Резюме:
1. Для входных параметров не внутренних типов данных следует заменить «передачу по значению» на «передачу по const-ссылке» с целью повышения эффективности. Например, изменить void func(A a) на void func(const A &a).
2. Для входных параметров внутренних типов данных не следует заменять «передачу по значению» на «передачу по const-ссылке». Иначе вы не добьётесь повышения эффективности, а понятность функции снизится. Например, void func(int x) не следует менять на void func(const int &x).
Выше решены два вопроса с собеседований:
- Если функция должна принимать указатель, нужно ли добавлять к этому указателю const, и в чём разница между размещением const в разных позициях указателя;
- Если написанная вами функция принимает в качестве параметра экземпляр сложного типа, в чём разница между передачей параметра по значению и по ссылке и когда нужно добавлять const к передаваемому параметру-ссылке.
7.Использование const в классах¶
В классе каждая функция, не изменяющая члены данных, должна объявляться как const. Если при написании const-членской функции вы случайно измените члены данных или вызовете другую неконстантную членскую функцию, компилятор укажет на ошибку, что, несомненно, повышает надёжность программы.
Членская функция, описанная с помощью ключевого слова const, называется константной членской функцией. Только константные членские функции имеют право оперировать константами или константными объектами; членские функции, описанные без ключевого слова const, нельзя использовать для работы с константными объектами.
Const-члены-переменные класса должны инициализироваться через список инициализации, как показано ниже:
class Apple{
private:
int people[100];
public:
Apple(int i);
const int apple_number;
};
Apple::Apple(int i):apple_number(i)
{
}
Const-объект может обращаться только к const-членским функциям, тогда как неконстантный объект может обращаться к любым членским функциям, включая const-членские функции.
Например:
// 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
Компиляция: bazel run basic_content/const/class_const/first_example:main
В этот момент возникнет ошибка: выше в методе getCount() вызывается метод add, который не модифицирован const, поэтому выполнение завершается ошибкой. Иными словами, const-членская функция может обращаться только к const-членским функциям.
Когда вызов изменён на:
в этот момент можно убедиться, что const-объект может обращаться только к const-членским функциям.Помимо инициализации const-констант через список инициализации, описанной выше, можно воспользоваться и следующими способами:
Первый: объединить определение константы со static, то есть:
Второй: инициализировать снаружи:
Разумеется, если вы компилируете с использованием C++11, можно инициализировать прямо при определении и написать просто:
Оба варианта поддерживаются в C++11!
При компиляции просто добавьте -std=c++11!
Здесь упоминается static; вкратце о нём:
В C++ статическую членскую переменную (static) нельзя инициализировать внутри класса. Внутри класса — только объявление; определение должно находиться вне тела определения класса, обычно инициализация выполняется в файле реализации класса.
Объявление в классе:
Использование в файле реализации класса:
В C++11 для этого случая нельзя объединить объявление и инициализацию, то есть использовать способ, описанный выше.
Похожие темы¶
- C++ чисто виртуальные функции и абстрактные классы | C++那些事
- C++ утверждения assert | C++那些事
- C++ битовые поля | C++那些事
- Реализация полиморфизма C++ на языке C | C++那些事
- C++ всё семейство const | C++那些事
- C++ оператор разыменования * | C++那些事
- C++ инициализация и присваивание | C++那些事
- C++ возврат нескольких значений | C++那些事
- C++ ключевое слово mutable | C++那些事
- C++ restrict и оптимизации компилятора | C++那些事