c++类和对象———拷贝构造和赋值运算符重载

衔接上一篇博客构造函数和析构函数c++类和对象————构造函数和析构函数


一、拷贝构造是什么?

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

二、拷贝构造

1.特点

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

1. 拷贝构造函数是构造函数的一个重载形式

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

3. 若未显式定义,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按 字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝

注意:在编译器生成的默认拷贝构造函数中,内置类型是按照字节方式直接拷贝的,而自定 义类型是调用其拷贝构造函数完成拷贝的。

4. 编译器生成的默认拷贝构造函数已经可以完成字节序的值拷贝了,还需要自己显式实现吗? 当然像日期类这样的类是没必要的。那么下面的类呢?验证一下试试?

// 这里会发现下面的程序会崩溃掉?这里就需要我们以后讲的深拷贝去解决。
typedef int DataType;
class Stack
{
public:
 Stack(size_t capacity = 10)
 {
 _array = (DataType*)malloc(capacity * sizeof(DataType));
 if (nullptr == _array)
 {
 perror("malloc申请空间失败");
 return;
 }
 _size = 0;
 _capacity = capacity;
 }
 void Push(const DataType& data)
 {
 // CheckCapacity();
 _array[_size] = data;
 _size++;
 }
 ~Stack()
 {
 if (_array)
 {
 free(_array);
 _array = nullptr;
 _capacity = 0;
 _size = 0;
 }
 }
private:
 DataType *_array;
 size_t _size;
 size_t _capacity;
};
int main()
{
 Stack s1;
 s1.Push(1);
 s1.Push(2);
 s1.Push(3);
 s1.Push(4);
 Stack s2(s1);
 return 0;
}

 

注意:类中如果没有涉及资源申请时,拷贝构造函数是否写都可以;一旦涉及到资源申请 时,则拷贝构造函数是一定要写的,否则就是浅拷贝。

5. 拷贝构造函数典型调用场景:

使用已存在对象创建新对象

函数参数类型为类类型对象

函数返回值类型为类类型对象

class Date
{
public:
 Date(int year, int minute, int day)
 {
 cout << "Date(int,int,int):" << this << endl;
 }
 Date(const Date& d)
 {
 cout << "Date(const Date& d):" << this << endl;
 }
 ~Date()
 {
 cout << "~Date():" << this << endl;
 }
private:
 int _year;
 int _month;
 int _day;
};
Date Test(Date d)
{
 Date temp(d);
 return temp;
}
int main()
{
 Date d1(2022,1,13);
 Test(d1);
 return 0;
}

 

为了提高程序效率,一般对象传参时,尽量使用引用类型,返回时根据实际场景,能用引用 尽量使用引用。

2.代码解释拷贝构造参数类型(重点)

当直接传d,而不是传引用传参就会一直循环调用拷贝构造,

代码如下(示例):

class Date
{
public:
 Date(int year = 1900, int month = 1, int day = 1)
 {
 _year = year;
 _month = month;
 _day = day;
 }
 // Date(const Date& d)   // 正确写法
    Date(const Date& d)   // 错误写法:编译报错,会引发无穷递归
//在main函数中调用了拷贝构造,传递了一个实参,实参需要拷贝各形参就会再次调用拷贝构造,再传一个实参,实参再调拷贝构造,以此循环;
 {
 _year = d._year;
 _month = d._month;
 _day = d._day;
 }
private:
 int _year;
 int _month;
 int _day;
};
int main()
{
 Date d1;
 Date d2(d1);//调用拷贝构造
 return 0;
}
//class Date
//{
//public:
//	Date(int year, int month, int day)
//	{
//		_year = year;
//		_month = month;
//		_day = day;
//	}
//	//拷贝构造
//	//如果不加别名,在传参之前还会在调用拷贝构造
//	//理解:实参先没有确定的空间,在开始传参时,临时分配空间,形参在这个空间中去拷贝
//	//这里用了&取别名,给d1取别名d,然后在d这个空间中去找;
//	//去掉& 。系统会先访问这个空间,将给个空间中的数在复制个d,就需要再次调用拷贝构造函数;
//	//调用函数就有形参转实参的过程,就需要拷贝;
//	Date(const Date& d)
//	{
//		_year = d._year+1;
//		_month = d._month+1;
//		_day = d._day+1;
//	}
//	void print(Date d)
//	{
//		cout << d._year << " " << d._month <<" " << d._day << endl;
//		return;
//	}
//private:
//	int _year;
//	int _month;
//	int _day;
//};
//
//void f(Date d)
//{
//
//}
//int main()
//{
//	Date d1(2023, 3, 20);
//	Date d2(d1);
//	//f(d1);
//	d1.print(d2);
//	return 0;
//
//}

 

