当前位置: 首页 > article >正文

【C++】拷贝构造函数与运算符重载

写在前面

拷贝构造函数与赋值运算符重载都是属于类的默认成员函数!
默认成员函数是程序猿不显示声明定义,编译器会中生成。

在程序编写中,我们也经常使用拷贝的方式来获取到对应的值,例如整形变量拷贝int a = 0; int b = a;等等。在程序的编写中,我们也会需要进行对象的拷贝,这样来获取到对应对象的值。


文章目录

  • 写在前面
  • 一、拷贝构造函数
    • 1.1、若未显式定义,编译器会生成默认的拷贝构造函数。
  • 二、赋值运算符重载
    • 2.1、赋值运算符重载(类的默认成员函数)
    • 2.2、重载运算符中的特殊:前置++和后置++的重载
    • 2.3、流插入与流提取的重载运算符


一、拷贝构造函数

拷贝构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用。是一个构造函数

拷贝构造函数也是特殊的成员函数,其特征如下:

  1. 拷贝构造函数是构造函数的一个重载形式
  2. 拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错,因为会引发无穷递归调用。

在这里插入图片描述
我们先创建一个标准的拷贝构造函数

class Date
{
public:
	Date() {
		cout << "Date()" << endl;
	}

	Date(int year, int month, int day) {
		_year = year;
		_month = month;
		_day = day;
	}

	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}

	Date(const Date& d1) { //拷贝构造函数
		_year = d1._year;
		_month = d1._month;
		_day = d1._day;
	}

private:
	int _year = 1;
	int _month = 1;
	int _day = 1;
};

int main() {
	Date d1(2024,1,1);
	printf("d1:>");
	d1.Print();
	
	Date d2(d1);
	printf("d2:>");
	d2.Print();

	return 0;
}

程序运行结果:
在这里插入图片描述

如果我们不使用类引用作为参数,而是使用传值的方式作为参数的话,会导致无穷递归调用
在这里插入图片描述

class Date
{
public:
	Date() {
		cout << "Date()" << endl;
	}

	Date(int year, int month, int day) {
		_year = year;
		_month = month;
		_day = day;
	}

	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}

	Date(Date d1) { //拷贝构造函数,使用传值方式
		_year = d1._year;
		_month = d1._month;
		_day = d1._day;
	}

private:
	int _year = 1;
	int _month = 1;
	int _day = 1;
};

int main() {
	Date d1(2024,1,1);
	printf("d1:>");
	d1.Print();
	
	Date d2(d1);
	printf("d2:>");
	d2.Print();

	return 0;
}
  • 我们知道,函数参数使用传值,那么形参就是实参的一份临时拷贝。

C++规定的拷贝:

  1. 内置类型直接拷贝
  2. 自定义类型必须调用该自定义类型对应的拷贝构造函数完成拷贝。
  • 此时,形参stack类的对象,我们实参也是stack类的对象d1,形参接收就需要进行拷贝,而这个时候又涉及到了类的拷贝,那么也是调用stack类的拷贝构造函数,但是拷贝构造函数又时需要传值……无限套娃,如下图在这里插入图片描述

程序运行结果:
在这里插入图片描述

  • 编译器会检查拷贝构造函数是否正确的编写。

在拷贝构造中,参数要加上const,加上const的好处:

  1. 防止在拷贝构造中写反,导致原对象被修改为其他值
  2. 如果原对象是const修饰的对象,也可以进行拷贝构造,不会造成权限放大。

1.1、若未显式定义,编译器会生成默认的拷贝构造函数。

默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。

拷贝构造对类型的处理:

  1. 对内置类型成员进行值拷贝/浅拷贝
  2. 对自定义类型成员会调用它的拷贝构造函数

类中的属性全部都是内置类型成员,我们可以使用默认生成的拷贝构造函数完成拷贝操作

在这里插入图片描述
我们把Date类自己编写的拷贝构造删除后,尝试使用默认的拷贝构造函数。

class Date
{
public:
	Date() {
		cout << "Date()" << endl;
	}

