C++【类与对象】——运算符重载

2023-05-16

文章目录

  • 一、运算符重载
    • 1.定义
  • 二、加号运算符重载
    • 1.code格式
      • (1)通过成员函数实现加号运算符重载
      • (2)通过全局函数实现加号运算符重载
    • 2.作用
    • 3.拓展
      • (1)运算符重载与函数重载可以同时发生
  • 三、左移运算符重载
    • 1.code格式
      • (1)通过全局函数实现左移运算符重载
    • 2.作用
    • 3.拓展
  • 四、递增运算符重载
    • 1.code格式
      • (1)前置递增运算符重载
      • (2)后置递增运算符重载
    • 2.作用
  • 五、赋值运算符重载
    • (1)浅拷贝&深拷贝
    • (2)自定义拷贝构造函数解决浅拷贝问题
    • (3)赋值运算符重载解决浅拷贝问题
  • 六、关系运算符重载
    • (1)code格式
    • (2)作用
  • 七、函数调用运算符重载(重点)
    • (1)code格式
    • (2)作用
  • 八、全文总结

Note:
i.视频为 黑马程序员C++视频(121-126),系列文章为视频听课笔记;
ii.难度指数:++
iii.不论变量、函数名、标识符形式怎样复杂,只要我们考虑编程的本质是对内存的操作,对内存进行分析,一切逻辑都会变得清晰。

一、运算符重载

1.定义

对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型。

二、加号运算符重载

1.code格式

(1)通过成员函数实现加号运算符重载

通过成员函数重载加号,实现两个示例对象属性相加并返回拥有相加属性的新的对象。

//代码示例
#include<iostream>
#include<string>
using namespace std;

class person
{
public:
	int m_A;
	int m_B;
	//通过成员函数实现加号运算符重载
	person operator +(person &p)//定义函数类型为person,
	{
		person temp;
		temp.m_A = this->m_A + p.m_A;
		temp.m_B = this->m_B + p.m_B;
		return temp;//返回拥有相加属性的新的对象
	}
};

void test01()
{
	person p1;
	p1.m_A = 10;
	p1.m_B = 20;
	person p2;
	p2.m_A = 10;
	p2.m_B = 20;
	person p3 = p1 + p2;
	cout << p3.m_A << endl;//20
	cout << p3.m_B << endl;//40
}

int main()
{
	test01();
}

(2)通过全局函数实现加号运算符重载

通过全局函数重载加号,实现两个示例对象属性相加并返回拥有相加属性的新的对象。

//代码示例
#include<iostream>
#include<string>
using namespace std;

class person
{
public:
	int m_A;
	int m_B;
};

//通过全局函数实现运算符重载
person operator + (person &p1, person &p2)
{
	person temp;
	temp.m_A = p1.m_A + p2.m_A;
	temp.m_B = p1.m_B + p2.m_B;
	return temp;//返回拥有相加属性的新的对象
}
void test01()
{
	person p1;
	p1.m_A = 10;
	p1.m_B = 20;
	person p2;
	p2.m_A = 10;
	p2.m_B = 20;
	person p3 = p1 + p2;
	cout << p3.m_A << endl;
	cout << p3.m_B << endl;
}

int main()
{
	test01();
}

2.作用

实现两个自定义数据类型(非编译器内置数据类型)的相加。

3.拓展

(1)运算符重载与函数重载可以同时发生

上述加号运算符重载涉及到operator +函数,也能实现函数重载。
函数重载:函数重载是函数名相同,但传入参数类型等不同的情况下能够实现对不同函数的调用。

//代码示例
#include<iostream>
#include<string>
using namespace std;

class person
{
public:
	int m_A;
	int m_B;
};
//通过全局函数实现运算符重载
person operator + (person &p1, person &p2)
{
	person temp;
	temp.m_A = p1.m_A + p2.m_A;
	temp.m_B = p1.m_B + p2.m_B;
	return temp;
}
//全局函数重载,与上面的函数第二个形参类型不同
person operator + (person &p1, int num)
{
	person temp;
	temp.m_A = p1.m_A + num;
	temp.m_B = p1.m_B + num;
	return temp;
}

