C++——拷贝构造和 运算符重载

85 阅读6分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第13天,点击查看活动详情

1. 拷贝构造函数

1.值传递

#include<iostream>
using namespace std;
class date
{
public:
	date(int year = 1, int month = 1, int day = 1)//全缺省构造
	{
		_year = year;
		_month = month;
		_day = day;
	}
	date(date d)//值传递  date d 会报错
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	date d1(2022,12,21);
	date d2(d1);//拷贝构造
	return 0;
}

  • 这里为什么会报错?
  • 存在递归拷贝

在这里插入图片描述 d对象初始化时需要自动调用构造函数, 调用函数之前需先传参, 传参过程中又是一个拷贝构造

  • 上面是一个严谨的分析过程,没看懂就继续往下看

在这里插入图片描述

d2 (d1) ——(用d1拷贝构造d2) 对象初始化之前自动调用构造函数 调用函数之前先传参 将参数d1传给构造函数 的参数d 的过程中, 由于是传值调用,相当于将d1的拷贝传给d 即拷贝构造 —— date d (d1) (用d1构造d) 再次进行拷贝构造 参数 d1传给 参数d 的过程中,又进行拷贝构造 无休止递归下去

2. 引用传递

在这里插入图片描述 由于d为d1的别名,所以 参数d1传给 d的过程中, 不会发生拷贝构造

#include<iostream>
using namespace std;
class date
{
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;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	date d1(2022,12,21);
	date d2(d1);//拷贝构造
	return 0;
}

加入const,是为了防止由于操作失误导改变d本身 如:假设 d._year =_year , _year代表d2._year ,将d2中的年赋值给d1的年,就会导致报错

3. 浅拷贝问题

#include<iostream>
using namespace std;
class stack
{
public:
	stack(int n)//构造函数
	{
		 _a = (int*)malloc(sizeof(int) * n);
		 _size = 0;
		 _capity = n;
	}
	~stack()//析构函数
	{
		free(_a);
		_a = nullptr;
		_size = _capity = 0;
	}

private:
	int * _a;
	int _size;
	int _capity;
};
int main()
{
	stack s1(10);
	stack s2(s1);//拷贝构造
	return 0;//空间会被释放两次,程序崩溃
}

  • 以上代码为什么一运行就会报错? -在这里插入图片描述 s1._a 指针指向 开辟的10个字节的空间,由于是拷贝构造,所以将s1._a指针的值 赋值给了 s2._a指针, 使s2._a指针同样指向与s1._a相同的位置, 又由于是先构造的后析构,所以理应先析构 s2 ,s2free这块空间后,由于s1._a与s2._a指向同一个位置,s1还会对这块空间再次free 同一块空间释放两次,会导致崩溃

2. 运算符重载

1. 为什么不可以直接比较?

#include<iostream>
using namespace std;
class date
{
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;
};
int main()
{
	date d1(2022,12,21);
	date d2(2022,12,22);
	return 0;
}

  • date 实例化出的对象 d1 与d2 的大小可以直接比较嘛?
  • 不可以,自定义类型是自己定义的,怎么比较大小由自己规定 内置类型 是编译器自己定义的类型,它知道要怎么比

C++为了增强代码的可读性引入运算符重载,运算符重载是具有特殊函数名的函数

2. 操作符 ==

1. 错误写法

#include<iostream>
using namespace std;
class date
{
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://需要将_year等改为public
	int _year;
	int _month;
	int _day;
};
bool operator ==(const date& d1, const date& d2)//由几个参数,就接收几个
{
//判断年月日是否都相等
	return d1._year == d2._year && d1._month == d2._month && d1._day == d2._day;
}
int main()
{
	date d1(2022,12,21);
	date d2(2022,12,22);
	d1 == d2;
	cout << (d1 == d2) << endl;// 0 代表假
	return 0;
}

但是这种方法会 把date类中prviate的里面的成员变量变成共有的 否则就会报错

2. 正确写法

写入类中作为成员函数

#include<iostream>
using namespace std;
class date
{
public:
	date(int year = 1, int month = 1, int day = 1)//构造
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
	bool operator ==( const date& d2)// 由于隐藏的this指针的存在,&d1传过来由this指针接收
	{
		return _year == d2._year && _month == d2._month && _day == d2._day;
	}
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	date d1(2022,12,21);
	date d2(2022,12,22);
	d1 == d2;//  d1.operator==(d2) 等价于 d1.operator==(&d1,d2)
	cout << (d1 == d2) << endl;
	return 0;
}

传入类中,由于隐藏的this指针的存在,所以&d1传过去被this接收了,_year代表 d1._year 但是因为它是隐藏的,所以&d1也不需要表现出来直接传入d2即可

