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

1.概念

类中有六个默认成员函数,在一个空类中,编译器会自动生成这六个默认成员函数(默认构造函数,析构函数,拷贝构造函数,移动构造函数,拷贝赋值运算符和移动赋值运算符)

构造和析构我们之前讲过,我们来看拷贝构造和运算符重载

2.拷贝构造

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

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

class Date
{
public:
	// 全缺省的构造函数
	Date(int year = 1900, int month = 1, int day = 1);
	//正确写法
	Date(const Date& date);
	//Date(const Date date);这样写,在调用拷贝构造的时候,会一直调用下去
	//Date d1(d) 因为当不加&,调用拷贝构造函数时,编译器会尝试创建d,并将d作为参数传给d1
	//在参数传递的过程中,如果参数为const Date date(并非const Date& date)
	//那么编译器会先创建一个Date对象,即date, 然后将d的副本传给构造函数
	//此过程会导致拷贝构造函数递归调用,因为在创建date的时候,需要调用拷贝构造
	//不加&,每次调用的时候都需要一个新的Date对象作为参数,新的有创建的新的,直到栈溢出
private:
	int _year;
	int _month;
	int _day;
};

若为显示定义拷贝构造,编译器会自动生成默认的拷贝构造函数,默认的拷贝构造函数对象按内存存储按字节完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝

class Time
{
public:
	Time(int hour = 1, int  minute = 1, int second = 1)
	{
		_hour = hour;
		_minute = minute;
		_second = second;
	}
	Time(const Time& time)
	{
		_hour = time._hour;
		_minute = time._minute;
		_second = time._second;
		std::cout << "Time(const Time & time)" << std::endl;
	}
private:
	int _hour;
	int _minute;
	int _second;
};

class Date_d
{
private:
	//自定义类型
	Time t;
	//内置类型
	int _year;
	int _month;
	int _day;
};
void teseTime()
{
	Date_d d;
	//用已存在d拷贝构造d1,此处调用Date_d拷贝构造函数
	//未显示定义,编译器会自动生成默认拷贝构造
	Date_d d1(d);
}
int main()
{
	//testClass();
	teseTime();
	return 0;
}

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

一个疑问,既然编译器自动生成的拷贝构造能完成我们的工作,那么还需要我们手动实现吗?

当然需要,编译器只能实现简单的浅拷贝,对于内存拷贝这种深拷贝,并无法实现。

/* 模版(泛型编程) */
template <class T>
class Stack 
{
public:
	Stack(size_t capacity = 4)
		:_capacity(capacity)
		,_size(0)
	{
		_array = new T[_capacity];
	}
	~Stack()
	{
		delete[] _array;
	}
	void push(const T& data)
	{
		if (_capacity == _size)
		{
			size_t newCapacity = _capacity * 2;
			T* newArray = new T[newCapacity];
			for (size_t i = 0; i < _size; i++)
			{
				newArray[i] = _array[i];
			}
			delete[] _array;
			_array = newArray;
			_capacity = newCapacity;
		}
		_array[_size++] = data;
	}
	void print()
	{
		for (int i = 0; i < _size; i++)
		{
			std::cout << _array[i] << std::endl;
		}
	}
private:
	T* _array;
	size_t _size;
	size_t _capacity;
};


void testStack()
{
	Stack <int>s;
	s.push(1);
	s.push(2);
	s.push(3);
	s.push(4);
	s.push(5);
	s.print();
	Stack <int>s1(s);

}

我们完全使用编译器自动生成的浅拷贝,将会得到以下错误

在我们调用构造函数创建s的时候,申请了8个元素的空间,里面存了5个元素(1,2,3,4,5)

生成s1的时候,使用s拷贝构造,而Stack类并没有显示定义拷贝构造函数,则编译器会给Stack类生成一份默认的拷贝构造函数,默认拷贝构造函数是值拷贝,即将s的内容原封不动的拷贝给s1,因此s和s1指向同一块空间,

当程序退出时,s和s1要销毁,s1先销毁,调用s1的析构,将空间释放了,到s的时候,因已经释放过一次,一块内存无法多次释放,最后我们的程序就会崩溃掉。

注:类中无涉及资源申请,可写可不写拷贝构造

