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

探索C++中的不变之美:const与构造函数的深度剖析

W...Y的主页😊

代码仓库分享💕 


🍔前言:

关于C++的博客中,我们已经了解了六个默认函数中的四个,分别是构造函数、析构函数、拷贝构造函数以及函数的重载。但是这些函数都是有返回值与参数的。提到参数与返回值我们就会想到可以修饰它们的一个关键字const。而且关于构造函数,我们并没有将内容全部讲完,所以我们今天这篇博客就是对const关键字的讲解以及构造函数的补充!话不多说,我们直接开始。

目录

const成员

 取地址及const取地址操作符重载

再谈构造函数

初始化列表


const成员

const对于我们有语言基础的人并不陌生,就是关于修饰变量使其成为一个不可修改的内容。在C++中也是如此,但是C++中类的出现,伴随的出现的就是一系列的成员函数,而被const修饰的成员函数就是const成员函数。

我们来看一下这段代码:

class Date
{
public:
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
void Print()
{
cout << "Print()" << endl;
cout << "year:" << _year << endl;
cout << "month:" << _month << endl;
cout << "day:" << _day << endl << endl;
}private:
int _year; // 年
int _month; // 月
int _day; // 日
};
void Test()
{
Date d1(2022,1,13);
d1.Print();
const Date d2(2022,1,13);
d2.Print();
}

上述代码是有错的,在编译器编译时就会出现

这是为什么呢?当我们使用const修饰d2时,d2的类型就是const Date类型,而我们去调用print函数去打印时,print隐藏的函数参数其实是Date* const this,所以参数不匹配导致程序报错。

那this指针的参数应该怎么是隐藏的,所以C++规定在函数后加上const的实际意义就是在this指针前加const。

所以正确的print函数应该在函数后加上const进行修饰。 

void Print() const
{
cout << "Print()const" << endl;
cout << "year:" << _year << endl;
cout << "month:" << _month << endl;
cout << "day:" << _day << endl << endl;
}

 这样无论在自定义类型的前面是否加上const进行修饰,都可以对上述函数进行调用。所以在调用时,我们可以将一个变量的权限放小,但是绝不能进行放大。

随之又会引出一个问题:成员函数有const进行修饰,无论实参有无const都能进行调用,那我们需不需要将所以的成员函数都加上const呢?

其实是不用的,我们加上const进行修饰的this指针指向的内容不被修改,如果我们的成员函数需要修改this指针所指向的内容,我们就不用去加const。

Date operator++(int);
Date& operator+=(int day);
Date& operator-=(int day);
Date& operator++();
Date& operator--();
Date operator--(int);

比如上述的运算符重载就不用加const,因为这些都是改变this指向的内容的。

bool Date::operator>(const Date& y) 
{if (_year > y._year){return true;}else if (_year == y._year && _month > y._month){return true;}else if (_year == y._year && _month == y._month && _day > y._day){return true;}return false;
}
int main()
{
Date s1();
const Date s2();
s1 < s2;//正确
s2 < s1;//报错
return 0;
}

 上述代码是<的运算符重载,在之前的博客中我们已经进行了复现,但是当我们的参数类型一个被const修饰,另一个没有const修饰当我们调用此函数s2 < s1时就会出现报错,因为不能将实参的权限进行放大,也就是参数类型不匹配,所以这种类似内容的函数就必须加上const进行修饰。

void Print() const;
bool operator==(const Date& y) const;
bool operator!=(const Date& y) const;
bool operator>(const Date& y) const;
bool operator<(const Date& y) const;
bool operator>=(const Date& y) const;
bool operator<=(const Date& y) const;
int operator-(const Date& d) const;
Date operator+(int day) const;
Date operator-(int day) const;

总结:

1.能定义const的成员函数都应该定义成const,这样const成员与非const成员都可以进行调用。调用条件(权限平移)(权限缩小)。

2.要修改成员变量的函数不能定义const。

 取地址及const取地址操作符重载

取地址操作运算符重载也是六大默认函数之一,通过重定义对对象进行取地址操作就是取地址操作符的重载。这两个默认成员函数一般不用重新定义 ,编译器默认会生成。为什么会是两个呢?因为有无const是有区别的,他们会形成函数重载。