3.代码解释编译器默认拷贝构造 (重点)

class Time
{
public:
 Time()
 {
 _hour = 1;
 _minute = 1;
 _second = 1;
 }
 Time(const Time& t)//拷贝构造函数
 {
 _hour = t._hour;
 _minute = t._minute;
 _second = t._second;
 cout << "Time::Time(const Time&)" << endl;
 }
private:
 int _hour;
 int _minute;
 int _second;
};
class Date
{
private:
 // 基本类型(内置类型)
 int _year = 1970;
 int _month = 1;
 int _day = 1;
 // 自定义类型
 Time _t;
};
int main()
{
 Date d1;
    
    // 用已经存在的d1拷贝构造d2,此处会调用Date类的拷贝构造函数
    // 但Date类并没有显式定义拷贝构造函数,则编译器会给Date类生成一个默认的拷贝构
造函数
 Date d2(d1);
 return 0;
}

 如果你看到这,那么请思考一下构造函数和拷贝构造的区别在于什么?都是用类型名构造的函数!

4.构造函数、析构函数和拷贝构造的应用

实现一个某一天过多少天以后是那一天

第一步确定那一天日期,我们就需要先用构造函数构造一个day

第二步实现算法,在代码实现时会用到拷贝构造

代码实现

class Date//构造一个日期类对象出来
{
public:
	Date(int year, int month, int day)//构造函数的初始化
	{
		_year = year;
		_day = day;
		_month = month;
	}
	int GetMonthDay(int year, int month)//用来计算某一年某一天的天数
	{
		assert(month > 0 && month < 13);
		int monthArray[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };//第一个0就是用来将月份和下标对齐的
		if (month == 2 && (_year % 4 == 0 || (_year % 100 != 0 && _year % 400 == 0)))//闰年的二月返回29天
		{
			return 29;
		}
		else//不是闰年的2月就是平年某一月的天数
			return monthArray[month];

		
	}
	Date GetXDay(int x)//传参时自动传d1的地址
	{
		Date tmp(*this);//this 代表d1;//这里就是拷贝构造,编译器自动实现了
		tmp._day += x;
		//int y = GetMonthDay(_year, _month);
		while (tmp._day > GetMonthDay(tmp._year, tmp._month))
		{
			tmp._day -= GetMonthDay(tmp._year, tmp._month);
			tmp._month++;
			if (tmp._month == 13)
			{
				tmp._month = 1;
				tmp._year++;
			}
		}
		return tmp;
	}
	void Print()
	{
		cout << _year << "年" << _month << "月" << _day <<"日"<< endl;
	}
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1(2023,2,3);
	Date d2 = d1.GetXDay(100);
	d1.Print();
	d2.Print();

	return 0;
}

三、const的成员函数

在学习赋值运算之前先引入const成员函数

将const修饰的“成员函数”称之为const成员函数,const修饰类成员函数,实际修饰该成员函数 隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改。

当我们不需要修改函数中任何变量时,我们就需要用const修饰

三、赋值运算符重载

1、运算符重载是什么?

C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其 返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。

函数名字为:关键字operator后面接需要重载的运算符符号。

