当前位置: 首页 > news >正文

奶奶都能看懂的 C++ —— const 限定符与指针

上一篇我们讲了指针,这一篇先从 const 讲起。

常量

嗯。const,顾名思义,就是不变。给任何数据类型加上 const,就指明了这个变量不会再变化。任何试图修改变量的操作都会报错,无法通过编译。比如:

const int a = 10;
a = 11; //Error!

当然,常量也必须在定义时初始化。

常量自己不能变,但这不代表不能使用。它可以被用于初始化其它对象:

int b = a * 2;
// b = 20

很简单的东西,不是吗?接下来让我们结合一下上一篇的引用和指针。

常量引用

我们可以使用 const 限定修饰一个引用。由于引用本身就不可以更改它绑定的对象,所以这里 const 只是阻止了对绑定对象的修改而已:

const int a = 10;
int b = 10;
const int &c = a;
const int &d = b;
int &e = a; //Error
a = 11;// Error
b = 11;// OK!
c = 11;// Error
d = 11;// Error

看看上面的代码,对 a,c,d 的修改都会产生编译错误。我们一个个分析:

  • a 是常量,但是 e 是个普通引用,非常量不能绑定到常量
  • a 的修改是不可行的,因为它是个常量
  • b 的修改是可行的,因为它是整型变量
  • cd 的修改不可行,因为它们是常量引用,不可修改

也就是说……

const 限定符应用在引用上时,只是让 C++ 认为引用指向的对象不可以被修改,而实际指向的对象到底是否为常量(是否可以修改)是没有影响的。

如果已经在对象上施加 const,那么指向它的引用也必须添加,来保持类型一致。可以这么理解:如果引用不加 const,那么 C++ 就认为引用指向的对象可以修改,这显然和对象的不可修改性不符,编译器不允许这样的事情发生。

试试这样想吧:你可以在可以随意使用的瓶子上贴上勿动的标签,但是不能给不能动的瓶子贴上可动的标签

还记得我们曾经把引用比作瓶子上贴的标签,那么这里的 const 限定符就好像在标签上加上一句:不能动!

特殊用法

在继续前进之前,我们来看点奇怪的常量引用。

int i = 10;
double s = 3.14;
const int &a = 1;
const int &b = i * 2;
const int &c = s * 2;

wow,这里的3、4、5行居然把表达式赋给引用,会报错的。

既然我都说了是奇怪的引用,当然不会编译错误啦。这其实是常量引用的特殊用法:如果一个引用添加了 const 限定,那么编译器允许使用任意表达式(包括字面值、算式、对象),并且能够自动转换。

你一定已经在学习指针前,了解过自动转换了。如果两个变量的类型不匹配,那么编译器会尝试自动转换。

也就是说,上方代码与下方等效:

int i = 10;
double s = 3.14;
int tmp1 = 1;
const int &a = tmp1;
int tmp2 = i * 2;
const int &b = tmp2;
int tmp3 = s * 2; //自动类型转换
const int &c = tmp3;
// b = 20, c = 6

但也别搞混了,这个只在引用带有 const 时生效,普通引用由于是可变的,所以只能绑定一个数据类型匹配的变量。

恭喜你,你已经掌握了引用中的 const,我们一鼓作气,继续看看指针中的 const

const 与指针

添加 const 限定

const int a = 10;
int b = 10;
const int *s = &a;
const int *t = &b;
int *s1 = &a; //Error
int *t1 = &b;

为什么 s1 的定义会报错,但是其它就不报错呢?

我们在常量引用中提到,const 只是告诉编译器,认为指向的对象不可变。举一反三,const 应用于指针,则表示认为指针所指的对象(那个地址对应的对象)不可变。这就解释了 3,4 行的定义。

而第五行的报错也和上文所述相似,你所指的对象不可变,又怎么能够让指针认为所指的对象可变呢?这是不符合常理的。

认为指向对象不可变,也就是解引用后获得的对象不能变化

*s = 11; //Error
*t = 11; //Error

但是和引用不一样,指针是对象,自己是可变的。上面的 const 限定只是让指针认为自己指向的对象不可变,但指针本身指向哪个对象是可变的。

s = &b; //OK
t = &a; //OK

上面的代码完全可以正常运行。

常量指针

那怎么让指针自己不可变呢?嗯,这里事情逐渐变得复杂了起来。

const int a = 10;
int b = 10;
int *const s = &a; //Error
int *const t = &b;
const int *const s1 = &a;
const int *const t1 = &b;

Wait Wait Wait 这有点太复杂了,我们还是一行行看。

注意到了吗?我们使用了 *const,它表示定义一个常量指针。顾名思义,指针本身是常量,不能变(不能改变保存的位置,即不能修改它指向的对象是哪一个)。