	Date(int year, int month, int day) {
		_year = year;
		_month = month;
		_day = day;
	}

	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}

private:
	int _year = 1;
	int _month = 1;
	int _day = 1;
};

int main() {
	Date d1(2024,1,1);
	printf("d1:>");
	d1.Print();
	
	Date d2(d1);
	printf("d2:>");
	d2.Print();

	return 0;
}

程序运行结果:
在这里插入图片描述
在上图中,我们也可以看出,程序没有编写拷贝构造函数,使用默认拷贝构造函数也完成了任务。但这并不代表可以一直使用默认构造函数
在这里插入图片描述
我们使用stack类来尝试一下,默认拷贝构造函数是否可以完成我们预想的结果。

class stack {
public:
	stack(int defintCapacity = 4) {
		_arr = (int*)calloc(defintCapacity, sizeof(int));
		if (nullptr == _arr)
		{
			perror("malloc申请空间失败");
			return;
		}
		_capacity = defintCapacity;
		_size = 0;
	}

	stack(int* arr, int defintCapacity) {
		if (nullptr == arr) {
			perror("malloc申请空间失败");
			return;
		}
		_arr = arr;
		_capacity = defintCapacity;
		_size = 0;
	}

	void push(int x) {
		//....扩容等
		_arr[_size++] = x;
	}
	 
	~stack() {
		free(_arr);
		_arr = nullptr;
	}
private:
	int* _arr;
	int _size;
	int _capacity;
};

int main() {
	stack s1;

	stack s2(s1);

	return 0;
}

程序运行结果:

在这里插入图片描述

  • 因为在默认的拷贝构造函数对象按内存存储按字节序完成拷贝,在拷贝结束后发现 s2对象的_arr数组地址和s1对象的_arr数组地址一样
  • 这时候我们如果我们s2存储内容,也会改变s1对象的内容,这不是我们想要的,而且对象结束生命周期之后对象会自动调用自己对应的析构函数。这时候析构函数多次释放同一个空间程序崩溃,如下图。
    在这里插入图片描述

使用浅拷贝的内存图布局如下图:
在这里插入图片描述
要解决这种问题,就需要涉及深拷贝(不才后面会专门写一篇笔记),下面代码是针对这种情况的特殊解决办法。

class stack {
public:
	stack(int defintCapacity = 4) {
		_arr = (int*)calloc(defintCapacity, sizeof(int));
		if (nullptr == _arr)
		{
			perror("malloc申请空间失败");
			return;
		}
		_capacity = defintCapacity;
		_size = 0;
	}

	stack(int* arr, int defintCapacity) {
		if (nullptr == arr) {
			perror("malloc申请空间失败");
			return;
		}
		_arr = arr;
		_capacity = defintCapacity;
		_size = 0;
	}
	stack(stack& s) { //需要程序猿自己编写拷贝构造函数(深拷贝)
		_arr = (int*)calloc(s._capacity, sizeof(int));
		if (nullptr == _arr)
		{
			perror("malloc申请空间失败");
			return;
		}
		memcpy(_arr, s._arr, s._capacity * sizeof(int));
		_capacity = s._capacity;
		_size = s._size;
	}

	void push(int x) {
		//....扩容等
		_arr[_size++] = x;
	}
	 
	~stack() {
		free(_arr);
		_arr = nullptr;
	}
private:
	int* _arr;
	int _size;
	int _capacity;
};

我们解决了内置类型拷贝构造的问题后,我们自定义类型是否需要每个都写拷贝构造函数呢?

在这里插入图片描述
我们使用栈实现队列

class stack {
public:
	stack(int defintCapacity = 4) {
		_arr = (int*)calloc(defintCapacity, sizeof(int));
		if (nullptr == _arr)
		{
			perror("malloc申请空间失败");
			return;
		}
		_capacity = defintCapacity;
		_size = 0;
	}

	stack(int* arr, int defintCapacity) {
		if (nullptr == arr) {
			perror("malloc申请空间失败");
			return;
		}
		_arr = arr;
		_capacity = defintCapacity;
		_size = 0;
	}

