C++ const、volatile和mutable关键字详解
对于cv(const 与 volatile)类型限定符和关键字mutable在《cppreference》中的定义为:
cv可出现于任何类型说明符中,以指定被声明对象或被命名类型的常量性(constness)或易变性(volatility)。
const----------定义类型为常量类型。
volatile--------定义类型为易变类型。
mutable用于指定不影响类的外部可观察状态的成员(通常用于互斥体、记忆缓存、惰性求值和访问指令等)。
- mutable------容许常量类类型对象修改相应类成员。
const
const实际上是一个类型说明,告诉编译器const修饰的目标是不变的,允许编译器对其进行额外的优化,如果后面代码不小心修改它了,就编译失败,告诉用户该目标被意外修改了,提高程序的安全性和可控性。
const修饰普通变量
被const修饰过的变量,编译器往往将其作为一个常量进行处理,同时,const修饰的变量在编译阶段会被编译器替换为相应的值,来提高程序的执行效率。
#include <iostream>using namespace std;int main() {const int i = 50; // 普通常量const static int si = 50; // 静态常量int* p_int = (int*)&i; // 强制类型转换为int*int* p_sint = (int*)&si;*p_int = 100; // 通过非常常量指针修改常量i的值,该行为是C++为未定义行为//*p_sint = 100;//编译不会报错,程序运行时崩溃,且该行为也是C++为未定义行为cout << "i:" << i << ", i的地址: " << &i << endl;//编译器阶段会将常量i替换为50cout << "*p_int:" << *p_int << ", *p_int的地址: " << p_int << endl;return 0;
}
【注:类型是 const修饰的对象,或常量对象的非可变子对象。这种对象不能被修改:直接尝试这么做是编译时错误,而间接尝试这么做(例如通过到非常量类型的引用或指针修改常量对象)的行为未定义。】
输出结果:
i:50, i的地址: 0x7fffffffd9d4
*p_int:100, *p_int的地址: 0x7fffffffd9d4
从i和*p_int打印出的地址都是0x7fffffffd9d4可以看出,我们偷偷修改i的值成功了(但该行为是C++未定义的行为),但是为何i和*p_int的结果却是不同的,这就从侧面证实了const常量具有宏替换的特性,即程序在编译阶段就会其进行部分的替换,例如上述例子中对语句
cout << "i:" << i << ", i的地址: " << &i << endl;
在编译阶段替换为
cout << "i:" << 50 << ", i的地址: " << &i << endl;
因此导致我们输出的i的值为50。
同时,当我们想要通过使用非常量指针修改静态常量si时候,编译通过,但是在运行过程中导致程序崩溃(这就是不按规矩办事的后果,使用了C++未定义的行为,编译器也帮不了我们,最终导致程序挂掉)。
const的内部链接性
通常情况下,在未声明为 extern 的变量声明上使用 const 限定符,会给予该变量内部连接(即名称在用于声明变量的文件的外部是不可见的)的特性。即与static类似(详情可参考《C++避坑—关键字static的使用及注意事项》),但是与其不同的是其可以通过extern来改变其内部连接性。
const修饰指针和引用
/********指针********/
//指向const对象的指针
const int* p1; //const修饰的是int,表示p1指向的内容不能被修改
int const* p2; //const修饰的是int,表示p2指向的内容不能被修改//指向对象的const指针
int* const p3; //const修饰的是*,表示p3的指向不能被修改//指向const对象的const指针
const int* const p4; //第一个修饰的是int,第二个修饰的是*,表示p4指向的内容和p4的指向都不能被修改
const int const* p5; //同上,表示p5指向的内容和p5的指向都不能被修改/*注:从上面我们可以总结出来的一个规律:
const优先修饰其左边最近的类型,
如果左边没有,就修饰右边离他最近的那个*//********引用********/
const int a = 0;
//由于a1引用a之后,不能引用其他实体,所以对于const int&可以看作是const int* const
const int& a1 = a; int b = 0;
const int& b1 = b;//C++允许无限定类型的引用/指针能被转换成到 const 的引用/指针
const在类中的应用
非静态数据成员可以被cv限定符修饰,这些限定符写在函数声明中的形参列表之后。其中,带有不同cv限定符(或者没有)的函数具有不同的类型,它们可以相互重载。在具有cv限定符的成员函数内,*this拥有同向的cv限定。例子如下:
#include <iostream>using namespace std;class A {public:A(int a) : a(a){};void show() { cout << "void show(): " << a << endl; }void show() const { cout << "void show() const: " << a << endl; } // 重载/*这里void show() 等价于 void show(A* this)void show() const 等价于 void show(const A* this)因此该成员函数show()可以被重载*/void set_a(int n) { a = n; }/*void set_a(int n) const{a = n;//此时*this拥有const限制,导致a不能够被修改}//程序报错*/// void print_a() const 等价于 void print_a(const A* this)void print_a() const { cout << "print_a() const: " << a << endl; }private:int a;
};int main() {A a1(1);const A a2(2);a1.show();a2.show();a1.print_a(); // 非const对象可以调用const成员函数// 根本原因是A* this可以隐式转换const A* this// 最终调用void print_a(const A* this)// 即void print_a() consta2.print_a();a1.set_a(2);// a2.set_a(1); // 编译报错,const对象不能调用非const成员函数,根本原因是根本原因是const A* this可以隐式转换A* thisreturn 0;
}
输出结果:
void show(): 1
void show() const: 2
print_a() const: 1
print_a() const: 2
对于上述例子我们可以得出:
const对象不能调用非const成员函数。 =>const成员函数内部不能调用其他非cosnt成员函数。- 非
const对象可以调用const成员函数。=> 非cosnt成员函数可以调用其他cosnt成员函数。
volatile
volatile 主要作用是告诉编译器其修饰的目标是易变的,在编译的时候不要去优化它(例如读取的时候,都从目标内存地址读取),防止编译器误认为某段代码中目标是不会被改变,而造成过度优化。
注:编译器大部分情况是从内存读取变量的值的,但有时候编译器认为在某段代码中,某个变量的值是没有变化的,所以认为寄存器里的值跟内存里是一样的,为了提高读取速度,编译器可能会从寄存器中读取变量,但是在某些情况下变量的值被其他元素(如另外一个线程或者中断服务)修改,这样导致程序读取变量的值不是最新的,产生异常。
因此,volatile 关键字对于声明共享内存中可由多个进程访问的对象或用于与中断服务例程通信的全局数据区域很有用。如果某个目标被声明为 volatile,则每当程序访问它的时候,编译器都会重新加载内存中的值。 这虽然降低了目标的读取速度,但是保证了目标读取的正确性,这也是保证我们程序可预见性的唯一方法。
下面我们通过一个读取系统时间的例子来看一下volatile在实际开发过程中的应用:
#include <iostream>
#include <ctime>
#include <unistd.h>
// #include <windows.h> //win下为该头文件using namespace std;
int main()
{const time_t time_val = 0; time_t *p_time_t = const_cast<time_t *>(&time_val);time(p_time_t);cout << time_val << endl;// 休眠1ssleep(1); // linux下sleep函数,单位为秒// Sleep(1000); // win下的sleep函数,单位为毫秒time(p_time_t);cout << time_val << endl;return 0;
}
注:
time函数在ctime头文件中定义,其主要作用是获取系统当前时间,其原型如下:std::time_t time( std::time_t* arg );返回编码为
std::time_t对象的当前日历时间,并将它存储于arg所指向的对象,除非arg是空指针。
输出结果:
0
0
很明显结果不符合我们的预期,具体原因就是我们上面介绍的const常量具有宏替换的特性,编译器认为这段可以更好的优化,在编译阶段就对其进行了替换。那我们如何修改才能达到我们的实现呢?对,就是添加volatile关键字修饰,具体实现如下:
#include <iostream>
#include <ctime>
#include <unistd.h>
// #include <windows.h> //win下为该头文件using namespace std;
int main()
{volatile const time_t time_val = 0;time_t *p_time_t = const_cast<time_t *>(&time_val);time(p_time_t);cout << time_val << endl;// 休眠1ssleep(1); // linux下sleep函数,单位为秒// Sleep(1000); // win下的sleep函数,单位为毫秒time(p_time_t);cout << time_val << endl;return 0;
}
输出结果:
1680339937
1680339938
从输出结果看出,结果符合我们的预期。
这时候你可能会有疑问:volatile const是什么鬼?const表示time_val是常量,volatile表示time_val是可变的,难道是易变的常量?这不是矛盾吗?
这里我们确实可以将time_val成为易变的常量,只不过常量(不可修改)意味着time_val在其作用域中(这里指的是main函数中)是不可以被改变的,但是在其作用域外面(这里指的是time()函数内)是可以被改变的。volatile const其实是在告诉编译器,在main()函数内,time_val是const的,你帮我看点,我不能随意的修改,但这个值在作用域外可能会被其他东西修改,这玩意也是volatile的,你编译的时候也别优化它了,在每次读取的时候,也老老实实从它的存储位置重新读取吧。
注:
volatile const和const volatile是一样的,都代表易变的常量。
volatile修饰常量、指针和引用
volatile修饰常量指针和引用的使用方法域const类似,这里不做过多的解释,但需要注意的是volatile没有像const的内部链接属性。
volatile修饰函数的参数
int sequare(volatile int* ptr)
{return *ptr * *ptr;
}
上述例子是为了计算一个易变类型的int的平方,但是函数内部实现存在问题,因为
return *ptr * *ptr;
其处理逻辑类似下面的情况:
int a = *ptr;
int b = *ptr;
return a * b;
由于*ptr是易变的,因此a、b获取的值可能是不一样的,因此最好采用如下的方式:
int sequare(volatile int* ptr)
{int a = *ptr;return a * a;
}
mutable
mutable主要是为了突破const的某些限制而设定的,即允许常量类型对象相应的类成员可以被修改,其常在非引用非常量类型的非静态数据成员中出现。
在上面的介绍中,我们知道在在获取类某些状态的成员函数中,如果不涉及状态的变更,我们一般会将成员函数声明成const,这将意味着在该函数中,所有的成员函数都不可以被修改,但有些时候我们需要在该const函数中修改一些跟类状态无关的数据乘员,那么这时候就需要mutable发挥作用了,即将该需要修改的成员使用mutable修饰。
#include <iostream>
using namespace std;class A
{
public:A(int data = 0) : int_data(data), times(0) {}void show() const{times++; //因为times被mutable修饰,突破了const的限制cout << "data : " << int_data << endl;}int getNumOfCalls() { return times; }private:int int_data;mutable int times;
};int main()
{A a(1);cout << "a的show()被调用了:" << a.getNumOfCalls() << "次。" << endl;a.show();cout << "a的show()被调用了:" << a.getNumOfCalls() << "次。" << endl;return 0;
}
输出结果:
a的show()被调用了:0次。
data : 1
a的show()被调用了:1次。
上例void show()被const修饰后,导致在该函数内类成员不能被修改,但由于times被mutable修饰后,突破了const的限制,使得times在该函数内部可以被修改。
mutable的另一个应用场景就是用来移除lambda函数中按复制捕获的形参的const限制。通常情况下(不提供说明符),复制捕获的对象在lambda体内是 const 的,并且在其内部无法修改被捕获的对象,具体的例子如下:
#include <iostream>
using namespace std;int main() {int a = 0;const int b = 0;auto f1 = [=]() {/*a++; // 错误,不提供说明符时复制捕获的对象在 lambda 体内是 const 的。b++; // 错误,同上,且按值传递const也会传递进来*/return a;};auto f2 = [=]() mutable { // 提供mutable说明符a++; // 正确,mutable解除const限制。/*b++; // 错误,mutable无法突破b本身的const限制*/return a;};cout << a << ", " << b << endl; // 输出0, 0cout << f1() << ", " << f2() << endl; // 输出0, 1return 0;
}
总 结
const主要用来告诉编译器,被修饰的变量是不变类型的,在有些情况下可以对其进行优化,同时如果后面代码不小心修改了,编译器在编译阶段报错。在类的应用中,const对象不能调用非const成员函数,非const对象可以调用const成员函数。
volatile主要用来告诉编译器,被修饰变量是易变,在编译的时候不要对其进行优化,读取它的时候直接从其内存地址读取。
同时,const和volatile限定的引用和指针支持下列的隐式转换:
- 无限定类型的引用/指针能被转换成
const的引用/指针 - 无限定类型的引用/指针能被转换成
volatile的引用/指针 - 无限定类型的引用/指针能被转换成
const volatile的引用/指针 const类型的引用/指针能被转换成const volatile的引用/指针volatile类型的引用/指针能被转换成const volatile的引用/指针
对于const修饰的成员函数内类成员const的属性,可以通过使用对mutable来解除const限制。同样的,mutable也可以用来移除lambda函数中按复制捕获的形参的const限制。
文章首发公众号:iDoitnow 如果喜欢话,可以关注一下
相关文章:
C++ const、volatile和mutable关键字详解
对于cv(const 与 volatile)类型限定符和关键字mutable在《cppreference》中的定义为: cv可出现于任何类型说明符中,以指定被声明对象或被命名类型的常量性(constness)或易变性(volatility&#…...
MySQL实验四:数据更新
MySQL实验四:数据更新 目录MySQL实验四:数据更新导读表结构sql建表语句模型图1、 SQL更新:将所有学生的年龄增加1岁代码2、SQL更新:修改“高等数学”课程倒数三名成绩,在原来分数上减5分代码解析3、SQl更新:…...
商汤科技推出“日日新SenseNova”,大模型体系赋能人工智能新未来
2023年4月10日,商汤科技SenseTime技术交流日活动在上海举行,分享了以“大模型大算力”推进AGI(通用人工智能)发展的战略布局,并公布了商汤在该战略下的“日日新SenseNova”大模型体系。 公开信息显示,商汤科…...
【中创AI】斯坦福人工智能年度报告:AI论文发表量中国世界第一!
斯坦福以人为本人工智能研究所 (HAI) 发布了最新一期的 2023 AI 指数 (2023 AI Index) 报告,探讨了过去一年机器学习的发展。 (斯坦福HAI于2019年初成立,致力于研究新的AI方法,并研究该技术对社会的影响。其每年发布一份AI指数报…...
Java基础(五)面向对象编程(基础)
学习面向对象内容的三条主线 Java类及类的成员:(重点)属性、方法、构造器;(熟悉)代码块、内部类面向对象的特征:封装、继承、多态、(抽象)其他关键字的使用:…...
寻找CSDN平行世界的另一个你
本文由 大侠(AhcaoZhu)原创,转载请声明。 链接: https://blog.csdn.net/Ahcao2008 寻找CSDN平行世界的另一个你摘要前言列表测试目的摘要 本文作了一个测试,看看在 CSDN 的博文中,艾特()某个好友,TA是否能够…...
ChatGPT的发展对客户支持能提供什么帮助?
多数组织认为客户服务是一种开销,实际上还可以将客户服务看成是一种机会。它可以让你在销售后继续推动客户的价值。成功的企业深知,客户服务不仅可以留住客户,还可以增加企业收入。客户服务是被低估的手段,它可以通过推荐、见证和…...
数据安全评估体系建设
数据安全评估是指对重要数据、个人信息等数据资产的价值与权益、合规性、威胁、脆弱性、防护等进行分析和判断,以评估数据安全事件发生的概率和可能造成的损失,并采取相应的措施和建议。 数据安全评估的重要性和背景 1.国家法律法规下的合规需要 目前数…...
论文阅读 - ANEMONE: Graph Anomaly Detection with Multi-Scale Contrastive Learning
目录 摘要 1 简介 2 问题陈述 3 PROPOSED ANEMONE FRAMEWORK 3.1 多尺度对比学习模型 3.1.1 增强的自我网络生成 3.1.2 补丁级对比网络 3.1.3 上下文级对比网络 3.1.4 联合训练 3.2 统计异常估计器 4 EXPERIMENTS 4.1 Experimental Setup 4.1.1 Datasets 4.1.2 …...
数据密集型应用存储与检索设计
本文内容翻译自《数据密集型应用系统设计》,豆瓣评分高达 9.7 分。 什么是「数据密集型应用系统」? 当数据(数据量、数据复杂度、数据变化速度)是一个应用的主要挑战,那么可以把这个应用称为数据密集型的。与之相对的是…...
Spring Boot集成Redis实现keyspace监听 | Spring Cloud 34
一、前言 在前面我们通过以下章节对Redis的keyevent(键事件通知)使用有了基础的了解: Spring Boot集成Redis实现keyevent监听 | Spring Cloud 33 现在开始我们正式学习Redis的keyspace(键空间通知),在本…...
如何搭建chatGPT4.0模型-国内如何用chatGPT4.0
国内如何用chatGPT4.0 在国内,目前可以通过以下途径使用 OpenAI 的 ChatGPT 4.0: 自己搭建模型:如果您具备一定的技术能力,可以通过下载预训练模型和相关的开发工具包,自行搭建 ChatGPT 4.0 模型。OpenAI提供了相关的…...
【故障定位】基于多元宇宙算法的主动配电网故障定位方法研究(Matlab代码实现)
💥💥💞💞欢迎来到本博客❤️❤️💥💥 🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。 ⛳️座右铭&a…...
基于html+css的自适应展示1
准备项目 项目开发工具 Visual Studio Code 1.44.2 版本: 1.44.2 提交: ff915844119ce9485abfe8aa9076ec76b5300ddd 日期: 2020-04-16T16:36:23.138Z Electron: 7.1.11 Chrome: 78.0.3904.130 Node.js: 12.8.1 V8: 7.8.279.23-electron.0 OS: Windows_NT x64 10.0.19044 项目…...
DolphinDB +Python Airflow 高效实现数据清洗
DolphinDB 作为一款高性能时序数据库,其在实际生产环境中常有数据的清洗、装换以及加载等需求,而对于该如何结构化管理好 ETL 作业,Airflow 提供了一种很好的思路。本篇教程为生产环境中 ETL 实践需求提供了一个解决方案,将 Pytho…...
pip3 升级软件包时遇到超时错误解决方法
如果你在使用 pip3 升级软件包时遇到超时错误,可能是因为下载速度缓慢或网络不稳定。以下是解决方法: 更改 pip3 源:你可以切换到其他 pip3 源,例如清华、阿里等等,以提高下载速度。 pip3 install -i https://pypi.tun…...
Linux环境开机自启动
1.制作服务 在/etc/systemd/system/路径下创建kkFile.service文件 cd /etc/systemd/system/ vim kkFile.service2.写入如下内容 [Unit] DescriptionkkFile service [Service] Typeforking ExecStart/sinosoft/yjya/kkFileView-4.0.0/bin/startup.sh [Install] WantedBymulti…...
字节8年测试经验,送给想要学习自动化测试的同学6条建议
我的职业生涯开始和大多数测试人一样,开始接触都是纯功能界面测试。那时候在一家电商公司做测试,做了有一段时间,熟悉产品的业务流程以及熟练测试工作流程规范之后,效率提高了,工作比较轻松,也得到了更好的…...
快速搭建springboot websocket客户端
一、前言WebSocket 是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议。HTML5 定义的 WebSocket 协议,能更好的节省服务器资源和带宽,并且能够更实时地进行通讯。HTML5 定义的 WebSocket 协议,能更好的节省服务器资源和带宽&…...
Python 操作 MongoDB 详解
嗨害大家好鸭!我是芝士❤ 一、前言 MongoDB属于 NoSQL(非关系型数据库), 是一个基于分布式文件存储的开源数据库系统。 二、操作 MongoDB 1. 安装 pymongo python 使用第三方库来连接操作 MongoDB, 所以我们首先安…...
【Axure高保真原型】引导弹窗
今天和大家中分享引导弹窗的原型模板,载入页面后,会显示引导弹窗,适用于引导用户使用页面,点击完成后,会显示下一个引导弹窗,直至最后一个引导弹窗完成后进入首页。具体效果可以点击下方视频观看或打开下方…...
多模态2025:技术路线“神仙打架”,视频生成冲上云霄
文|魏琳华 编|王一粟 一场大会,聚集了中国多模态大模型的“半壁江山”。 智源大会2025为期两天的论坛中,汇集了学界、创业公司和大厂等三方的热门选手,关于多模态的集中讨论达到了前所未有的热度。其中,…...
K8S认证|CKS题库+答案| 11. AppArmor
目录 11. AppArmor 免费获取并激活 CKA_v1.31_模拟系统 题目 开始操作: 1)、切换集群 2)、切换节点 3)、切换到 apparmor 的目录 4)、执行 apparmor 策略模块 5)、修改 pod 文件 6)、…...
日语学习-日语知识点小记-构建基础-JLPT-N4阶段(33):にする
日语学习-日语知识点小记-构建基础-JLPT-N4阶段(33):にする 1、前言(1)情况说明(2)工程师的信仰2、知识点(1) にする1,接续:名词+にする2,接续:疑问词+にする3,(A)は(B)にする。(2)復習:(1)复习句子(2)ために & ように(3)そう(4)にする3、…...
渗透实战PortSwigger靶场-XSS Lab 14:大多数标签和属性被阻止
<script>标签被拦截 我们需要把全部可用的 tag 和 event 进行暴力破解 XSS cheat sheet: https://portswigger.net/web-security/cross-site-scripting/cheat-sheet 通过爆破发现body可以用 再把全部 events 放进去爆破 这些 event 全部可用 <body onres…...
跨链模式:多链互操作架构与性能扩展方案
跨链模式:多链互操作架构与性能扩展方案 ——构建下一代区块链互联网的技术基石 一、跨链架构的核心范式演进 1. 分层协议栈:模块化解耦设计 现代跨链系统采用分层协议栈实现灵活扩展(H2Cross架构): 适配层…...
C++ 基础特性深度解析
目录 引言 一、命名空间(namespace) C 中的命名空间 与 C 语言的对比 二、缺省参数 C 中的缺省参数 与 C 语言的对比 三、引用(reference) C 中的引用 与 C 语言的对比 四、inline(内联函数…...
相机Camera日志分析之三十一:高通Camx HAL十种流程基础分析关键字汇总(后续持续更新中)
【关注我,后续持续新增专题博文,谢谢!!!】 上一篇我们讲了:有对最普通的场景进行各个日志注释讲解,但相机场景太多,日志差异也巨大。后面将展示各种场景下的日志。 通过notepad++打开场景下的日志,通过下列分类关键字搜索,即可清晰的分析不同场景的相机运行流程差异…...
Web 架构之 CDN 加速原理与落地实践
文章目录 一、思维导图二、正文内容(一)CDN 基础概念1. 定义2. 组成部分 (二)CDN 加速原理1. 请求路由2. 内容缓存3. 内容更新 (三)CDN 落地实践1. 选择 CDN 服务商2. 配置 CDN3. 集成到 Web 架构 …...
2025季度云服务器排行榜
在全球云服务器市场,各厂商的排名和地位并非一成不变,而是由其独特的优势、战略布局和市场适应性共同决定的。以下是根据2025年市场趋势,对主要云服务器厂商在排行榜中占据重要位置的原因和优势进行深度分析: 一、全球“三巨头”…...
