C++——类和对象(三)

一.赋值运算符

1.运算符重载

(1) 运算符重载是具有特殊名字的函数,他的名字是由operator和后面要定义的运算符共同构成。和其他函数一样,它也具有其返回类型和参数列表以及函数体

(2) 重载运算符函数的参数个数和该运算符作用的运算对象数量一样多。一元运算符有一个参数,二元运算符有两个参数,二元运算符的左侧运算对象传给第一个参数右侧运算对象传给第二个参数。

(3) 如果⼀个重载运算符函数是成员函数,则它的第一个运算对象默认传给隐式的this指针,因此运算符重载作为成员函数时,参数比运算对象少⼀个。

(4) 不能通过连接语法中没有的符号来创建新的操作符:比如operator@。

(5) .* :: sizeof ?: **.**注意以上5个运算符不能重载。

(6) 重载操作符至少有⼀个类类型参数,不能通过运算符重载改变内置类型对象的含义

(7) 重载++运算符时,有前置++和后置++,运算符重载函数名都是operator++,无法很好的区分。 C++规定,后置++重载时,增加⼀个int形参,跟前置++构成函数重载,方便区分。

(8) 重载>>和<<时,需要重载为全局函数,因为重载为成员函数,this指针默认抢占了第一个形参位 置,第一个形参位置是左侧运算对象, 调用时就变成了对象<<cout不符合使用习惯和可读性。 重载为全局函数把ostream/istream放到第一个形参位置就可以了,第二个形参位置当类类型对象。

cpp 复制代码
int  operator+(int x, int y)
{
	return x + y;
}

以上是一个错误的代码,通过这个代码也解释了第6条,必须有一个参数是类类型。

cpp 复制代码
bool operator==(const Date& d1, const Date& d2)
{
	return d1._year == d2._year&& d1._month == d2._month&& d1._day == d2._day;
}

这个才是正确的写法,其中的一个参数是类类型。

cpp 复制代码
Date d1(2024, 10, 12);
Date d2(2024, 10, 13);
operator==(d1, d2);//第一种方式
d1 == d2;//第二种方式

这是对operator的调用,有两种方式。

cpp 复制代码
class Date
{
public:
	bool operator==(const Date& d1)
	{
		return d1._year == _year && d1._month == _month && d1._day == _day;
	}
public:
	Date(int year = 1, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
	//private:
	int _year;
	int _month;
	int _day;
};

当我在类里面书写一个运算符重载时,当有两个参数,我只需要写一个即可,当有一个参数,我可以不写,因为第三条解释了,有一个隐含的this指针。

cpp 复制代码
Date d1(2024, 10, 12);
Date d2(2024, 10, 13);
d1.operator==(d2);//第一种方法
d1 == d2;//第二种方法

当我写在类里面的时候,也是有两种方法,但是与上面的两种略有不同。

cpp 复制代码
Date operator++(int)
{
	cout<<"后置++" << endl;
	Date tem = *this;
	_day++;
	return tem;
}
Date& operator++()
{
	cout << "前置++" << endl;
	_day++;
	return *this;
}

在后置++中加入的int形参可以不写参数名,因为我们并不需要传值,只是与前置++区分而已。

如果对这里的*this不太理解的话,可以看 C++----类和对象(一)-CSDN博客来了解。

cpp 复制代码
Date d1(2024, 10, 13);
Date d2(2024, 10, 13);
Date tem1 = d1++;
Date tem2 = ++d2;
d1.Print();
tem1.Print();
d2.Print();
tem2.Print();

这是一个测试代码。

可以看到tem1是后置++,所以得到的是++之前的值,而tem2是前置++,所以得到的是++后的值。

如果我想写一个输出Date的年月日的重载运算符。

当我写在类里面时

cpp 复制代码
void operator<<(ostream& out)
{
	out << _year <<"年"<< _month <<"月"<< _day<<"日" << endl;
}

那么代码就是以上的情况(不要忘记有隐藏的this指针)

但是当我要调用的时候,我写成这样

这样写是错误的,为什么呢?

因为这里已经进行了规定,当我写在类里面的时候,隐含了一个this,并且这个this在第一个参数的位置,根据第二条规定,二元运算符的左侧运算对象传给第一个参数,右侧运算对象传给第二个参数。所以我的左侧应该写一个Date类型的参数

cpp 复制代码
Date d1(2024, 10, 13);
d1 << cout;

所以这样才是正确的写法,但是这样写又太别扭了。那么我们应该怎么写呢?

这样我们只可以写在全局的函数了。

cpp 复制代码
void operator<<(ostream& out,const Date& d1  )
{
	out << d1._year << "年" <<d1. _month << "月" <<d1. _day << "日" << endl;
}

但是这样存在一个问题

因为我们的成员都是private的类型,在外面访问不到,所以我们要先了解一个用法,以后我会详细的讲解。

cpp 复制代码
friend void operator<<(ostream& out, const Date& d1);

将这个写在类的任意位置即可,这样我们便可以访问类里面的成员。

那么我想要连续的输出成员的年月日呢?

cpp 复制代码
ostream& operator<<(ostream& out,const Date& d1)
{
	out << d1._year << "年" <<d1. _month << "月" <<d1. _day << "日" << endl;
	return out;
}

我们只要把他的返回值改变就可以。

因为返回值是ostream的流,所以当输出d1后,他们就会返回cout,变成cout<<d2,在输出d2。

注意:当调用ostream或者istream两个流的时候,我们一定要用引用。

2.赋值运算符

赋值运算符重载是一个默认成员函数,用于完成两个已经存在的对象直接的拷贝赋值,这里要注意跟拷贝构造区分,拷贝构造用于⼀个对象拷贝初始化给另一个要创建的对象。

赋值运算符重载的特点:

  1. 赋值运算符重载是一个运算符重载,规定必须重载为成员函数。赋值运算重载的参数建议写成 const当前类类型引用,否则会传值传参会有拷贝

  2. 有返回值,且建议写成当前类类型引用,引用返回可以提高效率,有返回值目的是为了支持连续赋值场景。

  3. 没有显式实现时,编译器会自动生成⼀个默认赋值运算符重载,默认赋值运算符重载行为跟默认拷贝构造函数类似,对内置类型成员变量会完成值拷贝/浅拷贝(一个字节一个字节的拷贝),对自定义类型成员变量会调用他的赋值重载函数。

  4. 像Date这样的类成员变量全是内置类型且没有指向什么资源,编译器自动生成的赋值运算符重载就可以完成需要的拷贝 ,所以不需要我们显示实现赋值运算符重载。像ST这样的类,虽然也都是内置类型,但是_a指向了资源,编译器自动生成的赋值运算符重载完成的值拷贝/浅拷贝不符合我们的需求 ,所以需要我们自己实现深拷贝(对指向的资源也进行拷贝) 。像NEW这样的类型内部主要是自定义类型ST成员,编译器自动生成的赋值运算符重载会调用ST的赋值运算符重载也不需要我们显示实现NEW的赋值运算符重载。这里还有⼀个一技巧,如果⼀个类显示实现了析构并释放资源,那么他就需要显示写赋值运算符重载,否则就不需要。

代码的实现 :

cpp 复制代码
void operator=(const Date& d)
{
	_year = d._year;
	_month = d._month;
	_day = d._day;
}

这便是赋值运算符的应用。

cpp 复制代码
Date d1(2024, 10, 13);
Date d2(2024, 10, 12);
d2 = d1;
d2.Print();
Date d3 = d1;
d3.Print();

这里有两个等号,但是意义不相同,第一个是赋值运算符 ,第二个是拷贝构造

怎么区分呢?

拷贝构造是一个对象对另一个对象进行初始化,

而赋值重载是两个已经初始化好的对象进行拷贝。

cpp 复制代码
Date(const Date& d)
{
	_year = d._year;
	_month = d._month;
	_day = d._day;
	cout << "拷贝构造" << endl;
}
void operator=(const Date& d)
{
	cout << "这是赋值重载" << endl;
	_year = d._year;
	_month = d._month;
	_day = d._day;
}

以下是全部代码:

cpp 复制代码
#include<iostream>
using namespace std;
class Date
{
public:
	bool operator==(const Date& d1)
	{
		return d1._year == _year && d1._month == _month && d1._day == _day;
	}
	Date operator++(int)
	{
		cout<<"后置++" << endl;
		Date tem = *this;
		_day++;
		return tem;
	}
	Date& operator++()
	{
		cout << "前置++" << endl;
		_day++;
		return *this;
	}
	void operator<<(ostream& out)
	{
		out << _year <<"年"<< _month <<"月"<< _day<<"日" << endl;
	}
	friend ostream& operator<<(ostream& out, const Date& d1);
	void operator=(const Date& d)
	{
		cout << "这是赋值重载" << endl;
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
public:
	Date(int year = 1, 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;
		cout << "拷贝构造" << endl;
	}
	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
ostream& operator<<(ostream& out,const Date& d1)
{
	out << d1._year << "年" <<d1. _month << "月" <<d1. _day << "日" << endl;
	return out;
}
相关推荐
hccee14 分钟前
C# IO文件操作
开发语言·c#
hummhumm19 分钟前
第 25 章 - Golang 项目结构
java·开发语言·前端·后端·python·elasticsearch·golang
hunandede25 分钟前
av_image_get_buffer_size 和 av_image_fill_arrays
c++
J老熊29 分钟前
JavaFX:简介、使用场景、常见问题及对比其他框架分析
java·开发语言·后端·面试·系统架构·软件工程
zmd-zk43 分钟前
flink学习(2)——wordcount案例
大数据·开发语言·学习·flink
好奇的菜鸟1 小时前
Go语言中的引用类型:指针与传递机制
开发语言·后端·golang
Alive~o.01 小时前
Go语言进阶&依赖管理
开发语言·后端·golang
花海少爷1 小时前
第十章 JavaScript的应用课后习题
开发语言·javascript·ecmascript
手握风云-1 小时前
数据结构(Java版)第二期:包装类和泛型
java·开发语言·数据结构
喵叔哟1 小时前
重构代码中引入外部方法和引入本地扩展的区别
java·开发语言·重构