【C++修行之道】类和对象(四)运算符重载

目录

一、 运算符重载

函数重载和运算符重载有什么关系?

二、.*运算符的作用

三、运算符重载的正常使用

四、重载成成员函数

五、赋值运算符重载

1.赋值运算符重载格式

传值返回和引用返回

有没有办法不生成拷贝?

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

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

六、前置++和后置++重载 


一、 运算符重载

C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。
函数名字为:关键字operator后面接需要重载的运算符符号
函数原型:返回值类型 operator操作符(参数列表)。

注意:

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

函数重载和运算符重载有什么关系?

他们之间各论各的,没有关系

运算符重载:让自定义类型可以使用运算符,并且控制运算符的行为,增强可读性

函数重载:可以让函数名相同,参数不同的函数存在。

多个同一运算符的重载可以构成函数重载

 

二、.*运算符的作用

class OB
{
public:void func(){cout << "void func()" << endl;}
};typedef void(OB::* Ptrfunc)();// 成员函数指针类型int main()
{// 函数指针void (*ptr)();Ptrfunc fp = &OB::func;// 定义成员函数指针p指向函数func// 成员函数规定要加&才能取到函数指针OB temp;// 定义ob类对象temp(temp.*fp)();// 调用成员函数return 0;
}

typedef void(OB::* Ptrfunc)(); // 成员函数指针类型
  • 使用 typedef 定义了一个名为 Ptrfunc 的类型,这个类型是指向OB类中无参数、无返回值的成员函数的指针类型。
  • OB::*的含义,它表示这是一个指向OB类成员函数的指针 Ptrfunc
Ptrfunc fp = &OB::func; // 定义成员函数指针fp指向函数func
  • 在C++中,成员函数与普通函数在内存中的表示和存储方式有所不同。成员函数不仅包含函数的代码,还隐含地包含了一个指向类对象的this指针,这使得成员函数能够访问和修改对象的状态。
  • 在语法上,&类名::成员函数名是用来获取成员函数地址的标准方式
  • 如果不使用&运算符,编译器可能会将OB::func解析为对成员函数的调用
  • 运算符在这里的作用是明确告诉编译器:“我要的是这个成员函数的地址,而不是执行这个函数”。这样,编译器就能正确地生成获取成员函数地址的代码,而不是尝试调用该函数。
(temp.*fp)(); // 调用成员函数
  • .*:这是一个特殊的成员访问运算符,用于通过对象实例和成员函数指针来调用成员函数。当你有一个指向成员函数的指针,并且想要在某个特定的对象上调用这个函数时,就需要使用这个运算符。 
  • 在temp对象上,通过成员函数指针fp来调用它所指向的成员函数

三、运算符重载的正常使用

class Date
{
public:Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}
//private:int _year;int _month;int _day;};// 重载成全局, 无法访问私有成员, 怎么解决?
// 1.提供这些成员get和set
// 2.友元
// 3.重载成成员函数(一般重载成这种)
//// 这里会发现运算符重载成全局的就需要成员变量是公有的,那么问题来了,封装性如何保证?
// 这里其实可以用我们后面学习的友元解决,或者干脆重载成成员函数 
bool operator==(const Date& d1, const Date& d2)
{return d1._year == d2._year&& d1._month == d2._month&& d1._day == d2._day;
}// d1 - d2
// d1 + d2 无意义
// d1 * d2 无意义
// 一个类要重载哪些运算符是看需求, 看重载有没有价值和意义 int main()
{Date d3(2024, 4, 14);Date d4(2024, 4, 15);// 显示调用(可以正常使用)operator==(d3, d4);// 直接写,转换调用,编译器会转换成operator==(d3, d4)d3 == d4;return 0;
}

四、重载成成员函数

