C++中的const限定符

目录

一、const对象

二、const对象的引用

三、const与指针

常量指针

指针常量

顶层const与底层const

四、const与类

五、const与宏

六、const可以实现函数重载


一、const对象

const可以用来修饰变量或函数(类内),当用const修饰变量的时候,被修饰的变量就称为const常量,同理,被const修饰的函数称为常函数。

        顾名思义,常量即不能被改变的量,即const常量一旦定义就不能再更改,因此const对象必须初始化,初始化之后就不能再更改。初始值可以是任意的表达式,如下:

 当用函数给const对象进行初始化时,会在运行时初始化,用字面值给const对象初始化时则会执行编译时初始化。也可以用一个普通的非const对象给const对象进行初始化。即可以用任意表达式给const对象进行初始化操作。

        需要注意的是,当以编译时初始化的方式对const对象进行初始化时,编译器在编译过程中会把所有用到该变量的值全部替换成对应的值,比如上边的变量j,会全部替换成42。在这种情况下,编译器必须知道变量的初始值。什么情况下编译器不知道变量的初始值呢?我们知道,C++是支持分离式编译的,即可以将程序分割成若干个文件,每个文件可被独立编译。由于分离式编译的机制,一个文件中可能用到另一个文件中的变量,为了标记出某个变量是在其它文件中定义的,C++中将声明与定义区分开,比如:

int i;

就代表是对一个新变量i的定义,而:

extern int i;

就是对变量i的声明,它告诉当前文件,变量i是在其它文件中定义的。

        我们看到,当以编译时初始化的方式定义一个const变量时,编译器要在编译期间把所有const变量出现的地方都替换成具体的值,但是如果采用分离式编译的话,如果变量是在别的文件中定义,在当前文件中声明,那么当前文件在编译时是不能知道具体的值的,因此也就没法完成替换。因此,const限定符限定对象仅在当前文件中有效。即不同文件中的同名const对象相互之间是独立的,即是不同的对象。

        当以编译时初始化的方式定义一个const变量时,该变量会被限定在当前文件中有效,即不允许将这种变量跨文件使用,但是当以运行时初始化的方式来定义一个const变量时,则有在文件中共享的需求。const变量的跨文件使用与普通变量有一定区别,普通变量跨文件使用只需要在非定义文件中用extern关键字声明,而const变量无论是在当前文件中定义时,还是在其它文件中使用时都需要添加extern关键字。如下所示:

即普通变量只需要定义时使用extern,const变量定义和声明时都需要使用extern。

从上边初始化方式的不同,导致了const对象行为的不同,即const对象是否能跨文件使用取决于其初始化方式。对于编译时初始化的const对象,在不同文件中是独立的,但是由于其在编译期间会被替换成具体的值,所以只要不同文件中初始化用的值相同,也跟跨文件使用没差别。

二、const对象的引用

可以把引用绑定在const对象上,我们称之为对常量的引用,当然这里指的都是左值引用。引用即别名,不能常量引用来改变原对象的值。且定义常量引用的时候,要加上const。

 从上边例子中可以看到,普通引用是不能绑定到const对象上的,但是const引用可以绑定到普通对象上。

        一般情况下,引用的类型必须与其所引用对象一致。但是有两个例外,其中一个便是常量引用,其引用对象可以是常量,也可以是非常量,甚至可以是任意可以转换成对应类型的表达式。

如上图中例子,一句话,常量引用可以绑定非常量对象,但是常量对象只能被常量引用绑定。用常量引用绑定一个表达式(或者说是与引用类型不一样的变量),实际上绑定的是一个临时量

#include <bits/stdc++.h>
using namespace std;


int main(){
    double dval = 3.14;
    const int &ri = dval;
    cout<<dval<<" "<<ri<<endl;
    dval = 6.28;
    cout<<dval<<" "<<ri<<endl;
    return 0;

}

 

如上图结果,让int类型的常量引用,绑定一个double类型的变量,可以看到dval的改变并不会影响ri绑定的值,原理如下:

所谓临时量,就是编译器需要一个空间来暂存表达式的求职结果时暂时创建的一个未命名的对象。从上边例子也可以看到,让常量引用绑定类型不一致的对象最终绑定的是一个临时量,这样做显然是没什么意义的,所以不要这样做。

三、const与指针

像引用一样,指针也可以指向常量,但是不同之处在于指针本身也是对象,因为指针本身也可以是常量。指向常量的指针被称为常量指针,也被称作带有底层const的指针,指针本身是个常量时则被称作指针常量,也被称作带有顶层const的指针。如果一个指针既是常量指针也是指针常量,则称该指针既有底层const也有顶层const。

常量指针

如下图,可以用一个常量指针来指向一个常量,也可以指向非常量。一般情况下,指针的类型必须与所指对象类型一致,但是有两个例外,这里就是第一种例外,常量指针可以指向非常量对象。但是非常量指针不能指向常量对象。这点跟常量引用是类似的。

 

特别地,虽然可以用常量指针指向非常量对象,但是不能通过该指针改变非常量对象的值,就像常量引用可以绑定非常量,但是也不能通过该引用改变非常量的值。

 

指针常量

指针本身是对象,因此可以直接把指针定义为常量,这就是指针常量,即带有底层const的指针。

 指针常量跟其它常量一样,必须初始化,且一旦初始化之后就不能再改变,即不能把一个指针常量改为指向其它对象。但是可以通过指针常量改变其所指向对象的值。

        像上边图中一样,pip既是常量指针,也是指针常量,即在定义的时候有两个const,一个顶层的一个底层的。

顶层const与底层const

如上边常量指针与指针常量,一个是指向的对象是const常量,一个是本身是const对象,这是两个独立的问题。上边我用带有顶/底层const的指针来描述也是为了方便。更一般地,任意的对象本身是const,都可以称为顶层const,一般来讲只有指针才有底层const的概念。常量引用也可以看作是一种底层const,但是引用比较特殊,很多具体的实现中引用本身就是用指针常量来实现的,那么常量引用就可以看成既有顶层const也有底层const的指针了。

        需要注意的是在拷贝操作时需要注意const的影响,顶层const对拷贝操作没什么影响,但是如果一个对象(指针对象)具有底层const,那么底层const对拷贝操作的影响是不能忽略的。

 其实归根结底一句话,具有底层const的变量,不能给没有底层const的变量赋值,反之则是可以的。因为如果一个指针没有底层const,我们可能就会用它来改变其所指向的值,但是底层const指向的对象可能是常量,所以底层不能赋值给非底层,否则可能导致改变常量,这当然是不允许的。

四、const与类

在类内函数的参数列表后加上const关键字,就定义了类的常量成员函数(方法)。常量成员函数只能读成员变量,而不能改变成员变量。这样做的目的是防止成员函数修改被调用对象的值。

 我们都知道,在定义一个类对象的时候,这个类对象就对应了一个隐式的this指针指向该对象,默认情况下,这个this指针只能指向当前对象,因此本质是一个带有顶层const的指针,即this本身是一个常量。当定义一个类对象时,指定该对象为一个常量对象的时候,初始化的this指针是一个指向常量对象的指针,即此时的this既有顶层const也有底层const。

        const也可以用于函数传参中,const修饰形参时,相当于传递的参数加上了常量属性。如上图,每个成员函数的参数列表中都有一个隐式的参数this指针,常量成员函数的参数列表中的this指针是既有顶层const也有底层const的,即相当于对原先的参数this指针用const常量方式传递。常量对象以及常量对象的指针或引用都只能调用常量成员函数,常量成员函数只能读取成员变量,而不能改变成员变量。

        由于static修饰的静态成员函数是没有this指针作为参数的,因为静态成员函数属于类而不是某一个对象,即不能实例化,而常量成员函数是与具体的实例关联的,因此const关键字不能与static同时使用

五、const与宏

六、const可以实现函数重载

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值