C++学习 const 上

🌈 C++ Primer 的学习笔记

前言

这篇blog 主要是想具体讲讲新学到的const 当然不止是const 而是基于这个const引申出来的指针和引用。还是需要捋一捋的,这还是有点困难的。

我会把每一节的重点都摘出来,放在前面~

1️⃣首先讲讲const

2️⃣const引用和const指针 ❗️ 特别容易混淆

这边blog 常量指针/引用 指针/引用常量 详细讲的不错 可以看看~

文章目录

    • 前言
    • const 限定符
        • 初始化和const
        • 默认情况下,const对象只在文件内有效
        • 练习
      • 关于const 引用
        • 初始化和对const 的引用
        • 对const的引用可能引用一个并非const 的对象
      • 指针和const
        • 指向常量的指针
        • 常量指针
        • 练习

const 限定符

🌈敲重点!

🍎因为const对象一旦创建后其值就不能再改变,所以const对象必须初始化

🍐只能在const类型的对象上执行不改变其内容的操作

有时我们希望定义这样一种变量,它的值不能被改变。例如,用一个变量来表示缓冲区的大小。使用变量的好处是当我们觉得缓冲区大小不再合适时,很容易对其进行调整。另一方面,也应随时警惕防止程序一不小心改变了这个值。为了满足这一要求,可以用关键字const对变量的类型加以限定:

const int bufsize = 512;//输入缓冲区大小

这样就把 bufsize定义成了一个常量。任何试图为 bufsize赋值的行为都将引发错误:

bufSize = 512;//错误:试图向const对象写值

因为const对象一旦创建后其值就不能再改变,所以const对象必须初始化 一如既往,初始值可以是任意复杂的表达式:

const int i = get_size();//正确:运行时初始化
const int j=42;//正确:编译时初始化
const int k;//错误:k是一个未经初始化的常量
初始化和const

正如之前反复提到的,对象的类型决定了其上的操作。与非 const类型所能参与的操作相比,const类型的对象能完成其中大部分,但也不是所有的操作都适合。主要的限制就是只能在const类型的对象上执行不改变其内容的操作。例如,const int和普通的int一样都能参与算术运算,也都能转换成一个布尔值,等等。

在不改变 const 对象的操作中还有一种是初始化,如果利用一个对象去初始化另外一个对象,则它们是不是const都无关紧要:

int i =42;
const int ci = i;//正确:i的值被拷贝给了ci
int j= ci;//正确:ci的值被拷贝给了j

尽管ci是整型常量,但无论如何ci中的值还是一个整型数。ci的常量特征仅仅在执行改变ci的操作时才会发挥作用。当用ci去初始化j时,根本无须在意ci是不是一个常量。拷贝一个对象的值并不会改变它,一旦拷贝完成,新的对象就和原来的对象没什么关系了。

默认情况下,const对象只在文件内有效

当以编译时初始化的方式定义一个const对象时,就如对bufsize的定义一样:

const int bufSize = 512;//输入缓冲区大小

编译器将在编译过程中把用到该变量的地方都替换成对应的值。也就是说,编译器会找到代码中所有用到bufsize的地方,然后用512替换。

为了执行上述替换,编译器必须知道变量的初始值。如果程序包含多个文件,则每个用了const对象的文件都必须得能访问到它的初始值才行。要做到这一点,就必须在每一个用到变量的文件中都有对它的定义(参见2.2.2节,第41页)。为了支持这一用法,同时避免对同一变量的重复定义,默认情况下,const对象被设定为仅在文件内有效。当多个文件中出现了同名的const变量时,其实等同于在不同文件中分别定义了独立的变量

某些时候有这样一种const变量,它的初始值不是一个常量表达式,但又确实有必要在文件间共享。这种情况下,我们不希望编译器为每个文件分别生成独立的变量。相反,我们想让这类const对象像其他(非常量)对象一样工作,也就是说,只在一个文件中定义const,而在其他多个文件中声明并使用它。

解决的办法是,对于const变量不管是声明还是定义都添加extern关键字,这样只需定义一次就可以了:

