IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> C++学习笔记 - 阶段三:C++核心编程 - Chapter7:类和对象-C++运算符重载 -> 正文阅读

[C++知识库]C++学习笔记 - 阶段三:C++核心编程 - Chapter7:类和对象-C++运算符重载

阶段三:C++核心编程

Chapter7:类和对象-C++运算符重载

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

7.1 加号运算符重载

  • 作用:实现两个自定义数据类型相加的运算
    总结1:对于内置的数据类型的表达式的的运算符是不可能改变的,比如int double等等 只有对自定义的可以通过重载改变
    总结2:不要滥用运算符重载;比如看到的是两个数相加的重载 但是函数实现的内部是两个数相减,可以这样做也不会报错,但是这样不规范,也不便于维护代码
#include "iostream"
using namespace std;
/*
上课笔记如下:
对于内置数据类型,编译器知道如何进行计算。
如:
int a = 10;
int b = 10;
int c = a + b;
但是如果是两个自定义类型相加,如:Person,该怎么办呢,编译器是不知道的,因此我们要告诉编译器应该怎么计算
如:
class Person
{
public:
	int m_A;
	int m_B;
}
Person p1;//实例化第一个对象p1
p1.m_A = 10;
p1.m_B = 10;

Person p2;//实例化第二个对象p2
p2.m_A = 10;
p2.m_B = 10;

Person p3 = p1 + p2;//实例化第三个对象p3,等于p1+p2,那么编译器这时该怎么办呢?

按照道理应该是:
p1的第一个属性 + p2的第一个属性  = p3的第一个属性 即:p3.m_A = p1.m_A + p2.m_A;
p1的第二个属性 + p2的第二个属性  = p3的第二个属性 即:p3.m_B = p1.m_B + p2.m_B;
但是编译器并不会实现这样的计算方式,编译器不会按照我的这种想法去实现相加
那么我们应该怎么做呢?

首先抛开这里要学的新技术 运算符重载
我们可以通过一个成员函数来计算
如:
通过自己写成员函数,实现两个对象相加属性后返回新的对象
Person PersonAddPerson(Person &p)
{
	Person temp;
	temp.m_A = this->m_A + p.m_A;//让对象自身的属性加上当前传进来的对象的属性
	temp.m_B = this->m_B + p.m_B;
	return temp;
}
这时候,这里的PersonAddPerson是程序员自己起的名字,每个程序员起的都不一样,因此编译器希望自己起一个名字来统一规范
编译器给起了一个通用名称:
operator+ 
那么就写成了这样
Person operator+(Person &p)
{
	Person temp;
	temp.m_A = this->m_A + p.m_A;//让对象自身的属性加上当前传进来的对象的属性
	temp.m_B = this->m_B + p.m_B;
	return temp;
}
那么当我们调用的时候就是这样的:
Person p3 = p1.operator+(p2);
当我们使用了编译器统一的名字之后,这种写法可以简化为:
Person p3 = p1 + p2;
而简化后的写法正好符合我们的平常的认知。
这就是通过成员函数重载+号
*/
/*
这里分析
通过全局函数重载+号
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 p3 = operator+(p1,p2);
这种方式也可以简化为:
Person p3 = p1 + p2;
*/


class Person 
{
public:
	Person() {};
	Person(int a, int b)
	{
		this->m_A = a;
		this->m_B = b;
	}
	//成员函数实现 + 号运算符重载
	Person operator+(const Person& p) {
		Person temp;
		temp.m_A = this->m_A + p.m_A;
		temp.m_B = this->m_B + p.m_B;
		return temp;
	}


public:
	int m_A;
	int m_B;
};

//全局函数实现 + 号运算符重载
//Person operator+(const Person& p1, const Person& p2) {
//	Person temp(0, 0);
//	temp.m_A = p1.m_A + p2.m_A;
//	temp.m_B = p1.m_B + p2.m_B;
//	return temp;
//}

//运算符重载 可以发生函数重载 
Person operator+(const Person& p2, int val)
{
	Person temp;
	temp.m_A = p2.m_A + val;
	temp.m_B = p2.m_B + val;
	return temp;
}

void test() 
{

	Person p1(10, 10);
	Person p2(20, 20);

	//成员函数方式
	Person p3 = p2 + p1;  //相当于 p2.operaor+(p1)
	cout << "mA:" << p3.m_A << " mB:" << p3.m_B << endl;

	//函数重载的版本;这里相当于是一个Person + int的类型
	Person p4 = p3 + 10; //相当于 operator+(p3,10)
	cout << "mA:" << p4.m_A << " mB:" << p4.m_B << endl;

}

int main() 
{

	test();

	system("pause");

	return 0;
}