class Date
{
public:Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}d3.Func(d4);//bool Func(const Date& d)//{//	return this->_year == d._year//		&& this->_month == d._month//		&& this->_day == d._day;//}// d3.operator==(d4);bool operator==(const Date& d){cout << "类中";return _year == d._year&& _month == d._month&& _day == d._day;// 隐藏了this指针/*return this->_year == d._year&& this->_month == d._month&& this->_day == d._day;*/}//private:int _year;int _month;int _day;
};// 如果全局和类中都有运算符重载函数,编译器会选择调用类里的
bool operator==(const Date& d1, const Date& d2)
{cout << "全局";return d1._year == d2._year&& d1._month == d2._month&& d1._day == d2._day;
}
int main()
{Date d3(2024, 4, 14);Date d4(2024, 4, 15);// 显式调用d3.operator==(d4);// 转换调用 等价于d3.operator==(d4);d3 == d4;return 0;
}
  • 通过d3.operator==(d4)显式调用了类内的operator==函数。因为这里是直接通过对象d3来调用的,所以肯定是类内的版本被调用。
  • d3 == d4这种简洁的写法在C++中会被自动转换为对operator==的调用。当有多个版本的operator==可用时(如本例中的类内和全局版本),C++会根据一定的规则(如作用域和参数匹配)来选择调用哪一个。在这个例子中,由于类内的版本是成员函数,且其参数与全局版本相同,所以编译器会优先选择类内的版本。 

五、赋值运算符重载

1.赋值运算符重载格式

  • 参数类型:const T&,传递引用可以提高传参效率
  • 返回值类型:T&,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值
  • 检测是否自己给自己赋值
  • 返回*this :要复合连续赋值的含义

class Date
{
public:Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}Date(const Date& d){_year = d._year;_month = d._month;_day = d._day;}Date& operator=(const Date& d){// 自己给自己赋值if (this != &d){_year = d._year;_month = d._month;_day = d._day;}// 需要返回值的原因:支持连续赋值return *this;}
private:int _year;int _month;int _day;
};int main()
{Date d1(2024, 4, 14);// 拷贝构造// 一个已经存在的对象,拷贝给另一个要创建初始化的对象Date d2(d1);Date d3 = d1;Date d4(2024, 5, 1);// 赋值拷贝/赋值重载// 一个已经存在的对象,拷贝赋值给另一个已经存在的对象d1 = d4;d1 = d2 = d4;return 0;
}
  •  Date& operator=(const Date& d):这个函数重载了赋值运算符(=),允许我们使用=来将一个Date对象的值赋给另一个已经存在的Date对象。函数中首先检查自赋值的情况(即确保赋值操作的左右两边不是同一个对象),然后复制右边的对象的年、月和日到左边的对象,并返回左边对象的引用,以支持连续赋值操作。
  • *this是对象本身,对象在main的作用域里创建,因此出main作用域才析构销毁。而出函数作用域不会销毁,所以此处才能return *this 

传值返回和引用返回

传值返回,返回的是对象的拷贝

引用返回,返回的是对象的别名

class Date
{
public:Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}Date(const Date& d){cout << "Date(const Date& d)" << endl;_year = d._year;_month = d._month;_day = d._day;}void Print(){cout << _year << "-" << _month << "-" << _day << endl;}~Date(){cout << "~Date()" << endl;_year = -1;_month = -1;_day = -1;}private:int _year;int _month;int _day;
};

  • Date(const Date& d):这是一个拷贝构造函数,它接受一个Date对象的引用,并创建一个新的Date对象,其内容与传入的对象相同

有没有办法不生成拷贝?

使用引用返回

Date func()
{Date d(2024, 4, 14);return d;
}int main()
{const Date& ref = func();ref.Print();return 0;
}

 在main函数中,首先通过调用func函数获取了一个对Date对象的常量引用ref。由于func返回的是一个临时对象,这个对象在表达式结束后就会被销毁。但是,由于ref是对这个临时对象的引用,所以这个临时对象的生命周期会被延长,直到ref的生命周期结束。这是C++11引入的引用折叠和生命周期延长规则的结果。

Date& func()
{Date d(2024, 4, 14);//cout << &d << endl;return d;
}int fx()
{int a = 1;int b = 2;int c = 3;return a + b + c;
}int main()
{//Date& ref = func();const Date& ref = func();cout << &ref << endl;fx();return 0;
}

在main函数中,通过const Date& ref = func();获取了func函数返回的引用,并将其存储在常量引用ref中。由于func返回的是对局部变量的引用,这里的ref实际上引用了一个已经不存在的对象,因此这是不安全的 