// file_1.cc定义并初始化了一个常量,该常量能被其他文件访问
extern const int bufSize = fcn() ;
// file_ 1.h头文件
extern const int bufSize;//与file_1.cc中定义的bufsize是同一个

如上述程序所示,file_1.cc定义并初始化了bufsize。因为这条语句包含了初始值,所以它(显然)是一次定义。然而,因为bufsize是一个常量,必须用extern加以限定使其被其他文件使用。

file 1.h头文件中的声明也由extern做了限定,其作用是指明bufsize并非本文件所独有,它的定义将在别处出现。

如果想在多个文件之间共享const对象,必须在变量的定义之前添加extern关键字。

练习
//练习2.26:下面哪些句子是合法的?如果有不合法的句子,请说明为什么?
(a) const int buf;
(b) int cnt =0
(c) const int sz= cnt;
(d)++cnt; ++SZ

(a) const int buf;

这个句子是不合法的。在C++中,const关键字用于声明一个常量,它必须被初始化。因此,const int buf;缺少初始化表达式,正确的声明应该是const int buf = 0;

(b) int cnt = 0;
这个句子是合法的。它声明了一个整型变量cnt并初始化为0。

© const int sz = cnt;

这个句子是不合法的。这里有两个问题:首先,const变量必须在声明时初始化,不能在声明后赋值。其次,即使cnt已经被声明并初始化,sz作为const变量也不能从非const变量cnt那里赋值。正确的做法是直接在声明时初始化,例如const int sz = 5;

(d) ++cnt; ++SZ;

首先,++cnt;是合法的,它表示对变量cnt进行自增操作。然而,++SZ;是不合法的,因为SZ没有被声明为一个变量,而且变量名通常不以大写字母开头,这是C++中常见的命名约定,尽管这不是语法错误。如果SZ是一个未声明的变量,那么这个句子将导致编译错误。

关于const 引用

敲重点

🍌常量的引用 不能用作修改被绑定的对象

也就是说 原来的数据类型是const xx 类型 那么,对这个数据引用后就不能利用引用的变量去更改原有的值(好像有点绕 具体看下文)

❗️注意 常量的引用 和 **常量引用 ** 的不同 一个是被引用的变量 一个是变量本身 下面看的时候要认真一些

常量的引用 数据类型需要一致 相当于对一个const类型的变量进行引用,这个时候就叫常量的引用

常量引用 是一个引用类型的变量

🍑在初始化常量引用时允许用任意表达式作为初始值,允许为一个常量引用绑定非常量的对象、字面值,甚至是个一般表达式。

🍊常量引用仅对引用可参与的操作做出了限定,对于引用的对象本身是不是一个常量未作限定。因为对象也可能是个非常量,所以允许通过其他途径改变它的值:

可以把引用绑定到const对象上,就像绑定到其他对象上一样,我们称之为对常量的引用(reference to const)。与普通引用不同的是,对常量的引用不能被用作修改它所绑定的对象:

const int ci = 1024;
const int &r1 = ci;  //正确:引用及其对应的对象都是常量
r1 = 42;//错误:r1是对常量的引用
int &r2=ci;//错误:试图让一个非常量引用指向一个常量对象

因为不允许直接为ci赋值,当然也就不能通过引用去改变ci。因此,对r2的初始化是错误的。假设该初始化合法,则可以通过r2来改变它引用对象的值,这显然是不正确的。

常量引用是对const的引用

程序员们经常把词组“对 const的引用”简称为“常量引用”,这一简称还是挺靠谱的,不过前提是你得时刻记得这就是个简称而已。

严格来说,并不存在常量引用。因为引用不是一个对象,所以我们没法让引用本身恒定不变。事实上,由于C+语言并不允许随意改变引用所绑定的对象,所以从这层意义上理解所有的引用又都算是常量。引用的对象是常量还是非常量可以决定其所能参与的操作,却无论如何都不会影响到引用和对象的绑定关系本身

初始化和对const 的引用

