【C++】类的默认成员函数:深入剖析与应用(下)

💯前言

回顾上篇文章👉【C++】类的默认成员函数:深入剖析与应用(上)中对构造函数拷贝构造函数和析构函数的讨论,强调这些默认成员函数在类的创建、初始化和销毁过程中的重要性。

引出本篇将继续探讨剩余的重要默认成员函数,以更全面地理解类的内部机制。深入理解和掌握这些默认成员函数,对于每一位 C++ 开发者来说都至关重要。


💯赋值运算符重载

⭐运算符重载

C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。


函数名字为:关键字operator后面接需要重载的运算符符号。

函数原型:返回值类型 operator操作符(参数列表)

#include <iostream>// 1. 以一个简单的复数类为例,展示运算符重载// 复数类
class Complex {
public:// 实部和虚部double real;double imag;// 构造函数Complex(double r = 0, double i = 0) : real(r), imag(i) {}// 2. 重载加法运算符 +Complex operator+(const Complex& other) const {// 返回一个新的复数,实部为两个复数实部之和,虚部为两个复数虚部之和return Complex(real + other.real, imag + other.imag);}// 3. 重载输出流运算符 <<,这里需要声明为友元函数,因为它的第一个参数是流对象,不是类的成员friend std::ostream& operator<<(std::ostream& os, const Complex& c);
};// 4. 定义输出流运算符 << 的函数体
std::ostream& operator<<(std::ostream& os, const Complex& c) {os << c.real;if (c.imag >= 0) {os << " + ";} else {os << " - ";}os << std::abs(c.imag) << "i";return os;
}int main() {Complex c1(3, 4);Complex c2(1, -2);// 5. 使用重载的加法运算符Complex sum = c1 + c2;std::cout << "c1 + c2 = " << sum << std::endl;return 0;
}

❗注意 :

  • 不能通过连接其他符号来创建新的操作符:比如operator@
  • 重载操作符必须有一个类类型参数
  • 用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不能改变其含义
  • 作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this
  •   .*    ::    sizeof   ?:    .   注意以上5个运算符不能重载。这个经常在笔试选择题中出现。

🏆代码解释: 

#include <iostream>// 1. 以一个简单的自定义类为例
class MyClass {
public:int value;// 构造函数MyClass(int val = 0) : value(val) {}// 2. 重载加法运算符 +MyClass operator+(const MyClass& other) const {return MyClass(value + other.value);}
};int main() {MyClass obj1(5);MyClass obj2(3);// 3. 使用重载的加法运算符MyClass result = obj1 + obj2;std::cout << "Result value: " << result.value << std::endl;return 0;
}

 👆在上述代码中:

 

规则一:不能通过连接其他符号来创建新的操作符

 
  • 代码中只对已有的加法运算符+进行了重载,不能像规则中提到的那样创建一个operator@这样的新运算符。
 

规则二:重载操作符必须有一个类类型参数

 
  • 在重载加法运算符的函数MyClass operator+(const MyClass& other) const中,有一个参数是类类型const MyClass& other,满足该规则。
 

规则三:用于内置类型的运算符,其含义不能改变

 
  • 代码中没有尝试改变内置整型的加法运算符含义,比如不能让内置的int + int做其他奇怪的操作。
 

规则四:作为类成员函数重载时,其形参看起来比操作数数目少 1,因为成员函数的第一个参数为隐藏的 this

 
  • 当使用obj1 + obj2时,实际上是调用obj1.operator+(obj2),这里隐藏了一个指向obj1this指针,所以看起来参数比操作数少一个。
 

规则五:  .   ::   sizeof   ?:  . 这五个运算符不能重载*

 
  • 代码中没有尝试对这五个运算符进行重载,符合规则。
 

综上所述,通过这段代码展示了运算符重载的一些规则的实际应用情况

⭐赋值运算符重载