	void push(int x) {
		//....扩容等
		_arr[_size++] = x;
	}
	 
	~stack() {
		free(_arr);
		_arr = nullptr;
	}

	stack(stack& s) {
		_arr = (int*)calloc(s._capacity, sizeof(int));
		if (nullptr == _arr)
		{
			perror("malloc申请空间失败");
			return;
		}
		memcpy(_arr, s._arr, s._capacity * sizeof(int));
		_capacity = s._capacity;
		_size = s._size;
	}
private:
	int* _arr;
	int _size;
	int _capacity;
};

class MyQueue{

private:
	stack s1;
	stack s2;
};

int main() {
	stack s1;
	s1.push(1);
	s1.push(2);
	s1.push(3);

	stack s2(s1);

	return 0;
}

程序运行结果:
在这里插入图片描述

  • 这时,MyQueue类就不需要编写拷贝构造,因为在MyQueue对象进行拷贝时,会自动调用stack类的拷贝构造。
  • 不写拷贝构造函数不需要写构造函数与析构函数的逻辑是一样的。
  • 拷贝构造函数形参也尽可能的加上const修饰权限范围,这样可以防止形参对象的属性被修改,而且也可以防止实参的权限放大的问题

二、赋值运算符重载

C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数
在这里插入图片描述
日期类比较大小

class Date
{
public:
	Date() {
		cout << "Date()" << endl;
	}

	Date(int year, int month, int day) {
		_year = year;
		_month = month;
		_day = day;
	}

	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}

public:
	int _year = 1;
	int _month = 1;
	int _day = 1;
};

bool Less(const Date& s1, const Date& s2) {
	if (s1._year < s2._year) {
		return true;
	}
	else if (s1._year == s2._year && s1._month < s2._month) {
		return true;
	}
	else if (s1._year == s2._year && s1._month == s2._month && s1._day < s2._day) {
		return true;
	}

	return false;
}

bool Larger(const Date& s1, const Date& s2) {
	if (s1._year > s2._year) {
		return true;
	}
	else if (s1._year == s2._year && s1._month > s2._month) {
		return true;
	}
	else if (s1._year == s2._year && s1._month == s2._month && s1._day > s2._day) {
		return true;
	}

	return false;
}
int main() {
	Date d1(2022,5,4);	
	Date d2(2022,5,5);
	
	cout << Less(d1, d2) << endl;
	cout << Larger(d1, d2) << endl;

	return 0;
}
  • 在我们自定义的日期类中,我们想比较两个类的大小只能通过函数的形式来比较
  • 如果函数命名不规范时,我们难以比较

所以在C++中增加了赋值运算符重载,方便程序猿的使用

运算符重载关键字:operator后面接需要重载的运算符符号

我们使用运算符重载来改善上述代码:

class Date
{
public:
	Date() {
		cout << "Date()" << endl;
	}

	Date(int year, int month, int day) {
		_year = year;
		_month = month;
		_day = day;
	}

	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}

public:
	int _year = 1;
	int _month = 1;
	int _day = 1;
};

bool operator<(const Date& s1, const Date& s2) {
	if (s1._year < s2._year) {
		return true;
	}
	else if (s1._year == s2._year && s1._month < s2._month) {
		return true;
	}
	else if (s1._year == s2._year && s1._month == s2._month && s1._day < s2._day) {
		return true;
	}

	return false;
}

bool operator>(const Date& s1, const Date& s2) {
	if (s1._year > s2._year) {
		return true;
	}
	else if (s1._year == s2._year && s1._month > s2._month) {
		return true;
	}
	else if (s1._year == s2._year && s1._month == s2._month && s1._day > s2._day) {
		return true;
	}

	return false;
}
int main() {
	Date d1(2022, 5, 4);
	Date d2(2022, 5, 5);

	cout << operator<(d1, d2) << endl;
	cout << operator>(d1, d2) << endl;

	return 0;
}

我们只需要把函数名修改为:operator<operator>。这样就完成了 <>的运算符重载。我们在main函数中调用也是如此,使用operator<operator>来调用此函数。但是这样就和我们编写函数没什么区别。