拷贝构造的调用场景:1.使用已存在对象创建新对象。2,函数参数类型为类类型对象。3,函数返回值类型为类类型对象

class Date_1
{
public:
	Date_1(int year, int minute, int day)
	{
		std::cout << "Date_1(int,int,int):" << this << std::endl;
	}
	Date_1(const Date_1& d)
	{
		std::cout << "Date_1(const Date_1& d):" << this << std::endl;
	}
	~Date_1()
	{
		std::cout << "~Date_1():" << this << std::endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
Date_1 Test(Date_1 d)
{
	Date_1 temp(d);//拷贝构造
	return temp;//返回时生成临时对象(拷贝构造)
}
int main()
{
	Date_1 d1(2022, 1, 13);//构造
	Test(d1);//传值是调用拷贝构造生成d
	return 0;
}

为提高效率,尽量使用&

3.赋值重载

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

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

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

注意:

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

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

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

作为成员函数重载,其形参看起来比操作数数目少1,因为成员函数的第一参数为隐式类型this

. * :: sizeof ?:无法重载

#pragma once
#include <iostream>

class Date
{
public:
	// 全缺省的构造函数
	Date(int year = 1900, int month = 1, int day = 1);
	// 获取某年某月的天数
	int GetMonthDay(int year, int month) const;
	// 拷贝构造函数
// d2(d1)
	//正确写法
	Date(const Date& date);
	//Date(const Date date);这样写,在调用拷贝构造的时候,会一直调用下去
	//Date d1(d) 因为当不加&,调用拷贝构造函数时,编译器会尝试创建d,并将d作为参数传给d1
	//在参数传递的过程中,如果参数为const Date date(并非const Date& date)
	//那么编译器会先创建一个Date对象,即date, 然后将d的副本传给构造函数
	//此过程会导致拷贝构造函数递归调用,因为在创建date的时候,需要调用拷贝构造
	//不加&,每次调用的时候都需要一个新的Date对象作为参数,新的有创建的新的,直到栈溢出



	//重载流插入和流提取
	friend std::ostream& operator<<(std::ostream& out, const Date& obj);
	friend std::istream& operator>>(std::istream& in, Date& obj);


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

	// 析构函数
	~Date();
	// 日期+=天数
	Date& operator+=(int day);
	// 日期+天数
	Date operator+(int day) const;
	// 日期-天数
	Date operator-(int day) const;
	// 日期-=天数
	Date& operator-=(int day);
	// 前置++
	Date& operator++();
	// 后置++
	Date operator++(int) const;
	// 后置--
	Date operator--(int) const;
	// 前置--
	Date& operator--();
	// >运算符重载
	bool operator>(const Date& d)const;
	// ==运算符重载
	bool operator==(const Date& d)const;
	// >=运算符重载
	bool operator >= (const Date& d)const;
	// <运算符重载
	bool operator < (const Date& d)const;
	// <=运算符重载
	bool operator <= (const Date& d)const;
	// !=运算符重载
	bool operator != (const Date& d)const;
	// 日期-日期 返回天数
	int operator-(const Date& d);

	int toDays() const;
private:
	int _year;
	int _month;
	int _day;
};
#include "date.h"

/* 全缺省参数在声明处定义即可 */
Date::Date(int year, int month, int day)
{
	_year = year;
	_month = month;
	_day = day;
	std::cout << "Date(int year, int month, int day)" << std::endl;
}

/* 因成员函数第一个参数始终为this无法改变,所以使用友元声明(friend)*/
std::ostream& operator<<(std::ostream& out, const Date& obj)
{
	out << obj._year << "/" << obj._month << "/" << obj._day << std::endl;
	return out;
}


std::istream& operator>>(std::istream& in, Date& obj)
{
	in >> obj._year >> obj._month >> obj._day;
	return in;
}

/* 拷贝构造函数 */
Date::Date(const Date& d)
{
	_day = d._day;
	_month = d._month;
	_year = d._year;
	std::cout << "Date(const Date& d)" << std::endl;
}
/* 析构函数(日期类没有需要释放的内存)*/
Date::~Date()
{
	std::cout << "~Date()" << std::endl;
}

/* 运算符重载 */
Date& Date::operator=(const Date& d)
{
	std::cout << "Date & Date::operator=(const Date & d)" << std::endl;
	if (this == &d)
	{
		return *this;
	}
	_day = d._day;
	_month = d._month;
	_year = d._year;
	return *this;
}

Date& Date::operator+=(int day)
{
	_day += day;
	while (_day > GetMonthDay(_year, _month))
	{
		_day -= GetMonthDay(_year, _month);
		_month++;
		if (_month > 12)
		{
			_month = 1;
			_year++;
		}
	}
	return *this;

}



int Date::GetMonthDay(int year, int month)const
{
	if (month < 1 || month > 12)
	{
		std::cout << "错误日期" << std::endl;
		return -1;
	}
	int days[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
	if ((year % 400 == 0) || (year % 4 == 0 && year % 100 != 0))
	{
		days[2] += 1;
	}
	return days[month];
}

Date Date::operator+(int day)const
{
	Date d(*this);

	d += day;
	return d;
}

Date& Date::operator-= (int day)
{
	while (day > 0)
	{
		if (_day > day)
		{
			_day -= day;
			day = 0;
		}
		else
		{
			day -= _day;
			_month--;
			if (_month < 1)
			{
				_month = 12;
				_year--;
			}
			_day = GetMonthDay(_year, _month);
		}
	}
	return *this;
}

Date Date::operator-(int day)const
{
	Date d(*this);

	d -= day;
	return d;
}
Date& Date::operator++()
{
	_day++;
	if (_day > GetMonthDay(_year, _month))
	{
		_month++;
		_day = 1;
		if (_month > 12)
		{
			   _year++;
			_month = 1;
		}
	}
	return *this;
}
Date Date::operator++(int)
{
	Date d(*this);
	++_day;

	return d;
}


Date& Date::operator--()
{
	_day--;
	if (_day < GetMonthDay(2024, 2))
	{
		_month--;
		if (_month < 1)
		{
			_year--;
			_month = 12;
		}
		_day = GetMonthDay(2024, 2);
	}
	return *this;
}
Date Date::operator--(int)
{
	Date d(*this);

	--_day;
	return d;
}

bool Date::operator>(const Date& d) const
{
	if (_year > d._year) return true;
	if (_year < d._year) return false;
	if (_month > d._month) return true;
	if (_month < d._month) return false;
	return _day > d._day;
}

bool Date::operator==(const Date& d)const
{
	if (d._year != _year ||
		d._month != _month||
		d._day != _day)
	{
		return false;
	}
	return true;
}

bool Date::operator >= (const Date& d)const
{
	if (*this > d || *this == d)
	{
		return true;
	}
	return false;
}

bool Date::operator < (const Date& d)const
{
	if (*this >= d)
	{
		return false;
	}
	return true;
}

bool Date::operator <= (const Date& d)const
{
	if (*this > d)
	{
		return false;
	}
	return true;
}

bool Date::operator != (const Date& d)const
{
	if (*this == d)
	{
		return false;
	}
	return true;
}

int Date::toDays() const 
{
	/* 计算当前年+月+日的总天数 */
	int days = _year * 365 + _day;
	for (int i = 0; i < _month - 1; ++i) 
	{
		days += GetMonthDay(_year, i + 1);
	}
	// 加上闰年天数
	days += (_year / 4) - (_year / 100) + (_year / 400);
	return days;
}

int Date::operator-(const Date& d)
{
	return toDays() - d.toDays();
}

对于赋值运算符和拷贝构造的区别:赋值运算符是给已经存在的对象进行赋值操作,而拷贝构造是用于创建一个新的对象时候进行赋值操作。

注:赋值运算符,只能重载为成员函数,不能重载为全局函数(因赋值运算符如果不显示实现,编译器会自动生成一个默认的,此时用户再类外自己实现一个全局的赋值运算符重载,就会和编译器在类中生成的默认赋值运算符重载冲突了,故赋值运算符重载只能是类的成员函数)

关于前置++(--)和后置++(--)它们均为一元运算符,为了区分它们后置的都加了一个参数(int),该参数没有实际意义,只为了构成函数重载区分前后置,

关于const成员函数,我们将const修饰的“成员函数”称之为const成员函数,const修饰类成员函数,实际修饰该成员函数隐含的this指针,表示在给成员函数,无法对类的成员进行修改。

  • 7
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值