2.3.1节(第46页)提到,引用的类型必须与其所引用对象的类型一致,但是有两个例外。第一种例外情况就是在初始化常量引用时允许用任意表达式作为初始值,只要该表达式的结果能转换成(参见2.1.2节,第32页)引用的类型即可。尤其,允许为一个常量引用绑定非常量的对象、字面值,甚至是个一般表达式:

int i =42;·
const int &r1 = i;//允许将const int&绑定到一个普通int对象上
const int &r2=42;  //正确:r1是一个常量引用
const int &r3 = r1 * //正确:r3是一个常量引用
int &r4 = r1 * 2;//错误:r4是一个普通的非常量引用

要想理解这种例外情况的原因,最简单的办法是弄清楚当一个常量引用被绑定到另外一种类型上时到底发生了什么:

double dval = 3.14;
const int &ri = dval;

此处ri引用了一个int 型的数。对ri的操作应该是整数运算,但dval却是一个双精度浮点数而非整数。因此为了确保让ri绑定一个整数,编译器把上述代码变成了如下形式:

const int temp =dval;
//由双精度浮点数生成一个临时的整型常量
const int &ri = temp;
// 让ri绑定这个临时量

接下来探讨当ri不是常量时,如果执行了类似于上面的初始化过程将带来什么样的后果。如果ri不是常量,就允许对ri赋值,这样就会改变ri所引用对象的值。注意,此时绑定的对象是一个临时量而非 dval。程序员既然让 ri引用dval,就肯定想通过ri改变dval的值,否则为什么要给ri赋值呢? 如此看来,既然大家基本上不会想着把引用绑定到临时量上,C++语言也就把这种行为归为非法。

对const的引用可能引用一个并非const 的对象

必须认识到,常量引用仅对引用可参与的操作做出了限定,对于引用的对象本身是不是一个常量未作限定。因为对象也可能是个非常量,所以允许通过其他途径改变它的值:

int i =42;
int &r1 = i;
//引用ri绑定对象i
const int &r2 = i;
// r2也绑定对象i,但是不允许通过r2修改i的值
r1 = 0;
//r1并非常量,i的值修改为0
r2 = 0;
//错误:r2是一个常量引用

r2绑定(非常量)整数i是合法的行为。然而,不允许通过r2修改i的值。尽管如此,i的值仍然允许通过其他途径修改,既可以直接给i赋值,也可以通过像r1一样绑定到i的其他引用来修改。

指针和const

🌈 敲重点​

和上一节一样 常量指针和指向常量的指针 这俩是不一样的!

⛵️指向常量的指针 指向常量的指针不能用于改变其所指向对象的值。存放常量对象的地址 只能使用指向常量的指针。和引用一样,指向常量的指针没有规定所指向的对象是一个常量,仅仅要求不能通过该指针改变对象的值,没有规定那个对象的值不能通过其他途径改变。

🌊 常量指针 必须初始化 一旦初始化, 他的值(也就是存放再指针的地址)不会再改变了 。*放在const之前说明指针是一个常量,也就是不变的是指针本身的值而不是指向的那个值

要想弄清楚声明的含义最行之有效的办法是从右向左阅读

指向常量的指针

指向常量的指针不能用于改变其所指向对象的值。存放常量对象的地址 只能使用指向常量的指针

带有前缀 const

const double pi = 3.14;// pi是个常量,它的值不能改变
double *ptr = π//错误:ptr是一个普通指针const double *cptr = π//正确:cptr可以指向一个双精度常量
*cptr = 42;//错误:不能给*cptr赋值double dval = 3.14;//dval是一个双精度浮点数,它的值可以改变
cptr = &dval;//正确:但是不能通过cptr改变dval的值

指针类型不一定要与所致的对象一致

和引用一样,指向常量的指针没有规定所指向的对象是一个常量,仅仅要求不能通过该指针改变对象的值,没有规定那个对象的值不能通过其他途径改变。

常量指针

常量指针必须初始化 一旦初始化, 他的值(也就是存放再指针的地址)不会再改变了