运算符重载的作用是可以直接在main函数中使用<> 来进行比较。如下:

int main() {
	Date d1(2022, 5, 4);
	Date d2(2022, 5, 5);

	cout << operator<(d1, d2) << endl;
	cout << operator>(d1, d2) << endl;

	printf("\n");

	cout << (d1 < d2) << endl;
	cout << (d1 > d2) << endl;
	return 0;
}

程序运行结果:

在这里插入图片描述

  • 虽然我们是使用了<>来进行比较,但是我们通过operater来重载<>后,我们就可以直接使用<>来进行比较。本质上还是使用operator<operator>来调用此函数编译器会自己处理的过程。如下图:在这里插入图片描述

  • 这时,operater重载的<>是全局的,对类中的属性要求就一定是public类型,如果是私有的无法访问,所以我们要重载类对象的<>时,可以把operater函数作为类的成员函数。

运算符重载必须使用满足C++对运算符重载的规定:

  1. 不能通过连接其他符号来创建新的操作符:比如operator@
  2. 重载操作符必须有一个类类型参数,即不能全部是内置类型,比如bool operator+(int& a ,int& b){...}
  3. 用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不能改变其含义,如改为减…
  4. 作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this
  5. .* ::(域作用限定符) sizeof ?:(三目) . 注意以上5个运算符不能重载。
    在这里插入图片描述
    我们把上述例子的全局运算符重载函数改为Date类的内置成员函数。
class Date
{
public:
	Date() {
		cout << "Date()" << endl;
	}

	Date(int year, int month, int day) {
		_year = year;
		_month = month;
		_day = day;
	}

	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}

	bool operator<(const Date& s2) {
		if (_year < s2._year) {
			return true;
		}
		else if (_year == s2._year && _month < s2._month) {
			return true;
		}
		else if (_year == s2._year && _month == s2._month && _day < s2._day) {
			return true;
		}

		return false;
	}

	bool operator>( const Date& s2) {
		if (_year > s2._year) {
			return true;
		}
		else if (_year == s2._year && _month > s2._month) {
			return true;
		}
		else if (_year == s2._year && _month == s2._month && _day > s2._day) {
			return true;
		}

		return false;
	}
public:
	int _year = 1;
	int _month = 1;
	int _day = 1;
};


int main() {
	Date d1(2022, 5, 4);
	Date d2(2022, 5, 5);

	cout << (d1 < d2) << endl;
	cout << (d1 > d2) << endl;

	printf("\n");

	cout << d1.operator<(d2) << endl;
	cout << d1.operator>(d2) << endl;

	return 0;
}
  • 作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this
  • 而且作为类成员函数重载时,就算属性是私有的,我们也可以进行比较。
  • 类成员函数重载时,在main函数中,也可以直接使用<>来进行比较,编译器最终也是会转换为d1.operator<(d2)在这里插入图片描述

2.1、赋值运算符重载(类的默认成员函数)

赋值运算符重载与 拷贝构造函数不同处:

  • 拷贝构造函数适用于:用一个已经存在的对象初始化另外一个对象
  • 赋值运算符重载函数适用于:已经存在的两个对象之间赋值拷贝

在这里插入图片描述

class Date
{
public:
	Date() {
		cout << "Date()" << endl;
	}

	Date(int year, int month, int day) {
		_year = year;
		_month = month;
		_day = day;
	}
	
	Date(const Date& d1) {
	cout << "Date(const Date& d1)" << endl;
		_year = d1._year;
		_month = d1._month;
		_day = d1._day;
	}

	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}

	bool operator<(const Date& s2) {
		if (_year < s2._year) {
			return true;
		}
		else if (_year == s2._year && _month < s2._month) {
			return true;
		}
		else if (_year == s2._year && _month == s2._month && _day < s2._day) {
			return true;
		}

		return false;
	}

	bool operator>(const Date& s2) {
		if (_year > s2._year) {
			return true;
		}
		else if (_year == s2._year && _month > s2._month) {
			return true;
		}
		else if (_year == s2._year && _month == s2._month && _day > s2._day) {
			return true;
		}

		return false;
	}
	
	void operator=(const Date& s2) {
		_year = s2._year;
		_month = s2._month;
		_day = s2._day;
	}
