文章目录
c++ const 和 constexpr 知识点总结
一、const
1. const修饰变量
修饰普通变量(常量)
const int a = 0; // 必须赋初始值,然后不能被重新赋值
- 默认情况下,const对象被设定为仅在文件内有效。如果需要多文件共享const对象,需要在const对象的声明和定义处加上extern修饰。
file1.cpp
extern const int aext = 100;
file2.cpp
extern const int aext; // 使用的是file1.cpp中的aext变量
修饰指针类型
分为顶层const和底层const
- 底层const(指向常量的指针)
const int *p; // 修饰的是指针指向的对象,表示指向的是常量对象。可以使用非常量变量取地址初始化,只是限制不能通过p来改变这个变量的值
- 顶层const:(常量指针)
int * const p = nullptr; // 修饰的是指针本身,表示常量指针。 常量对象必须被初始化
- 顶层+底层:(指向常量的常量指针)
const int * const p = nullptr; // 必须被初始化,可以使用非常量对象取地址初始化
- 顶层和底层的一般概念:
- 顶层可以理解为对象本身是常量; 底层则与指针和引用等符合类型的基本类型部分有关,引用都是底层const。
- 顶层const对于拷贝操作来说可以忽略const属性,这与后面的函数形参类型原理一致。
const int a = 32; // a为顶层 const int &r = a; // r为底层,用于声明引用的const都是底层 const int * const p = &a; // 指针既可以为顶层,也可以为底层 int b = a; // 正确,顶层属性忽略
修饰引用类型
- 常引用变量能接收非常量左值、常量左值、右值。
- 常量引用可以接收表达式,只要表达式的值能转成常量类型。(但是不能接收返回局部变量和数值常量如0的函数表达式)
int a = 0;
const int b = 0;
int &b = a;
const int &aa = a; // 非常量左值,相当于const int *p能指向一个非常量变量。
const int &bb = b; // 常量左值(普通引用不能引用常量值)
const int &cc = 0; // 右值(普通引用不行)
const int &dd = a + b; // 接收表达式(普通引用不行),可以理解为常引用的是一个a+b返回的临时变量。
2. const修饰函数
const修饰函数参数
- 当作为函数形参的时候,顶层const属性可以被忽略。
- 非const可以传递给顶层const形参
- 顶层const形参和非const形参会造成重定义
int a = 23;
void print(const int aa) { };
print(a); // 非const变量可以传递给const属性形参
void print(int aa) { }; // 错误,和const版本重定义
void print( int * const p);
void print(int *p); // 错误,与顶层const版本重定义
void print(const int *p); // 单独的底层const版本, 指向的是常量对象,不能通过指针改指向变量的值。可以接收非常量参数
int a = 9;
- 底层const属性不会被忽略
void print(int *p);
void print(const int *p); // 底层const版本,不会重定义
void print(const int a);
void print(const int &a); // 引用是底层的,不会重定义
const修饰函数返回值
- 函数返回普通const变量(值传递),值返回对于const与非const修饰意义不大
- 函数返回const 指针
- 函数返回const引用
const int print(); // 值传递变量到外部,相当于拷贝一份内容到外界接收地址
const int a = print();
int a = print(); // 非常量也可以接收返回值,所以返回const意义不大
const int *printConst(); // 返回的常量对象
const int *a = printConst(); // 只能由const指针接收
const int &r = printR(); // 由const int &接收返回值。前提不能返回局部变量和数值常量如0。
const修饰成员函数(this指针)
- const修饰成员函数时,const关键字放在函数后面。此时类成员函数时实质上修饰的是成员函数的this指针。
- const修饰成员函数时,不能改变对象的成员并且不能调用非const成员函数,因为非const成员函数可以修改数据成员。
- 非const成员函数可以调用const成员函数,const成员函数可以访问非const变量但不能改变他们的值
- static函数不能被const修饰
class A {
public:
void print() const; // const成员不能访问非const成员函数,能只读访问非const变量
void print1(); // 非const成员能访问const成员函数和成员变量。
int a;
const int a;
}
3. const修饰对象
- const对象或者const对象指针只能访问const成员函数,只读访问成员变量。
Class ClassA {
public:
void printC() const;
void print();
const int ac = 9;
int a;
}
const ClassA cls;
cls.printC();
cls.print(); // 错误 不能访问非const函数
::std::cout << cls.ac << cls.a << ::std::endl; // 可以只读访问非const的变量
cls.a++; // 不能改变非const成员变量
4. 常量数组,常量指针数组,常量引用数组
- 常量数组可以使用大括号 { }初始化。
const int arr[3] = { 1, 2, 3};
- 常量指针数组:同样必须初始化,可以使用大括号{ }来初始化
int ia = 3;
int ib = 4;
int ic = 3;
const int *arr[3] = { &ia, &ib, &ic };
-
常量引用数组: 没有这种语法,引用本身是个别名,不占内存(实质上和占指针大小内存),编译器不知道给引用数组分配多少内存。
二、constexpr
1. constexpr特性和const之间的差异
- 常量表达式:指的是在编译阶段就能得到计算结果的表达式
- constexpr 变量,在编译阶段就能确定结果值的变量
c++11新规定,constexpr声明的变量表示使用编译器来验证变量的值。如果编译阶段不能确定常量结果值,编译会报错。
constexpr修饰的指针只能初始化为nullptr或者0或者某个固定地址中的对象,因为必须在编译阶段就确定常量属性,函数内的变量通常地址不固定。(有固定地址:全局变量,static变量) - const修饰的变量可以在编译阶段确定值,也可以在运行阶段确定值。constexpr相对于const实质上是把常量值的确定时间提前到编译阶段。
constexpr int a = 33; // 常量表达式,全局变量
int b = 33; // 全局变量
const int *p; // 表示p是指向常量的指针
constexpr int *p = nullptr; // 表示常量指针,转移成了顶层指针,且在编译阶段要确定实际值。
constexpr const int *pp = &a; // constexpr修饰pp是常量指针,const表示指向整型常量,a必须在编译时有地址(static,全局)。
constexpr int *p = &b;
- constexpr函数
constexpr函数如果返回常量表达式,则在编译阶段,调用这个函数的地方会被直接替换成结果值;如果是运行时才确定,则返回的不是常量表达式;另外常量表达式在代码中和内联函数一致,在调用点展开,因此定义位置和内联函数一样,放在头文件中。
constexpr int getCount(int count) { return count * 3;};
int ia = 3;
::std::array<int, getCount(3)> arr; // 返回常量表达式
::std::array<int, getCount(ia)> arr; // 编译错误,返回的不是常量表达式
2. constexpr 在c++11中的规范
- c++11 规定constexpr函数不得包含多余一个可执行语句,也就是说constexpr函数的函数体只能有一条语句,可以用条件运算符?: 或者递归来编写逻辑。
cosntexpr int print(int a) { return a > 4 ? a : 4; };
- constexpr函数仅限于传入和返回字面类型。也就是在编译阶段能确定值的值。所有内建类型除了void都符合。
constexpr int print(double x = 3.3, int a = 3);
3. constexpr 在c++14 17 中的规范
- c++14 规定constexpr函数内可以有多条语句
constexpr int printTt(int a)
{
if (a > 4) {
return a;
} else {
return 4;
}
}
- constexpr传入和返回值可以包含void
constexpr void print(void);
constexpr在c++98,c++11,c++14的实现
备注:
- const static 变量的声明式和定义式; 当要对const static变量取地址时,需要给出定义式。
Class ClassA {
public:
const static int a = 3; // 声明式
}
xxx.cpp
const int ClassA::a; // 定义式,声明式已经赋了初始值,定义式不用在赋值。
- 类中const成员函数如果想要改变类中的non-static成员变量,可以用mutable修饰变量
Class classA {
public:
void changeInnerValue() const;
mutable int aa;
}
void ClassA::changeInnerValue() const
{
aa = 22; // 如果aa不用mutable修饰,const函数是不能改变其值的
}