void test01()
{
	person p1;
	p1.m_A = 10;
	p1.m_B = 20;
	person p2;
	p2.m_A = 10;
	p2.m_B = 20;
	person p3 = p1 + p2;//调用上面第一个全局函数
	cout << "p3.m_A=" << p3.m_A << endl;//20
	cout << "p3.m_B=" << p3.m_B << endl;//40
	person p4 = p1 + 5;//由于加号运算符后面的数据类型为整型,调用上面第二个全局函数实现加号运算符的重载
	cout << "p4.m_A="<< p4.m_A << endl;//15
	cout << "p4.m_B="<< p4.m_B << endl;//25
}

int main()
{
	test01();
}

该图为上述代码的输出,由输出可见,由于加号运算符前后数据类型不同,在进行加法运算时,加号运算符重载时调用的是两个不同的全局函数。
在这里插入图片描述

【一点思考】
1)重载类似于利用同一个标志符,但使其在不同情况下发挥的作用不同。例如运算符重载是指运算符相同,但数据类型不同时能够实现自定义数据类型的运算;函数重载是函数名相同,但是参数类型等不同的情况下能够实现对不同函数的调用。
2)加号运算符的重载通过定义一个名为operator +的成员函数或者全局函数来实现,当需要定义多个operator +函数实现自定义类型数据的相加运算时,也会发生函数重载的现象。

三、左移运算符重载

左移运算符即<<

1.code格式

(1)通过全局函数实现左移运算符重载

通过全局函数重载左移运算符,实现自定义数据类型——的输出。

//代码示例一
#include<iostream>
#include<string>
using namespace std;

class person
{
public:
	int m_A;
	int m_B;
};

void operator << (ostream &out,person &p)//out为cout的引用
{
	out << p.m_A << endl;
	out << p.m_B << endl;
}
void test01()
{
	person p;
	p.m_A = 10;
	p.m_B = 20;
	cout << p;//注意这里调用<<重载函数,所传参数为cout和p!
	cout << p << "hello world" << endl;//报错!
}

int main()
{
	test01();
	return 0;
}

代码示例一运行结果:
在这里插入图片描述

代码示例一能够实现<<重载,但不能实现不断重载,即cout << p <<...会报错,这是因为<<重载函数返回类型是voidvoid不能继续对重载函数进行调用。要实现对重载函数的不断调用,只要实现重载函数返回值为cout即可,具体代码见示例二。

//代码示例二
#include<iostream>
#include<string>
using namespace std;

class person
{
public:
	int m_A;
	int m_B;
};

ostream & operator << (ostream &out,person &p)
{
	out << p.m_A << endl;
	out << p.m_B << endl;
	return out;
}
void test01()
{
	person p;
	p.m_A = 10;
	p.m_B = 20;
	cout << p << "hello";
}

int main()
{
	test01();
	return 0;
}

代码示例二运行结果:
在这里插入图片描述

2.作用

可以输出自定义数据类型。

【总结】
1)左移运算符的重载通过定义一个名为operator <<的全局函数来实现;
2)这里出现了新的数据类型,即cout的数据类型:ostream
cout能作全局函数的参数也是我没想到的hiahiahia

3.拓展

思考:当上述代码示例中类的属性为私有属性时,怎么输出?
答:利用全局函数作友元,在类中加上friend+全局函数声明即可。代码见示例三:

//代码示例三
#include<iostream>
#include<string>
using namespace std;

class person
{
	friend ostream & operator << (ostream &out, person &p);
	friend void test01();
private:
	int m_A;
	int m_B;
};

ostream & operator << (ostream &out,person &p)
{
	out << p.m_A << endl;
	out << p.m_B << endl;
	return out;
}
void test01()
{
	person p;
	p.m_A = 10;
	p.m_B = 20;
	cout << p << "hello";
}

int main()
{
	test01();
	return 0;
}

四、递增运算符重载

1.code格式

(1)前置递增运算符重载

前置递增运算符的作用是:先递增,后执行表达式,具体示例见代码中main函数的前三行。
代码如下所示:

//前置递增运算符重载
#include<iostream>
#include<string>
using namespace std;

class MyInteger 
{
	friend ostream & operator << (ostream &out, MyInteger MyInt);
public:
	//利用构造函数对成员属性进行初始化,不初始化就没有示例的值输出
	MyInteger()
	{
		m_num = 0;
	}
	//通过成员函数重载前置++运算符
	MyInteger& operator ++()//必须返回引用,目的是对一个数据进行操作,否则将返回新的对象的成员属性
	{
		//先递增
		this->m_num++;
		//再将自身返回
		return *this;
	}
private:
	int m_num;
};

//左移运算符重载全局函数
ostream & operator << (ostream &out, MyInteger MyInt)
{
	cout << MyInt.m_num<< endl;
	return out;
}

//测试重载的前置递增效果
void test01()
{
	MyInteger myint;
	//若重载函数返回为非引用,输出结果为:
	cout << ++(++myint) << endl;//2
	cout << myint << endl;//1
	//若重载函数返回为引用,输出结果为:
	cout << ++(++myint) << endl;//2
	cout << myint << endl;//2
}
int main()
{
	//看一下前置递增运算符的作用,重载要实现相同的作用
	int a = 0;
	cout << ++a << endl;//1
	cout << a << endl;//1
	test01();
	return 0;
}

【关于前置递增运算符重载函数返回必须为引用的原因】: 如果重载函数返回值非引用,那么每次++myint后都会创建一个新的MyInteger对象,再进行递增就不是基于实例对象myint了,而是在每次++后创建的新的MyInteger实例对象上。

(2)后置递增运算符重载

后置递增运算符的作用是:先执行表达式,后递增。具体示例见代码中main函数前三行。
代码如下所示:

//后置运算符重载实现
#include<iostream>
#include<string>
using namespace std;

class MyInteger
{
	friend ostream & operator << (ostream &out, MyInteger myint);
public:
	MyInteger()
	{
		m_num = 0;
	}
	MyInteger operator ++(int)//不能返回引用,因为temp仅为局部变量
	{
		//先返回原先的值->先记录先前的值
		MyInteger temp = *this;
		//再递增
		this->m_num++;
		//最后返回
		return temp;
	}
private:
	int m_num;
};

ostream & operator << (ostream &out, MyInteger myint)
{
	cout << myint.m_num;
	return out;
}
void test01()
{
	MyInteger myint;
	cout << myint++ << endl;//0
	cout << myint << endl;//1
}

int main()
{
	//看一下后置递增运算符的作用,重载要实现相同的作用
	int a = 0;
	cout << a++ << endl;//0
	cout << a << endl;//1
	test01();
	return 0;
}

【关于重载函数为什么不能返回引用】:因为temp为局部变量,函数执行结束后即释放。在引用中我们提到,引用作为函数返回值时,不能返回局部变量的引用。

2.作用

通过递增运算符重载,实现自定义的整型数据递增,例如类中某整型成员属性的的递增。同理,上述代码也可进行递减运算符的重载,进而实现自定义整型数据的递减。

五、赋值运算符重载

(1)浅拷贝&深拷贝

【深浅拷贝的起源&图解&解决方法】:
【起源】:前面学到,c++在创建类时会默认分配三个函数
1)默认构造函数(无参,函数体为空)
2)默认析构函数(无参,函数体为空)
3)默认拷贝构造函数(实现对属性的值拷贝
然而,当创建的类中包含堆区指针数据的成员属性时,若调用默认拷贝构造函数(即浅拷贝)会导致堆区指针内存的重复释放(释放由析构函数实现),进而导致程序无法运行。
实际上,c++在创建类时还会默认分配赋值运算符重载函数,即operator =函数。
【动态图解】:

【解决方法】:
1)自定义拷贝构造函数,实现深拷贝
2)赋值运算符重载

下面我们将从解决浅拷贝方法的角度来介绍赋值运算符重载。

(2)自定义拷贝构造函数解决浅拷贝问题

