c++常量
const
修饰变量
const关键字可以用来防止对象发生变化。一个const对象必须有以下特征:
- 必须已经初始化
- 不能被修改
- 是线程安全的
- 只能调用const成员函数
值
1 | const int ci = 1; |
指针
常量指针
指向常量的指针,简称常量指针。
1 | const int ci = 1; |
指针常量
指针本身是一个常量。
1 | int i = 1; |
引用
常量左值引用
1 | int i = 1; |
常量右值引用
1 | int i = 1; |
修饰函数
const修饰的成员函数不能修改当前对象的属性
1 | class A { |
物理常量和逻辑常量
- 物理常量,也叫比特位常量,当前对象的每一个比特都不能被修改,这也是c++对于const的实现。
- 逻辑常量,有些时候,一个const函数依旧希望能修改某些比特位,但是其从逻辑上讲依旧是const的。
mutable
mutable用来打破物理常量的限制,实现逻辑常量。
1 | class ThreadSafeCounter { |
const_cast
const_cast可以添加或删除const/volatile修饰符到一个变量上。
1 | void func(int* ){ } |
1 |
|
通过const_cast删除常量修饰从而修改常量的行为是未定义的!!
constexpr
修饰变量
constexpr是隐式const
1
2
3
4
5
6int main() {
const int a = 1;
constexpr int b = 2;
a = 3; // error
b = 3; // error
}constexpr变量只能接收常量表达式(在编译时可以确定值),与const不同
1
2
3
4
5
6
7
8
9int main() {
int c = 1;
const int a = c;
constexpr int b = c; // error
constexpr int b2 = a; // error
const int a2 = 2;
constexpr int b3 = a2;
}
修饰函数
- constexpr修饰的函数,可以是编译时,也可能是运行时
- constexpr函数中不能使用static、thread_local
- 所有依赖必须都是编译时,函数才会是编译时(允许常量表达式初始化的变量)
- 被constexpr接收时,函数必须是编译时
- 如果是编译时运行,是纯函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16constexpr int add(int a, int b) {
// static int c = 10; // error
return a + b;
}
int main() {
// 不同的编译器实现不同,可能是编译时也可能是运行时
int i = add(1, 3);
constexpr int b = add(1, 2);
int v = 3;
int c = add(v, 3);
//constexpr int a = add(v, 3); // error
constexpr int e = add(b, 2);
return 0;
}
类函数
- 至少有一个constexpr修饰的构造函数
- 类中可以定义constexpr函数和非constexpr函数
- constexpr修饰的类对象只能调用constexpr修饰的成员函数
1
2
3
4
5class MyDouble {
double myVal;
constexpr MyDouble(double v) : myVal(v) {}
constexpr double getVal() {return myVal;}
};
C++20
从C++20开始,支持在constexpr函数中使用stl(编译时分配的内存必须在编译时释放)
1 |
|
consteval (c++20)
consteval只能修饰函数,被consteval修饰的函数必须是编译时运行的,调用consteval函数返回的一定是编译时常量。
- 不能应用在析构函数上
- 其它和constexpr一样
1 | consteval int add(int a, int b) { |
1 |
|
constinit (c++20)
constinit与const没有关系,constinit修饰的变量并不是常量,是可以被修改的。constinit保证变量一定是静态初始化的。
static
静态变量分为全局静态变量、局部静态变量、类中静态成员变量。
按照初始化的类型分为静态初始化(static initialization)和动态初始化(dynamic initialization)。
静态初始化
指的是用常量来对静态变量进行初始化,包括zero initialization和const initialization;对于静态初始化的变量,是在程序编译时完成的初始化。
动态初始化
指的是需要调用函数才能完成的初始化,或者是复杂类型的初始化等,对于这种全局静态变量、类的静态成员变量,是在main()函数执行前,加载时调用相应的代码进行初始化的。而对于局部静态变量,是在函数执行至此初始化语句时才开始执行的初始化。
1 | consteval int sqr(int n) { |
Static Initialization Order Fiasco
static变量如果是动态初始化,那么编译单元之间的static变量初始化的顺序是不确定的。
1 | int square(int n) { |
1 |
|
使用constinit来解决static初始化顺序的问题
1 | constexpr int quad(int n) { |
1 |
|
std::is_constant_evaluated (c++20)
判断函数是编译期执行还是运行时执行