Последнее обновление:2026-10-13
C++ const: константы и два вида const-указателей¶
Об авторе¶
Публичный аккаунт в WeChat:

1. Смысл const¶
Тип const — это тип, описанный с помощью квалификатора типа const; значение переменной или объекта типа const не может обновляться.
2. Роль const¶
- Позволяет определять константы
-
Проверка типов
-
Разница между const-константами и константами, определёнными макросом
#define:const-константа имеет тип, поэтому компилятор может выполнять проверки безопасности; у макроопределения #define нет типа данных — это лишь простая замена строк, поэтому проверка безопасности невозможна.Благодарим двух знатоков, указавших на проблему; см.: issue -
const-константы поддерживают все типы.
- В остальных случаях это просто
const-квалифицированная переменная; не путайте её с константой.
-
-
Защита от изменения, охранный эффект и повышение надёжности программы
-
Экономия памяти и избежание ненужных выделений
- const целочисленного/перечисляемого типа, инициализированный константным выражением (т.е. константное выражение), по характеру использования гораздо ближе к константе времени компиляции: если не брать его адрес и использовать только значение, он обычно оптимизируется в непосредственный операнд, как
#define; в память он попадает только при взятии адреса или odr-use между единицами трансляции, и во всей программе существует лишь одна его копия. - После разворачивания
#defineв литерал он в виде непосредственного операнда вшивается в инструкции; сами инструкции тоже занимают память, поэтому несколько использований означают несколько вхождений (см. обсуждение в issue #5). - Замечание: не всякая const-переменная является константным выражением — таковы только переменные целочисленного/перечисляемого типа, инициализированные константным выражением; остальные const-переменные — лишь const-квалифицированные переменные (например, с плавающей точкой, пользовательских типов); правильным средством выражения констант времени компиляции является
constexpr.
- const целочисленного/перечисляемого типа, инициализированный константным выражением (т.е. константное выражение), по характеру использования гораздо ближе к константе времени компиляции: если не брать его адрес и использовать только значение, он обычно оптимизируется в непосредственный операнд, как
3. Объекты const по умолчанию локальны в файле¶
Обратите внимание: переменные не 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-объекта нужно использовать указатель типа const void*.
Ещё один важный момент: разрешается присваивать адрес не-const-объекта указателю на const-объект.
Присваивание адреса не-const-объекта указателю на const-объект:
Мы не можем изменить значение val через указатель ptr, даже если он указывает на не-const-объект!
Мы не можем изменить базовый объект через указатель на const-объект, однако если указатель указывает на не-const-объект, сам указуемый объект можно изменить другими способами. Значение, на которое указывает const-указатель, изменить можно — просто не через указатель на const-объект! Изменим так:
Резюме:
1. Для указателя на константу нельзя изменять значение объекта через указатель.
2. Нельзя сохранять адрес const-объекта в указателе void*; для этого нужно использовать указатель типа const void*.
3. Разрешается присваивать адрес не-const-объекта указателю на 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, нельзя использовать для работы с константными объектами.
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-объекты могут обращаться к любым функциям-членам, включая 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() — const-функция-член, и разрешение перегрузки add() внутри неё совпадёт только с const-версией (int add(int num) const); лишь закомментировав эту const-версию, получим ошибку из-за вызова не-const add(). То есть: const-объекты могут обращаться только к const-функциям-членам, а не-const-объекты — к любым функциям-членам (см. issue #338).
Когда вызов меняется на:
Это доказывает, что const-объекты могут обращаться только к const-функциям-членам.
Помимо инициализации const-констант списком инициализации, как показано выше, можно использовать и следующие способы:
Первый: объединить определение константы со static, то есть:
Второй: инициализировать снаружи:
Конечно, если вы компилируете в режиме C++11, можно инициализировать прямо при определении и просто написать:
Оба варианта поддерживаются в C++11!
Достаточно добавить -std=c++11 при компиляции!
Здесь упомянули static — скажем о нём кратко:
В C++ статические переменные-члены static нельзя инициализировать внутри класса. Внутри класса — только объявление; определение должно находиться вне тела определения класса, обычно инициализацию делают в файле реализации класса.
Объявление в классе:
Использование в файле реализации класса:
Что касается этого пункта, C++11 не позволяет объявить и сразу инициализировать — то есть используется способ, описанный выше.
Частые вопросы¶
Чем указатель на const отличается от const-указателя?¶
const int* p — указатель на константу: значение менять нельзя. int* const p — константный указатель: нельзя переприсвоить сам указатель. Правило: const слева от * защищает значение, справа — указатель.
Может ли const-объект вызвать не-const метод?¶
Нет, const-объект вызывает только const-методы. Не-const объект вызывает любые.
const или #define для констант?¶
Лучше const: есть проверка типов, соблюдение областей видимости и отлаживаемость. #define — текстовая подстановка без типа.
Похожие темы¶
- 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++那些事