1.赋值运算符重载格式
  • 参数类型:const T&,传递引用可以提高传参效率
  • 返回值类型:T&,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值
  • 检测是否自己给自己赋值
  • 返回*this :要复合连续赋值的含义
    #include <iostream>template <typename T>
    class MyClass {
    public:T value;MyClass(T val = 0) : value(val) {}// 重载赋值运算符MyClass& operator=(const MyClass& other) {if (this!= &other) {value = other.value;}return *this;}
    };int main() {MyClass<int> obj1(5);MyClass<int> obj2(3);// 测试赋值运算符obj1 = obj2;std::cout << "obj1.value: " << obj1.value << std::endl;// 测试连续赋值MyClass<int> obj3(7);obj3 = obj2 = obj1;std::cout << "obj2.value: " << obj2.value << std::endl;std::cout << "obj3.value: " << obj3.value << std::endl;return 0;
    }

    👆在上述代码中:

  • MyClass& operator=(const MyClass& other)重载了赋值运算符,参数类型为const T&,传递引用避免了不必要的对象拷贝,提高了传参效率。

  • 返回值类型为MyClass&,返回引用可以提高返回的效率,使得支持连续赋值成为可能。

  • 在函数内部首先检测是否自己给自己赋值,即通过if (this!= &other)进行判断,如果是自己给自己赋值则直接返回,避免不必要的操作。

  • 最后返回*this,满足连续赋值的含义,例如obj3 = obj2 = obj1,先计算obj2 = obj1,然后obj3再赋值为这个结果。

2.赋值运算符只能重载成类的成员函数不能重载成全局函数

🏆代码解释: 

#include <iostream>class MyClass {
public:int value;MyClass(int val = 0) : value(val) {}// 成员函数形式重载赋值运算符MyClass& operator=(const MyClass& other) {if (this!= &other) {value = other.value;}return *this;}
};int main() {MyClass obj1(5);MyClass obj2(3);// 使用成员函数重载的赋值运算符obj1 = obj2;std::cout << "obj1.value after assignment: " << obj1.value << std::endl;// 尝试用全局函数重载赋值运算符(这是错误的做法,编译会报错)// MyClass operator=(MyClass lhs, const MyClass& rhs) {//     lhs.value = rhs.value;//     return lhs;// }return 0;
}

👆在上述代码中:

 
  • MyClass类内部,以成员函数的形式正确地重载了赋值运算符operator=。在main函数中,可以成功地使用这个重载的赋值运算符进行对象赋值操作。

  • 如果尝试像注释部分那样以全局函数的形式重载赋值运算符,编译器会报错。这是因为赋值运算符的重载有特殊的规则,它通常只能作为类的成员函数进行重载,以确保正确地处理对象的内部状态和资源管理等问题。

3. 用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝。

❗注意 : 

内置类型成员变量是直接赋值的,而自定义类型成员变量需要调用对应类的赋值运算符
重载完成赋值。

 🏆代码解释: 

#include <iostream>class AnotherClass {
public:int data;AnotherClass(int val = 0) : data(val) {}AnotherClass& operator=(const AnotherClass& other) {if (this!= &other) {data = other.data;}return *this;}
};class MyClass {
public:int num;AnotherClass obj;MyClass(int n = 0, int val = 0) : num(n), obj(val) {}
};int main() {MyClass obj1(5, 10);MyClass obj2(3, 15);// 编译器生成的默认赋值运算符被调用,逐字节拷贝obj1 = obj2;std::cout << "obj1.num: " << obj1.num << std::endl;std::cout << "obj1.obj.data: " << obj1.obj.data << std::endl;return 0;
}

👆在上述代码中:

 
  • MyClass类中有一个内置类型成员变量num和一个自定义类型成员变量obj(属于AnotherClass类)
  • 当没有显式实现MyClass的赋值运算符重载时,编译器会生成一个默认的赋值运算符重载,以值的方式逐字节拷贝。对于内置类型成员变量num,直接进行赋值操作。对于自定义类型成员变量obj,会调用AnotherClass类中的赋值运算符重载来完成赋值。
  • main函数中,通过obj1 = obj2演示了这个过程,可以看到赋值后的结果。 

⭐前置++和后置++重载

