C++面向对象(类和对象)—— 运算符重载(对象特性、仿函数)

在这里插入图片描述

在这里插入图片描述

🎁个人主页:工藤新一¹

🔍系列专栏:C++面向对象(类和对象篇)

​ 🌟心中的天空之城,终会照亮我前方的路

🎉欢迎大家点赞👍评论📝收藏⭐文章

五、运算符重载

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

5.0不同数据类型的运算

  • 对于内置数据类型(如:int,float,double…),我们的编译器知道如何对其执行运算的功能
int a = 10, b = 20, c = a + b;
cout << c << endl;
//输出c = 30;
  • 对于自定义数据类型,我们的编译器是否还存在对其执行运算的功能呢?

在这里插入图片描述

在这里插入图片描述


  • 我们可以通过自己实现成员函数,将两个对象相加属性后返回新的对象,来实**现自定义类型的加减法运算**
#include<iostream>
using namespace std;
class Person {
public:
	Person PersonAddP(Person& p) 
	{
		Person temp;
        //当前对象的m_a与参数p的m_a相加
		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;
};
int main(){
	Person p1, p2, p3;
	p1.m_a = 10;
	p1.m_b = 20;

	p2.m_a = 20;
	p2.m_b = 30;
    
//调用PersonAddP方法,并将结果赋值给p3
	p3 = p1.PersonAddP(p2);
	cout << "p3.m_a == " << p3.m_a << endl;
	cout << "p3.m_b == " << p3.m_b << endl;
	return 0;
}

在这里插入图片描述

在这里插入图片描述

  • 因此,我们可以自己去实现内置数据类型的运算功能,但为了使用方便,**C++**为我们提供了简易清晰的内置类型的计算方式
  • 于是就有了,内置数据类型的通用名称:
    • **opetator + 运算符号 + ()**

5.1加号运算符重载

  • 作用:实现两个自定义数据类型的相加运算

5.1.1通过成员函数重载+ 号:

  • 核心代码块:
	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;
	}

	//调用operator+ 成员函数
	Person p3 = p1.operator+ (p2);
//可简化为:
	Person p3 = p1 + p2;//--->这就是我们本预期的构架,使自定义数据类型与我们的内置数据类型的运算功能相同。
#include<iostream>
using namespace std;

class Person {
public:
	//1.通过成员函数实现加法重载
	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;
	}

	int m_a;
	int m_b;
};

void test()
{
	Person p1, p2, p3;
	p1.m_a = 10;
	p1.m_b = 30;

	p2.m_a = 40;
	p2.m_b = 30;
	
	p3 = p1.operator+(p2);//成员函数的本质调用
	cout << "p3.m_a == " << p3.m_a << endl;
	cout << "p3.m_b == " << p3.m_b << endl;
	p3 = p1 + p2;
	cout << "p3.m_a == " << p3.m_a << endl;
	cout << "p3.m_b == " << p3.m_b << endl;
}
int main(){test();return 0;}

在这里插入图片描述


5.1.2通过全局函数重载+ :

  • 核心代码块:
	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;
    }

	//调用operator+ 全局函数
	Person p3 = operator(p1, p2);
//可简化为:
	Person p3 = p1 + p2;
#include<iostream>
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 test()
{
	Person p1, p2, p3;
	p1.m_a = 10;
	p1.m_b = 30;

	p2.m_a = 40;
	p2.m_b = 30;
	
	p3 = operator+ (p1, p2);//全局函数的本质调用
    p3 = p1 + p3;
	cout << "p3.m_a == " << p3.m_a << endl;
	cout << "p3.m_b == " << p3.m_b << endl;
}
int main(){test();return 0;}

5.1.3函数重载

  • 运算符重载也可以发生函数重载:

在这里插入图片描述

  • 核心代码块:
Person operator+(Person& p, int num){
	Person temp;
	temp.m_a = p.m_a + num;
	temp.m_b = p.m_b + num;
	return temp;
}

p4 = p3 + 100;//Person + int

在这里插入图片描述