函数原型:返回值类型 operator操作符(参数列表)

注意: 不能通过连接其他符号来创建新的操作符:比如operator@

重载操作符必须有一个类类型参数 、

用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不 能改变其含义

作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐 藏的this .* :: sizeof ?: . 注意以上5个运算符不能重载。这个经常在笔试选择题中出 现。

2、运算符重载的作用

在之前的c中,运算符有 +   -    *    /   +=    -=  <   >   <=   >=等 ;现在想要实现一个日期加上一个天数或者比较两个日期谁大或谁小时,就需要运用到运算符重载,因为在编译器不知道的大小,它不是编译器内置的类型

3、日期的比较

1、运算符的声名

int GetMonthDay(int year, int month, int day);
class Date
{
	//友元
	friend ostream& operator<<(ostream& out,  Date& d);
	friend istream& operator>>(istream& in, Date& d);
public:
	Date(int year = 1900, int month = 1, int day = 1);//构造函数
	Date(const Date& d);//拷贝构造
	void print();
	bool operator==(Date d)const;//只是读取数据进行比较,不需要修改,所以我们用coust修饰
	bool operator>(Date d)const;
	bool operator>=(Date d)const;
	bool operator<(Date d)const;
	bool operator<=(Date d)const;
	bool operator!=(Date d)const;

private:
	int _year;
	int _month;
	int _day;

};;

2、函数实现

bool Date::operator==(Date d)const//只读并不用修改数值
{
	return _year == d._year
		&& _month == d._month
		&& _day == d._day;//只需要判断年月日是否相等来判断天日期是否相等
}
//d1>d2
bool Date:: operator>(Date d)const//这里括号里是缺省型参数,缺少的是this,*this就是>前面的参数d1
{
	if (_year < d._year)//第一步判断年的大小//_year等价于this->_year
	{
		return false;
	}
	else if (_year > d._year)
	{
		return true;
	}
	else//年相等
	{
		if (_month < d._month)//判断月
		{
			return false;
		}
		else if (_month > d._month)
		{
			return true;
		}
		else//月相等
		{
			if (_day < d._day)//判断天
			{
				return false;
			}
			else
			{
				return true;
			}
		}
	}
}
bool Date::operator>=(Date d)const//由于我们已经重定义了==和>,所以在这里可以直接使用
{
	return (*this == d) || (*this > d);//*this就是>=前面的那个日期,
}
bool Date::operator<(Date d)const
{
	return !(*this >= d);//用了>=只需要取反就是<
}
bool Date::operator<=(Date d)const
{
	return !(*this > d);//对>取反就是<=
}
bool Date::operator!=(Date d)const
{
	return !(*this == d);
}
int main()
{
	Date d1(2024, 3, 3);
	Date d2(2024, 3, 23);
	cout <<"<" << d1.operator<(d2) << endl;
	cout << "=="<<d1.operator==(d2) << endl;
	cout << (d1 == d2) << endl;//转化为运算符重载,operator==(d1, d2)
	return 0;
}

4、 前置++和后置++重载

前置++是先对变量++再用,

后置++是先对变量使用再对变量++;

class Date
{
public:
 Date(int year = 1900, int month = 1, int day = 1)
 {
 _year = year;
 _month = month;
 _day = day;
 }
 // 前置++:返回+1之后的结果
 // 注意:this指向的对象函数结束后不会销毁,故以引用方式返回提高效率
 Date& operator++()
 {
 _day += 1;
 return *this;
 }
 // 后置++:
 // 前置++和后置++都是一元运算符,为了让前置++与后置++形成能正确重载
 // C++规定:后置++重载时多增加一个int类型的参数,但调用函数时该参数不用传递,编译器
自动传递
 // 注意:后置++是先使用后+1,因此需要返回+1之前的旧值,故需在实现时需要先将this保存
一份,然后给this+1
 //       而temp是临时对象,因此只能以值的方式返回,不能返回引用
 Date operator++(int)
 {
 Date temp(*this);
 _day += 1;
 return temp;
 }
private:
 int _year;
 int _month;
 int _day;
};

 5、日期的+-运算