Date* operator&()
{cout << "Date* operator&()" << endl;return this;}const Date* operator&()const
{cout << "const Date* operator&()const" << endl;return this;}
int main()
{// const对象和非const对象都可以调用const成员函数const Date d1(2023, 10, 31);d1.Print();Date d2(2023, 1, 1);d2.Print();cout << &d1 << endl;cout << &d2 << endl;return 0;
}

这里许多人就会有疑问,这里不会产生二义性吗?针对cout << &d2 << endl;因为d2没有被const修饰,所以既可以调用理论上来说两个函数都可以进行调用。但是C++会优先匹配最合适的类型,因为d2没有被const进行修饰,所以优先会调用没有被const修饰的函数。

如果将没有const修饰的函数进行屏蔽,两种实参照样可以进行调用。

再谈构造函数

之前我们就讲过构造函数已经将了有80%了,现在我们将构造函数中剩下的20%进行收尾。我们先来复习一下之前的构造体系:

class Date
{
public:
Date(int year, int month, int day)
{_year = year;_month = month;_day = day;
}
private:
int _year;
int _month;
int _day;
};

这是函数体内初始化,我们进行对象的初始化时就会调用此函数,当我们没有构造函数时,我们就会调用C++提供的默认构造函数进行匹配。

构造函数的特征:
1. 函数名与类名相同。
2. 无返回值。
3. 对象实例化时编译器自动调用对应的构造函数。
4. 构造函数可以重载。

虽然上述构造函数调用之后,对象中已经有了一个初始值,但是不能将其称为对对象中成员变量
的初始化,构造函数体中的语句只能将其称为赋初值,而不能称作初始化。因为初始化只能初始
化一次,而构造函数体内可以多次赋值。