*** 放在const之前说明指针是一个常量,也就是不变的是指针本身的值而不是指向的那个值**

int errNumb = 0;
int *const curErr = &errNumb;// curErr将一直指向errNumb
const double pi = 3.14159;
const double *const pip = π // pip是一个指向常量对象的常量指针

如同2.3.3节(第52页)所讲的,要想弄清楚这些声明的含义最行之有效的办法是从右向左阅读。此例中,离curErr最近的符号是const,意味着curErr本身是一个常量对象,对象的类型由声明符的其余部分确定。声明符中的下一个符号是*,意思是 curErr是一个常量指针。最后,该声明语句的基本数据类型部分确定了常量指针指向的是一个int对象。与之相似,我们也能推断出,pip是一个常量指针,它指向的对象是一个双精度浮点型常量。

指针本身是一个常量并不意味着不能通过指针修改其所指对象的值,能否这样做完全依赖于所指对象的类型。例如,pip是一个指向常量的常量指针,则不论是pip所指的对象值还是pip自己存储的那个地址都不能改变。相反的,curErr指向的是一个一般的非常量整数,那么就完全可以用curErr去修改errNumb的值:

*pip = 2.72;
//错误:pip是一个指向常量的指针
//如果curErr所指的对象(也就是errNumb)的值不为0
if(*curErr) {errorHandler ();*curErr = 0;//正确:把curErr所指的对象的值重置
}
练习

🐋 ​ps 对于不确定的式子 可以代入代码敲一敲

//练习2.27:下面的哪些初始化是合法的?请说明原因。
(a)int i = -1,&r = 0;
(b) int *const p2 = &i2;
(c) const int i = -1,&r = 0;
(d) const int *const p3 = &i2;
(e) const int *pl = &i2;
(f)const int &const r2;
(g)const int i2= i,&r = i;
//练习2.28:说明下面的这些定义是什么意思,挑出其中不合法的。
(a)int i, *const cp;
(b)int *p1, *const p2;
(C) const int ic,&r = ic;
(d) const int *const p3;
(e)const int *p;
//练习2.29:假设已有上一个练习中定义的那些变量,则下面的哪些语句是合法的?请说明原因。
(a)i = ic;
(b)p1 = p3;
(c)p1 = ⁣
(d)p3 = ⁣
(e)p2= pl;
(f)ic = *p3 ;

🔑 答案答案

//练习2.27:下面的哪些初始化是合法的?请说明原因。
(a) int i = -1,&r = 0;  
//可能一些小伙伴看不懂 这其实相当于 int i = -1,int &r = 0;  
//  这类引用应初始化一个变量 而不是字面量 不合法(具体看引用篇)
(b) int *const p2 = &i2; 
//p2 是一个常量指针  也就是地址不变的指针 如果i2是int类型的则合法, 只能指向int
(c) const int i = -1,&r = 0; 
//正确  相当于const int &r=0;  是一个常量引用 可以指向字面量
//在初始化常量引用时允许用任意表达式作为初始值,允许为一个常量引用绑定非常量的对象、字面值,甚至是个一般表达式。
(d) const int *const p3 = &i2; 
//指向常量的常量指针 不可以用这个指针更改i2的值 同时这个指针的地址也不会变(e) const int *pl = &i2; 
//指向常量的指针  不可以用这个指针更改i2的值(f)const int &const r2; 
//这个 应该是语法错误(g)const int i2= i,&r = i; 
// 相当于 const int &r =i;
//如果i是常量 可以这么做
//练习2.28:说明下面的这些定义是什么意思,挑出其中不合法的。
(a)int i, *const cp;
//int *const cp  常量指针  初始化后不能变地址 也就是地址不能更改,且一定要初始化
(b)int *p1, *const p2;
//int *const p2  常量指针  初始化后不能变地址 也就是地址不能更改,且一定要初始化 
(C) const int ic,&r = ic;
//需要初始化 const int &r=ic  
(d) const int *const p3;
//需要初始化
(e)const int *p;
//指向常量的指针 需要指向const int 类型的值   不能通过指针改变指向的值的内容
//练习2.29:假设已有上一个练习中定义的那些变量,则下面的哪些语句是合法的?请说明原因。
(a)int i;const int ic;i = ic;
//合法  i只是用了ic的值 
(b)int *p1;const int *const p3;p1 = p3; 
//int 指针变量 =指向int 的常量指针 类型不一致(c)int *p1;const int icp1 = ⁣
//不合法 必须是const int *p1 才可(d) const int *const p3;const int ic;p3 = ⁣
//不合法 p3不可更改地址(e)int *const p2;int *p1p2= pl;
//常量指针 值不可更改  p2
//pl已经初始化了 (f)	const int ic;const int *const p3;ic = *p3 ;
//不能  const类型 是常量 不可更改