自定义拷贝构造函数见代码中所示,其开辟了新的堆区内存储存实例对象p1中的成员属性,实现了将实例对象p中指针数据m_age指向的值赋值给p1,而不是单纯的将m_age赋值。

#include<string>
#include<iostream>
using namespace std;

class person 
{
public:
	//创建年龄属性,将其开辟到堆区
	person(int age)
	{
		cout << "构造函数调用" << endl;
		m_age = new int(age);//注意格式
	}
	//自定义拷贝构造函数,实现深拷贝
	person(const person &p)
	{
		cout << "自定义拷贝函数调用" << endl;
		m_age = new int(*(p.m_age));
	}
	~person()
	{
		cout << "析构函数调用" << endl;
		if (m_age != NULL)
		{
			delete m_age;//释放堆区内存
			m_age = NULL;
		}
	}
	int * m_age;
};

void test01()
{
	person p(18);
	person p1(p);
	cout << "p的年龄为:" << *p.m_age << endl;//18
	cout << "p1的年龄为:" << *p1.m_age << endl;//18
}
int main()
{
	test01();
	return 0;
}

上述代码执行的输出结果如下所示:
在这里插入图片描述

(3)赋值运算符重载解决浅拷贝问题

赋值运算符重载函数见代码中person & operator = (person &p)所示:

【思考】:
要学会分析定义重载函数返回值为不同类型的逻辑,如下所示代码中,若定义赋值运算符重载函数的返回值类型为person类,则执行一次重载函数,都会创建一个新的person实例对象。

#include<string>
#include<iostream>
using namespace std;

class person 
{
public:
	person(int age)
	{
		cout << "有参构造函数调用" << endl;
		//创建年龄属性,将其开辟到堆区
		m_age = new int(age);//注意格式
	}
	~person()
	{
		cout << "析构函数调用" << endl;
		if (m_age != NULL)
		{
			delete m_age;
			m_age = NULL;
		}
	}
	person & operator = (person &p)//连续赋值情况下,p3=p2=p1,首先执行p2=p1;这里定义的返回值和返回类型是p2的返回值和类型。若返回值类型为person,则创建的是一个新的person实例对象,报错!
	{
		//编译器默认浅拷贝
		//m_age = p.m_age;
		//先判断是否有p1中属性在堆区,如果有先释放干净
		if (m_age != NULL)
		{
			delete m_age;
			m_age = NULL;
		}
		//指向深拷贝
		m_age = new int(*p.m_age);
		return *this;
	}
	int * m_age;
};

void test01()
{
	person p(18);//堆区创建内存存储
	person p1(20);//堆区创建内存存储
	p1 = p;//调用operator+赋值运算符重载函数
	cout << "p的年龄为:"<<*p.m_age<<endl;
	cout << "p1的年龄为:" << *p1.m_age << endl;
}

int main()
{
	//test01();
	int a = 10;
	int b = 20;
	int c = 30;
	c = b = a;//内置数据类型连续赋值
	cout << "a的值为" << a << endl;//10
	cout << "b的值为" << b << endl;//10
	cout << "c的值为" << c << endl;//10
/***********************************************/
	person p1(10);
	person p2(20);
	person p3(30);
	p3 = p2 = p1;//自定义类属性连续赋值
	cout << "p1的值为" << *p1.m_age << endl;//10
	cout << "p2的值为" << *p2.m_age << endl;//10
	cout << "p3的值为" << *p3.m_age << endl;//10
	return 0;
}

六、关系运算符重载

(1)code格式

以关系运算符“==”和“!=”为例,核心函数为bool operator ==(person &p)bool operator !=(person &p)

#include<iostream>
#include<string>
using namespace std;

class person 
{
public:
	person(int age, string name)
	{
		m_age = age;
		m_name = name;
	}
	//“==”关系运算符重载
	bool operator ==(person &p)
	{
		if (this->m_age == p.m_age && this->m_name == p.m_name)
		{
			return true;
		}
		else
			return false;
	}
	//“!=”关系运算符重载
	bool operator !=(person &p)
	{
		if (this->m_age != p.m_age || this->m_name != p.m_name)
		{
			return true;
		}
		else
			return false;
	}
	int m_age;
	string m_name;
};