#include<iostream>
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& p, int num)
{
	Person temp;
	temp.m_a = p.m_a + num;
	temp.m_b = p.m_b + num;
	return temp;
}
void test()
{
	Person p1, p2, p3, p4;
	p1.m_a = 10;
	p1.m_b = 30;

	p2.m_a = 40;
	p2.m_b = 30;
	
	p3 = operator+ (p1, p2);

	p4 = p3 + 100;//函数重载
	cout << "p4.m_a == " << p4.m_a << endl;
	cout << "p4.m_b == " << p4.m_b << endl;
}
int main(){test();return 0;}
  • 总结1:对于内置数据类型表达式的运算符是不可以更改的
  • 总结2:不要滥用运算符重载

5.2左移运算符重载(输出运算符)

  • 作用:输出自定义的数据类型

在这里插入图片描述


5.2.1成员函数重载左移运算符

  • *核心代码块:利用成员函数重载左移运算符
	Person operator<< (Person& p)
	{
    	...
	}

//如何正确输出 p?
cout << p << endl;

在这里插入图片描述

  • 进一步推断:

在这里插入图片描述

  • 因此,我们通常不会利用成员函数重载左移运算符 << !
    因为无法(很难)实现,cout在左侧,cout << p; 。

5.2.2全局函数重载左移运算符

  • cout的数据类型:
    • 点击 cout,右键转到定义

在这里插入图片描述

在这里插入图片描述

  • 由此,我们得知**cout的数据类型:ostream(输出流对象)
class Person {public: int m_a; int m_b; };

void operator<< (ostream& cout, Person& p)
{
    cout << "m_a == " << p.m_a;
    cout << "m_b == " << p.m_b;
}
int main()
{
    Person p;
    p.m_a = 10, p.m_b = 20;
    cout << p;
    system("pause");
}

在这里插入图片描述

  • 细节注意:我们通常会将“请按任意键继续”,进行回车换行。

方法一:

//直接在全局函数内 << endl;
	cout << "m_a == " << p.m_a << endl;
    cout << "m_b == " << p.m_b << endl;
  • 但其实,这并不是我们最想要的结果,我们更顺手的是:cout << endl;

方法二:

5.2.2.1重温链式编程思想

在这里插入图片描述

  • 我们先前有讲述过,之所以可以实现无限的追加输入一定离不开 —— **链式编程思想 **

在这里插入图片描述

  • 核心代码实现:
class Person {public: int m_a; int m_b; };

//以引用的方式带动链式存储
ostream& operator<< (ostream& cout, Person& p)
{
    cout << "m_a == " << p.m_a;
    cout << " m_b == " << p.m_b;
    return cout;//--->cout的数据类型属于标准输出流对象ostream
}
int main()
{
    Person p;
    p.m_a = 10, p.m_b = 20;
    cout << p << endl;
    system("pause");
}

在这里插入图片描述

在这里插入图片描述

  • 流程图示:

在这里插入图片描述


5.2.3成员属性的权限设置

对于成员属性的权限设置,我们最好将其私有化来确保数据的安全性能。

  1. 数据保护:防止外部代码直接访问或修改类的内部数据,避免数据被非法篡改。
  2. 隐藏实现细节:用户只需要知道如何使用类,而不需要了解内部实现。
  3. 接口稳定性:即使内部实现发生变化,只要公共接口保持不变,外部代码无需修改。
  4. 数据验证:通过公共方法**(如构造函数、set 方法,友元)**对数据进行验证,确保数据的合法性。
#include<iostream>
using namespace std;

class Person {
	friend ostream& operator<<(ostream& cout, Person& p);

public:
	Person(int m_a, int m_b)
	{
		this->m_a = m_a;
		this->m_b = m_b;
	}
private:
	int m_a;
	int m_b;
};

ostream& operator<< (ostream& cout, Person& p)
{
	cout << "m_a == " << p.m_a;
	cout << " m_b == " << p.m_b;
	return cout;
}
int main()
{
	Person p(10, 20);//构造函数的赋值语法
    
	cout << p << " Hello World" << endl;
	system("pause");return 0;}
  • 总结:重载左移运算符配合友元可以实现输出自定义数据类型

5.3递增运算符重载

  • 作用:通过重载递增运算符,实现自己的整形数据
5.3.1重载左移
#include<iostream>
using namespace std;

class MyInteger {
	friend ostream& operator<< (ostream& cout, MyInteger& myint);
public:

	MyInteger() {
		m_Num = 0;
	}
private:
	int m_Num;
};

//全局函数重载左移运算符(<<)
ostream& operator<< (ostream& cout, MyInteger& myint)
{
	cout << myint.m_Num << endl;
	return cout;
}
int main()
{
	MyInteger myint;
	cout << myint << endl;
	return 0;
}

5.3.2重载前置++运算符
  • 前置递增返回引用
#include<iostream>
using namespace std;

class MyInteger {
	friend ostream& operator<< (ostream& cout, MyInteger& myint);
public:

	MyInteger() {
		m_Num = 0;
	}

	//重载前置递增运算符
	MyInteger& operator++ ()
	{
		//先进行++运算
		m_Num++;

		//再将自身作为返回
		return *this;
	}
private:
	int m_Num;
};

//全局函数重载左移运算符(<<)
ostream& operator<< (ostream& cout, MyInteger& myint)
{
	cout << myint.m_Num << endl;
	return cout;
}
int main()
{
	MyInteger myint;
	cout << ++myint << endl;
	return 0;
}

5.3.3重温引用的重要意义
//为什么这里我们要返回引用,而不是直接返回MyInteger值呢
MyInteger& operator++ ()--->引用做成员函数的返回值
	{
		//先进行++运算
		m_Num++;

		//再将自身作为返回
		return *this;
	}
  • 我们先来对比内置数据类型的递增操作

在这里插入图片描述在这里插入图片描述

  • 说明在内置数据类型中进行递增操作,是会对“同一个对象”所进行的操作,什么意思呢?

  • MyInteger& operator++ ()引用做函数的返回值

在这里插入图片描述在这里插入图片描述


  • MyInteger operator++ ()值返回
class MyInteger {
	friend ostream& operator<< (ostream& cout, const MyInteger& myint);
public:

	MyInteger() {
		m_Num = 0;
	}

	//重载前置递增运算符 - 返回值
	MyInteger operator++ ()
	{
		//先进行++运算
		m_Num++;

		//再将自身作为返回
		return *this;
	}
private:
	int m_Num;
};
/*
	ostream& operator<< (ostream& cout, MyInteger&myint)
	
	这里,MyInteger& myint 是一个 引用参数,表示它只能绑定到一个 左值(lvalue),而不能绑定到 右值(rvalue)。然而,++(++myint) 返回的是一个 右值(临时对象),而不是左值。因此,当你尝试将 ++(++myint) 传递给 operator<< 时,编译器会报错,因为它无法将右值绑定到引用参数。
解决方法:
	要解决这个问题,需要修改 operator<< 的定义,使其能够接受右值。最简单的方法是将参数改为 引用到常量(const reference):

	ostream& operator<< (ostream& cout, const MyInteger& myint)
	这样,operator<< 就可以接受左值和右值作为参数,而不会报错。
*/
ostream& operator<< (ostream& cout, const MyInteger& myint)
{
	cout << myint.m_Num << endl;
	return cout;
}

void test(){
	MyInteger myint;
	cout << ++(++myint) << endl;
	cout << myint << endl;
}
int main(){test();return 0;}

在这里插入图片描述

在这里插入图片描述

  • 由此,我们可以得出以返回值的方式对重载自增的运算是有缺漏的。

在这里插入图片描述

  • 因此引用的主要作用是为了对* 同一份数据 *进行递增操作。

5.4.3不要返回局部变量的引用
  • 局部变量,存放在栈区:

    • 栈区特点:代码块执行完毕后,局部变量(栈区内存被释放)
  • 乱码输出:

在这里插入图片描述
在这里插入图片描述

  • 代码分析:
int& test()
{
    int a = 10;//--->局部变量,存放在栈区
    return 10;
}
//局部变量a 的内存空间已经被被释放-->非法操作

5.4.4重载后置++运算符
  • 如何解决**函数重定义**现象?— 返回类型不可做重载条件

在这里插入图片描述

  • 使用占位参数区分前置与后置递增操作
  • 后置递增返回值!