#include <iostream>class Counter {
private:int count;
public:Counter(int c = 0) : count(c) {}// 前置++重载Counter& operator++() {++count;return *this;}// 后置++重载Counter operator++(int) {Counter temp(*this);++count;return temp;}int getCount() const {return count;}
};int main() {Counter c(5);std::cout << "Initial count: " << c.getCount() << std::endl;// 前置++++c;std::cout << "After pre-increment: " << c.getCount() << std::endl;// 后置++Counter c2 = c++;std::cout << "After post-increment (original object): " << c.getCount() << std::endl;std::cout << "Value of new object after post-increment: " << c2.getCount() << std::endl;return 0;
}

👆在上述代码中:

  • 定义了一个Counter类,其中包含一个私有成员变量count
  • 前置++运算符重载函数operator++()直接增加count的值,并返回修改后的对象引用,实现了先增加再返回的操作。
  • 后置++运算符重载函数operator++(int)创建了一个当前对象的副本,然后增加count的值,最后返回副本,实现了先返回再增加的操作。注意这里的参数int只是一个占位符,用于区分前置和后置运算符重载。
  • main函数中,演示了前置++和后置++的不同行为


💯const成员

将const修饰的“成员函数”称之为const成员函数,const修饰类成员函数,实际修饰该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改。

 我们来看以下代码:

#include <iostream>class Date {
public:Date(int year, int month, int day) : _year(year), _month(month), _day(day) {}// 非 const 成员函数void Print() {std::cout << "Print()" << std::endl;std::cout << "year:" << _year << std::endl;std::cout << "month:" << _month << std::endl;std::cout << "day:" << _day << std::endl << std::endl;}// const 成员函数void Print() const {std::cout << "Print()const" << std::endl;std::cout << "year:" << _year << std::endl;std::cout << "month:" << _month << std::endl;std::cout << "day:" << _day << std::endl << std::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();
}int main() {Test();return 0;
}

分析如下:

 
  1. const Date d2(2022, 1, 13);是一个 const 对象,尝试调用非 const 成员函数Print()会导致编译错误,因为 const 对象只能调用 const 成员函数。
  2. Date d1(2022, 1, 13);是非 const 对象,可以调用非 const 成员函数Print(),也可以调用 const 成员函数Print() const
  3. 在 const 成员函数Print() const中,如果尝试调用非 const 成员函数,会导致编译错误,因为 const 成员函数不能调用非 const 成员函数。
  4. 在非 const 成员函数Print()中,可以调用 const 成员函数Print() const,因为非 const 成员函数可以调用 const 成员函数。


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

这两个默认成员函数一般不用重新定义 ,编译器默认会生成。 

#include <iostream>class Date {
public:Date* operator&() {return this;}const Date* operator&() const {return this;}private:int _year; // 年int _month; // 月int _day; // 日
};

这两个运算符一般不需要重载,使用编译器生成的默认取地址的重载即可,只有特殊情况,才需
要重载,比如想让别人获取到指定的内容!


💯总结

C++ 类的默认成员函数有

构造函数、拷贝构造函数、析构函数👉【C++】类的默认成员函数:深入剖析与应用(上)

赋值运算符重载、const 成员函数及取地址操作符重载等。它们分别在对象创建、复制、销毁、赋值、只读访问及特定地址获取等场景发挥重要作用。

掌握这些对编写高质量 C++ 代码至关重要。🌟🌟🌟


以后我将深入研究继承、多态、模板等特性,并将默认成员函数与这些特性结合,以解决更复杂编程问题!欢迎关注我👉【A Charmer】

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

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

相关文章

UG NX12.0建模入门笔记:1.2 鼠标的基本操作

文章目录 前言&#xff1a;鼠标的操作1.鼠标左键&#xff1a;单击—>单选&#xff1b;长按并滑动—>框选。2.鼠标右键&#xff1a;在不同的地方单击弹出不同的菜单。3.鼠标中键&#xff1a;滚动中键—>放大缩小【镜头拉近拉远】。4.鼠标中键&#xff1a;摁住鼠标中键&…

