29、CV关键字
本文介绍下C++中的这几个关键字:
const
constexpr
volatile
mutable
const
const
字面意思为只读,可用于定义变量,表示变量是只读的,不可以更改,如果更改,编译期间就会报错。
主要用法如下:
- 用于定义常量,
const
的修饰的变量不可更改。
const int value = 5;
- 指针也可以使用
const
,这里有个小技巧,从右向左读,即可知道const
究竟修饰的是指针还是指针所指向的内容。
char *const ptr; // 指针本身是常量
const char* ptr; // 指针指向的变量为常量
- 在函数参数中使用
const
,一般会传递类对象时会传递一个const
的引用或者指针,这样可以避免对象的拷贝,也可以防止对象被修改。
class A{};
void func(const A& a);
- 修饰类的成员变量,表示是成员常量,不能被修改,可以在初始化列表中被赋值。
class A {
const int value = 5;
};
class B {
const int value;
B(int v) : value(v) {}
};
- 修饰类成员函数,表示在该函数内不可以修改该类的成员变量。
class A {
void func() const;
};
- 修饰类对象,const类对象只能调用该对象的const成员函数。
class A {
void func() const;
};
const A a;
a.func();
你可能会说,可以通过指针或者引用等方式改动const
的值啊,嗯,确实可以改动而且不报错,但编译器不保证你会得到想要的结果,它会是个未定义行为,一般会出现****常量折叠。
constexpr
constexpr
是c++11
新引入的关键字,用于编译时的常量和常量函数,这里直接介绍constexpr
和const
的区别:
两者都代表可读,const
只表示read only
的语义,只保证了运行时不可以被修改,但它修饰的仍然有可能是个动态变量,而constexpr
修饰的才是真正的常量,它会在编译期间就会被计算出来,整个运行过程中都不可以被改变。
constexpr
可以用于修饰函数,这个函数的返回值会尽可能在编译期间被计算出来当作一个常量,但是如果编译期间此函数不能被计算出来,那它就会当作一个普通函数被处理。如下代码:
constexpr int func(int i) { return i + 1; }
int main() {
int i = 2;
func(i); // 普通函数
func(2); // 编译期间就会被计算出来
}
所以平时编程过程中,函数尽量都用constexpr
修饰下,如果你有看过gcc
源码,你会看得到,绝大多数函数都用了constexpr
修饰。
volatile
详见:volatile关键字
volatile
需要注意的一点是:它与多线程没有关系,它的作用只有内存可见性,可以防止编译器对volatile修饰的变量做优化,举例:
- 不对变量加
volatile
,编译器会对变量做一些优化:
- 而加了
volatile
修饰,生成的汇编是这样:
C++的volatile
一般只会用在与硬件通信,平时我们编程几乎用不到。
如果想要使用原子操作,就要乖乖的使用atomic
,不要以为volatile
能解决问题。
参考链接:https://en.cppreference.com/w/cpp/language/cv
mutable
mutable
表示可更改,一般用于让类中的const
函数可以更改类内值。
比如:
struct A {
const char* GetName() const {
++count;
std::cout << count << std::endl;
return "Hello Meow";
}
int count;
};
在一个被const
修饰的成员函数中,如果我改动了里面的成员变量的值,编译器会报错。
那怎么办,可以将这个要改变的成员变量用mutable
修饰,这样编译器就会跳过对它的const
限制。
这个mutable
就是为了突破const
的限制而设置的。
struct A {
const char* GetName() const {
++count;
std::cout << count << std::endl;
return "Hello Meow";
}
mutable int count;
};
这里可能有些朋友在想,那我直接把const
去掉不就行了,为啥还非得再加个mutable
。
这就涉及到函数设计的理念了,比如上面的GetName
的含义,设计之初就是想它做一个Get操作,不想对整个对象有任何改动的行为,然后后面加了个count
的字段,想调试下这个GetName
被调用了多少次,但是这个count
其实可以理解为是独立于整个类对象的设计,所以这里加个mutable
也很合理。
还有一点很关键的是,STL很多函数都是const
的,const
函数内部只能调用对象的const
函数,我们想要塞进去一个自定义函数,也同样需要是const
,这时如果我们自定义的函数有改动成员的需求,就需要mutable
,所以要兼容STL
,就不得不用mutable
关键字。