MyInteger operator++(int)//int--->代表占位参数,用于区分前置与后置
{
    MyInteger temp = *this;//先记录当前结果,this->myint
    m_Num++;//再将m_Num进行++操作
    return temp;//返回临时值
}

  • 图示后置递增返回值的原理:

  • temp是一个局部对象局部对象temp,在当前代码**(MyInteger operator++(int))**块执行结束后,就会被释放掉,如果此时继续返回temp的引用(temp已被释放掉),就会造成非法操作


5.4赋值运算符重载

  • 回顾:C++编译器至少给一个类添加4个编译器

​ 1.默认构造函数(无参,函数体为空)

​ 2.默认析构函数(无参,函数体为空)

​ 3**.默认拷贝构造函数**,对成员属性进行赋值

​ 4.赋值运算符 operator=,对属性进行值拷贝

如果类中有属性指向堆区,做赋值操作时也会出现深浅拷贝问题


5.4.1开辟堆区数据

  • 在构造函数中开辟堆区空间
#include<iostream>
using namespace std;

class Person {
public:
	Person(int age)//age = 18
	{
		//将18 创建到堆区,
//并使用指针 m_Age 维护堆区的数据,[new int(age)的返回值就是一个int*]
		this->m_Age = new int(age);
	}

	int* m_Age;//写成指针,并且将m_Age 真实的数据开辟到堆区
};

void test01(){
	Person p1(18);
	//p1.m_Age 也是一个指针,因此需要解引用
	cout << "p1的年龄为:" << *p1.m_Age << endl;
}
int main(){test01();return 0;}

在这里插入图片描述


5.4.2堆区内存手动释放

  • 析构函数释放堆区内存
	~Person(){
        if(this->m_Age != NULL)
        {
			delete m_Age;
            m_Age = NULL;
        }
    }
#include<iostream>
using namespace std;

class Person {
public:

	Person(int age)//age = 18
	{
		//将18 创建到堆区,
//并使用指针 m_Age 维护堆区的数据,[new int(age)的返回值就是一个int*]
		this->m_Age = new int(age);
	}

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

	int* m_Age;//写成指针,并且将m_Age 真实的数据开辟到堆区
};

void test01()
{
	Person p1(18);
	//p1.m_Age 也是一个指针,因此需要解引用
	cout << "p1的年龄为:" << *p1.m_Age << endl;

	Person p2(20);
	p2 = p1;
	cout << "p1的年龄为:" << *p1.m_Age << endl;
	cout << "p2的年龄为:" << *p2.m_Age << endl;
}
int main(){test01();return 0;}

在这里插入图片描述

在这里插入图片描述

  • 为什么会产生这样的现象呢?

5.4.3重温浅拷贝与深拷贝问题

  • 浅拷贝堆区数据被重复释放

在这里插入图片描述


  • 深拷贝解决堆区数据重复释放

在这里插入图片描述


  • 代码实现:
#include<iostream>
using namespace std;

class Person {
public:

	Person(int age)//age = 18{
		this->m_Age = new int(age);
	}

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

	//重载 赋值运算符
//p2 = p1;-->当p2 去调用p1的时候,需要将p1当作参数传入形参列表
	void operator= (Person& p) 
	{
		//编译器提供的是浅拷贝 - m_Age = p.m_Age;

		//先判断是否有属性在堆区,如果有就先释放干净,再深拷贝
		//eg:p2(20) - p2在堆区开辟了一个m_Age = 20的数据
		if (this->m_Age != NULL)
		{
			delete this->m_Age;
			this->m_Age = NULL;
		}
		//深拷贝
		this->m_Age = new int(*p.m_Age);// - 传入的m_Age其实是一个指针
	}
	int* m_Age;//写成指针,并且将m_Age 真实的数据开辟到堆区
};

void test01(){
	Person p1(18);
	
	Person p2(20);
	p2 = p1;
	cout << "p1的年龄为:" << *p1.m_Age << endl;
	cout << "p2的年龄为:" << *p2.m_Age << endl;
}
int main(){test01();return 0;}
  • *缺陷的存在:连等情况

在这里插入图片描述

Person& operator=(Person& p)
{
    if(this->m_Age != NULL)
    {
		delete m_Age;
        this->m_Age = NULL;
    }
    //深拷贝
    this->m_Age = new int(*p.m_Age);
}
this->m_Age = new int(*p.m_Age);
return *this;
#include<iostream>
using namespace std;