下载 jdk 历史版本的方法

先访问Oracle官网 Oracle | Cloud Applications and Cloud Platform 点击Products展示所有的铲平 选择 Java 这里有所有的版本&#xff0c;自己选择需要的版本 Java Archive | Oracle 如果返回 Request Header Or Cookie Too Large 可以通过以下方法清除Cookies

StarRocks大批量数据导入方案-使用 Routine Load 导入数据

本文详细介绍如何使用Routine Load 导入数据 一、准备工作 1.1 安装基础环境 主要是安装StarRocks和Kafka&#xff0c;本文直接跳过不做详细介绍~ 二、概念及原理 2.1 概念 导入作业&#xff08;Load job&#xff09; 导入作业会常驻运行&#xff0c;当导入作业的状态为 R…

python之selenium接管打开的谷歌浏览器窗口——隐藏爬虫特征,跳过登陆弹窗验证

文章目录 引言使用selenium接管打开的谷歌浏览器总结 引言 我们知道通过selenium打开的浏览器与本地电脑上打开的浏览器是不同的&#xff0c;selenium通过插件打开浏览器页面会显示爬虫特征信息&#xff0c;且在访问某些网站时&#xff0c;很容易被检测出是一个爬虫机器&#x…

linux 环境运行 jenkins.war包,有可能会出现字体问题,jdk版本:11 jenkins 版本:2.420

jenkins的目录&#xff1a; /usr/jenkins 启动命令 java -Djava.awt.headlesstrue sudo timedatectl set-timezone Asia/Shanghai-Xmx1024m -jar jenkins.war --httpPort8090 任意目录启动&#xff1a; nohup java -Djava.awt.headlesstrue -Xms1024m -Xmx1024m -jar /usr/j…

QT--QPushButton设置文本和图标、使能禁能、信号演示

按钮除了可以设置显示文本之外&#xff0c;还可以设置图标 文本 可以获取和设置按钮上显示的文本 // 获取和设置按钮的文本 QString text() const void setText(const QString &text)该属性&#xff0c;既可以在 Qt 设计师右侧的属性窗口中修改&#xff0c;也可以在代码…

OQE-OPTICAL AND QUANTUM ELECTRONICS

文章目录 一、征稿简介二、重要信息三、服务简述四、投稿须知五、联系咨询 一、征稿简介 二、重要信息 期刊官网&#xff1a;https://ais.cn/u/3eEJNv 三、服务简述 四、投稿须知 1.在线投稿&#xff1a;由艾思科蓝支持在线投稿&#xff0c;请将文章全文投稿至艾思科蓝投稿系…

linux环境下C程序的编译过程以及makefile的简单使用

在windows下&#xff0c;很多用来进行编程软件对于写好的文件&#xff0c;点击编译即可生成想要文件。如.exe可执行文件&#xff0c;.hex文件或者.bin文件等等。软件为我们省略了很多事。但是对于linux初学者来说&#xff0c;初次接触linux系统&#xff0c;面对命令行黑框框有点…

NetSuite Comparative Income Statement Amount如何选取自定义期间?

最近用户有一个关于自定义报表的需求也帮我们又增加了一个关于Report的忽略点&#xff0c;即如何在自定义报表比较利润表Comparative Income Statement的时候能够让相对金额Comparative Amount实现和金额Amount实现同比的关系。也就是当金额显示为 Jan 2024-Oct 2024时&#xf…

(38)MATLAB分析带噪信号的频谱

文章目录 前言一、MATLAB仿真代码二、仿真结果画图总结 前言 本文给出带噪信号的时域和频域分析&#xff0c;指出频域分析在处理带噪信号时的优势。 首先使用MATLAB生成一段信号&#xff0c;并在信号上叠加高斯白噪声得到带噪信号&#xff0c;然后对带噪信号对其进行FFT变换&…

从头预训练一只迷你 LLaMA 3_llama3 预训练预处理