1、声名在类内

class Date
{
	//友元
	friend ostream& operator<<(ostream& out,  Date& d);
	friend istream& operator>>(istream& in, Date& d);
public:
	Date(int year = 1900, int month = 1, int day = 1);
	Date(const Date& d);
	void print();
	Date& operator+=(int day);//这里由于需要修改*this所以没有加const
	Date operator+(int day)const;
	Date& operator-=(int day);
	Date operator-(int day)const;
	int operator-(const Date d)const;
	Date& operator++();
	Date operator++(int);
	Date& operator--();
	Date operator--(int);
	//void operator << (ostream & out);

private:
	int _year;
	int _month;
	int _day;

};

2、函数实现

Date& Date::operator+=(int day)
{
	if (day < 0)
	{
		*this -= day;
		return *this;
		
	}
	_day += day;
	while (_day > GetMonthDay(_year, _month))
	{
		_day -= GetMonthDay(_year, _month);
		_month++;
		if (_month == 13)
		{
			++_year;
			_month = 1;
		}
	}
	return *this;
}//出来作用域*this不存在,所以需要加&返回
Date Date::operator+(int day)const
{ 
	/*Date tmp(*this);
	tmp._day += day;
	while (tmp._day > GetMonthDay(tmp._year, tmp._month))
	{
		tmp._day -= GetMonthDay(tmp._year, tmp._month);
		tmp._month++;
		if (tmp._month == 13)
		{
			++_year;
			tmp._month = 1;
		}
	}*/
	//优化,由于上面的代码当day<0时,就会导致天数不对
	
	Date tmp(*this);
	if (day < 0)
	{
	
		tmp -= day;
		return tmp;
	}
	tmp += day;
	return tmp;
}
Date& Date:: operator-=(int day)
{
	if (day < 0)
	{
		*this += day;
		return *this;
	}
	_day -= day;
	while (_day <= 0)
	{
		_month--;
		if (_month ==0)
		{
			_year--;
			_month = 12;
		}
		_day += GetMonthDay(_year, _month);
	}
	return *this;
}//出来作用域,*this销毁,所以传引用返回进行临时的保存*this的值
Date Date::operator-(int day)const
{
	Date tmp(*this);
	if (day < 0)//只需要判断day大于0还是小于0;
	{
		tmp += day;
		return tmp;
	}
	tmp -= day;
	return tmp;
}

int Date::operator-(const Date d)const//这里的-和上面的不一样在于参数类型不同,也就是一个-多少天;这里的-是日期-日期返回的是两个日期的差值
{
//找到较大的日期
	Date max = *this;
	Date min = d;
	int flag = 1;
	if (*this < d)
	{
		max = d;
		min = *this;
		flag = -1;
	}
	int n = 0;
	while (min != max)
	{	
		++min;
		++n;

	}
	return n*flag;
}
Date& Date:: operator++()//这里代码前置++,先对*this++,在用*this值
{
	*this += 1;//*this+1就是加上1天返回下一天的日期
	return *this;
}
Date Date:: operator++(int)//这里为了区分前置++和后置++,在括号中加入了int代表后置++
{
	Date tmp(*this);//这里是因为是后置++,先用*this的值,在对*this++,
	(*this)+=1;
	return tmp;
}
Date& Date:: operator--()
{
	*this -= 1;
	return *this;
}
Date Date:: operator--(int)
{
	Date tmp(*this);
	(*this) -= 1;
	return tmp;
}

 

 


总结

提示:这里对文章进行总结:
例如:以上就是今天要讲的内容,本文仅仅简单介绍了pandas的使用,而pandas提供了大量能使我们快速便捷地处理数据的函数和方法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

爱吃喵的鲤鱼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值