先简单描述一下:
限定词const告诉编译器"这是不会改变的"(这就允许编译器执行额外的优化);而限定词volatile则告诉编译器"不知道何时会改变",防止编译器依据变量的稳定性做任何优化。当读在代码控制之外的某个值时,例如读一块通信硬件中的寄存器,将使用这个关键字。无论何时需要volatile变量的值,都能读到,即使在该行之前刚刚读过。
"在代码的控制之外"的某个存储空间的一个特殊例子是在多线程程序中。如果正在观察被另一个线程或进程修改的特殊标识符,这个标识符应该是volatile的,所以编译器不会认为他能够对标识符的多次读入进行优化。
注意当编译器不进行优化时,volatile可能不起作用,但是当开始优化代码时(当编译器开始寻找冗余的输入时),可以防止出现重大的错误。
看完这些是不是更疑惑了(反正我是看的一脸懵逼),不要着急我们慢慢来。常量概念是为了使程序员能够在变和不变之间画一条界线。这在C++程序设计项目中提供了安全性和可控性。下面我们一起详细的来学习 什么时候、为什么和怎么样使用关键字const,并讨论关键字volatile,它是const的近亲(因为他们都关系到变化)并具有完全相同的语法。
常量
const的最初动机是取代预处理器#defines来进行值替换。从这以后它曾被用于指针、函数变量、返回类型、类对象以及成员函数。所有这些用法都稍有区别,但他们在概念上是一致的。
1.值替代
当用C语言进行程序设计,预处理器可以不受限制的建立宏并用它来代替值。因为预处理器只做些文本替代,他既没有类型检查概念,也欸有类型检查功能,所以预处理器的值替代会产生一些微小的问题,这些问题在C++中可以通过使用const值而避免,具体方法是把值替代移交给编译器。
例如: const int bufsize=100;
这样就可以在编译时 编译器需要知道这个值的任何地方使用bufsize,同时编译器还可以执行 常量折叠(constant folding),也就是说,编译器在编译时可以通过必要的计算把一个复杂的常量表达式通过缩减简单化。这一点在数组定义里显得尤其重要:
(char buf[bufsize];)
可以为所有的内建数据类型(char,int,float和double)以及由它们所定义的变量(也就是类的对象)使用限定符const.因为预处理器会引入错误,所以我们应该完全用const取代#define的值替代。
1.1头文件里的const
必须把const定义放进头文件里。这样,通过包含头文件,可把const定义单独放在一个地方并把它分配给一个编译单元。C++中的const默认为 内部连接(internal linkage),也就是说,const仅在const被定义过的文件里才是可见的,而在连接时不能被其他编译单元看到。当定义一个const时,必须赋一个值给它,除非用extern做出了清楚的说明:
extern const int bufsize;
通常C++编译器并不为const创建存储空间,相反他把这个定义保存在它的符号表里。但是,上面的extern强制进行了存储空间分配(另外还有一些情况,如取一个const的地址,也要进行存储空间分配),由于extern意味着使用外部连接,因此必须分配存储空间,这也就是说有几个不同的编译单元应当能够引用它,所以它需要有存储空间。
通常情况下,当extern不是定义的一部分时,不会分配存储空间。如果使用const,那么编译时会进行常量折叠。 当然,想绝对不为任何const分配存储是不可能的,尤其对于复杂的结构。在这种情况下,编译器建立存储,这会组织常量折叠(因为没有办法让编译器确切的知道内存的值是什么------要是知道的话,他也不必分配内存了)。
由于编译器不能完全避免为const分配内存,所以const的定义必须默认内部连接,即连接仅在特定的编译单元内;否则,由于众多的const在多个cpp文件内分配存储,容易引起连接错误,连接程序在多个对象文件里看到同样的定义就会”抱怨“。然而,因为const默认内部连接,所以连接程序不会跨过编译单元连接那些定义,因此不会有冲突。在大部分场合使用内建数据类型的情况,包括常量表达式,编译都能执行常量折叠。
1.2const的安全性
const的作用不仅限于在常数表达式里代替#define。如果用运行期间产生的值初始化一个变量而且知道在变量生命期内是不变的,则用const限定该变量是程序设计中的一个很好的做法。如果偶然试图改变它,编译器会给出出错信息。
//Using const for safety
#include <iostream>
using namespace std;
const int i=100;
const int j=i+10;
long address=(long)&j;
char buf[j+10];
int main(){
cout<<"type a character & CR:";
const char c=cin.get(); //不能改变
const char c2= c + 'a';
cout<<c2;
}
对于i,j都是变异期间的const。由于需要用j的地址,所以迫使编译器给j分配存储空间。即使分配了存储空间,把j值保存在程序的某个地方,由于编译器知道j是const,而且知道j值是有效的,因此,这仍不能妨碍在决定数组buf的大小时使用j。
在主函数中,对于标识符c有另一种const,因为其值在编译期间是不知道的。这就意味着需要存储空间,而编译器不想保留它的符号表里的任何东西(和C语言的行为一样)。初始化必须在定义点进行,而且一旦初始化,其值就不能改变。我们看到c2由c的值计算出来,也会看到这类常量的作用域与其他任何类型const的作用域是一样的--------这是对#define用法的另一种改进。
就实际来说,如果想让一个值不变,就应该使之成为const。这不仅为防止意外的更改提供安全措施,也消除了读存储器和读内存操作,使编译器产生的代码更有效。(没彻底理解,期待有大佬在评论区解答)
1.3const与聚合
const可以用于聚合,你要相信编译器不会真的把一个聚合保存到他的符号表中,所以必须分配内存。在这种情况下,const意味着”不能改变的一块存储空间“。然而,不能在编译期间使用它的值,因为编译器在编译期间不需要知道存储的内容。这样就不难明白下面的代码是非法的。
//const 与 聚合
const int i[] ={1,2,3,4};
// float f[i[3]]; 这是非法的
struct S{ int i,j;};
const S s[]={{1,2},{3,4}};
// double d[s[1].j]; 也是非法的
int main(){}
在一个数组定义里,编译器必须能产生这样的代码,它们移动栈指针来存储数组。在上面这两种非法定义里,编译器给出”提示“是因为他不能在数组定义里找到一个常数表达式。
2.const与指针
还可以使指针成为const。当处理const指针时,编译器仍将努力避免存储分配并进行常量折叠,但在这种情况下,这些特征似乎很少有用。所以我也简单的根据自己的理解进行记录。
2.1指向const的指针
正如任何复杂定义一样,定义指针的技巧是在标识符的开始处读它并从里向外。
const int * u;
这样读:”u是一个指针,它指向一个const int.“ 这里不需要初始化,因为u可以指向任何标识符(也就是说,它不是一个const),但它所指的值是不能被改变的。 这是一个容易混淆的部分。有人可能认为:想要指针本身不变,即包含在指针u里的地址不变,可简单地像这样把const从int 的一边移向另一边:
int const *v;
有人肯定可能会读成“v是一个指向int 的const指针”。(这是错的,千万不要记!!!!!!)然而,实际上应该读成“v是一个指向恰好是const的int的普通指针”。即const又把自己与Int结合在一起,效果和前面定义一样。两个定义是一样的,这一点很容易混淆,所以为了使程序更有可读性,应该坚持用第一种形式。
2.2const指针
上面提到了指向const的指针如何去定义,那么想使指针本身成为一个const指针我们该如何去做呢??必须把const标明的部分放在*的右边,如:
int d=i;
int * const w = & d;
现在它读成“w是一个指针,这个指针是指向int 的const指针”。因为指针本身现在是const指针,编译器要求给它一个初始值,这个值在指针生命周期内不变。然而要改变它所指向的值是可以的,比如:
*w=2;
也可以使用下面两种合法形式中的任何一种把一个const指针指向一个const对象:
int d=1;
const int * const x=&d;
int const * const x2 = &d;
现在,指针和对象都不能改变。
2.3赋值和类型检查
C++关于类型检查是非常精细的,这一点也扩展到指针赋值。可以把一个非const对象的地址赋给一个const指针,因为也许有时并不想改变某些可以改变的东西。然后,不能把一个const对象的地址赋给一个非const指针,因为这样做可能通过被赋值的指针改变这个对象的值。当然,总能用类型转换强制进行这样的赋值,但是,这是一个不好的程序设计习惯,因为这样就打破了对象的const属性以及由const提供的安全性。例如:
int d=1;
const int e=2;
int *u= &d; //合法
// int * v=&e; 这是非法的
int *w=(int *)&e; //合法但是很危险!!
int main(){}
虽然C++有助于防止错误发生,但如果程序员自己打破了这种安全机制,它也是无能为力的。