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

1.加号运算符重载

1.成员函数重载调用

函数的定义部分(这里的person是返回值类型,不是说构造函数)

class person
{
public:
	person operator+(person& p)
	{
		person temp;
		temp.a = this->a + p.a;
		temp.b = this->b + p.b;
		return temp;
	}
	int a;
	int b;
};

函数的调用部分

perpon p3=p1+p2;

这样p3里面的a和b的值就是说p1和p2的a,b的值加起来的。

注意函数名必须是“operator+”,因为这样后面类相加的时候,可以直接简写为p3=p1+p2;

而不用写成person p3=p1.operator+(p2);

2.全局函数重载调用

函数定义部分

person operator+(person& p1, person& p2)
{
	person temp;
	temp.a = p1.a + p2.a;
	temp.b = p1.b + p2.b;
	return temp;
}

函数调用部分

person p3=p1+p2;

注意:

1.函数名必须是“operator+”,因为这样后面类相加的时候,可以直接简写为p3=p1+p2;

而不用写成person p3=p1.operator+(p2);

2.全局函数的重载方式并不能硬套到成员函数方式的重载里,否则会报错。

3.之所以能写成p3=p1+p2;,其中p1+p2是根据形参列表来的。

3.思维发散

除了可以类与类相加,还可以更改重载函数内容,让他可以类与常数相加。

函数定义部分

person operator+(person& p1, int num)
{
	person temp;
	temp.a = p1.a + num;
	temp.b = p1.b + num;
	return temp;
}

函数调用部分

person p3=p1+10;

4.注意

operator+加号重载只能用于类的计算

错误示范

int operator+(int num1, int num2)
{
	int a;
	return a;
}

2.左移运算符重载

1.作用

通常用于输出自定义类

cout<<p<<endl;

其中p是一个类,类内有很多属性,单纯这样输出的话会报错,需要对<<进行重载,来让程序知道给如何输出。

2.与加号运算符区别

左移运算符重载的形参涉及到ostream类(即cout),要写在全局函数,加号运算符重载则可以写在成员函数。

在C++中,左移运算符(<<)重载通常不以成员函数的形式实现,而是作为全局函数(非成员函数)来定义。这样做主要是出于灵活性和一致性的考虑:

  1. 操作数的对称性:当使用左移运算符时,我们通常是在做类似这样的操作 std::cout << object;。这里,std::cout是左侧的操作数,而object是右侧的操作数。如果将<<运算符重载为成员函数,则意味着必须在左侧操作数上调用该函数,即它应该是流对象的一个成员函数。然而,由于标准库中的流类(如ostream)并不知道你的自定义类的存在,你无法直接修改这些流类来添加成员函数。

  2. 访问权限问题:如果你尝试将<<运算符重载为成员函数,则需要在你的类中进行定义。这意味着你需要将这个运算符函数声明为类的成员,从而导致左侧操作数也必须是你自定义的类类型。但实际上,我们希望左侧是一个流对象(比如std::ostream),这会导致设计上的限制。

  3. 友元函数:为了克服上述限制,通常的做法是将<<运算符重载函数作为全局函数实现,并且为了让它能够访问类的私有或保护成员,可以将其声明为对应类的友元函数。这样既保持了封装性,又提供了足够的灵活性来处理输出操作。

3.代码实现

#include<iostream>
using namespace std;
#include<string>
class person
{
public:
	person(int a, int b)
		{
		this->a = a;
		this->b = b;
		}
	int a;
	int b;
};
void operator<<(ostream &cout, person &p)
{
	cout << p.a << endl;
	cout << p.b << endl;
}
int main()
{
	person p1(1, 1);
	cout << p1;
	system("pause");
	return 0;
}

4.注意

1.cout做形参必须用引用
  1. 避免拷贝std::ostream 类型的对象通常比较复杂,包含许多内部状态和指向动态分配资源的指针。如果通过值传递(即不使用 &),则会创建该对象的一个副本。对于像流这样的对象,复制它们通常是不可行的,因为这样做会导致多个对象试图管理相同的底层资源,容易引发错误如双重释放等。使用引用可以确保我们操作的是同一个对象,而不是它的副本。

5.引用方式

cout<<p;//p是实例化的类名

6.链式思想改进

假设以这种形式输出的话,会缺少换行

#include<iostream>
using namespace std;
#include<string>
class person
{
public:
	person(int a, int b)
		{
		this->a = a;
		this->b = b;
		}
	int a;
	int b;
};
ostream& operator<<(ostream &cout, person &p)
{
	cout << p.a << endl;
	cout << p.b << endl;
	return cout;
}
int main()
{
	person p1(1, 1);
	cout << p1 << endl;;
	system("pause");
	return 0;
}

 

3.递增运算符重载

4.赋值运算符重载

1.类中默认存在的函数

其中operator=用来给类与类之间进行赋值操作

2.operator=存在的问题

其问题和浅拷贝的问题一致

假如使用了指针将数据存放在堆区(利用new)的时候,当另一个对象根据此拷贝出来的时候,与之共用的是一个指针,当程序执行完,调用析构函数(使用delete释放new开辟的空间)的时候,根据先进后出原则,会出现内存重复释放,程序会崩溃。

3.解决方法

(对operator=内部进行修改,使其成为深拷贝)

#include<iostream>
using namespace std;
#include<string>
class person
{
public:
	int* age ;
	person(int age)
	{
		this->age = new int(age);
	}
	void operator=(person &p)
	{
        //防止内存泄漏,因为在new之前age就有可能有值了
		if (age != NULL)
		{
			delete age;
		}
		age = new int(*p.age);
	}
	~person()
	{
		delete age;
	}
};
int main()
{
	person p1(18), p2(20);
	*p1.age = 18;
	p2 = p1;
	cout << *p1.age << endl;
	cout << *p2.age << endl;
	system("pause");
	return 0;
}

