顶层const、底层const、函数重载与const形参

1.  顶层const(top-level const)与底层const(low-level const)



为了更好的研究指针和引用,我们引入了底层const这个名词。注意,底层const是针对C++里面的指针和引用而引入的。


我们知道,指针本身是不是常量与该指针所指向的是不是常量,这是两个相互独立的问题。为了区分这两种情况,我们引入两个专有名词:顶层const(top-level const)与底层const(low-level const)。用 顶层const(top-level const)表示指针本身是一个常量,而用 底层const(low-level const)表示指针所指的对象是一个常量。由指针引入,更一般的,顶层const可以表示任意的对象(任何数据类型)是常量。而底层const则与指针和引用等复合类型的基本类型部分有关。


为了检验一下有没有正确理解什么是顶层const(top-level const)与底层const(low-level const),请看几个小例子:
int i = 0;
int *const p1 = &i;			//不能改变p1的值,因为p1是一个顶层const
const int ci = 1;			//不能改变ci的值,因为ci是一个顶层const
const int *p2 = &ci;		<span style="white-space:pre">	</span>//允许改变p2的值,因为p2是一个底层const
const int * const p3 = p2;	<span style="white-space:pre">	</span>//靠左的是底层const,靠右的const是顶层const
const int &r = ci;			//用于声明引用的const都是底层const,因为引用本身就是一个顶层const,只能在初始化的时候进行赋值

对于拷贝操作,顶层const没有什么要求,而底层const要求左值必须比右值更严格。
  • 拷贝操作时,对于顶层const,可以把一个顶层const值赋给一个非顶层const变量,也可以把一个非顶层const值初始化给一个顶层const值。如上面例子中:
int *const p1 = &i;		//把一个非顶层const值初始化给一个顶层const值
i = ci;				//变量ci是顶层const,但变量i不是顶层const
p2 = p3;			//变量p3是顶层const,但变量p2不是顶层const
  • 拷贝操作时,对于底层const,左值必须和右值相同或更严格。也就是说,对于指针和引用,如果右值是const,则左值必须是底层const。还是上面的例子:
int *p = p3;		//error:p3包含底层const,而p没有
p2 = p3;		//正确:p2与p3都是底层const
p2 = &i;		//正确:int*能转换成const int*
int &r = ci;		//error:r只具有顶层const而不具有底层const,而ci具有底层const
const int &r2 = i;	//正确:可以把普通变量赋给const int&


需要注意的一点:对于常量对象取地址,得到的也是一种底层const

const int ci = 0;
const int* iptr = &ci;		//正确:iptr与右值具有同样的底层const
int* iptr2 = &ci;			//error:对于常量对象取地址,得到的也是一种底层const


2. const形参与函数重载


对于函数调用,和其他初始化过程一样,当用实参初始化形参时,会忽略掉形参的顶层const。这句话的意思也就是说,如果想通过顶层const进行函数重载,是不会通过编译的。函数重载机制会自动假装忽略掉顶层const而保留底层const。
也就是说:

void func(const int i);
void func(int i);

编译器认为这是同一个函数,而不是函数重载,进而报错|error: redefinition of 'void func(int)'|。
这个例子告诉我们,一个拥有顶层const的形参无法和另一个没有顶层const的形参区分开来。
但是,我们可以通过底层const来进行函数重载。

Record lookup(Account&);		//函数作用于Account引用
Record lookup(const Account&);		//新函数,函数作用于常量Account引用
Record lookup(Account*);		//新函数,函数作用于指向Account的指针
Record lookup(const Account*);		//新函数,函数作用于指向Account常量的指针

当然,当我们传递一个非常量对象或者指向非常量对象的指针时,编译器会优先选用非常量版本的函数。

3.  写在最后的题外话(面试常问的问题):

对于函数形参中的引用,尽量使用常量引用。首先,如果使用普通引用,会带给调用者一种误导,即函数可以修改它的实参的值。此外,使用普通引用而非常量引用,也会极大地限制函数所能接受的实参类型。因为我们不能把const对象、字面值或者需要类型转换的对象传递给普通的引用形参。而const引用就可以。
  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值