关于const,之前已经在多篇博客里面有介绍过了。我们知道const本身的含义很简单,就是被它修饰的对象不可以改变。但是,当他和指针或是引用一起使用的时候,我们往往比较难区分到底是指针(或引用)本身是const的,还是它所指向的对象是const的。最近读《C++ Primer》英文第五版的时候,发现了一个新的术语——Top-Level const和Low-Level const,他对于我们理解很多复杂场景下的const很有帮助。而且这个概念我在该书的中文第四版中并未见到过,不知道是原来的英文版就没有,还是中文版翻译的时候没有翻译过来。

PS:本文所说的对象指所有类型的对象,而不局限与复合类型的对象。

  • Top-Level const——对象本身是一个常量;
  • Low-Level const——指针指向的或引用引用的对象是const。

top-level const可以出现在任何对象类型中,比如基本类型、指针类型、类类型等。而对于low-level const,只出现在基于基本类型的复合类型中,比如指针和引用。所以,对于所有的基本类型里面的const都是top-level的。但这里需要注意的是指针,它的top-level const和low-level const是相互独立的。其实这个我已经在之前的好几篇博客中分析过了,就是所谓的常量指针(对应low-level const)和指针常量(对应top-level const)的区分。这里再简单复习一下:

常量指针——指向常量(const)的指针;指针常量——指针本身是常量。

const char* p1;    //常量指针,指向常量的指针;const is low-level
char const* p2;    //同上
char* const p3;     //指针常量,指针是常量;const is top-level

区分方法:const在*左边的为常量指针,const在*右边的为指针常量。现在我们也可以说,*号左边的const是low-level的,*号右边的const是top-level的。

思考:为什么引用和指针的作用比较相似,但是在top-level const和low-level const的概念上,它却不像指针,反而和其他类型一样,只有low-level const。是的,引用中的const永远是low-level的。为什么呢?——因为引用不是对象(references are not objects)。是的,引用不是对象,而top-level const是修饰对象的。

int i = 0;
int *const p1 = &i;         // 我们不能改变p1本身的值,即p1永远只能指向i,因为const是top-level的
const int ci = 42;          // 我们不能改变ci的值,因为const是top-level的。基本类型永远是top-level的
const int *p2 = &ci;        // 我们不能改变p2(指向的值),因为const是low-level的(但可以改变p2本身的值)
const int *const p3 = p2;   // *右边的const是top-level的,即p3本身的值不可变;*号左边的const是low-level的,即p3指向的对象是const的
const int &r = ci;          // 引用中的const永远是low-level的

const和指针的混合使用已经够复杂的了,那为什么还要引入这两个概念呢?而它们又有什么作用呢?继续看…

top-level还是low-level const在我们拷贝一个对象时会有些区别——top-level的const在对象拷贝中会被丢弃(The distinction between top-level and low-level matters when we copy an object. When we copy an object, top-level consts are ignored)。
i = ci;      // 我们把ci的值拷贝给了i,ci中的top-level const被丢弃了
p2 = p3;    // 因为p2和p3指向的类型是一致的,所以我们这样拷贝赋值没有问题,但是需注意的是在拷贝的过程中,p3中的top-level const被丢弃了

但是拷贝的过程中,top-level const永远不会被丢弃。因为在程序设计的很多场景中,都设计到对象的拷贝,所以这个概念还是比较重要的。举一个函数重载的例子,我们知道函数重载是依靠参数列表来区分不同函数的,而函数传参的过程就是一个对象拷贝的过程。因为top-level的const在拷贝中会被丢弃,所以我们是不能依靠top-level的const来重载函数,但是可以使用low-level的const来重载:

// 依靠top-level const不能重载函数
Record lookup(Phone);   
Record lookup(const Phone);     // 重复定义,与上面函数定义相同
Record lookup(Phone *);
Record lookup(Phone* const);    // 重复定义,与上面函数定义相同

// 以来low-level const可以重载函数
Record lookup(Account&);        
Record lookup(const Account&);  // 新函数,与上面的函数为不同的函数
Record lookup(Account*);        
Record lookup(const Account*);  // 新函数,与上面的函数为不同的函数