C++中的const关键字
const关键字的使用
const关键字可以修饰C++的内置变量、指针、自定义对象、成员函数、返回值和函数参数,以告诉编译器某些值需要保持不变的。也就是说,const关键字是在编译过程中发挥作用的。
const修饰普通类型的变量
当一个变量被const修饰时,程序不能改变这个变量的值
const修饰指针变量
const修饰指针变量时,情况会变复杂,有三种情况
const修饰指针指向的内容
此时该指针指向的内容是不可修改的,更准确的说,是不能通过这个指针来修改它指向的内容1
2
3
4
5int a = 8;
const int * p = &a;
int const * p = &a;//两种写法等价
*p = 9; //错误的
a = 9;//正确的const修饰指针
即指针常量,指针不能修改1
2
3
4
5int a = 8;
int* const p = &a;
*p = 9; // 正确
int b = 7;
p = &b; // 错误const修饰指针和指针指向的内容
则既不能通过指针修改指向的内容,也不能修改指针本身为了更好地理解上述的三种情况为什么这样声明,推荐阅读C Right-Left Rule (Rick Ord’s CSE 1311
2int a = 8;
const int * const p = &a;
const修饰函数参数和返回值
const修饰函数参数时,参数在函数体内无法被修改。最常见的情况是用const修饰指针防止其被意外篡改,以及const+引用传递以免去自定义类型在值传递时构造临时对象的开销。
const修饰函数返回值时,有下面三种不同的情况
- 修饰内置类型的返回值
返回的是一个临时变量,const修饰没有实际意义,与不修饰时一样 - 修饰自定义类型的返回值
则返回值不能作为左值使用,既不能被赋值也不能被修改.同时也不能调用返回对象的非const成员函数 - 修饰指针或引用类型的返回值
- 函数返回指向常量的指针
1
2
3
4const int * func();
const int * p =func();
*p = 10; // 错误
p++; //正确 - 函数返回指针常量
1
2
3
4int* const func();
int* const p = func();
*p = 10; //正确
p++; //错误 - 函数返回指向常量的指针常量
1
2
3
4const int* const func();
const int* const p = func();
*p = 10; //错误
p++; //错误 const修饰引用返回值
返回一个只读的引用1
2
3const MyClass& func();
const MyClass& ref = func();
ref.setValue(10); //错误
- 函数返回指向常量的指针
const修饰类的成员函数
在成员函数的标识符后面修饰,保证在调用过程中不会修改对象中任何没有mutable修饰的成员变量。
1 | |
注意:const关键字不能与static关键字同时使用,因为static关键字修饰静态成员函数,静态成员函数不含有this指针,即不能实例化,const成员函数必须具体到某一实例。
volatile关键字
volatile修饰的变量表示其可以被某些编译器未知的因素修改。例如
- 操作系统
- 硬件
- 其他的线程
这样编译器就不会对volatile修饰的变量进行优化。例如
1 | |
在上面的代码中,无论是将i赋值给a还是b,都要从i的地址中读取值,而如果i没有被volatile修饰,那么编译器就会优化对b的赋值,直接将上次从i的地址中读取的值赋值给b,而不是重新读取i的地址中的值。
1 | |
上面的代码也是合法的,·且const和volatile关键字都能发挥作用
- 在
a的作用域内,无法对a进行修改,因为它是const修饰的 - 但是
a的值可能被其他线程或者别的外部事件改变,因为它是volatile修饰的
mutable关键字
mutable关键字用于修饰类的成员变量。
- 当成员变量被
mutable修饰时,表示该变量时可变的,即使包含它的对象被声明为const类型 mutable修饰成员变量不会影响其在类外部的可见状态
const与#define的区别
#define是预处理指令,在预处理阶段对宏定义进行分析和替换,而const是关键字,是由编译器在编译阶段处理的#define没有作用域的限制(但也得在#define后使用啊不然就违反因果律了),宏可以被定义在当前程序的任意位置或者被其包含的头文件中,只有遇到对应的#undef才失效。而const修饰的变量有作用域的限制。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17namespace definePI {
#define PI 3.1415926
const float pi = 3.1415926;
}
int main() {
printf("%f\n", PI); // 输出3.1415926
printf("%f\n", pi); // 编译错误
#undef PI
printf("%f\n", PI); // 编译错误
printf("%f\n", E); // 编译错误
}
namespace defineE {
#define E 2.7182818
}#define可以重定义(需要先#undef取消定义)但是const不行1
2
3
4
5
6
7
8
9
10
11
12
13
14
15#define X 30
const int Y=10;
int main()
{
cout<<"Value of X: "<<X<<endl; //输出30
#undef X
#define X 300
cout<<"Value of X: "<<X<<endl; //输出300
cout<<"Value of Y: "<<Y<<endl; //输出10
Y=100; //error, we can not assign value to const
cout<<"Value of Y: "<<Y<<endl;
return 0;
}
constexpr
constexpr在C++11中被引进,字面意思是const expression。constexpr修饰的变量是编译期常量,且必须用常量表达式初始化。
1 | |
literal type
constexpr只能用于修饰literal type的变量,一个变量是literal type当且仅当它属于下面的类型
- 标量类型 scalar type
包含- 算数类型
- 枚举类型
- 指针类型
- 指向类成员的指针类型
std::nullptr_t- 以上类型的
cv限定版本(即用const或volatile修饰的版本)
- 引用
literal type的数组- 具有以下所有性质的
cv-qualified类- 拥有平凡(
trivial)析构函数(即默认析构函数)(C++20前的版本)或constexpr析构函数(C++20) - 所有非静态且非
variant的成员(variant变量在编译时无法确定变量的具体类型)和基类都不是volatile修饰的literal type, 并且是下面几种类型之一lambda类型- 满足下列条件的聚合
union- 没有
variant成员 - 或者至少有一个非
volatile修饰的literal type的variant成员
- 没有
- 非
union的聚合类型,并且每个匿名联合体成员也都满足- 没有
variant成员 - 或者至少有一个非
volatile修饰的literal type的variant成员
- 没有
- 具有至少一个
constexpr构造函数的类型,且该构造函数不是拷贝或移动构造函数
- 拥有平凡(
与const的区别
const修饰的变量可以在运行时才初始化,而constexpr则一定会在编译期初始化。- 而
const表示的是read only的语义,只保证修饰的变量运行时不可以被直接更改,并未区分是编译期常量还是运行期常量。
constexpr指针
constexpr只对指针本身有效,而不会对指针指向的对象生效
1 | |
一个constexpr指针的初始值必须是nullptr或者0,或者是指向存储于某个固定地址中的对象。
constexpr函数
constexpr修饰的函数要求其返回类型以及所有的形参都是literal type,且函数体中必须有且仅有一条return语句(除非函数的返回值是void)。这样在它们被调用时,编译期会把它们直接展开替换为结果值。
1 | |
有意思的是,C++允许constexpr返回的值不是常量
1 | |
前面提到返回值类型为void的函数也可以用constexpr来修饰,乍一看这样的函数没有任何意义,但实际上constexpr修饰的函数除了返回值是编译期常量外,还在编译期运行,这样就能在编译期执行一些操作。
使用场景
数组大小、模板参数和switch语句都要求编译期常量,使用constexpr修饰的变量或函数可以用于这些场景。
1 | |
参考
- [RUNOOB] C++ const 关键字小结
- [RUNOOB] C/C++ 中 volatile 关键字详解
- [cppreference.com] C++ keyword: const
- [cppreference.com] cv (const and volatile) type qualifiers
- [cppreference.com] C++ keyword: volatile
- [cppreference.com] C++ named requirements: LiteralType (since C++11)
- [cnblog] C++11新特性:constexpr变量和constexpr函数