现在我们还有一种可以初始化的办法:

 Date(int year, int month, int day):_year(year),_month(month)         ,_day(day),_ref(year),_n(1){// 初始化列表}

这样的初始化我们称之为初始化列表。

初始化列表

初始化列表:以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个"成员变量"后面跟
一个放在括号中的初始值或表达式。

class Date
{
public:
Date(int year, int month, int day)
: _year(year), _month(month), _day(day)
{}
private:
int _year;
int _month;
int _day;
};

这样写我们照样可以进行初始化。这两种方法都可以进行初始化,他们的区别在哪呢?

上述的例子使用两种初始化都可以,但是有些成员变量就只能使用初始化列表进行初始化。因为在类中私有成员都只是声明,没有开辟空间,而特殊的成员变量只能在定义的时候进行赋值,比如:引用、const修饰……所以我们要在初始化列表进行定义。

在内置类型中构造函数将内置类型进行赋随机值,而特殊内置类型只能赋值一次所以不能再被改变,所以我们就要一次性将其赋值好!!!

class A
{
public:
A(int a)
:_a(a)
{}
private:
int _a;
};
class B
{
public:
B(int a, int ref)
:_aobj(a)
,_ref(ref)
,_n(10)
{}
private:
A _aobj;  // 没有默认构造函数
int& _ref;  // 引用
const int _n; // const
};

所以引用成员变量、const成员变量、自定义类型成员(且该类没有默认构造函数时)都要进行初始化列表赋值。

当我们去定义一种自定义类型时,如果没有对应的构造函数,程序就会报错。所以当我们定义一个类嵌套在另一个类时,在创建类的构造函数时创建成全缺省参数的构造函数。

下面给大家看一个题:

class A
{
public:A(int a):_a1(a),_a2(_a1){}void Print() {cout<<_a1<<" "<<_a2<<endl;}
private:int _a2;int _a1;
};
int main() {A aa(1);aa.Print();
}
A. 输出1  1
B.程序崩溃
C.编译不通过
D.输出1  随机值

这道题应该选D,这是为什么呢?成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关 ,所以_a2是在私有成员中先声明的,所以在初始化中先定义_a2,因为_a1在后面所以先为随机值,所以_a2为随机值,_a1为1.

最后我们来总结一下初始化列表解决的问题:

1、必须在定义的地方显示初始化  引用  const   没有默认构造自定义成员
2、有些自定义成员想要显示初始化,自己控制
3.   尽量使用初始化列表初始化
4. 构造函数能不能只要初始化列表,不要函数体初始化
答:不能,因为有些初始化或者检查的工作,初始化列表也不能全部搞定

class Stack
{
public:Stack(int n = 2):_a((int*)malloc(sizeof(int)* n)), _top(0), _capacity(n){//...//cout << "Stack(int n = 2)" << endl;if (_a == nullptr){perror("malloc fail");exit(-1);}memset(_a, 0, sizeof(int) * n);}

当我们进行动态内存开辟时,我们就需要进行函数内外的配合,因为在初始化列表中不能进行其他操作,而在函数体内可以,为了避免开辟失败,我们需要进行指针的检查,以及其他操作。所以80-100%初始化列表搞定,还有需要用函数体,他们可以混着用


以上就是本次全部内容,感谢大家观看!!!

相关文章:

探索C++中的不变之美:const与构造函数的深度剖析

W...Y的主页&#x1f60a; 代码仓库分享&#x1f495; &#x1f354;前言&#xff1a; 关于C的博客中&#xff0c;我们已经了解了六个默认函数中的四个&#xff0c;分别是构造函数、析构函数、拷贝构造函数以及函数的重载。但是这些函数都是有返回值与参数的。提到参数与返回…...

DDoS类型攻击对企业造成的危害

超级科技实验室的一项研究发现&#xff0c;每十家企业中&#xff0c;有四家(39%)企业没有做好准备应对DDoS攻击&#xff0c;保护自身安全。且不了解应对这类攻击最有效的保护手段是什么。 由于缺乏相关安全知识和保护&#xff0c;使得企业面临巨大的风险。 当黑客发动DDoS攻击…...

深入理解JVM虚拟机第十五篇:虚拟机栈常见异常以及如何设置虚拟机栈的大小

大神链接:作者有幸结识技术大神孙哥为好友,获益匪浅。现在把孙哥视频分享给大家。 孙哥链接:孙哥个人主页 作者简介:一个颜值99分,只比孙哥差一点的程序员 本专栏简介:话不多说,让我们一起干翻JavaScript 本文章简介:话不多说,让我们讲清楚JavaScript里边的Math 文章目…...

Rocketmq5延时消息最大时间

背景 Rocketmq5中支持延时消息的时间&#xff0c;通过Message.setDelayTimeSec可以设置延时消息的精确时间。 问题 当设置时间超过3天时出现异常 org.apache.rocketmq.client.exception.MQBrokerException: CODE: 13 DESC: timer message illegal, the delay time should no…...

uniapp @click点击事件在新版chrome浏览器点击没反应

问题描述 做项目时&#xff0c;有一个弹出选择的组件&#xff0c;怎么点都不出来&#xff0c;最开始还以为是业务逻辑限制了不能点击。后来才发现别人的电脑可以点出来&#xff0c;老版本的浏览器也可以点出来&#xff0c;最后定位到是新版的chrome就不行了 这是我的浏览器版本…...

beanDefinition读取器

编程式定义 BeanDefinition&#xff1a;自定义一个BeanDefinition&#xff0c; AbstractBeanDefinition beanDefinition BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition(); 设置beanClass 注册到容器中 B…...

linux 上flink单机安装详解

目录 一 准备安装包 二 解压 三 配置环境变量 四 验证是否部署成功 一 准备安装包 官网地址&#xff1a; Downloads | Apache Flink 百度网盘资源&#xff1a; 链接: https://pan.baidu.com/s/15aXmF3JLxnOlPiDxId637Q?pwdsqsx 提取码: sqsx 这里准备的版本是flink1.13…...

数据链路层中存在的报文ip,arp,rarp

IP数据报 ARP请求/应答报 RARP请求/应答报 IP数据报 这里的目的地址和源地址是MAC地址。 这个被称为 MAC 地址&#xff0c;是一个网卡的物理地址&#xff0c;用十六进制&#xff0c;6 个 byte 表示。 MAC 地址是一个很容易让人误解的地址。因为 MAC 地址号称全球唯一&…...

【Tricks】PC端微信输入时,文本出现右对齐情况怎么恢复

应该是摁到某个快捷键&#xff0c;于是光标就变成如下图所示的样子&#xff1a; 如果再输入字符&#xff0c;则字符就会变成下图所示的样子&#xff08;对齐输入框右侧&#xff09;&#xff1a; 解决办法&#xff1a;ctrl J 解决办法&#xff1a;ctrl J 解决办法&#xff1…...

python使用requests+excel进行接口自动化测试

在当今的互联网时代中&#xff0c;接口自动化测试越来越成为软件测试的重要组成部分。Python是一种简单易学&#xff0c;高效且可扩展的语言&#xff0c;自然而然地成为了开发人员的首选开发语言。而requests和xlwt这两个常用的Python标准库&#xff0c;能够帮助我们轻松地开发…...

Android NDK开发详解之调试和性能分析的ndk-gdb

Android NDK开发详解之调试和性能分析的ndk-gdb 要求用法选项 NDK 包含一个名为 ndk-gdb 的 Shell 脚本&#xff0c;可以启动命令行原生调试会话。偏好使用 GUI 的用户则应阅读在 Android Studio 中调试这篇文档。 要求 要运行命令行原生调试&#xff0c;必须满足以下要求&am…...

html获取网络数据,列表展示 一

html获取网络数据&#xff0c;列表展示 js遍历json数组中的json对象 image.png || - 判断数据是否为空&#xff0c;为空就显示 - <!DOCTYPE html> <html><head><meta charset"utf-8"><title>网页列表</title></head><b…...

配置管理工具-Confd

1 简介 1.1 Confd介绍 Confd是一个轻量级的配置管理工具。通过查询后端存储&#xff0c;结合配置模板引擎&#xff0c;保持本地配置最新&#xff0c;同时具备定期探测机制&#xff0c;配置变更自动reload。对应的后端存储可以是etcd&#xff0c;redis、zookeeper等。[1] 通过…...

0基础学习PyFlink——使用DataStream进行字数统计

大纲 sourceMapSplittingMapping ReduceKeyingReducing 完整代码结构参考资料 在《0基础学习PyFlink——模拟Hadoop流程》一文中&#xff0c;我们看到Hadoop在处理大数据时的MapReduce过程。 本节介绍的DataStream API&#xff0c;则使用了类似的结构。 source 为了方便&…...

OpenCV官方教程中文版 —— 图像去噪

OpenCV官方教程中文版 —— 图像去噪 前言一、原理二、OpenCV 中的图像去噪1.cv2.fastNlMeansDenoisingColored()2.cv2.fastNlMeansDenoisingMulti() 前言 目标 • 学习使用非局部平均值去噪算法去除图像中的噪音 • 学习函数 cv2.fastNlMeansDenoising()&#xff0c;cv2.fa…...

AcWing 102. 最佳牛围栏(前缀和+二分+DP)

AcWing 102. 最佳牛围栏 1、问题 2、分析 &#xff08;1&#xff09;暴力做法 看到这道题以后&#xff0c;我们可以先想一个最暴力的做法&#xff0c;就是我们去枚举所有长度至少为 F F F的区间&#xff0c;然后求出这个区间的和&#xff0c;再求出这个区间的平均值。最后在…...

React-表单受控绑定和获取Dom元素

一、表单受控组件 1.声明一个react状态 说明&#xff1a;useState const [value,setValue]useState("") 2.核心绑定流程 2.1绑定react状态 <div><input value{value}type"text"></input> 2.2绑定onChange事件 说明&#xff1a;e.…...

python hashlib模块及实例

hashlib 模块密码加密密码撞库密码加盐 一&#xff0c;hashlib模块 hashlib模块是用来为字符串进行加密的模块&#xff0c;通过该作用就可以为用户的密码进行加密。 通过模块中的hash算法可以为任意长度的字符串加密成长度相同的一串hash值。该hash算法得到的hash值有一下几个…...

垃圾回收GC

为什么要有垃圾回收? JVM之所以要有垃圾回收,是因为它能够自动管理内存,避免内存泄漏和内存溢出的问题,垃圾回收机制会自动检测和清理不再使用的对象,释放内存空间,使得开发者不需要手动管理内存,降低了开发难度和错误风险,同时,垃圾回收还可以优化内存分配,提高程序性能和响…...

kubernetes-service微服务

目录 一、service微服务 二、Ipvs模式 三、ClusterIP 1.ClusterIP 2.headless 四、NodePort 1.NodePort 2.默认端口 五、LoadBalancer 1.LoadBalancer 2.metallb 六、ExternalName 一、service微服务 Kubernetes Service微服务是一种基于Kubernetes的微服务架构&…...

Oracle查询表空间大小

1 查询数据库中所有的表空间以及表空间所占空间的大小 SELECTtablespace_name,sum( bytes ) / 1024 / 1024 FROMdba_data_files GROUP BYtablespace_name; 2 Oracle查询表空间大小及每个表所占空间的大小 SELECTtablespace_name,file_id,file_name,round( bytes / ( 1024 …...

解决Ubuntu22.04 VMware失败的问题 ubuntu入门之二十八

现象1 打开VMware失败 Ubuntu升级之后打开VMware上报需要安装vmmon和vmnet&#xff0c;点击确认后如下提示 最终上报fail 解决方法 内核升级导致&#xff0c;需要在新内核下重新下载编译安装 查看版本 $ vmware -v VMware Workstation 17.5.1 build-23298084$ lsb_release…...

Objective-C常用命名规范总结

【OC】常用命名规范总结 文章目录 【OC】常用命名规范总结1.类名&#xff08;Class Name)2.协议名&#xff08;Protocol Name)3.方法名&#xff08;Method Name)4.属性名&#xff08;Property Name&#xff09;5.局部变量/实例变量&#xff08;Local / Instance Variables&…...

在 Nginx Stream 层“改写”MQTT ngx_stream_mqtt_filter_module

1、为什么要修改 CONNECT 报文&#xff1f; 多租户隔离&#xff1a;自动为接入设备追加租户前缀&#xff0c;后端按 ClientID 拆分队列。零代码鉴权&#xff1a;将入站用户名替换为 OAuth Access-Token&#xff0c;后端 Broker 统一校验。灰度发布&#xff1a;根据 IP/地理位写…...

从零实现STL哈希容器:unordered_map/unordered_set封装详解

本篇文章是对C学习的STL哈希容器自主实现部分的学习分享 希望也能为你带来些帮助~ 那咱们废话不多说&#xff0c;直接开始吧&#xff01; 一、源码结构分析 1. SGISTL30实现剖析 // hash_set核心结构 template <class Value, class HashFcn, ...> class hash_set {ty…...

Java面试专项一-准备篇

一、企业简历筛选规则 一般企业的简历筛选流程&#xff1a;首先由HR先筛选一部分简历后&#xff0c;在将简历给到对应的项目负责人后再进行下一步的操作。 HR如何筛选简历 例如&#xff1a;Boss直聘&#xff08;招聘方平台&#xff09; 直接按照条件进行筛选 例如&#xff1a…...

HarmonyOS运动开发:如何用mpchart绘制运动配速图表

##鸿蒙核心技术##运动开发##Sensor Service Kit&#xff08;传感器服务&#xff09;# 前言 在运动类应用中&#xff0c;运动数据的可视化是提升用户体验的重要环节。通过直观的图表展示运动过程中的关键数据&#xff0c;如配速、距离、卡路里消耗等&#xff0c;用户可以更清晰…...

七、数据库的完整性

七、数据库的完整性 主要内容 7.1 数据库的完整性概述 7.2 实体完整性 7.3 参照完整性 7.4 用户定义的完整性 7.5 触发器 7.6 SQL Server中数据库完整性的实现 7.7 小结 7.1 数据库的完整性概述 数据库完整性的含义 正确性 指数据的合法性 有效性 指数据是否属于所定…...

MFC 抛体运动模拟:常见问题解决与界面美化

在 MFC 中开发抛体运动模拟程序时,我们常遇到 轨迹残留、无效刷新、视觉单调、物理逻辑瑕疵 等问题。本文将针对这些痛点,详细解析原因并提供解决方案,同时兼顾界面美化,让模拟效果更专业、更高效。 问题一:历史轨迹与小球残影残留 现象 小球运动后,历史位置的 “残影”…...

PostgreSQL——环境搭建

一、Linux # 安装 PostgreSQL 15 仓库 sudo dnf install -y https://download.postgresql.org/pub/repos/yum/reporpms/EL-$(rpm -E %{rhel})-x86_64/pgdg-redhat-repo-latest.noarch.rpm# 安装之前先确认是否已经存在PostgreSQL rpm -qa | grep postgres# 如果存在&#xff0…...