void test01()
{
	person p1(18, "TOM");
	person p2(25, "吴晗");
	person p3(18, "TOM");
	if (p1 == p3)
	{
		cout<<"p1和p3是相等的"<<endl;
	}
	if (p1 != p2)
	{
		cout << "p1和p2是不相等的" << endl;
	}
}

int main()
{
	test01();
	return 0;
}

(2)作用

重载任意关系运算符,实现自定义数据类型的对比。

七、函数调用运算符重载(重点)

(1)code格式

函数调用运算符即(),该部分涉及()的重载。

//运算符()重载
#include<iostream>
#include<string>
using namespace std;

//利用重载实现打印输出
class Myprint
{
public:
	void operator ()(string zfc)
	{
		cout << zfc << endl;
	}
};
//利用重载实现两数相加
class Myadd 
{
public:
	int operator()(int num1, int num2)
	{
		return num1 + num2;
	}
};
//测试打印输出
void test01()
{
	Myprint p1;
	p1("hhhhhhhhhhhh");//使用起来非常像函数调用,因此运算符()重载函数在使用时也称为仿函数
	Myprint()("吴晗");//匿名函数对象,涉及实例对象以及重载函数,执行完后立即释放

}
//测试两数相加
void test02()
{
	Myadd shs;
	int sum1 = shs(12, 13);
	cout << "两数之和=" << sum1 << endl;//25
	int sum2 = Myadd()(19, 81);
	cout << "两数之和=" << sum2 << endl;//100
}

int main()
{
	test01();
	test02();
	return 0;
}

(2)作用

知道运算符()也可以重载,了解仿函数以及匿名函数对象,后面学习STL会经常用到。

【总结】:
1)仿函数:由于运算符()重载函数的使用与函数调用类似,因此其在使用时被称为仿函数。
2)匿名函数对象:类名+()即为一个匿名函数对象,执行完后立即释放。

八、全文总结

