C++威力强大的助手 --- const

news2024/12/29 10:46:28

 Welcome to 9ilk's Code World

       

(๑•́ ₃ •̀๑) 个人主页:        9ilk

(๑•́ ₃ •̀๑) 文章专栏:     C++之旅 


const是个奇妙且非比寻常的东西,博主从《Effective C++》一书中认识到关于const更深层次的理解,写此博客进行巩固。


🏠 const的作用

  • const可以指定一个“不该被改动的对象”,编译器会强制实施这个约束
  • const可修饰类外的作用域(全局域,命名空间域等)中的常量,文件,函数或静态常量等
  • const可修饰类内的static和non-static成员

🏠 const修饰指针和迭代器

📌 const修饰指针

const修饰指针无外乎三种:

char greet[] = "hello";
char* pg = greet;

//指针指向内容不可变
const char * pg = greet;
char const * pg = greet;

//指针本身不可变
char * const pg = greet;

//指针本身和指针执行内容都不可变
const char * const pg = greet;

总结:

1.const出现在*号左边时,表示指针指向的内容是常量。

2.const出现在*号右边时,表示指针本身是常量。

3.const出现在*号左右边时,表示指针本身和指针指向的内容都是常量

📌 const与迭代器

  • 声明迭代器为const

我们把迭代器看作一个类型,它的作用就像个T*的指针,当声明迭代器为const时,类比const int是int这个int类型变量不可能,此时说明迭代器类型变量是不能变的,也就是类比修饰一个T* const的指针。

vector<int> v  = {4,1,2,9,10};
const vector<int>::iterator it = v.begin();

it++; //错误,迭代器类型对象不可变
(*it)++; //正确,指向内容可以变 

我们也可以利用下面代码进行验证,当生成解决方案时会对其进行报错:

template<class T>
void fun()
{
	const T x;
	cout << typeid(x).name() << endl;
	x++;
}
int main()
{
	//op o;
	//cout << o[1];
	vector<int> v = {3,5};
	vector<int>::iterator it = v.begin();;
	fun<vector<int>::iterator>();
	return 0;
}
  • const迭代器

  如果你希望迭代器所指的东西不可被改动(也就是类似希望模拟个const T*指针),你需要的是const_iterator.

std::vector<int> v = {1,5,2,3,4};

std::vector<int>::const_iterator it = v.begin();

*it  = 10; //错误,此时是const迭代器,指向内容不可改变。
it++; //此时不是声明迭代器为const,本身迭代器可以改变。

注:我们平时所说的const迭代器就是const_iterator,请注意区分与前者声明迭代器为const进行区分。

🏠 const与函数

📌 const与函数参数

建议:如果对于局部对象或对于函数参数没有改动的需求时,建议将他们声明为const,此时可以避免不必要的麻烦。比如:将“==”键值的“=”。

📌const与函数返回值

  • 函数返回一个常量,往往可以降低因客户错误而造成的意外,而且具有安全性和高效性

    假设有这样的一个有理数类:

class Rational;