Date& func()
{static Date d(2024, 4, 14);return d;
}
// 出了作用域,返回对象还在没有析构,那就可以用引用返回,减少拷贝
// a、返回对象生命周期到了,会析构,传值返回
// b、返回对象生命周期没到,不会析构,传引用返回int main()
{const Date& ref = func();//ref.Print();return 0;
}
  • func函数返回一个对静态局部变量d的引用,该变量在函数第一次被调用时被初始化并在程序的整个生命周期内持续存在。由于d是静态的,它不会在func函数返回后被销毁,因此可以安全地返回它的引用。 

  • 在main函数中,调用了func函数并将返回的引用赋值给const Date& ref。由于返回的是引用,因此没有发生任何拷贝操作,这是效率更高的做法。

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

class Date
{
public:Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}int _year;int _month;int _day;
};
// 赋值运算符重载成全局函数,注意重载成全局函数时没有this指针了,需要给两个参数
Date& operator=(Date& left, const Date& right)
{if (&left != &right){left._year = right._year;left._month = right._month;left._day = right._day;}return left;
}
// 编译失败:
// error C2801: “operator =”必须是非静态成员

原因:赋值运算符如果不显式实现,编译器会生成一个默认的。此时用户再在类外自己实现一个全局的赋值运算符重载,就和编译器在类中生成的默认赋值运算符重载冲突了,故赋值运算符重载只能是类的成员函数。

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

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

class Time
{
public:Time(){_hour = 1;_minute = 1;_second = 1;}Time& operator=(const Time& t){if (this != &t){_hour = t._hour;_minute = t._minute;_second = t._second;}return *this;}
private:int _hour;int _minute;int _second;
};
class Date
{
private:// 基本类型(内置类型)int _year = 1970;int _month = 1;int _day = 1;// 自定义类型Time _t;
};
int main()
{Date d1;Date d2;d1 = d2;return 0;
}

既然编译器生成的默认赋值运算符重载函数已经可以完成字节序的值拷贝了,还需要自己实
现吗?当然像日期类这样的类是没必要的。那么下面的类呢?验证一下试试?

// 这里会发现下面的程序会崩溃掉?这里就需要我们以后讲的深拷贝去解决。
typedef int DataType;
class Stack
{
public:Stack(size_t capacity = 10){_array = (DataType*)malloc(capacity * sizeof(DataType));if (nullptr == _array){perror("malloc申请空间失败");return;}_size = 0;_capacity = capacity;}void Push(const DataType& data){// CheckCapacity();_array[_size] = data;_size++;}~Stack(){if (_array){free(_array);_array = nullptr;_capacity = 0;_size = 0;}}
private:DataType* _array;size_t _size;size_t _capacity;
};
int main()
{Stack s1;s1.Push(1);s1.Push(2);s1.Push(3);s1.Push(4);Stack s2;s2 = s1;return 0;
}

注意:如果类中未涉及到资源管理,赋值运算符是否实现都可以;一旦涉及到资源管理则必须要实现。

六、前置++和后置++重载 

class Date
{
public:Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}// 前置++:返回+1之后的结果// 注意:this指向的对象函数结束后不会销毁,故以引用方式返回提高效率Date& operator++(){_day += 1;return *this;}// 后置++:// 前置++和后置++都是一元运算符,为了让前置++与后置++形成能正确重载// C++规定:后置++重载时多增加一个int类型的参数,但调用函数时该参数不用传递,编译器自动传递// 注意:后置++是先使用后+1,因此需要返回+1之前的旧值,故需在实现时需要先将this保存一份,然后给this + 1//       而temp是临时对象,因此只能以值的方式返回,不能返回引用Date operator++(int){Date temp(*this);_day += 1;return temp;}
private:int _year;int _month;int _day;
};void test02()
{Date d1(2024, 4, 14);Date d2 = ++d1;d1.Print();d2.Print();Date d3 = d1++;d1.Print();d3.Print();/*d1.operator++(1);d1.operator++(100);d1.operator++(0);d1.Print();*/
}int main()
{test02();return 0;
}

今天就先到这了!!!

看到这里了还不给博主扣个:
⛳️ 点赞☀️收藏 ⭐️ 关注!