完结 欢迎批评指正~ 点个赞点个赞~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/20513.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Linux C/C++动态库制作

概念:程序在编译时不会把库文件的二进制代码链接到目标程序中,而是在运行时候才被载入。 如果多个进程中用到了同一动态库中的函数或类,那么在内存中只有一份,避免了空间浪费问题。 特点: 程序运行在运行的过程中&…

统计信号处理基础 习题解答10-6

题目 在例10.1中,把数据模型修正为: 其中是WGN,如果,那么方差,如果,那么方差。求PDF 。把它与经典情况PDF 进行比较,在经典的情况下A是确定性的,是WGN,它的方差为&#…

5.算法讲解之-二分查找(简单易懂)

1.简介 1.二分查找的思路简单易懂,较难的是如何处理查找过程中的边界条件,当较长时间没写二分查找的时候就容易忘记如何处理边界条件。 2.只有多写代码,多做笔记就不易忘记边界条件 2.算法思路 正常查找都是从头到尾查找一个数字是否在数组中…

使用pycharm+opencv进行视频抽帧(可以用来扩充数据集)+ labelimg的使用(数据标准)

一.视频抽帧 1.新创建一个空Pycharm项目文件,命名为streach zhen 注:然后要做一个前期工作 创建opencv环境 (1)我们在这个pycharm项目的终端里面输入下面的命令: pip install opencv-python --user -i https://pypi.t…

SettingWithCopyWarning: A value is trying to be set on a copy of a slice fro

SettingWithCopyWarning: A value is trying to be set on a copy of a slice fro 错误代码&#xff1a; while i < len(data_csv_data):if data_csv_data[flowmember][i] j:data_csv_data[label][i] data_csv_label[label][j-1]data_csv_data[classes][i]data_csv_label[…

[数据集][目标检测]猕猴桃检测数据集VOC+YOLO格式1838张1类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;1838 标注数量(xml文件个数)&#xff1a;1838 标注数量(txt文件个数)&#xff1a;1838 标注…

企业级宽表建设

1 宽表概述 宽表&#xff0c;从字面意义上讲就是字段比较多的数据库表&#xff0c;通常情况下是讲很多相关的数据&#xff0c;包括实时表、维度表、指标等格言录在一起形成的一张数据表。 2 宽表的优点 2.1 开发效率提升 由于把不同的信息放在同一张表存储&#xff0c;宽表…

sensitive-word 敏感词 v0.17.0 新特性之 IPV4 检测

敏感词系列 sensitive-word-admin 敏感词控台 v1.2.0 版本开源 sensitive-word-admin v1.3.0 发布 如何支持分布式部署&#xff1f; 01-开源敏感词工具入门使用 02-如何实现一个敏感词工具&#xff1f;违禁词实现思路梳理 03-敏感词之 StopWord 停止词优化与特殊符号 04-…

详解 Spark 核心编程之 RDD 持久化