4.新方法存在问题

当出现

p3=p2=p1;

程序会报错,因为p2=p1;之后返回值为空,相当于p3=NULL;

5.解决(添加返回值)

#include<iostream>
using namespace std;
#include<string>
class person
{
public:
	int* age ;
	person(int age)
	{
		this->age = new int(age);
	}
	person& operator=(person &p)
	{
		if (age != NULL)
		{
			delete age;
		}
		age = new int(*p.age);
		return *this;
	}
	~person()
	{
		delete age;
	}
};
int main()
{
	person p1(18), p2(20),p3(30);
	*p1.age = 18;
	p3=p2 = p1;
	cout << *p1.age << endl;
	cout << *p2.age << endl;
	cout << *p3.age << endl;
	system("pause");
	return 0;
}

其中返回值必须是person&类型,不然会报错,原因:

在 C++ 中,赋值运算符 operator= 的返回值类型通常是类的引用(Person&),而不是类的对象本身(Person)。这是因为返回引用有以下几个重要的原因和好处:


1. 支持链式赋值

C++ 允许链式赋值操作,例如:

p3 = p2 = p1;

这实际上是按以下顺序执行的:

p3.operator=(p2.operator=(p1));
  • 如果 operator= 返回一个对象(Person),那么每次赋值都会创建一个新的临时对象。
  • 如果 operator= 返回引用(Person&),则不会创建额外的临时对象,而是直接返回当前对象的引用,允许链式赋值。

示例:

假设 operator= 返回 Person 而不是 Person&

Person operator=(person &p) {
    if (age != NULL) {
        delete age;
    }
    age = new int(*p.age);
    return *this; // 返回一个新对象
}

在这种情况下,p3 = p2 = p1; 会先调用 p2 = p1,然后返回一个临时对象,再将这个临时对象赋值给 p3。这种行为不仅低效,还容易导致资源管理问题(如内存泄漏)。


2. 避免不必要的拷贝

如果 operator= 返回 Person 对象,那么每次赋值都会触发一次拷贝构造函数或移动构造函数来创建返回的对象。这会导致性能开销,尤其是在对象较大或复杂的情况下。

通过返回引用(Person&),可以避免这些不必要的拷贝操作,提高效率。


3. 符合标准库和语言习惯

C++ 标准库中的所有容器(如 std::string, std::vector 等)都遵循这一约定,即 operator= 返回引用(T&)。这是 C++ 的通用惯例,开发者通常也期望赋值运算符的行为符合这一模式。


4. 示例对比

返回值为 Person 的错误实现

Person operator=(person &p) {
    if (age != NULL) {
        delete age;
    }
    age = new int(*p.age);
    return *this; // 返回一个新对象
}

在这种情况下:

p3 = p2 = p1;
  • p2 = p1 会返回一个临时对象。
  • 然后这个临时对象会被赋值给 p3
  • 由于返回的是一个新对象,可能导致深拷贝、浅拷贝问题,甚至内存泄漏。

返回值为 Person& 的正确实现

Person& operator=(person &p) {
    if (this != &p) { // 检查自赋值
        if (age != NULL) {
            delete age;
        }
        age = new int(*p.age);
    }
    return *this; // 返回当前对象的引用
}

在这种情况下:

p3 = p2 = p1;
  • p2 = p1 返回 p2 的引用。
  • 然后 p3 = p2 使用 p2 的引用完成赋值。
  • 整个过程没有创建任何临时对象,效率更高且更安全。

总结

赋值运算符 operator= 的返回值类型必须是类的引用(Person&),而不是类的对象(Person),原因如下:

  1. 支持链式赋值操作。
  2. 避免不必要的拷贝,提高性能。
  3. 符合 C++ 标准库和语言习惯。
  4. 更好地管理动态内存,避免潜在的资源泄漏或未定义行为。

因此,在你的代码中,operator= 的返回值类型应该是 Person&,而不是 Person

5.关系运算符重载

#include<iostream>
using namespace std;
#include<string>
class person
{
public:
	int age ;
	bool operator==(person &p)
	{
		if (this->age == p.age)
			return true;
		else return false;
	}
};
int main()
{
	person p1, p2;
	p1.age = 18;
	p2.age = 18;
	if (p1 == p2)
	{
		cout << "相等" << endl;
	}
	else 	
		cout << "相等" << endl;
	system("pause");
	return 0;
}

6.函数调用运算符重载

1.引入

函数调用运算符()也可以重载;

由于重载后使用的方式非常像函数的调用,因此成为仿函数

仿函数没有固定写法,非常灵活;

2.使用场景

1.打印
#include<iostream>
using namespace std;
#include<string>
class person
{
public:
	int age ;
	void operator()(string str)
	{
		cout << str << endl;
	}
};
int main()
{
	person p1;
	p1("hello world");
	system("pause");
	return 0;
}

2.运算
#include<iostream>
using namespace std;
#include<string>
class person
{
public:
	int age ;
	void operator()(int num1,int num2)
	{
		cout << num1+num2 << endl;
	}
};
int main()
{
	person p1;
	p1(1,2);
	system("pause");
	return 0;
}

3.匿名函数对象

#include<iostream>
using namespace std;
#include<string>
class person
{
public:
	int age;
	void operator()(int num1, int num2)
	{
		cout << num1 + num2 << endl;
	}
};
int main()
{
	person()(1, 2);
	system("pause");
	return 0;
}

好处:匿名函数对象在程序结束时会立即被释放,不占用空间。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值