我将向你展示如何使用 LLama 3.1&#xff08;一个本地运行的模型&#xff09;来执行GraphRAG操作&#xff0c;总共就50号代码。。。 首先&#xff0c;什么是GraphRAG&#xff1f;GraphRAG是一种通过考虑实体和文档之间的关系来执行检索增强生成的方式&#xff0c;关键概念是节…

AndroidLogger 使用问题

Q1&#xff1a;解压zip后&#xff0c;启动Notepad未看到AndroidLogger工具栏 请检查plugins下安装位置是否正确&#xff0c;必须与下图一致&#xff0c;再确认Notepad 是否为 x64 &#xff1f; Q2&#xff1a;使用 adb 可以显示已连接&#xff0c;但是获取不到日志 暂时不确定问…

理工科考研想考计算机,湖南大学、重大、哈工大威海、山东大学,该如何选择?

C哥专业提供——计软考研院校选择分析专业课备考指南规划 计算机对理工科同学来说&#xff0c;还是性价比很高的&#xff0c;具有很大的优势&#xff01; 一、就业前景广阔 高需求行业 在当今数字化时代&#xff0c;计算机技术几乎渗透到了各个领域&#xff0c;无论是互联网…

Spring Boot实现接口限流

API限流是一种重要的策略&#xff0c;用于控制对API的访问速率&#xff0c;以保护后端服务免受过载和滥用。以下是API限流的必要性&#xff1a; 防止服务过载&#xff1a; 当API的请求量突然激增时&#xff0c;如果没有限流措施&#xff0c;可能会导致服务器资源耗尽&#xff0…

【部署篇】RabbitMq-03集群模式部署

一、准备主机 准备3台主机用于rabbitmq部署&#xff0c;文章中是在centos7上安装部署rabbitmq3.8通过文章中介绍的方式可以同样在centos8、centos9上部署&#xff0c;只需下载对应的版本进行相同的操作。 主机IP角色说明192.168.128.31种子节点192.168.128.32普通节点192.16…

2.Node.js 缓冲器(Buffer)

二、常用模块 2.1Buffer(缓冲器) 2.1.1概念 Buffer是一个类似于数组的对象&#xff0c;用于表示固定长度的字节序列 Buffer本质是一段内存空间&#xff0c;专门用来处理二进制数据 2.2.2特点 Buffer大小固定无法调整&#xff1b; Buffer性能较好&#xff0c;可以直接操…

科技云报到:大模型时代下,向量数据库的野望

科技云报到原创。 自ChatGPT爆火&#xff0c;国内头部平台型公司一拥而上&#xff0c;先后发布AGI或垂类LLM&#xff0c;但鲜有大模型基础设施在数据层面的进化&#xff0c;比如向量数据库。 在此之前&#xff0c;向量数据库经历了几年的沉寂期&#xff0c;现在似乎终于乘着Ch…

Linux驱动开发——设备树

文章目录 1 什么是设备树&#xff1f;2 DTS、DTB和DTC3 DTS语法3.1 dtsi头文件3.2 设备节点3.3 标准属性3.4 根节点compatible属性3.5 向节点追加或修改内容 4 创建小型模板设备树5 设备树在系统中的体现6 绑定信息文档7 设备树常用OF操作函数7.1 查找节点的OF函数7.2 查找父/子…

Unity Meta Quest 开发调试工具 Immersive Debugger

XR 开发者社区链接&#xff1a; 赠送原版GPT、完整课程、项目下载、项目孵化宣发、答疑、投融资&#xff08;YY&#xff09; 此工具可以在头显当中对 Unity 脚本的参数进行调整&#xff08;相当于在编辑器 Inspector 里调整脚本参数&#xff09;&#xff0c;并且还可以查看 Un…

免费送源码:Java+B/S+MySQL springboot电影推荐系统 计算机毕业设计原创定制

摘 要 随着互联网与移动互联网迅速普及&#xff0c;网络上的电影娱乐信息数量相当庞大&#xff0c;人们对获取感兴趣的电影娱乐信息的需求越来越大,个性化的电影推荐系统成为一个热门。然而电影信息的表示相当复杂&#xff0c;己有的相似度计算方法与推荐算法都各有优势&#…