7.2 左移运算符重载

#include "iostream"
using namespace std;

/*
* 4.5.2 左移运算符重载
* 
* 左移运算符:<<
* 作用:可以输出自定义数据类型
* 
* 总结:重载左移运算符配合友元可以实现输出自定义数据类型
*/
/*
如:int a = 10;
cout << a << endl;


class Person
{
public:
	int m_A;
	int m_B;
};
Person p;
p.m_A = 10;
p.m_B = 10;
cout << p.m_A << endl;//这样是ok的 因为这里的m_A和m_B是整型的,是内置运算符
cout << p.m_B << endl;
但是这样可以吗:cout << p << endl;
我这里想实现的就是输出p的所有属性数据,这样行不行呢?
这样肯定是不可以的,因为编译器不知道p里面是什么,这里的p是一个对象,不是内置运算符
一旦这样写就会报错: 没有与这些操作数匹配的“<<”运算符
但是我们怎么操作呢?我们需要重载这个符号
*/
class Person 
{
	friend ostream& operator<<(ostream& out, Person& p);//友元,可以访问私有属性

public:
	Person(int a, int b)
	{
		this->m_A = a;
		this->m_B = b;
	}

	/*
	成员函数 实现不了  p << cout 不是我们想要的效果
	如果这样可以的话,那么最终实现的时候应该这样写:p.operator<<(p);这样是什么?不是我们想要的效果,我们只有一个对象,
	或者:void operator<<(cout){},那么简化的时候:p << cout 也不是我们想要的效果 因为我们想要的是 cout << p
	因此我们不会利用成员函数去重载左移运算符,因此我们只能用全局运算符去重载左移运算符
	*/
	//void operator<<(Person& p)
	//{
	//	
	//}

private:
	int m_A;
	int m_B;
};
/*
全局函数实现左移重载,成员函数不行
ostream对象只能有一个
当返回值未知的时候我们先写一个void,当知道了是什么返回值的类型的时候,再替换掉void
void operator<<(cout, p) //operator<<(cout,p)  这样简化成 cout << p
这里需要知道cout是什么数据类型,转到定义 __PURE_APPDOMAIN_GLOBAL extern _CRTDATA2_IMPORT ostream cout;
ostream翻译过来是输出流对象 cout是标准的输出流对象,o是输出 stream是数据流对象
ostream是一个类 因此就写成了这样
ostream& operator<<(ostream& out, Person& p)
endl是换行的意思
*/
ostream& operator<<(ostream& out, Person& p) //这里用out来替换cout,是因为这里写了out相当于给cout起了别名,因为这里是引用
{
	out << "a:" << p.m_A << " b:" << p.m_B;
	return out;
}

void test() 
{

	Person p1(10, 20);

	cout << p1 << "hello world" << endl; //链式编程,本质就是输出一段之后输出的类型和下一段的类型是一样的
}

int main() 
{

	test();

	system("pause");

	return 0;
}

7.3 递增运算符重载

#include "iostream"
using namespace std;

/*
* 4.5.3 递增运算符重载
* 
* 作用: 通过重载递增运算符,实现自己的整型数据
* 
* 总结,注意重点: 前置递增返回引用,后置递增返回值
* 
* 
* 思考,学会了递增,也要学会递减
*/
/*
内置数据类型的 ++ 是如何进行的呢?
int a = 10;
cout << ++a <<endl;  //11    前置递增是要 先 递增 后 进行表达式运算
cout << a << endl;   //11

int b = 10;
cout << b++ <<endl; //10     后置递增是要 先 进行表达式运算 后 递增
cout << b <<endl;   //11

然后我们要自定义,要递增运算符重载 ++
我们要怎么去写呢?
先试着这样写
class MyInteger
{
public:
	MyInteger() //构造函数里面对这个数据进行初始化
	{
		m_Num = 0;
	}
private://有一个私有成员属性
	int m_Num;
};
当我们去创建对象的时候
MyInteger myint;
cout << myint << endl;  // 输出就是0
cout << ++myint << endl;// 输出就是1
cout << myint++ << endl;// 输出就是1,这里是因为上面那行代码已经先执行了
cout << myint << endl;  // 输出就是2,这里是因为上面那行代码已经先执行了
因此我们就要分开写前置递增和后置递增
//前置递增

//后置递增

*/


//自定义整型
class MyInteger 
{
	friend ostream& operator<<(ostream& out, MyInteger myint);//不写友元,外部无法访问私有属性

public:
	MyInteger() //构造函数对属性进行初始化
	{
		m_Num = 0;
	}
	//重载++运算符
	