class Person {
public:

	Person(int age)
	{
		this->m_Age = new int(age);
	}
	~Person() {
		if (this->m_Age != NULL)
		{
			delete m_Age;
			m_Age = NULL;
		}
	}
	Person& operator= (Person& p) 
	{
		if (this->m_Age != NULL)
		{
			delete this->m_Age;
			this->m_Age = NULL;
		}
		this->m_Age = new int(*p.m_Age);// - 传入的m_Age其实是一个指针
		return *this;
	}
	int* m_Age;//写成指针,并且将m_Age 真实的数据开辟到堆区
};

void test01(){
	Person p1(10), p2(20), p3(30);

	p3 = p2 = p1;
    //输出时不要忘记进行解引用操作,因为我们的成员属性,本身是一个在堆区开辟的指针
	cout << "p1 == " << *p1.m_Age << endl;
	cout << "p2 == " << *p2.m_Age << endl;
	cout << "p3 == " << *p3.m_Age << endl;
}
int main(){test01();return 0;}

5.5关系运算符重载

  • 作用:重载关系运算符,可以让两个自定义类型对象进行比对操作
#include<iostream>
using namespace std;

class Person {
public:
	Person(string name, int age){
		m_Name = name;
		m_Age = age;
	}
	//成员函数一般只需要一个参数,或者想一想,p1调用p2,那么参数只有p2
	bool operator==(Person& p){
		if (this->m_Name == p.m_Name && this->m_Age == p.m_Age) return true;
		
		return false;
	} 
    
    bool operator!=(Person& p)
	{
		if (this->m_Name == p.m_Name && this->m_Age == p.m_Age) return false;

		return true;
	}
	string m_Name;
	int m_Age;
};

int main(){
    
	Person p1("啦啦啦", 17);
	Person p2("呦呦呦", 3);
	if (p1 == p2) cout << "p1 和 p2 是同一个人!" << endl;
	else cout << "p1 和 p2 不是同一个人!" << endl;
    
    if(p1 != p2) cout <<  "p1 和 p2 不是同一个人!" << endl;
	else cout << "p1 和 p2 是同一个人!" << endl;
	return 0;
}

在这里插入图片描述


5.6函数调用运算符重载 — 仿函数

  • 函数调用运算符 “ () ” 也可以重载
  • 由于重载后使用的方式特别像函数的调用,因此称为仿函数
  • 仿函数没有固定写法,非常灵活

5.6.1仿函数 - 输出

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

//仿函数
class My_Print {
public:

	void operator()(string name)
	{
		cout << name << endl;
	}
};

void test() {
	My_Print my_print;
	my_print("我是啦啦啦呀!");
}


void My_Print1(string name) {
	cout << name << endl;
}
//普通函数
void test1() {
	My_Print1("我是呦呦呦呀!");
}
int main() { test(); test1(); return 0; }

在这里插入图片描述


5.6.2仿函数的灵活性

  • 两数相加
#include<iostream>
using namespace std;

class My_Add {
public:

	int operator()(int num1, int num2)
	{
		return num1 + num2;
	}
};
int main()
{
	My_Add my_add;
	cout << my_add(100, 200);
	return 0;
}

5.6.3拓展内容 - 匿名函数对象

  • 类名 + ( ): 匿名对象

  • 匿名函数对象: 匿名对象 + 仿函数

  • 临时使用:当只需要使用一个对象一次,并且不需要在其他地方引用它时,可以使用匿名对象来简化代码。例如,在调用一个方法时,直接创建一个匿名对象作为参数传递,而不需要单独定义一个变量。

  • 减少变量声明:避免为临时对象声明过多的变量,使代码更加简洁。

#include<iostream>
using namespace std;

class My_Add{
public:
    
    int operator()(int num1, int num2)
    {
        return num1 + num2;
    }
};
int main()
{
    //My_Add() - 匿名对象特点:当前行执行结束后,立即被释放
    //且重载了()
    cout << My_Add()(100, 200) << endl;
    /*
    对比:
    	My_Add my_add;
    	my_add(100, 200);
    */
	return 0;
}

🌟 各位看官好我是工藤新一¹呀~

🌈 愿各位心中所想,终有所致!

评论 21
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值