3. 操作符 >

#include<iostream>
using namespace std;
class date
{
public:
	date(int year = 1, int month = 1, int day = 1)//构造
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
	bool operator >( const date& d2)// 由于隐藏的this指针的存在,&d1传过来由this指针接收
	{
		if (_year > d2._year)//年大为大
		{
			return true;
		}
		else if (_year == d2._year && _month > d2._month) //年相,月大为大
		{
			return true;
		}
		else if (_year == d2._year && _month == d2._month && _day > d2._day)//年 月相等,天大为大
		{
			return true;
		}
		else
		{
			return false;
		}
	}
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	date d1(2022,12,21);
	date d2(2022,12,22);
	d1 > d2;//等价于 operator>(&d1,d2)
	cout << (d1 > d2) << endl;
	return 0;
}

判断大于时 ,同样存在一个隐藏的this指针,我们只需要判断 年大的就为大 ,年相等 月大的就为大,年月相等,天大的就为大,其他情况都为假

4. 注意事项

1. 不能通过连接其他符号来创建新的操作符 (如 operator@) 2.重载操作符必须有一个类类型或者枚举类型的操作数 在这里插入图片描述 date 就属于是自定义类型

3.用于内置类型的操作符,其含义不能改变(如 int 加法 不能改变) 4.作为类成员的重载函数时,其形参看起来比操作数数目少1成员函数的操作符有一个默认的形参this,限定为第一个形参 在这里插入图片描述 this指针是隐藏的,所以也不用传过来,this指针默认为第一个参数,并且代表d1

5. ( . * ) (:: ) (sizeof ) (? : 三目运算符) ( . ) 以上5个运算符不能重载

5.操作符 !=

#include<iostream>
using namespace std;
class date
{
public:
	date(int year = 1, int month = 1, int day = 1)//构造
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
	bool operator==(const date& d2)//d1==d2
	{
		return _year == d2._year && _month == d2._month && _day == d2._day;
	}
	bool operator!=(const date& d2)
	{
		return !(*this == d2);//借助上面的d1==d2的相反即 d1!=d2
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	date d1(2022,12,21);
	date d2(2022,12,22);
	d1 != d2;
	cout << (d1 != d2) << endl;
	return 0;
}

借助上面已经写好的d1==d2 ,取其相反 即为 d1!=d2

6. 操作符 <

#include<iostream>
using namespace std;
class date
{
public:
	date(int year = 1, int month = 1, int day = 1)//构造
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
	bool operator >(const date& d2)// 由于隐藏的this指针的存在,&d1传过来由this指针接收
	{
		if (_year > d2._year)//年大为大
		{
			return true;
		}
		else if (_year == d2._year && _month > d2._month) //年相,月大为大
		{
			return true;
		}
		else if (_year == d2._year && _month == d2._month && _day > d2._day)//年 月相等,天大为大
		{
			return true;
		}
		else
		{
			return false;
		}
	}
	bool operator<(const date& d2)// d1 < d2
	{
		return !(*this > d2);
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	date d1(2022,12,21);
	date d2(2022,12,22);
	d1 < d2;
	cout << (d1 < d2) << endl;
	return 0;
}

若直接写d1<d2 的条件太复杂,直接采用 d1 >d2 的相反,*this 代表 d1

7 .赋值操作符

1. 正常使用

#include<iostream>
using namespace std;
class date
{
public:
	date(int year = 1, int month = 1, int day = 1)//构造
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
	void operator=(const date& d2)//为了防止自己给自己赋值的事情发生,如:d1=d1
	{
		if ( this == &d2)
		{
			_year = d2._year;
			_month = d2._month;
			_day = d2._day;
		}
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	date d1(2022,12,21);
	date d2(2022,12,22);
	d1 =d2;
	return 0;
}

2. 连续赋值情况的考虑

在这里插入图片描述

如果为内置类型,如int 则可以进行连续赋值 对于 int 来说,j=z ,返回值为z ,i=z,生成最终结果 但是如果为自定义类型,连续赋值就不可以,从右开始 d3=d1 同样也应该有一个返回值,用来与d2进行赋值

传值返回

在这里插入图片描述 由于*this为date 类型,属于传值返回,即返回一个临时变量,所以需进行拷贝构造 同时也会多开辟一块空间存储, 就会导致当return ( * this) 返回时,传入 拷贝构造中创建临时变量 ,再次从中返回时,才能返回到 主函数中

传引用返回

在这里插入图片描述 传 * this的引用作为变量的别名,相当于 * this本身,不会消耗一块空间,自然在return*this 返回时,不会进入拷贝构造中,而是直接返回