一、问题引出 /** 案例&#xff1a;对同一份数据文件分别做 WordCount 聚合操作和 Word 分组操作 期望&#xff1a;针对数据文件只进行一次分词、转换操作得到 RDD 对象&#xff0c;然后再对该对象分别进行聚合和分组&#xff0c;实现数据重用 */ object TestRDDPersist {def …

Jupyter Notebook快速搭建

Jupyter Notebook why Jupyter Notebook Jupyter Notebook 是一个开源的 Web 应用程序&#xff0c;允许你创建和分享包含实时代码、方程、可视化和解释性文本的文档。其应用包括&#xff1a;数据清洗和转换、数值模拟、统计建模、数据可视化、机器学习等等。 Jupyter Notebo…

东芝机械人电池低报警解除与机器人多旋转数据清零

今天启动一台设备,触摸屏一直显示机器人报警(翻译过后为电池电量低),更换电池后关机重启后也不能消除,所以打开示教器,下面就来说说怎么解决此项问题(可以参考官方发的手册,已手册为主)。 一,设备 下面来看看机械手的照片与示教器的照片 四轴机械手(六轴机器人有可…

可视化大屏也在卷组件化设计了?分享一些可视化组件

hello&#xff0c;我是大千UI工场&#xff0c;这次分享一些可视化大屏的组件&#xff0c;供大家欣赏。&#xff08;本人没有源文件提供&#xff09;

动态内存基础实践

文章目录 1.new 创建堆内存对象2.delete释放内存空间3.malloc申请内存4.free释放malloc申请的内存空间 1.new 创建堆内存对象 2.delete释放内存空间 3.malloc申请内存 4.free释放malloc申请的内存空间 #include <iostream> #include <string>using namespace s…

基础数学内容重构(后缀0个数)

今天也是参加了一下宁波大学的校赛&#xff0c;其中有一道题是求后缀0的个数&#xff0c;题意是让我们求一下式子的后缀0个数&#xff1a; 看上去比较复杂&#xff0c;但是通过化简我们可以知道以上式子就是求&#xff08;n 1&#xff09;&#xff01;&#xff0c;这里化简的过…

用贪心算法计算十进制数转二进制数(小数部分)

在上一篇博文用贪心算法计算十进制数转二进制数&#xff08;整数部分&#xff09;-CSDN博客中&#xff0c;小编介绍了用贪心算法进行十进制整数转化为二进制数的操作步骤&#xff0c;那么有朋友问我&#xff0c;那十进制小数转二进制&#xff0c;可以用贪心算法来计算吗&#x…

[C++]vector的模拟实现

下面是简单的实现vector的功能&#xff0c;没有涉及使用内存池等复杂算法来提高效率。 一、vector的概述 &#xff08;一&#xff09;、抽象数据类型定义 容器&#xff1a;向量&#xff08;vector&#xff09;vector是表示大小可以变化的数组的序列容器。像数组一样&#xf…

带你学习Mybatis之Mybatis映射文件

Mybatis映射文件 增删改查 简单地增删改查 <select id"selectUser" resultType"User"> select * from user where id #{id}</select><insert id"addUser"> insert into user (name,account) values (#{name},#{account…

[sylar]后端学习:配置环境(一)

1.介绍 基于sylar大神的C高性能后端网络框架来进行环境配置和后续学习。网站链接&#xff1a;sylar的Linux环境配置 2.下载 按照视频进行下载&#xff0c;并进行下载&#xff0c;并最好还要下载一个vssh的软件。可以直接在网上搜索即可。 sylar_环境配置&#xff0c;vssh下…

CentOS 运维常用的shell脚本

文章目录 一、操作系统磁盘空间查看实时获取系统运行状态获取cpu、内存等系统运行状态获取系统信息二、应用程序获取进程运行状态查看有多少远程的 IP 在连接本机三、用户管理统计当前 Linux 系统中可以登录计算机的账户有多少个创建用户四、自动化管理自动备份日志文件监控的页…

MySQL常见操作

MySQL字符串连接 在MySQL中&#xff0c;字符串连接可以使用CONCAT()函数或双竖线||操作符进行。下面是两种方法的示例&#xff1a; 使用CONCAT()函数&#xff1a; CONCAT(,2001,, ABC)使用双竖线||操作符&#xff1a; ,2001, || ABC您可以根据自己的偏好选择其中一种方法来…