public:
	int _year = 1;
	int _month = 1;
	int _day = 1;
};


int main() {
	Date d1(2022, 5, 4);
	Date d2(2000, 1, 1);

	d1 = d2;

	return 0;
}

程序运行结果

在这里插入图片描述

  • 程序运行结果没有问题,但是我们在内置类型赋值的时候可以连续赋值的,如int a,b; a=b=12;,但是我们把赋值运算符重载的返回值设置了void,程序运行不了连续赋值的结果。如下图:在这里插入图片描述

我们可以把返回值设置为Date类作为返回值。如下程序:

Date operator=(const Date& s2) {
	_year = s2._year;
	_month = s2._month;
	_day = s2._day;
	
	return *this;
}
  • 这样我们就可以完成连续赋值的处理,如下图
    在这里插入图片描述

但是,我们直接使用传值返回,会造成大量无用的拷贝构造,这样会对性能造成一点影响:在这里插入图片描述

我们就可以把传值返回改为传引用返回虽然this指针是形参,会随着函数的生命周期结束而销毁,但是我们可以*this作为返回,这样我们返回的是对象的地址对象的生命周期不会随着函数的生命周期结束而销毁。

Date& operator=(const Date& s2) {
	_year = s2._year;
	_month = s2._month;
	_day = s2._day;
	
	return *this;
}

程序运行结果:
在这里插入图片描述

  • d1 = d2其底层与d1.operator=(d2)是一样的在这里插入图片描述
  • 当然,在使用重载赋值运算符时,程序猿会出现自己与自己赋值的情况。这时候我们可以在重载运算符中加入自己给自己赋值的条件判断。这样可以避免自己给自己赋值。
Date& operator=(const Date& s2) {
	if (this != &s2) {
		_year = s2._year;
		_month = s2._month;
		_day = s2._day;
	}
		
	return *this;
}

赋值运算符重载格式:

  • 参数类型: const T&,传递引用可以提高传参效率
  • **返回值类型:**T&,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值
  • 检测是否自己给自己赋值
  • 返回*this 要复合连续赋值的含义

因为赋值运算符重载是类的默认成员函数,即用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝。

注意编译器默认生成的赋值运算符重载函数:内置类型成员变量是直接赋值的,而自定义类型成员变量需要调用对应类的赋值运算符重载完成赋值。与拷贝构造的行为一样。

需要注意的是,编译器生成的赋值运算符拷贝逐字节拷贝即浅拷贝的方式。 遇到指针等相关拷贝的情况,需要程序猿编写深拷贝。

赋值运算符只能重载成类的成员函数不能重载成全局函数

原因:赋值运算符如果不显式实现,编译器会生成一个默认的。此时用户再在类外自己实现一个全局的赋值运算符重载,就和编译器在类中生成的默认赋值运算符重载冲突了,故赋值运算符重载只能是类的成员函数。
在这里插入图片描述

2.2、重载运算符中的特殊:前置++和后置++的重载

在重载运算符中有前置++后置++两个特殊的运算符重载。因为在程序中这两个运算符的函数名都是一样的。需要函数重载来区别前置++后置++。(重载运算符--同理)
在这里插入图片描述
我们先实现前置++,还是使用上述的Date类为例。

class Date
{
public:
	Date() {
		//cout << "Date()" << endl;
	}

	Date(int year, int month, int day) {
		_year = year;
		_month = month;
		_day = day;
	}
	Date(const Date& d1) {
		cout << "Date(const Date& d1)" << endl;
		_year = d1._year;
		_month = d1._month;
		_day = d1._day;
	}

	void Print(){
		cout << _year << "-" << _month << "-" << _day << endl;
	}