现在来看代码:

  • 第三行,它定义了一个本身是常量的指针(而认为指向的对象是可变的),但是却绑定到了不可变常量 a,因此报错(上文已经强调过,不能认为不可变的东西可变)
  • 第四行,它定义了一个本身是常量的指针,绑定到了变量 b,没问题。
  • 第五行,它定义了一个本身是常量的指针,且认为指向对象不可变,绑定到了不可变常量,没问题。
  • 第六行,它定义了一个本身是常量的指针,且认为指向对象不可变,绑定到了变量,没问题

为了更加清晰说明什么叫本身不可变,什么叫认为指向对象不可变,再给出以下代码:

const int c = 11;
t = &c; //Error
s1 = &c; //Error
t1 = &c; //Error
*t = 12;
*s1 = 12; //Error
*t1 = 12; //Error

仔细想想为什么那些行会报错吧。

  • 本身不可变的指针,不可以重新指向其它位置。因此 2,3,4 行报错
  • 之前提到过,如果指针认为自己指向的对象不可变,那么它解引用后不可变,所以 6,7 行报错

好好思考一下,分清楚什么是本身不可变,什么是认为指向的对象不可变(解引用后不可变)。

Tips:还是再回忆下 const 修饰的真正含义吧。如果认为指向对象不可变,那么这和指向对象实际是否可变没有任何关系。

如果你理解了,真得好好夸夸自己,连这么复杂的东西都搞懂了!

顶层与底层

我们把本身不可变的 const,称作顶层 const;认为指向对象不可变的 const,称作底层 const。

从之前的讲解中,我们不难得到推论:

  • 引用本身不可变,所以只有认为不可变的底层 const 存在。
  • 对于指针,如果放在距离变量名远的地方,那么是底层;距离变量名近的地方,是顶层
  • 底层 const 只和指针、引用有关,而顶层 const 可以修饰大部分对象

很好。接下来我们就要涉及一些更加深入的话题了。

我们曾经在特殊用法那里提过一嘴自动转换。众所周知,在执行赋值操作时,可能会进行自动转换。而变量可以转换为常量,常量也可以转换为变量:

int a = 10;
const int b = a; //b 被顶层 const 修饰,它本身不可变
int c = b;

但对于指针和引用来说,事情就更加复杂了。

当你赋值,涉及指针、引用时,源和目标的顶层 const 可以不同,但顶层(决定本身是否可变)必须满足自动转换(不可变拷贝到可变)。注意上面代码第二行,和下面代码最后一行。

const int d = 20;
const int *const p1 = &b;
const int *p2 = &d;
p1 = p2;//Error
p2 = p1;//现在,p1,p2 都指向 b。

对于底层 const,这决定了源、目标认为其所指向的对象是否可变。在此过程中,源和目标的底层 const 可以不同,但是底层(指向对象是否可变)必须满足自动转换(可变拷贝到不可变)

int e = 1;
int *p3 = &d; //Error
int *p4 = &e;
p2 = p4; //为变量的指针增加不可变修饰

但注意一下,上面只是赋值操作,如果是新创建指针,那么顶层 const 无所谓(正如之前所述):

const int *const m = p1;
const int *m1 = p1;

我知道你确实有点晕了。

总结一下,修改指针操作时,看等号左侧是否有顶层 const 很重要,有顶层 const 就不能修改;而任何操作时,都有必要去检查下等号右侧的底层 const,如果有,那么左边也必须有,否则左侧随意。

试试这样想吧:const 就是一种修饰。指针是瓶子的标签,你可以让瓶子(对象)本身不可变(顶层 const 修饰),但这样你必须在标签(指针)上写上“别动瓶子”(底层 const 修饰)。如果你看到了“别动”的标签(底层 const 修饰的指针),想根据这个标签给瓶子再贴一个标签,或者把别的瓶子上的标签移过来(创建新指针/修改旧指针),那么另一个标签上也得写“别动”(底层 const 修饰)。

如果你的标签上没有“别动”(没有底层 const),说明瓶子本身一定是可以动的(没有顶层 const),所以新创建的标签写不写“别动”都无所谓(有没有底层 const 并没有关系)。

而如果一个标签是强力胶,撕不下来(指针有顶层 const 修饰),那么它就不能移动。但是你还是可以根据这个标签,移动其它可以移动的标签(将其它无顶层 const 修饰的指针,赋值为它),或者创建一个新的标签,是否为强力胶都可以(创建新的指针时,顶层 const 修饰并不重要)。

用比喻来说,顶层 const 决定了标签有没有强力胶;底层 const 决定我们是否认为瓶子能动。如果有强力胶,一个标签本身就不能移动了,但不影响其它标签。如果我们根据一个标签,不认为瓶子能动,那么也就没办法再贴上能动的标签了。

注意了,我这里一直强调根据某个标签,是因为这是在指针的语境下来说的,我们必须根据指针来进行寻找对象、赋值等操作,而不是直接操作对象。好好想想,上文所述“根据某个标签”,指的就是赋值等号右侧的内容。