	//前置++
	/*
	这里一定是要返回引用,不要返回值,因此要写成 MyInteger& 而不是 MyInteger
	因为如果对于内置数据类型而言,如:
	int a = 0;
	cout << ++(++a) << endl;//这里输出为2
	cout << a << endl;      //这里输出为2
	说明内置数据类型 ++(++a)是对a进行递增
    所以我们自定义也要达到这样的效果,
	而如果我们返回值类型是值而不是引用,是 MyInteger 而不是 MyInteger&
	就会出现这样的结果,如:
	MyInteger myInt;
	cout << ++(++myInt) << endl;  
	//此时输出是2,因为执行完(++myInt)这个之后,就是一个新的对象了,
	//因此再执行++(++myInt)时候,就是对(++myInt)这个新对象进行递增,故而读取myInt的时候是1而不是2
	cout << myInt << endl;        //此时输出是1
	因此,要返回值是引用,这样就保证了一直是对一个量进行操作,
	我们要做的是一个高仿 不能只仿制 内置数据类型的一部分功能 要全部
	*/
	MyInteger& operator++() //
	{
		//先进行++的运算,这样让自己的属性先进行++的操作
		m_Num++;
		//再将自身做一个返回
		return *this;
	}

	//后置++
	/*
	MyInteger operator++()这里如果这样写,就会报错函数重定义,因此要来一个区别,就来一个参数不同,
	这里的int就是一个占位参数,这里就是让编译器区分前置和后置递增
	返回值不能作为区分函数重定义与否的标志,参数可以
	后置要返回值而不是引用,因为局部对象temp再返回之后,就被释放掉了,如果在对其进行操作就是违法会报错
	*/
	MyInteger operator++(int) 
	{
		//先返回
		MyInteger temp = *this; //记录当前本身的值,然后让本身的值加1,但是返回的是以前的值,达到先返回后++;
		m_Num++;
		return temp;//最后把记录的结果返回
	}
	/*
	* 注意:
	前置递增返回值类型必须是引用
	后置递增返回值类型必须是值
	*/
private:
	int m_Num;
};

//只能用全局函数来写
ostream& operator<<(ostream& out, MyInteger myint) 
{
	out << myint.m_Num;
	return out;
}


//前置++ 先++ 再返回
void test01() 
{
	MyInteger myInt;
	cout << ++myInt << endl;  //重载前置递增运算符之后,此代码才OK
	cout << myInt << endl;    //重载左移运算符之后,此代码才OK
}

//后置++ 先返回 再++
void test02() 
{
	MyInteger myInt;
	cout << myInt++ << endl; //重载后置递增运算符之后,此代码才OK
	cout << myInt << endl;
}

int main() 
{

	test01();
	//test02();

	system("pause");

	return 0;
}

7.4 赋值运算符重载

#include "iostream"
using namespace std;

/*
* 4.5.4 赋值运算符重载
* 
* 
* 前面学的是c++编译器至少给一个类添加3个函数
* 这里会增添一个,C++也就学这4个编译器默认的添加的函数
* 
* 
* c++编译器至少给一个类添加4个函数
* 1.默认构造函数(无参,函数体为空)
* 2.默认析构函数(无参,函数体为空)
* 3.默认拷贝构造函数,对属性进行值拷贝
* 4.赋值运算符 operator=, 对属性进行值拷贝
* 
* 如果类中有属性指向堆区,做赋值操作时也会出现深浅拷贝问题
*/

class Person
{
public:

	Person(int age)
	{
		//将年龄数据开辟到堆区
		m_Age = new int(age);
	}

	//重载赋值运算符 
	/*
	为什么要自己写 重载赋值运算符 函数呢?编译器不是会自动创建吗?
	这是因为编译器创建的是浅拷贝,如果我们的数据是存在于堆区,
	那么就会误入浅拷贝的大坑,因此要用深拷贝来解决浅拷贝带来的问题
	这个问题存在于析构的时候,会造成二次释放,报错
	所以这是要自己去写这个函数的原因
	而这个函数有一个需要注意的地方就是 这个函数的返回值一定要有 且是自身
	这样才能达到和编译器默认的内置数据类型一样的赋值功能 就是连续赋值:a = b = c;
	*/
	Person& operator=(Person& p)
	{
		/*
		编译器提供的代码是浅拷贝
        m_Age = p.m_Age;
		我们不应该是向编译器那样去做,因此我们要先做一个判断
		*/
		if (m_Age != NULL)
		{
			delete m_Age;
			m_Age = NULL;
		}

		//提供深拷贝 解决浅拷贝的问题
		m_Age = new int(*p.m_Age);

		//返回自身
		/*
		对于编译器自身的内置数据类型含有以下的功能
		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
		因此我们这里也要写出来有这样的功能,
		所以我们需要有返回值,因此返回值类型不能是void 也就是必须有返回值
		且返回值得是自身;那就是this指针,还要找到指针本身 就要解引用 因此就是*this
		*/
		return *this;
	}


