本博客作为我学习的笔记播客,仅供参考,如果有误,欢迎纠正。
在学习c++的过程中,开始接触const关键字时,感觉很简单,就是表示常量,不能修改,对于普通变量来说,确实如此。但是当const与引用和指针结合之后,情况就变得很复杂了,本篇想从根本上来解释这个问题。所有内容都是参考《c++primer》。本篇播客是按照知识点来进行讲解。
1.const对象必须初始化:
初始值可以是任意复杂的表达式和字面值。但是类型必须支持转换。
const int i = get_size();//正确,使用函数返回值初始化
const int j = 42;
const int k;//错误,没有初始化。
2.利用一个对象去初始化另一个对象,双方是不是const都可以,即对象双方是不是const的,无关紧要。《c++primer》p53
注意:这里指的是初始化。这里意思是不区分const。这里的只包含普通的变量情况,引用和指针对const还是有要求,即常量可以引用/指向非常量和常量(非常量可以隐式转为常量),非常量不能引用/指向常量。
int i = 42;
const int j = i;//正确,使用一个非常量初始化一个常量。
int z = j;//正确,使用一个常量初始化一个非常量。
const int* a = &i;
const int* b = &j;
int* c = &i;
int* d = &j;//错误,非常量不能指向常量。
3.const对象在文件中的作用范围:
const 对象和static对象一样,被设定为仅在当前文件有效,即当多个文件出现了同名的const变量时,其等同于在不同文件中分别定义了独立的变量。
措施:对于const对象不管是声明还是定义都添加extern关键字,这样就可以只定义一次。其他文件也能使用了
//在1.cpp中定义一个const变量 extern const int i = 42; //2.cpp extern const int i;//这里的i与上面的i是同一个。
4.常量引用仅对引用可参与的操作做出限定,对于引用的对象本身是一个常量还是一个非常量未作限定。《c++primer》p56
注意:这里是指的是常量引用。
int i = 42; const int &r1 = i;//正确,使用非常量进行初始化。 const int &r2 = 42;//正确,使用字面值初始化。 const int &r3 = r1;//正确,使用一个常量进行初始化。 int &r4 = r1;//错误,r4不是常量,r1是const,即不是对常量对象进行初始化,就需要考虑const属性,不能任意初始化。 int &r5 = i;//正确,非常量对象,使用非常量进行初始化。 r1 = 40;//错误,r1是常量引用,不能修改。 r5 = 30;//正确,r5是非常量引用,可以修改。
非常量对象不能引用常量对象,即不能用常量对象来初始化非常量对象的引用。
5.指向常量的指针(指针常量)与常量引用一样,并没有规定其所指向的对象必须是一个常量,只是规定不能通过该指针改变对象的值而已。(指向可以改,不能改指向的值)
int p = 42; const int pi = 3; const int* p1 = &p;//正确,p1指向一个常量 const int* p2 = π//正确,p2指向非常量 *P1 = 4;//错误,不能修改 int dval = 3.14; p2 = &dval;//正确,指向一个非常量 int* p3 = pi;//错误,非常量不能指向一个常量。
这里指针和引用是一样的,非常量的不能指向/引用常量的,常量的可以指向/引用非常量和常量。
6.常量指针:指针是对象和引用不是,既然是对象,那就可以给指针增加const属性,即常量指针。一旦初始化之后,常量指针的值(存放在指针中的那个地址地址)就不能更改。(不能改指向,但可以改指向的值)
int i = 3; int* const p = &i;//p将一直指向i; int a = 4; p = &a;//错误,不能在指向a i = 1;//但指向的变量可以更改。 const int j = 0; int* const p1 = &j;//错误,错误显式const int*不能初始化int* const。 前面我们说说了常量对象初始化的时候,是不区分const的。但是这里的常量指针表示 的就是一个常量指针,即顶层指针不能使用底层指针初始化。 下面是我的理解: p1表示一个常量指针,即不能改变指向,可以改变值,j是一个常量,我们不能更改, 这不是冲突了嘛。 而且不想改变指向的值,我们是使用指针常量来实现的。 const int* const p2 = &i; const int* const p2 = &j;
常量指针和指针常量的区分,很不容易区分清楚,这里需要好好理解,什么时候可以更改指向,什么时候可以改指向对象的值。
7.顶层const和底层const
顶层const和底层const就是常量指针和指针常量,顶层const(常量指针)表示的是指针本身就是一个常量对象,而底层const(指针常量)表示的是指针指向的是一个常量。这两句话请好好理解。其实从形式上很好区分这两个,从变量的右往左读。const在*右边是顶层const,反之底层const。
int i = 0; int* const p1 = &i;//顶层指针,也是常量指针,不能改变p1,但可以改变i const int b = 1; const int* p2 = &b;//底层指针,也是指针常量,可以改变p2 const int* const p3 = p2;//第一个const表示底层const,第二个const表示顶层const,p3和p2都不能改变。
注意因为引用并不是对象,因此引用并没有顶层和底层的说法。
《c++primer》p58有一句话,当执行拷贝操作时,顶层const和底层const区别明显,其中顶层const不受什么影响(原文)。
一般来说,非常量可以隐式转换为常量。
int i = 0; const int j = 1; int& a = i; int& b = j;// 错误,非常量引用常量 int* c = &i; int* d = &j;// 错误,非常量指向常量 const int& e = i; const int& f = j; const int* g = &i; const int* h = &j; int* const k = &i; int* const l = &j;//错误,const int*不能初始化 int* const //这段代码建议自己复制到自己的电脑上去,看看是否报错。 //前面两个报错是因为非常量应用常量。 //后面一个错误情况,用下面的代码分析 const int a = 10; int b = a; const int* const p = new int(10); int* p1 = p;//错误,非常量指向常量 int* const p2 = p;//错误p2 const int* const p2 = p;//正确p2 const int* p3 = p; //这里p1报错,是因为非常量指向常量, const int* r = &b;//正确,常量可以应用非常量,因为非常量可以转换为常量,反之,不可以。 因为顶层const在拷贝时不受影响,即可以从形式上忽略,即变为int* p2 = p;就是第一个错误非常量指向常量。 这种情况给他加上const就行了,p3相对于错误的p2就是加上const,正确。我们补上p2省略的const, 就是正确的p2的情况。p3就是没有顶层const的情况,这种就不需要考虑const了。
以上就是我对顶层const和底层const的理解。不知道讲解清楚没有,如果有错,欢迎指出。