Перейти к содержанию

Последнее обновление:2026-10-12

C++ константы const и его модификация

Об авторе

Официальный аккаунт в WeChat:

wechat

1.Смысл const

Константный тип — это тип, описанный с помощью модификатора типа const; значение переменных или объектов константного типа не может быть изменено.

2.Роль const

  • Позволяет определять константы
const int a=100;
  • Проверка типов

    • Различие между const-константами и константами, определёнными макросом #define:

      const-константы имеют тип, и компилятор может выполнять проверку безопасности; у макроопределений #define нет типа данных — это лишь простая замена строк, поэтому проверка безопасности невозможна.Благодарим двух знатоков, указавших на проблему здесь, см.: issue

    • const-константы поддерживают все типы.

    • В остальных случаях это просто переменная с квалификатором const; не путайте её с константой.
  • Предотвращает изменение, играет защитную роль и повышает надёжность программы

void f(const int i){
    i++; // error!
}
  • Позволяет экономить память и избегать ненужных выделений памяти

    • С точки зрения ассемблера, определение константы через const даёт лишь соответствующий адрес в памяти, а не непосредственное значение, как в случае с #define.
    • Константа, определённая через const, имеет во время работы программы только одну копию, тогда как константа, определённая через #define, имеет в памяти несколько копий.

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
Когда указатель получает свойство const, указатель не может изменять адрес, на который он указывает
Когда свойство цели — char, содержимое может быть изменено через указатель, например: *char='y';
Когда свойство цели — const char, содержимое не может быть изменено через указатель

Конкретные примеры использования:

(1) Указатель на константу

const int *ptr;
*ptr = 10; // error

ptr — это указатель на const-объект типа int; const определяет тип int, то есть тип объекта, на который указывает ptr, а не сам ptr, поэтому ptr может не получать начального значения. Однако нельзя изменять через ptr значение указуемого объекта.

Кроме того, нельзя сохранять адрес const-объекта в указателе типа void*; необходимо использовать указатель типа const void*, чтобы сохранить адрес const-объекта.

const int p = 10;
const void * vp = &p;
void *vp = &p; // error

Ещё один важный момент: разрешается присваивать указателю на const-объект адрес неконстантного объекта.

Присваивание указателю на const-объект адреса неконстантного объекта:

const int *ptr;
int val = 3;
ptr = &val; // ok

Мы не можем изменять значение val через указатель ptr, даже если он указывает на неконстантный объект!

Мы не можем изменять базовый объект через указатель на const-объект, однако если такой указатель указывает на неконстантный объект, сам объект можно изменить другими способами. Значение, на которое указывает const-указатель, изменять можно — только не через указатель на const-объект! Например, так:

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

Резюме:
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=&num; // 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=&num; // 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=&num; // error! const int* -> int*
    cout<<*ptr<<endl;
}

Если в приведённом выше коде заменить на const int *ptr или на const int *const ptr, всё будет работать нормально!

(3) Константный указатель на константу

Поняв два предыдущих случая, следующий понять гораздо проще:

const int p = 3;
const int * const ptr = &p; 

ptr — это const-указатель, который указывает на const-объект типа int.

6.Использование const в функциях

const модифицирует возвращаемое значение функции

Смысл в основном тот же, что и при модификации const обычных переменных и указателей:

(1)const int

const int func1();

Само по себе это бессмысленно, поскольку возвращаемое значение и так присваивается другой переменной!

(2)const int*

const int* func2();

Содержимое, на которое указывает указатель, неизменно.

(3)int *const

int *const func2();

Сам указатель неизменяем.

const модифицирует параметры функции

(1)Переданный параметр и сам указатель неизменяемы внутри функции — бессмысленно!

void func(const int var); // 传递过来的参数不可变
void func(int *const var); // 指针本身不可变

Это означает, что параметр не может быть изменён в теле функции, но здесь в этом нет никакого смысла: var сам по себе является формальным параметром и внутри функции не меняется. То же относится и к случаю, когда передаваемый формальный параметр — указатель.

Если входной параметр передаётся «по значению», то функция автоматически создаёт временную переменную для копирования этого параметра, поэтому такой входной параметр изначально не нуждается в защите, и не следует добавлять модификатор const.

(2)Содержимое, на которое указывает параметр-указатель, является константой и не изменяется

void StringCopy(char *dst, const char *src);

Здесь src — входной параметр, dst — выходной. После добавления модификатора const к src, если какой-либо оператор в теле функции попытается изменить содержимое src, компилятор укажет на ошибку. Это одно из применений const.

(3)Параметр является ссылкой — для повышения эффективности и одновременно предотвращения изменения.

void func(const A &a)

Для параметров не внутренних типов данных функция, объявленная как 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 Apple b(3);
b.add(); // error
в этот момент можно убедиться, что const-объект может обращаться только к const-членским функциям.

Помимо инициализации const-констант через список инициализации, описанной выше, можно воспользоваться и следующими способами:

Первый: объединить определение константы со static, то есть:

static const int apple_number

Второй: инициализировать снаружи:

const int Apple::apple_number=10;

Разумеется, если вы компилируете с использованием C++11, можно инициализировать прямо при определении и написать просто:

static const int apple_number=10;
// 或者
const int apple_number=10;

Оба варианта поддерживаются в C++11!

При компиляции просто добавьте -std=c++11!

Здесь упоминается static; вкратце о нём:

В C++ статическую членскую переменную (static) нельзя инициализировать внутри класса. Внутри класса — только объявление; определение должно находиться вне тела определения класса, обычно инициализация выполняется в файле реализации класса.

Объявление в классе:

static int ap;

Использование в файле реализации класса:

int Apple::ap=666

В C++11 для этого случая нельзя объединить объявление и инициализацию, то есть использовать способ, описанный выше.

Похожие темы

Комментарии