29、CV关键字

厨子大约 4 分钟C++C++基础编程程序厨

本文介绍下C++中的这几个关键字:

  • const
  • constexpr
  • volatile
  • mutable

const

const字面意思为只读,可用于定义变量,表示变量是只读的,不可以更改,如果更改,编译期间就会报错。

主要用法如下:

  1. 用于定义常量,const的修饰的变量不可更改。
const int value = 5;
  1. 指针也可以使用const,这里有个小技巧,从右向左读,即可知道const究竟修饰的是指针还是指针所指向的内容。
char *const ptr; // 指针本身是常量
const char* ptr; // 指针指向的变量为常量
  1. 在函数参数中使用const,一般会传递类对象时会传递一个const的引用或者指针,这样可以避免对象的拷贝,也可以防止对象被修改。
class A{};
void func(const A& a);
  1. 修饰类的成员变量,表示是成员常量,不能被修改,可以在初始化列表中被赋值。
class A {
 const int value = 5;
};

class B {
 const int value;
 B(int v) : value(v) {}
};
  1. 修饰类成员函数,表示在该函数内不可以修改该类的成员变量。
class A {
 void func() const;
};
  1. 修饰类对象,const类对象只能调用该对象的const成员函数。
class A {
 void func() const;
};
const A a;
a.func();

你可能会说,可以通过指针或者引用等方式改动const的值啊,嗯,确实可以改动而且不报错,但编译器不保证你会得到想要的结果,它会是个未定义行为,一般会出现****常量折叠open in new window

constexpr

constexprc++11新引入的关键字,用于编译时的常量和常量函数,这里直接介绍constexprconst的区别:

两者都代表可读,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关键字open in new window

volatile需要注意的一点是:它与多线程没有关系,它的作用只有内存可见性,可以防止编译器对volatile修饰的变量做优化,举例:

  • 不对变量加volatile,编译器会对变量做一些优化:
img
img
  • 而加了volatile修饰,生成的汇编是这样:
img
img

C++的volatile一般只会用在与硬件通信,平时我们编程几乎用不到。

如果想要使用原子操作,就要乖乖的使用atomic,不要以为volatile能解决问题。

参考链接:https://en.cppreference.com/w/cpp/language/cvopen in new window

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关键字。