你们的点赞就是博主更新最大的动力!
有问题可以评论或者私信呢秒回哦。

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

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

相关文章

【Elasticsearch】开源搜索技术的演进与选择:Elasticsearch 与 OpenSearch

开源搜索技术的演进与选择&#xff1a;Elasticsearch 与 OpenSearch 1.历史发展2.OpenSearch 与 Elasticsearch 相同点3.OpenSearch 与 Elasticsearch 不同点3.1 版本大不同3.2 许可证不同3.3 社区不同3.4 功能不同3.5 安全性不同3.6 性能不同3.7 价格不同3.8 两者可相互导入 4…

unity知识点 专项四 一文彻底说清楚(锚点(anchor)、中心点(pivot)、位置(position)之间的关系)

一 概述 想要使UI控件在屏幕中达到正确的显示效果&#xff0c;比如自适应屏幕尺寸、固定边距等等&#xff0c;首先要理清楚几个基本概念和设置&#xff1a;锚点(anchor)、中心点(pivot)、位置(position)、UI缩放模式、父物件的transform设置 二 Anchor、Pivot与Position 2…

Javascript常见数据结构和设计模式

在JavaScript中&#xff0c;常见的数据结构包括两大类&#xff1a;原始数据类型&#xff08;Primitive Types&#xff09;和对象类型&#xff08;Object Types&#xff09;。对象类型又可以进一步细分为多种内置对象、数组、函数等。下面是一些JavaScript中常见的数据结构&…

Vulnhub靶场DC-6练习

目录 0x00 准备0x01 主机信息收集0x02 站点信息收集1. wordpress扫描2. wordlists字典爆破 0x03 漏洞查找与利用1. 漏洞查找2. CVE-2018-15877漏洞利用3. 反弹shell5. nmap提权 0x04 总结 0x00 准备 下载链接&#xff1a;https://download.vulnhub.com/dc/DC-6.zip 介绍&#…

近红外光谱脑功能成像(fNIRS):2.实验设计、指标计算与多重比较

一、实验设计的策略与方法 近红外光谱成像&#xff08;INIRS&#xff09;作为一种非侵入性脑功能成像技术&#xff0c;为研究大脑活动提供了一种高效、生态效度高的方法。然而&#xff0c;为了充分利用INIRS技术并确保实验结果的准确性和可靠性&#xff0c;研究者必须精心设计实…

高阶面试-dubbo的学习

SPI机制 SPI&#xff0c;service provider interface&#xff0c;服务发现机制&#xff0c;其实就是把接口实现类的全限定名配置在文件里面&#xff0c;然后通过加载器ServiceLoader去读取配置加载实现类&#xff0c;比如说数据库驱动&#xff0c;我们把mysql的jar包放到项目的…

【库架一体立体库】与【传统立体库】对比

导语 大家好&#xff0c;我是社长&#xff0c;老K。专注分享智能制造和智能仓储物流等内容。 随着冷链物流行业的快速发展&#xff0c;对于冷藏设施的要求也在不断提高。库架一体式智能立体冷藏库以其高效、节能、智能化的特点&#xff0c;正逐渐成为行业发展的新趋势。 分享一…

UML中用例之间的可视化表示

用例除了与参与者有关联关系外&#xff0c;用例之间也存在着一定的关系&#xff0c;如泛化关系、包含关系、扩展关系等。 4.2.1 包含关系 包含关系指的是两个用例之间的关系&#xff0c;其中一个用例&#xff08;称为基本用例&#xff0c;Base Use Case&#xff09;的行为包…

el-tree 获取当前勾选节点的选中状态以及选中值对象 触发check-change多次事件问题原因

1.需求 现在需要一个树状结构的资产树 但是现在需求是 获取当前选中的值的状态是选中还是取消选中 然后再用当前选中 or 取消选中的值 进行 选中 or 取消选中的操作 一开始使用的是 check-change 方法 接收参数如图 但是我勾选父节点 或者 子节点后 他会打印一堆数据 是因…

理解JS与多线程

理解JS与多线程 什么是四核四线程&#xff1f; 一个CPU有几个核它就可以跑多少个线程&#xff0c;四核四线程就说明这个CPU同一时间最多能够运行四个线程&#xff0c;四核八线程是使用了超线程技术&#xff0c;使得单个核像有两个核一样&#xff0c;速度比四核四线程有多提升。…