const Rational operator*(const Rational& r1,const Rational& r2)
{//...};

Rational a,b,c;
...
(a*b) = c;

由于是有理数类,客户可能会将乘积再做一次赋值,此时将operator*的返回值类型设置为const就可以避免这样无意义的赋值动作。

🏠 const与成员函数

📌 const成员函数的好处

1. const成员函数易使class接口比较容易被理解,清楚知道哪个函数可以改动对象内容而哪个函数不行。

2. 有了const成员函数,const对象就能被“操作”,因为const对象只能调用const成员函数;能操作const对象,就能利用传引用(const 类类型 &)提高效率。

  • const成员函数与普通成员函数的区别

我们知道普通成员函数参数都有一个隐含的this指针,是不能修改的,也就是类 * const this;而const成员函数的this指针,类型是const 类 * const this,也就是说此时对象的内容也不能被修改

class Date
{
public:
//... 
	const char& operator[](size_t position)const //operator[]for const对象
	{
		return _date[position];
	}

	char& operator[](size_t position) //operator[]for non-const 对象
	{
		return _date[position];
	}

private:
	string _date;

};

int main()
{

 Date d1("2024/08/04");
 cout << d1[0] << endl; //调用的是non-const版本

 const Date d2("2024/08/04")
 cout << d2[0]; //调用的是const版本
 return 0;
}

  • 说明

1. 两成员函数如果只是常量性不同,也是可以被重载的。因为两个版本隐含的this类型不同,同时

返回值类型也跟着不同。

2.const对象d2的对象指针为const Date* ,更匹配const版本的operator[],因此调用const版本;

而非const对象d1的对象指针为Date*,更匹配非const版本。

📌 bitwise const VS logical const

        到这里,我们思考一下什么成员函数如果是const意味着什么?目前有以下两个流行概念需要向大家介绍一下:

  • bitwise constness

这种观点的人认为,成员函数只有在不改变对象之任何成员变量时才可以说是const,也就是说不改变对象内的任何一个bit。这种论点的好处是很容易找到违反点:只需找到对成员变量的赋值动作即可。

这种观点正是C++对常量性的定义,因此const成员函数不可以更改对象内任何非静态成员变量。

如果只有指针(而非所指向内容)隶属于对象,(比如有这样的一个类,将数据存储于char*而不是string),不修改char*而修改char*指向内容,此时编译器是认为是bitwise constness的,可以正常通过编译。

class Block
{
public:
	Block( char* ch )
		:pText(ch)
	{}
	 char& operator[](size_t position)const
	{
		return pText[position];
	}


private:
	char* pText;
};

int main()
{
	char arr[] = "hello";
	char* ch = arr;
	const Block cctb(ch);
    char* pc = &cctb[0];
	*pc = 's';
	return 0;
}

此时可以通过这个漏洞修改成员变量指针指向的内容而不违法const

(注:如果成员变量是string,由于operator[]需要访问pText成员变量,并且你需要保证Block对象的状态不被修改,所以pTextoperator[]调用也必须是const的,因此不会出现上述漏洞。)

这种情况导出所谓的logical constness.

  • logical constness
class BigArray 
{
    vector<int> v; 
    int accessCounter;
public:
    int getItem(int index) const 
   { 
        accessCounter++;
        return v[index];
   }
};

此类提供了一个 getItem 接口,除此之外,为了计算外部访问数组的次数,该类还设置了一个计数器 accessCounter ,可以看到用户每次调用 getItem 接口,accessCounter 就会自增,很明显,这里的成员 v 是核心成员,而 accessCounter 是非核心成员

我们希望接口 getItem 不会修改核心成员,而不考虑非核心成员是否被修改,此时 getItem 所具备的 const 特性就被称为 logic constness

问题:在这个函数中虽然accesCounter修改对对象而言可以被接受,但是编译器只认bitwise constness不允许修改怎么办?

  • mutable

mutable是C++中一个与const相关的摆动场,他可以释放掉non-static成员变量的bitwise constness约束。

class BigArray {
    vector<int> v; 
    mutable int accessCounter; //像这样的成员变量可能总是会被更改。
public:
    int getItem(int index) const { 
        accessCounter++;
        return v[index];
    }
};

总结:当const成员函数接受某些修改之后不改变成员函数逻辑状态的成员变量时,这时可以使用mutable来释放const约束。但注意mutable可能会违反对象的不变性,需要慎用。

📌 在const和non-const成员函数中避免重复

   对于“bitwise constness”非我所欲的问题,mutable是个解决办法,但并不能解决所有的难题。假设有个类,类内的operator[ ]不单只是返回一个引用指向某字符,也执行边界检验,志记访问信息,甚至可能进行数据完善性检验等...把所有这些放进const版本和非const版本的operator[ 里的问题是会导致代码膨胀以及大量代码重复:

class Text
{

public:
  char& operator[](size_t pos)
  {
      //... 边界检验
      //... 志记数据访问
      //... 检验数据完整性
     return text[pos];
  }

 const char& operator[](size_t pos)const
 {
      //... 边界检验
      //... 志记数据访问
      //... 检验数据完整性
     return text[pos];
 }


private:
   string text;

};

避免代码重复的安全做法:

class Text
{

public:
  const char& operator[](size_t pos)
  {
      //... 边界检验
      //... 志记数据访问
      //... 检验数据完整性
     return text[pos];
  }

 char& operator[](size_t pos)const
 {
    return   (char&)(((const Text &)(*this))[pos]);
 }


private:
   string text;

};
  • 说明

1. 这份代码进行了两次转型实现了了“运用const成员函数实现其non-const兄弟”,避免了代码重

复。

2.第一次转型将(*this)也就是这个对象类型强转为const Text&是为了匹配const版本调用const版

本的operator[ ],否则会陷入无限调用非const版本;第二次转型则是用来从const operator[ ]的

返回值中移除const,这其中并未有权限放大的问题,强转是可行的。

3. 反向调用也就是“令const版本调用non-const版本以避免代码重复”是一件错误的事,因为non-

const并未承诺绝不改变其对象的逻辑状态,因此这种做法可能使得对象被改动,当然编译器也不

允许const调用非const,也是一种权限的放大。


总结:

1. 将某些东西声明为const可帮助编译器侦测出错误用法,比如错误的赋值行为使得不必要的对象改动。

2.const可施加于任何作用域的对象,函数参数,函数返回值,成员函数。

3.如果在const成员函数内想改变非核心成员变量以达目的,可利用mutable解除const约束。

4.当const与非const成员函数实质有着等价的实现且代码有大量重复时,可考虑复用const版本以实现非const版本。

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

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

相关文章

无人机之运输的优势

无人机在进行运输任务时使用的是电力驱动&#xff0c;从而可以减少对环境的污染和碳排放&#xff0c;对于改善大气质量和减少碳足迹具有积极的意义。 无人机运输可以避免人为错误和事故的发生&#xff0c;通过预先设定的飞行路线&#xff0c;进行精确点投放。此外&#xff0c;还…

零基础小白备考PMP需要多长时间?

PMP考试在中国大陆&#xff0c;平均每三个月安排一次考试。报名缴费一般在考试前两个月&#xff0c;报完名后开始进入备考&#xff0c;所以基本上是2-3个月的时间。 PMP考试备考不是越久越好&#xff0c;把备考战线拉得太长 &#xff0c;我们的精力都是有限的&#xff0c;后期…

学习HTTP2中的HPACK算法

文章目录 HPACK动态表中的数据插入点两种基本数据类型 Integer String HPACK 专业术语&#xff1a; Header Field&#xff08;头部字段&#xff09;&#xff1a;指的是一个由name - value组成的键值对。名称和值都被视为不透明的字节序列。Dynamic Table&#xff08;动态表&a…

哪个软件可以识别字幕并生成文本?5款最佳工具分享

你是否曾在深夜&#xff0c;抱着手机或电脑&#xff0c;对着那些充满异国情调却无从下手的外语视频感到束手无策&#xff1f; 那些或激昂、或深情、或幽默的对话&#xff0c;因为语言的隔阂而变得遥不可及&#xff0c;让你的观看体验大打折扣。 别急&#xff0c;今天我来告诉你…

AI绘画进阶 ComfyUI 实战教程:轻松给图片添加文字,附工作流教程使用

大家好&#xff0c;我是设计师阿威 在AI绘画中书写文字一直是个老大难的问题&#xff0c;直到SDXL的出现&#xff0c;文字生成才迎来转机&#xff0c;可以在提示词中指定一些英文字符&#xff0c;不过也是经常出错&#xff0c;生成中文就更加不可求了。 本文介绍一种在图片中…

2018-Comment-网鼎杯复现,二次注入

进入靶场 发现只有一个发帖功能&#xff0c;尝试发帖 提交后要去登录&#xff0c;但这里提示了账号密码&#xff0c;但密码后三位不知&#xff0c;可以尝试暴力破解 bp抓包 假设后三位是数字&#xff0c;设置payload 爆破成功&#xff0c;后三位为666 登录成功 但除了发帖没有…

安泰电压放大器放大的是什么信号

电压放大器是一种广泛应用于电子设备中的放大器&#xff0c;它主要用于放大电压信号。电压信号是指以电压形式传输的信号&#xff0c;可以是来自于传感器、音频设备、无线通信设备等各种电子设备中的信号。 电压放大器的基本原理 电压放大器是一种电子设备&#xff0c;它可以将…

使用idea对spring全家桶的各种项目进行创建

目录 1. 简介2. spring2.1 简介2.2 创建 3. springmvc3.1 介绍3.2 创建 4. springboot4.1 简介4.2 创建&#xff08;仅仅就其中一种&#xff09; 5. 其他&#xff1a;maven6. 参考链接 1. 简介 因为总是分不清spring全家桶&#xff0c;所以就在这里进行一个总结。 2. spring …

Java毕业设计-基于SSM框架的大型商场会员管理系统项目实战(附源码+论文)

大家好&#xff01;我是程序猿老A&#xff0c;感谢您阅读本文&#xff0c;欢迎一键三连哦。 &#x1f49e;当前专栏&#xff1a;Java毕业设计 精彩专栏推荐&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; &#x1f380; Python毕业设计 &…

在手机中安装Fiddler CA证书后,完美解决Fiddler无法进行手机HTTPS请求抓包的难题!

Fiddler抓包的通用配置 关与fidder抓取手机包的配置方法就不多说了&#xff0c;网上有很多方式&#xff0c;配置方式如下图&#xff1a; 但是即使成功配置&#xff0c;在抓取手机中https包时也会出错&#xff0c;这个时候&#xff0c;我们就需要在手机中安装Fiddler的 CA证书!…

80.SAP ME - SAP ERP向SAP ME传输物料主数据的方法

目录 SAP ERP与ME传输物料的几种方式 1.自动传输物料到SAP ME 2.手动发送 2.1 BD10 发送物料 2.2 DRFOUT 执行数据复制 2.3 POIM 发送主数据 SAP ERP与ME传输物料的几种方式 1.自动传输物料到SAP ME 这是标准方法&#xff0c;需要全面的配置&#xff0c;当在ERP里修改物…

Powerdesigner连接mysql数据库,逆向工程生成ER图 (保姆级教程:下载->连接->配置)看这一篇就够了

一、下载powerdesigner 下载的教程请看如下链接&#xff0c;我太懒了&#xff0c;直接借鉴&#xff01; 把别大佬的博客搬过来了嘿嘿~我真聪明&#xff01;ㄟ( ▔, ▔ )ㄏ 操作到完成汉化就好&#xff01;&#xff01;第5步不看了&#xff0c;别按那个走&#xff0c;因为新手…

数据获取- 抓住股市脉搏,用Python轻松获取比亚迪股票数据!

Hey小伙伴们&#xff0c;今天给大家带来一个超级实用的项目教程——如何用Python和tushare库来获取比亚迪的股票数据&#xff01;&#x1f31f; &#x1f50d; 项目背景 股市是个充满机遇与挑战的地方&#xff0c;而获取实时准确的股票数据则是每个投资者的基础技能。今天&am…

2024华数杯全国大学生数学建模竞赛B题-VLSI电路单元的自动布局

超大规模集成电路(VLSI&#xff0c;VeryLarge Scale Integration)将大量电路单元集成于单一芯片。随着设计复杂度增加&#xff0c;如今开展VLSI设计已离不开电子设计自动化(EDA&#xff0c;Electronic DesignAutomation)工具的支持。EDA作为算法密集型产业&#xff0c;需要对数…

Python 爬虫入门(七):requests 库的使用「详细介绍」

Python 爬虫入门&#xff08;七&#xff09;&#xff1a;requests 库的使用「详细介绍」 前言1. 初识 requests1.1 安装 requests 库1.2 发送 GET 请求1.3 发送 POST 请求 2. HTTP 请求详解2.1 请求方法2.2 请求头2.3 请求参数 3. 处理响应3.1 响应内容3.2 响应状态码3.3 响应头…

控件的拖拽移动及定时器及画家

定时器时时都在调用 virtual void timerEvent(QTimerEvent *event);//定时器更新函数 timer new QTimer(this);timer->start(1000);connect(timer,&QTimer::timeout,[this](){static int num 0;ui->led1->display(num);if(num10){timer->stop();}}); 画图事…

【杂谈】计算机世界的原理——二进制数和位运算以及各种进制数的表示方法

【杂谈】计算机世界的原理——二进制数和位运算以及各种进制数的表示方法 1.常见位运算符和常用操作2.各种进制数的表示方法 1.常见位运算符和常用操作 大家都知道&#xff0c;计算机中是使用二进制数储存数据的。在这一篇文章&#xff0c;我将会带领大家初步探索二进制数。在后…

8G 显存玩转书生大模型 Demo 进阶任务

使用 LMDeploy 完成 InternLM-XComposer2-VL-1.8B 的部署&#xff0c;并完成一次图文理解对话&#xff0c;记录复现过程并截图。 使用 LMDeploy 完成 InternVL2-2B 的部署&#xff0c;并完成一次图文理解对话&#xff0c;记录复现过程并截图。 总结&#xff1a;貌似没加上下文…

2024最新最全面的Selenium 3.0 + Python自动化测试框架

文档说明 Selenium是一个用于Web应用程序自动化测试的工具。Selenium测试直接运行在浏览器中&#xff0c;就像真正的用户在操作一样。 Selenium测试的主要功能包括&#xff1a; 测试与浏览器的兼容性&#xff1a;测试应用程序是否能很好的工作在不同的浏览器和操作系统之上。…

聚焦汽车软件开发与测试:静态代码扫描、单元测试与集成测试等方面的实践应用

2024年7月18-19日&#xff0c;龙智携汽车软件开发及管理解决方案创新亮相2024 ATC汽车软件与安全技术周。龙智技术支持部负责人&Atlassian认证专家叶燕秀、龙智功能安全高级工程师景玉鑫在活动主会场联合发表了精彩演讲&#xff0c;分享推动汽车软件开发与功能安全的创新实…