C++类与对象——拷贝构造与运算符重载

发布于:2025-03-16 ⋅ 阅读:(13) ⋅ 点赞:(0)

拷贝构造函数和赋值运算符重载就是C++类默认六个函数之二。

拷贝构造函数:

如果⼀个构造函数的第⼀个参数是自身类类型的引用,且任何额外的参数都有默认值,则此构造函数 也叫做拷贝构造函数,也就是说拷贝构造是⼀个特殊的构造函数。

拷贝构造函数的特点:

  1. 拷贝构造函数是构造函数的⼀个重载。
  2. 拷贝构造函数的第⼀个参数必须是类类型对象的引⽤,使⽤传值⽅式编译器直接报错,因为语法逻辑上会引发⽆穷递归调⽤。 拷贝构造函数也可以多个参数,但是第⼀个参数必须是类类型对象的引用,后⾯的参数必须有缺省值。
  3. C++规定自定义类型对象进⾏拷贝⾏为必须调⽤拷贝构造,所以这⾥自定义类型传值传参和传值返回都会调⽤拷贝构造完成。
  4. 若未显式定义拷贝构造,编译器会⽣成⾃动⽣成拷贝构造函数。⾃动⽣成的拷贝构造对内置类型成 员变量会完成值拷贝/浅拷贝(⼀个字节⼀个字节的拷⻉),对⾃定义类型成员变量会调⽤他的拷贝构造。
  5. 传值返回会产⽣⼀个临时对象调⽤拷贝构造,传引⽤返回,返回的是返回对象的别名(引⽤),没有产⽣拷贝。但是如果返回对象是⼀个当前函数局部域的局部对象,函数结束就销毁了,那么使⽤引⽤返回是有问题的,这时的引⽤相当于⼀个野引⽤,类似⼀个野指针⼀样。传引⽤返回可以减少拷贝,但是⼀定要确保返回对象,在当前函数结束后还在,才能⽤引⽤返回。

 这是正确的拷贝构造函数,如果参数不是引用的话,首先传参的时候就要进行拷贝,会调用拷贝构造,拷贝构造有会调用拷贝构造,就这样无穷递归下去了,所以编译器会直接报错。

深拷贝与浅拷贝 

 浅拷贝就是一个字节一个字节的拷贝,一般情况下也没有什么问题,但是当有动态开辟的内存时,如果使用浅拷贝的方式写拷贝构造,那么当拷贝构造了对象时就会导致内存重复释放的问题。

上面的类中就是浅拷贝,会导致内存重复释放,

两个对象,aa,bb中的a指向的都是同一块堆内存,析构的时候,bb先析构,把这块内存释放了,aa析构的时候又释放了一次,这就会报错。

解决办法就是深拷贝:

 就只要把a也进行动态开辟内存就行了。

就和前面的析构函数,只要类的成员中有动态开辟的成员,就要自己写析构,写拷贝构造并且是深拷贝。 没有动态开辟的成员,就可以不写析构函数和拷贝构造函数,默认提供的就可以了。

赋值运算符重载:

运算符重载:

  1. 当运算符被⽤于类类型的对象时,C++语⾔允许我们通过运算符重载的形式指定新的含义。C++规定类类型对象使用运算符时,必须转换成调⽤对应运算符重载,若没有对应的运算符重载,则会编译报错。
  2. 运算符重载是具有特殊名字的函数,他的名字是由operator和后⾯要定义的运算符共同构成。和其他函数⼀样,它也具有其返回类型和参数列表以及函数体。
  3. 重载运算符函数的参数个数和该运算符作⽤的运算对象数量⼀样多。⼀元运算符有⼀个参数,⼆元运算符有两个参数,⼆元运算符的左侧运算对象传给第⼀个参数,右侧运算对象传给第⼆个参数
  4. 如果⼀个重载运算符函数是成员函数,则它的第⼀个运算对象默认传给隐式的this指针,因此运算符重载作为成员函数时,参数比运算对象少⼀个
  5. 运算符重载以后,其优先级和结合性与对应的内置类型运算符保持⼀致。
  6. 不能通过连接语法中没有的符号来创建新的运算符,比如:operator@.
  7. 有五个运算符是不能重载的.* ,  ::   ,sizeof ,  ?: .
  8. 重载操作符⾄少有⼀个类类型参数,不能通过运算符重载改变内置类型对象的含义,如: int operator+(int x, int y)
  9. ⼀个类需要重载哪些运算符,是看哪些运算符重载后有意义,⽐如Date类重载operator-就有意 义,但是重载operator+就没有意义。
  10. 重载++运算符时,有前置++和后置++,运算符重载函数名都是operator++,⽆法很好的区分。 C++规定,后置++重载时,增加⼀个int形参,跟前置++构成函数重载,方便区分。
  11. 重载 << 和 >> 时,需要重载为全局函数,因为重载为成员函数,this指针默认抢占了第⼀个形参位置,第⼀个形参位置是左侧运算对象,调⽤时就变成了 对象 << cout ,不符合使用习惯和可读性。重载为全局函数把ostream/istream放到第⼀个形参位置就可以了,第⼆个形参位置当类类型对象。