Vivado FFT IP核使用

1. 今日摸鱼任务 学习Vivado FFT IP核的使用 Vivado_FFT IP核 使用详解_vivado fft ip核-CSDN博客 这篇写的很详细啦 简单做一点笔记进行记录 2. FFT IP核 xfft_0 ff (.aclk(aclk), // input wire aclk.aresetn(aresetn)…

C++编译链接原理

从底层剖析程序从编译到运行的整个过程 三个阶段 一、编译阶段二、链接阶段三、运行阶段 为了方便解释&#xff0c;给出两端示例代码&#xff0c;下面围绕代码进行实验&#xff1a; //sum.cpp int gdata 10; int sum(int a,int b) {return ab; }//main.cpp extern int gdata…

Java基础---复习01

main方法 一个程序有且只有一个main方法&#xff0c;main方法是java程序的唯一入口。 修饰符 修饰类修饰方法修饰域public都可以访问都可以访问private私有类只能本类只能本类protected子类可以继承、访问&#xff0c;同包下的类也可以访问子类可以继承、访问&#xff0c;同…

[AI 大模型] Google Gemini

文章目录 [AI 大模型] Gemini简介模型架构发展新技术和优势示例 [AI 大模型] Gemini 简介 Google Gemini 是 Google 最新推出的多模态 AI 大模型&#xff0c;旨在提升 AI 在各个领域的应用能力。Gemini 能够处理文本、图像、音频、视频和代码等多种数据类型&#xff0c;展现出…

暑期备考2024小学生古诗文大会:吃透真题和知识点(持续)

2024年上海市小学生古诗文大会的自由报名初赛将于10月19日&#xff08;星期六&#xff09;正式开始&#xff0c;还有3个多月的时间。 为帮助孩子们备考&#xff0c;我持续分享往年上海小学生古诗文大会真题&#xff0c;这些题目来自我去重、合并后的1700在线题库&#xff0c;每…

云计算渲染时代:选择Blender或KeyShot进行高效渲染

在云渲染技术日益成熟的背景下&#xff0c;挑选一款贴合项目需求的3D渲染软件显得尤为关键。当前&#xff0c;Blender与KeyShot作为业界领先的全能渲染解决方案&#xff0c;广受推崇。它们虽皆能创造出令人信服的逼真视觉效果&#xff0c;但在特色功能上各有所长。本篇文章旨在…

稀疏建模介绍,详解机器学习知识

目录 一、什么是机器学习&#xff1f;二、稀疏建模介绍三、Lasso回归简介四、Lasso超参数调整与模型选择 一、什么是机器学习&#xff1f; 机器学习是一种人工智能技术&#xff0c;它使计算机系统能够从数据中学习并做出预测或决策&#xff0c;而无需明确编程。它涉及到使用算…

华为HCIP Datacom H12-821 卷30

1.单选题 以下关于OSPF协议报文说法错误的是? A、OSPF报文采用UDP报文封装并且端口号是89 B、OSPF所有报文的头部格式相同 C、OSPF协议使用五种报文完成路由信息的传递 D、OSPF所有报文头部都携带了Router-ID字段 正确答案&#xff1a;A 解析&#xff1a; OSPF用IP报…

游戏AI的创造思路-技术基础-决策树(1)

决策树&#xff0c;是每个游戏人必须要掌握的游戏AI构建技术&#xff0c;难度小&#xff0c;速度快&#xff0c;结果直观&#xff0c;本篇将对决策树进行小小解读~~~~ 目录 1. 定义 2. 发展历史 3. 决策树的算法公式和函数 3.1. 信息增益&#xff08;Information Gain&…

无线网卡怎么连接台式电脑?让上网更便捷!

随着无线网络的普及&#xff0c;越来越多的台式电脑用户希望通过无线网卡连接到互联网。无线网卡为台式电脑提供了无线连接的便利性&#xff0c;避免了有线网络的束缚。本文将详细介绍无线网卡怎么连接台式电脑的四种方法&#xff0c;包括使用USB无线网卡、内置无线网卡以及使用…