你也可以配上下面的表格举例,一起理解(注意代码是无法运行的,这里只是为了看清楚而写出了每个类型,所以没有用赋值的等号):

  • 当操作为:创建指针,并赋值时:
    • int *p <- const int a Error 试图认为不可变的常量可变
    • const int *p <- const int a OK
    • const int *p <- int a OK
    • int *p1 <- int *const p OK
    • int *const p1 <- int *const p OK
    • const int *p1 <- int *const p OK
    • const int *p1 <- int *p OK
    • const int *const p1 <- int *p OK
    • const int *const p1 <- int *const p OK
    • const int *const p1 <- const int *const p OK
    • const int *p1 <- const int *const p OK
    • int *const p1 <- const int *p Error 试图根据指向不可变对象的指针,认为指向可变对象
    • int *const p1 <- const int *const p Error 试图根据指向不可变对象的指针,认为指向可变对象
  • 当操作为:修改左侧已经创建的指针时:
    • int *p <- const int a Error 试图认为不可变的常量可变
    • const int *p <- const int a OK
    • const int *p <- int a OK
    • int *p1 <- int *const p OK
    • int *const p1 <- int *const p Error 试图修改不可变的指针
    • const int *p1 <- int *const p OK
    • const int *p1 <- int *p OK
    • const int *const p1 <- int *p Error 试图修改不可变的指针
    • const int *const p1 <- int *const p Error 试图修改不可变的指针
    • const int *const p1 <- const int *const p Error 试图修改不可变的指针
    • const int *p1 <- const int *const p OK
    • int *const p1 <- const int *p Error 试图根据指向不可变对象的指针,认为指向可变对象 / 试图修改不可变的指针
    • int *const p1 <- const int *const p Error 试图根据指向不可变对象的指针,认为指向可变对象 / 试图修改不可变的指针

OKOKOK,我知道这很难,但是再读一遍,理解一下指针的 const 修饰吧。

哦对了,赋值记得保证除了 const 外的数据类型一致。别忘了这个基础。

如果你连这个都掌握了,你已经对 const 有了相当程度的认识。恭喜!

下一篇,我们将离开指针的苦海,继续讲解 C++。当然,也是奶奶级的拆解哦。

http://www.hskmm.com/?act=detail&tid=34161

相关文章:

  • 2025年10月工程管理系统推荐:十强榜单全维度对比评测
  • 2025年10月制冰机产品推荐:十款热门机型对比评测榜
  • 2025年10月除甲醛空气净化器产品对比评测排行榜
  • 2025年棒球帽厂家推荐排行榜,时尚潮流/运动休闲/户外防晒棒球帽公司精选推荐
  • 2025年10月深圳酒店综合对比与实用排行榜解析
  • 2025年10月北京金融街附近豪华酒店综合对比与排行榜单
  • 2025年10月北京金融街附近豪华酒店对比排行与实用评测指南
  • 2025年10月金融街附近豪华酒店对比排行与评测分析
  • 2025年10月深圳酒店综合对比与排行评测指南
  • 2025年10月深圳酒店住宿选择对比与排名指南
  • 2025年10月北京治疗肺癌医生综合对比与实力排行榜分析
  • 2025年10月北京治疗肺癌医生排行榜与专业能力深度评测
  • 2025年10月连锁酒店品牌综合对比与投资排行分析
  • 2025年10月连锁酒店排行榜前十名深度对比与投资价值分析
  • 2025年10月连锁酒店品牌综合实力对比与投资价值排行分析
  • 2025年角接触轴承厂家推荐排行榜,高精度/高承载/高精密球/机床主轴/汽车/定制/可替代进口/高转速/高刚性轴承公司推荐!
  • 2025年定型机厂家推荐排行榜,拉幅定型机/门富士定型机/节能定型机/余热回收/废气回收/烟气回收/智能排风/双层定型机公司精选
  • 2025年印染水洗机厂家推荐排行榜,高效节能与智能控制的行业首选!
  • 2025年10月深圳婚姻纠纷律师综合对比与专业排行分析
  • 2025年10月亲子旅游景区综合对比与排行榜解析
  • grpc c++服务 通过jenalloc进行内存管理
  • 家理律师事务所联系方式:婚姻家事法律服务专业机构
  • gRPC C++内存问题与jemalloc解决方案
  • 2025年10月北京婚姻纠纷律所对比与排名分析报告
  • 2025年10月北京婚姻纠纷律所专业对比与排行榜分析
  • 视频监控设备同时云台控制/onvif云台控制/一键云台控制/监控画面显示
  • 专题:2025年游戏科技的AI革新研究报告:全球市场趋势研究报告|附130+份报告PDF、数据仪表盘汇总下载
  • 2025年10月北京离婚房产律师专业能力对比与排行分析
  • 2025年10月北京离婚房产律师专业能力对比与服务质量排行分析
  • Jemalloc简介及使用方法