【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.rhkb.cn/news/452493.html

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

相关文章

【工具变量】A股上市企业大数据应用(2001-2023年)-参考柏淑嫄实践

数据简介&#xff1a;企业数字化转型的浪潮孕育出大数据&#xff0c;大数据技术是在数据处理和应用中释放大数据多元价值的必要手段。大数据作为企业发展的战略资源和生产要素对企业转型发展具有重要意义。对上市企业大数据应用程度进行测算不仅有助于了解大数据相关技术在企业…

Triton Inference Server 架构原理

文章目录 TensorRT-LLM & Triton Server 部署回顾部署梳理Triton 架构 为什么要使用 backend &#xff1f;triton_model_repo 目录结构Ensemble 模式BLS 模式 上篇文章进行了 TensorRT-LLM & Triton Server 部署 &#xff0c;本篇简单讲讲 Triton Inference Server 的架…

ECCV2024 Tracking 汇总

一、OneTrack: Demystifying the Conflict Between Detection and Tracking in End-to-End 3D Trackers paper&#xff1a; https://www.ecva.net/papers/eccv_2024/papers_ECCV/papers/01174.pdf 二、VETRA: A Dataset for Vehicle Tracking in Aerial Imagery paper&#…

基于ECS和NAS搭建个人网盘

前言 在数字化时代&#xff0c;数据已成为我们生活中不可或缺的一部分。个人文件、照片、视频等数据的积累&#xff0c;使得我们需要一个安全、可靠且便捷的存储解决方案。传统的物理存储设备&#xff08;如硬盘、U盘&#xff09;虽然方便&#xff0c;但存在易丢失、损坏和数据…

2013 lost connection to MySQL server during query

1.问题 使用navicat连接doris&#xff0c;会有这个错误。 2.解决 换低版本的navicat比如navicat11。

【LVGL快速入门(二)】LVGL开源框架入门教程之框架使用(UI界面设计)

零.前置篇章 本篇前置文章为【LVGL快速入门(一)】LVGL开源框架入门教程之框架移植 一.UI设计 介绍使用之前&#xff0c;我们要学习一款LVGL官方的UI设计工具SquareLine Studio&#xff0c;使用图形化设计方式设计出我们想要的界面&#xff0c;然后生成对应源文件导入工程使用…

人工智能公司未达到欧盟人工智能法案标准

关注公众号网络研究观获取更多内容。 据路透社获得的数据显示&#xff0c;领先的人工智能&#xff08;AI&#xff09;模型在网络安全弹性和防止歧视性输出等领域未能满足欧洲关键监管标准。 《欧盟人工智能法案》将在未来两年分阶段实施&#xff0c;旨在解决人们对这些技术在…

【计网】从零开始理解TCP协议 --- 拥塞控制机制,延迟应答机制,捎带应答,面向字节流

时间就是性命。 无端的空耗别人的时间&#xff0c; 其实是无异于谋财害命的。 --- 鲁迅 --- 从零开始理解TCP协议 1 拥塞控制2 延迟应答3 捎带应答4 面向字节流5 TCP异常情况TCP小结 1 拥塞控制 尽管TCP拥有滑动窗口这一高效的数据传输机制&#xff0c;能够确保在对方接收…

倍福TwinCAT程序中遇到的bug

文章目录 问题描述&#xff1a;TwinCAT嵌入式控制器CX5140在上电启动后&#xff0c;X001网口接网线通讯灯不亮&#xff0c;软件扫描不到硬件网口 解决方法&#xff1a;硬件断电重启后&#xff0c;X001网口恢复正常 问题描述&#xff1a;TwinCAT软件点击激活配置后&#xff0c;…

RHCE----时间服务器

配置 需要两个服务器&#xff0c;一个服务器&#xff08;服务端IP&#xff1a;192.168.19.130&#xff0c;客户端&#xff1a;192.168.19.131&#xff09; 客户端&#xff08;client131&#xff09; [rootserver1 ~]# vim /etc/chrony.conf 添加阿里云服务器&#xff0c;…

VMware虚拟机软件安装、卸载