	bool operator<(const Date& s2) {
		if (_year < s2._year) {
			return true;
		}
		else if (_year == s2._year && _month < s2._month) {
			return true;
		}
		else if (_year == s2._year && _month == s2._month && _day < s2._day) {
			return true;
		}

		return false;
	}

	bool operator>(const Date& s2) {
		if (_year > s2._year) {
			return true;
		}
		else if (_year == s2._year && _month > s2._month) {
			return true;
		}
		else if (_year == s2._year && _month == s2._month && _day > s2._day) {
			return true;
		}

		return false;
	}
	
	Date& operator=(const Date& s2) {
		_year = s2._year;
		_month = s2._month;
		_day = s2._day;
		
		return *this;
	}

	Date& operator++() {//前置++
		_day++;
		return *this;//因为是前置++,可以把加完后的结果直接当做返回值。
	}

public:
	int _year = 1;
	int _month = 1;
	int _day = 1;
};

int main() {
	Date d1(2022, 5, 4);
	
	++d1;
	d1.Print();
	return 0;
}

程序运行结果:

在这里插入图片描述

  • operator++():因为++是自增不需要参数,所以我们把形参列表设为空。

我们operator++()当做为前置++,但后置++也是相同的函数名operator++,所以在C++中我们后置++的形参中增加一个参数,让其形成函数重载,以达到前置++后置++的区别

但是 ++是自增,不需要接收任何形参,但是需要完成函数重载,所以我们只需要在形参列表中增加一个int类型即可完成重载,该类型可以不添加变量名称。如下

	Date& operator++() {//前置++
		_day++;
		return *this;//因为是前置++,可以把加完后的结果直接当做返回值。
	}
	
	Date operator++(int) {//后置++
		Date d1 = *this;//因为是后置加加,所以要把原来的结果先储存为一个临时变量。之后,我们才可以进行自增处理
		_day++;
		return d1;//返回的是临时变量的值。
	}
  • 后置++中,operator++(int)int参数不是为了接收具体的值。仅仅是占位,跟前置++构成函数重载。

其中前置++后置++的调用会由编译器区分。我们程序员不需要理会如何进行调用。

在我们程序员自己编写的前置++后置++会有性能的区别。因为 后置++需要进行拷贝构造,创建一个临时变量来作为返回值,而且也是传值返回。但是在内置类型的++--中性能区别不大

2.3、流插入与流提取的重载运算符

在上面例中,我们Date类对象的内容打印是使用print函数来进行Date类属性的显示,为了更好的打印,我们可以使用流的重载运算符来进行自定义类型的打印。
在这里插入图片描述
不才在文档查询网站中,查询到cinistream类的对象和coutosterem类的对象,那么我们在Date类中,就可以通过istream类和osterem类来自定义引用对象来进行标准输入输出。
在这里插入图片描述
我们先以打印为例,即重载<<运算符。

class Date
{
public:
	Date() {
		//cout << "Date()" << endl;
	}

	Date(int year, int month, int day) {
		_year = year;
		_month = month;
		_day = day;
	}
	Date(const Date& d1) {
		cout << "Date(const Date& d1)" << endl;
		_year = d1._year;
		_month = d1._month;
		_day = d1._day;
	}
	void operator<<(ostream& out) {
		out << _year << "年" << _month << "月" << _day << "日";
	}

private:
	int _year = 1;
	int _month = 1;
	int _day = 1;
};


int main() {
	Date d1(2022, 5, 4);
	//d1.Print();
	cout << d1;
	Date n1 = d1;
	//n1.Print();
	cout << d1 << endl << n1 << endl;

	return 0;
}

运行结果:在这里插入图片描述

  • 因为我们是把重载运算符写入了类中,成为了类的方法,所以我们直接使用cout << d1是无法访问的
  • cout << d1其本质是cout.operator(d1),我们在库中是没有自定义类型重载的。如下图在这里插入图片描述
  • 所以我们在Date类中重载的<<运算符,需要d1 << cout这样来使用,因为这才符合d1.operator(cout)的函数调用

但是d1.operator(cout)明显不符合我们是使用习惯,但是在类中,第一个形参默认是this指针,无法改变的。所以我们就把operator<<定义在类外面,作为全局函数。