C++【类与对象】——运算符重载串讲,涉及:
1)加号运算符,+:实现自定义数据类型相加
2)左移运算符,<<:实现自定义数据类型输出
3)递增运算符,前置++后置++:实现自定义数据类型递增
4)关系运算符,==!=:实现自定义数据类型比较操作
5)函数调用运算符,():定义()实现自定义功能
核心函数为operator加上述运算符,难点为重载运算符的连续调用

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C++【类与对象】——运算符重载 的相关文章

  • Hive基本DDL操作

    1 数据库的DDL操作 xff08 1 xff09 创建数据库 数据库在HDFS上的默认存储路径是 user hive warehouse 数据库名 db hive gt create database db hive 两者等价hive g
  • Windows和Linux双系统时间不对的问题。

    解决Windows与Ubuntu双系统时间同步问题 2016年07月11日 21 05 30 阅读数 xff1a 33115 1 问题发现 本子上装的是Window 10 win7升级 和Ubuntu GNOME 14 04的双系统 一直以
  • stm32移植freertos报错keil

    Using Compiler 39 V5 06 update 7 build 960 39 folder 39 d Keil v5 ARM ARMCC Bin 39 compiling main c USER FreeRTOSConfig
  • ARMv8 (arrch64)pytorch深度学习环境搭建

    Aarch64 安装Anaconda 和 pytorch 一 安装miniconda 参考链接 xff1a 82条消息 Jetson Nano xff08 aarch64 xff09 搭建miniconda 和mmdetection环境 瑾
  • GPU 选择 深度学习 图像识别

    GPU 选择 深度学习 图像识别 1 显卡1 1 Nvidia显卡分类1 1 1 Geforce系列1 1 2 Quadro系列1 1 3 Tesla系列1 2 GPU几个比较重要的参数GPU架构 xff1a CUDA核心数量 xff1a
  • git 合并两个分支

    这里讲解具体的思想 xff0c 具体的提交命令百度上有 目前的场景是将两个非master上的分支进行合并 xff0c 并且不造成冲突 比如现在有两个分支a xff0c b 现在将b合并到a 首先及将b分支上的代码clone下来 xff0c
  • ubuntu中解决ROS--Gazebo添加模型库,解决打开后无模型的问题

    ubuntu中解决Gazebo添加模型库 xff0c 解决打开后无模型的问题 1 在主目录中ctrl 43 h 下载https bitbucket org osrf gazebo models downloads ExBot ROS专区 x
  • 大小端模式

    定义 xff1a 数据存放在地址中 xff0c 一个地址码对应一个字节 内存的低地址存放数据的低位 xff0c 则存储方式为小端存储 xff1b 内存的低地址存放数据的高位 xff0c 则为大端存储 判断大小端的方法有两种 xff1a 数据
  • Matlab二维插值

    Matlab二维插值 interp2函数 xff08 网格节点 xff09 Y 61 interp2 x y z xi yi method method nearest 最邻近插值linear xff08 默认 xff09 双线性插值cub
  • 4、ROS话题通信实战,代码逐行解析

    在前面小节已经实现了ROS工作空间的创建 xff0c 这里开始功能包的开发实战了 首先创建功能包 xff1a cd ROS src 创建功能包topic xff0c 指定依赖roscpp和std msgs包 catkin create pk
  • 大数据学习(三十)JOIN过程中的mapreduce阶段

    前言 xff1a join分为mapjoin 和 common 普通 join mapjoin 是没有reduce阶段 只有map阶段 在map阶段进行join操作 xff08 此知识点也会在大表join小表中体现 xff09 common
  • Gunicorn介绍、安装及使用

    1 简介 Gunicorn Green Unicorn 是一个 UNIX 下的 WSGI HTTP 服务器 xff0c 它是一个 移植自 Ruby 的 Unicorn 项目的 pre fork worker 模型 它既支持 eventlet
  • Spring Boot POM 详解

    正如这幅图所展示的那样 xff0c 在Spring IO Framework体系中 xff0c Spring Boot处在Execution layer xff0c 来看看官方对这层的解释 xff1a The Spring IO Execu
  • 基于阿木实验室P200飞行器simulink模型开发的多旋翼无人机自抗扰控制器(ADRC)参数调整和仿真

    1 飞行器模型参数 参考P200飞行器参数 利用网站https www flyeval com 计算飞行器如下 xff1a 2 参数设置具体如下 1 xff09 模型主要包括总线 期望数据生成模块 控制器 PWM生成器 飞行器对象模型 2
  • mkdir()函数

    一 创建目录 1 1 direct h 头文件 int mkdir const char path mode t mode 函数名 mkdir 功 能 建立一个目录 用 法 int mkdir const char dirname 头文件库
  • react路由基础与传参

    react router dom 1 react的一个插件 xff0c 专门用来实现单页面 2 基于react的项目基本都会用到它 路由的基本使用 1 路由导航区标签为Link或NavLink标签 lt Link to 61 34 xxx
  • ROS TF工具的使用

    1 打印坐标系转换关系 tf echo 命令格式 xff1a rosrun tf tf echo lt source frame gt lt target frame gt 输出数据类型 xff1a geometry msgs Transf
  • RK平台defconfig,Kconfig,Makefile配置

    1 简介 本文是基于RK平台 xff0c defconfig Kconfig Makefile配置总结 在编译内核之前 xff0c 我们可以对内核源代码进行配置 配置的目的主要是确定哪些模块会编译到内核当中 每次添加移植驱动文件时都要修改M
  • EEG信号中常见的干扰和噪声信号

    原文章 转载是为收集整理 xff0c 方便看 xff0c 如不允许 xff0c 联系可删 在EEG ERP研究中 xff0c 最令人头痛的问题之一是各种干扰和噪声信号混入到EEG ERP信号中 xff0c 因此 xff0c 数据分析的第一步
  • Linux编程——多路复用实现TCP双向通信

    ubuntu下模拟服务器与单个客户端之间的双向通信 xff0c 多路复用实现 功能与使用 服务器与客户端可以双向收发消息 xff0c 如果任意一方被外部强制断开 xff0c 另一方也会退出程序 任意一方输入 quit 并发送 xff0c 客

随机推荐