VMware是一个软件.这个软件可以刻画出来多个不同配置的计算机硬件(裸机). VMware只能负责产生裸机,要想使用这台机器.需要为其安装操作系统. VMware常见的场景就是用来安装Linux操作系统.... 我们以后要学习大数据hadoop软件,hadoop软件在linux环境下安装.因此我们需要有台装有…

Kylin-Server-10-SP1通过网络升级到SP3

环境说明 虚拟化环境&#xff1a;VMware Workstation 16 Pro 测试镜像&#xff1a;Kylin-Server-10-SP1-Release-Build04-20200711-x86_64.iso Kylin-Server-V10-SP3-General-Release-2212-X86_64.iso 远程连接工具&#xff1a;MobaXterm 虚拟机配置&#xff1a;4C 4G 20G硬盘…

【深度学习中的注意力机制1】11种主流注意力机制112个创新研究paper+代码——缩放点积注意力(Scaled Dot-Product Attention)

【深度学习中的注意力机制1】11种主流注意力机制112个创新研究paper代码——缩放点积注意力&#xff08;Scaled Dot-Product Attention&#xff09; 【深度学习中的注意力机制1】11种主流注意力机制112个创新研究paper代码——缩放点积注意力&#xff08;Scaled Dot-Product A…

5G NR:UE初始接入信令流程浅介

UE初始接入信令流程 流程说明 用户设备&#xff08;UE&#xff09;向gNB-DU发送RRCSetupRequest消息。gNB-DU 包含 RRC 消息&#xff0c;如果 UE 被接纳&#xff0c;则在 INITIAL UL RRC MESSAGE TRANSFER 消息中包括为 UE 分配的低层配置&#xff0c;并将其传输到 gNB-CU。IN…

小白投资理财 - 解读资产指标

小白投资理财 - 解读资产指标 资产指标详情总资产&#xff08;Total Assets&#xff09;净资产&#xff08;Net Assets&#xff09;资产负债率&#xff08;Debt to Asset Ratio&#xff09;固定资产周转率&#xff08;Fixed Asset Turnover Ratio&#xff09;总资产周转率&…

软件开发的项目管理的风险有哪些?

软件开发项目管理中可能面临的风险&#xff1a; 序号风险类型描述1需求不明确项目需求没有被清晰定义或频繁变更&#xff0c;导致开发方向不明确或需要重做工作。2技术风险采用的技术可能存在缺陷或不兼容&#xff0c;或者团队缺乏必要的技术技能。3资源不足项目可能因为人力…

大数据存储计算平台EasyMR:大数据集群动态扩缩容,快速提升集群服务能力

在当今的数据驱动时代&#xff0c;组织面临着数据量的爆炸性增长。为了有效管理和存储这些数据&#xff0c;许多组织依赖于 Hadoop 这样的分布式存储系统。Hadoop 集群通过在多个节点上存储数据的冗余副本&#xff0c;提供了高可靠性和可扩展性。然而&#xff0c;随着数据量的不…

深⼊理解指针(2)

目录 1. 数组名的理解 2. 使⽤指针访问数组 3. ⼀维数组传参的本质 4. ⼆级指针 5. 指针数组 6. 指针数组模拟⼆维数组 1. 数组名的理解 我们在使⽤指针访问数组的内容时&#xff0c;有这样的代码&#xff1a; int arr[10] {1,2,3,4,5,6,7,8,9,10}; int *p &arr[…

Java项目-基于Springboot的福聚苑社区团购系统项目(源码+说明).zip

作者&#xff1a;计算机学长阿伟 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、ElementUI等&#xff0c;“文末源码”。 开发运行环境 开发语言&#xff1a;Java数据库&#xff1a;MySQL技术&#xff1a;SpringBoot、Vue、Mybaits Plus、ELementUI工具&#xff1a;IDEA/…

字节 HLLM 论文阅读

github连接&#xff1a;https://github.com/bytedance/HLLM 探讨问题&#xff1a; 推荐LLM的三个关键问题&#xff1a; LLM预训练权重通常被认为是对世界知识的概括&#xff0c;其对于推荐系统的价值&#xff1f;对推荐任务进行微调的必要性&#xff1f;LLM是否可以在推荐系统…