作为全局重载运算符函数时,我们久可以把第一个参数设置为ostrem,第二次参数设置为我们自定义类型Date,这样久可以完成cout << d1的使用。如下图
在这里插入图片描述

  • 此时虽然,cout << d1没有报错,但是我们类中的属性时私有的,为了访问到我们私有的属性,我们在类中可以把全局重载运算符函数operater<<声明为友元函数。(不才后面会专门出一篇笔记讲解)
class Date
{
	friend void operator<<(ostream& out, const Date& d1);//把operator<<函数声明为友元函数
	
public:
	Date() {
		//cout << "Date()" << endl;
	}

	Date(int year, int month, int day) {
		_year = year;
		_month = month;
		_day = day;
	}
	Date(const Date& d1) {
		//cout << "Date(const Date& d1)" << endl;
		_year = d1._year;
		_month = d1._month;
		_day = d1._day;
	}

private:
	int _year = 1;
	int _month = 1;
	int _day = 1;
};

void operator<<(ostream& out, const Date& d1) {
	out << d1._year << "年" << d1._month << "月" << d1._day << "日";
}
  • 在类中声明前加上friend关键字,声明void operator<<(ostream& out, const Date& d1)函数时友元函数。

运行结果:
在这里插入图片描述
和之前的运算符重载函数相同,我们为了多次运用<<,我们的返回值久设置为ostrem,这样我们就可以实现复合使用cout << d1 << endl。又因为coutiostream创建的对象,而且cout的生命周期是程序的声明周期,所以我们可以把ostream设置为ostream&引用返回

ostream& operator<<(ostream& out, const Date& d1) {
	out << d1._year << "年" << d1._month << "月" << d1._day << "日";
	return out;
}

运行结果:
在这里插入图片描述
同理,流插入也是如此

istream& operator>>(istream& in, Date& d1) {
	in >> d1._year >> d1._month >> d1._day;
	return in;
}
  • 也需要在类中声明为友元函数。

以上就是本章所有内容。若有勘误请私信不才。万分感激💖💖 如果对大家有用的话,就请多多为我点赞收藏吧~~~💖💖
请添加图片描述

ps:表情包来自网络,侵删🌹


http://www.kler.cn/a/500545.html

相关文章:

  • 【Powershell】Windows大法powershell好(二)
  • 【Ubuntu与Linux操作系统:一、Ubuntu安装与基本使用】
  • 搭建prometheus+grafana监控系统抓取Linux主机系统资源数据
  • 直流无刷电机控制(FOC):电流模式
  • 为AI聊天工具添加一个知识系统 开发环境准备
  • 【Bluedroid】HFP连接流程源码分析(一)
  • 基于ResNet的CIFAR-10分类实现与分析
  • django基于Python的校园个人闲置物品换购平台
  • Android Framework WMS全面概述和知识要点
  • 浅谈云计算03 | 云计算的技术支撑(云使能技术)
  • 基于华为ENSP的OSPF-开放式最短路径优先协议保姆级别详解(1)
  • JAVA 嵌套列表初始化和转字符串
  • 十大排序简介
  • 新冠肺炎服务预约微信小程序的设计与实现ssm+论文源码调试讲解
  • 【Unity精品插件】Love/Hate:专注于 AI 行为与情感互动
  • 1F1B 非交错式调度模式与 GPipe 策略的内存节省优势
  • 【CSS】HTML页面定位CSS - position 属性 relative 、absolute、fixed 、sticky
  • 用JSONEncoder解决Object of type Color is not JSON serializable报错
  • 【数据结构-堆】2233. K 次增加后的最大乘积
  • 【python】str.upper()、str.join()、stri.strip()用法
  • Java 项目中引入阿里云 OSS SDK
  • Pytorch使用手册-优化 Vision Transformer 模型以用于部署(专题十六)
  • ADB->查看进程并强杀进程
  • 【论文阅读】MAMBA系列学习
  • 小学校园安全用电 防触电设备功能介绍 #电不伤人,电不漏电#