	~Person()
	{
		if (m_Age != NULL)
		{
			delete m_Age;
			m_Age = NULL;
		}
	}

	//年龄的指针
	int* m_Age;

};

/*
该函数就是为了验证 a = b = c;就是返回值类型的问题
*/
void test01()
{
	Person p1(18);

	Person p2(20);

	Person p3(30);

	p3 = p2 = p1; //赋值操作

	cout << "p1的年龄为:" << *p1.m_Age << endl;//18

	cout << "p2的年龄为:" << *p2.m_Age << endl;//18

	cout << "p3的年龄为:" << *p3.m_Age << endl;//18
}

int main() 
{

	test01();

	//int a = 10;
	//int b = 20;
	//int c = 30;

	//c = b = a;
	//cout << "a = " << a << endl;
	//cout << "b = " << b << endl;
	//cout << "c = " << c << endl;

	system("pause");

	return 0;
}

7.5 关系运算符重载

#include "iostream"
using namespace std;

/*
* 4.5.5 关系运算符重载
* 
* 作用:重载关系运算符,可以让两个自定义类型对象进行对比操作
* 
* 
*/
/*
首先我们先看一下 内置数据类型 在关系运算符方面实现的功能是什么
int a = 10;
int b = 10;
if(a == b)
{
	cout << "a和b相等" << endl;
}
那么我们要在自定义类型中实现同样的功能
如:
Person p1;
Person p2;
if(p1 == p2)
{
	cout << "p1和p2相等" << endl;
}
if(p1 != p2)
{
	cout << "p1和p2不相等" << endl;
}
这就要用到重载关系运算符
*/
class Person
{
public:
	Person(string name, int age)//利用构造函数给属性进行赋初值的操作
	{
		this->m_Name = name;
		this->m_Age = age;
	};

	bool operator==(Person& p)
	{
		if (this->m_Name == p.m_Name && this->m_Age == p.m_Age)
		{
			return true;
		}
		else
		{
			return false;
		}
	}

	bool operator!=(Person& p)
	{
		if (this->m_Name == p.m_Name && this->m_Age == p.m_Age)
		{
			return false;
		}
		else
		{
			return true;
		}
	}

	string m_Name;
	int m_Age;
};

void test01()
{
	//int a = 0;
	//int b = 0;

	Person a("孙悟空", 18);
	Person b("孙悟空", 18);

	if (a == b)
	{
		cout << "a和b相等" << endl;
	}
	else
	{
		cout << "a和b不相等" << endl;
	}

	if (a != b)
	{
		cout << "a和b不相等" << endl;
	}
	else
	{
		cout << "a和b相等" << endl;
	}
}


int main() 
{

	test01();

	system("pause");

	return 0;
}

7.6 函数调用运算符重载

#include "iostream"
using namespace std;

/*
* 4.5.6 函数调用运算符重载
* 
* 函数调用运算符 ()  也可以重载
* 由于重载后使用的方式非常像函数的调用,因此称为仿函数
* 仿函数没有固定写法,非常灵活    在后续的STL里面用的非常多
* 
*/
/*



*/
//打印输出的类
class MyPrint
{
public:
	//重载函数调用运算符
	void operator()(string text)
	{
		cout << text << endl;
	}

};
void test01()
{
	//重载的()操作符 也称为仿函数
	MyPrint myFunc;
	myFunc("hello world");
}

//两个数加法类
class MyAdd
{
public:
	int operator()(int v1, int v2)
	{
		return v1 + v2;
	}
};

void test02()
{
	MyAdd add;
	int ret = add(10, 10);
	cout << "ret = " << ret << endl;

	/*
	匿名对象调用
	上面的方式是创建了一个对象,然后用上述的写法。
	如果我只是调用一次仿函数,而不是继续对该创建的对象进行其他的操作,
	那么其实也可以不创建对象,利用匿名对象调用的方式
	就是下面的写法 
	MyAdd()(100, 100)
	直接是仿函数跟上参数列表的数据就可以了
	*/
	cout << "MyAdd()(100,100) = " << MyAdd()(100, 100) << endl;
}
/*
为什么说仿函数非常的灵活
看着两个例子
第一个函数 void operator()(string text)
第二个函数 int operator()(int v1, int v2)

他们的返回值可以不同 参数个数也可以不同 因此很灵活
*/
int main() 
{

	test01();
	test02();

	system("pause");
	return 0;
}
  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2021-10-15 11:36:23  更:2021-10-15 11:36:50 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 2:56:44-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码