下面以Date类为例,展示运算符重载

 头文件

#pragma once
#include<iostream>
using namespace std;

class Date
{
public:
	//友元函数
	friend ostream& operator<<(ostream& cout, const Date& d);

	// 获取某年某月的天数
	int GetMonthDay(int year, int month)
	{
		static int d[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
		int day = d[month];
		if (month == 2 && (year % 4 == 0 && year % 100 != 0 || year % 400 == 0))
			day++;
		return day;
	}
	// 全缺省的构造函数
	Date(int year = 1900, int month = 1, int day = 1);
	// 拷贝构造函数
    // d2(d1)
	Date(const Date& d);

	// 赋值运算符重载
    // d2 = d3 -> d2.operator=(&d2, d3)
	Date& operator=(const Date& d);

	// 析构函数
	//~Date();

	// 日期+=天数
	Date& operator+=(int day);

	// 日期+天数
	Date operator+(int day);

	// 日期-天数
	Date operator-(int day);

	// 日期-=天数
	Date& operator-=(int day);

	// 前置++
	Date& operator++();

	// 后置++
	Date operator++(int);

	// 后置--
	Date operator--(int);

	// 前置--
	Date& operator--();

	// >运算符重载
	bool operator>(const Date& d);

	// ==运算符重载
	bool operator==(const Date& d);

	// >=运算符重载
	bool operator >= (const Date& d);

	// <运算符重载
	bool operator < (const Date& d);

	// <=运算符重载
	bool operator <= (const Date& d);

	// !=运算符重载
	bool operator != (const Date& d);

	// 日期-日期 返回天数
	int operator-(const Date& d);

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

源文件 

#include"test.h"

ostream& operator<<(ostream& cout, const Date& d)
{
	cout << d._year << "/" << d._month << "/" << d._day << endl;
	return cout;
}

//全缺省构造函数
Date::Date(int year, int month, int day)//声明和定义只能一个写缺省值
{
	_year = year;
	_month = month;
	_day = day;
}

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

// 赋值运算符重载
Date& Date::operator=(const Date& d)
{
	_year = d._year;
	_month = d._month;
	_day = d._day;
	return *this;
}

// 日期+=天数
Date& Date::operator+=(int day)
{
	if (day < 0)
		return *this -= -day;

	_day += day;
	while (_day > GetMonthDay(_year, _month))
	{
		_day -= GetMonthDay(_year, _month);
		_month++;
		if (_month == 13)
		{
			_year++;
			_month = 1;
		}
	}
	return *this;
}

// 日期+天数
Date Date::operator+(int day)
{
	Date tmp = *this;
	tmp += day; // 复用 += 运算符
	return tmp;
}

// 日期-天数
Date Date::operator-(int day)
{
	Date tmp = *this;
	tmp -= day; // 复用 -= 运算符
	return tmp;
}

// 日期-=天数
Date& Date::operator-=(int day)
{
	if (day < 0)
		return *this += -day;

	_day -= day;
	while (_day <= 0)
	{
		_month--;
		if (_month == 0)
		{
			_year--;
			_month = 12;
		}
		_day += GetMonthDay(_year, _month);
	}
	return *this;
}

// 前置++
Date& Date::operator++()
{
	*this += 1; // 复用+=
	return *this;
}

// 后置++
Date Date::operator++(int)
{
	Date tmp = *this;
	*this += 1;//复用+=
	return tmp;
}

// 后置--
Date Date::operator--(int)
{
	Date tmp = *this;
	*this -= 1;//复用-=
	return tmp;
}

// 前置--
Date& Date::operator--()
{
	*this -= 1;//复用-=
	return *this;
}

//大量的复用
// >运算符重载
bool Date::operator>(const Date& d)
{
	if (_year != d._year)return _year > d._year;
	if (_month != d._month)return _month > d._month;
	return _day > d._day;
}

// ==运算符重载
bool Date::operator==(const Date& d)
{
	return (_year == d._year
		&& _month == d._month
		&& _day == d._day);
}

// >=运算符重载
bool Date::operator >= (const Date& d)
{
	return (*this > d) || (*this == d);
}

// <运算符重载
bool Date::operator < (const Date& d)
{
	return !(*this >= d);
}

// <=运算符重载
bool Date::operator <= (const Date& d)
{
	return !(*this > d);
}

// !=运算符重载
bool Date::operator != (const Date& d)
{
	return !(*this == d);
}

// 日期-日期 返回天数
int Date::operator-(const Date& d)
{
	Date maxd = *this;
	Date mind = d;//防止改变原数据
	int n = 0;
	int flag = 1;
	if (*this < d)
	{
		maxd = d;
		mind = *this;
		flag = -1;
	}

	while (mind != maxd)
	{
		mind += 1;
		n++;
	}
	return n * flag;
}

补充:

	Date d2(2025, 3, 15);
	Date d3 = d2;

上面的d3虽然用的是 = ,但是,实际上是调用拷贝构造函数,不是赋值重载! 只有原本就已经实例化了的对象才能用赋值重载,没有实例化的就是构造函数。