类和对象(三)运算符重载,初始化列表

运算符重载

函数名字为:关键字operator后面接需要重载的运算符符号。
函数原型:返回值类型 operator操作符(参数列表)
函数参数:由运算符本身决定,有几个操作数,那么便有几个参数(全局),在类中作为其重载函数时其参数便是操作数减一;操作符有一个默认的形参this,限定为第一个形参
不能通过连接其他符号来创建新的操作符:比如operator@
重载操作符必须有一个类类型或者枚举类型的操作数
用于内置类型的操作符,其含义不能改变,例如:内置的整型+,不 能改变其含义
.* 、:: 、sizeof 、?: 、. 注意以上5个运算符不能重载
理解:内置类型可以直接支持运算符,但是自定义类型却不支持运算符,这个时候我们只需要重新定义自定义类型的运算符就可以了。
赋值运算符重载
全局的赋值运算符重载

bool operator==(const Date& d1, const Date& d2)
//当其在类中时,左操作数是this指向调用函数的对象
{

	return d1._year == d2._year
		&& d1._month == d2._month
		&& d1._day == d2._day;

}

当函数设置为全局的运算符重载函数时,我们便无法调用此时的私有成员变量,那么这个时候就需要用到友元函数了。
友元函数:友元函数可以直接访问类的私有成员,它是定义在类外部的普通函数,不属于任何类,但需要在类的内部声明,声明时需要加friend关键字
在类中的赋值运算符重载

// d2 = d3 -> d2.operator=(&d2, d3)

	Date& operator=(const Date& d)
	{
		if (this != &d)//为了防止相同的对象重复赋值
		{
			_year = d._year;
			_month = d._month;
			_day = d._day;
		}

		return *this;//返回*this是为了满足连续赋值的特性

	}

在这里插入图片描述

当全局的赋值运算符重载和类中赋值运算符重载都存在时,优先调用类中的赋值运算符重载。

日期类的实现:

Date(int year = 1, int month = 1, int day = 1)
	{
		assert(year > 1 && month > 0 && month < 13 && day < GetMonthDay(year, month));//防止出现日期构造不规范
		_year = year;
		_month = month;
		_day = day;
	}
	// 拷贝构造函数

    // d2(d1)

	Date(const Date& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
	
	bool isLeapYear(int year)
	{
		return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);
	}
	// 获取某年某月的天数

	int GetMonthDay(int year, int month)
	{
		const static int monthday[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
		if (month == 2 && isLeapYear(year))
		{
			return monthday[month] + 1;

		}
		else
			return monthday[month];
	}
	void Print()
	{
		cout << _year << " " << _month << " " << _day << endl;
	}

	// 赋值运算符重载

// d2 = d3 -> d2.operator=(&d2, d3)

	Date& operator=(const Date& d)
	{
		if (this != &d)//为了防止相同的对象重复赋值
		{
			_year = d._year;
			_month = d._month;
			_day = d._day;
		}

		return *this;

	}
	// 日期+天数

	//Date& operator+(int day)
	//{
	//	Date ret(*this);
	//	if (day < 0)//防止出现天数为负的情况
	//	{
	//		ret._day -= day;
	//	}
	//	else
	//	{
	//		ret._day += day;
	//		while (ret._day > GetMonthDay(ret._year, ret._month))
	//		{
	//			ret._day -= GetMonthDay(ret._year, ret._month);
	//			ret._month += 1;
	//			if (ret._month == 13)
	//			{
	//				ret._month = 1;
	//				ret._year += 1;

	//			}

	//		}
	//	}
	//	
	//	return ret;
	//}
	Date operator+(int day)//直接调用+=
	{
		Date ret(*this);
		ret += day;
		return ret;
	}
	// 日期+=天数

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

			}

		}
		return *this;
	}
	// 日期-天数

	/*Date operator-(int day)
	{
		Date ret(*this);
		ret._day -= day;
		while (ret._day < 0)
		{
			ret._month-=1;
			if (ret._month == 0)
			{
				ret._month = 12;
				ret._year -= 1;
			}
			ret._day += GetMonthDay(ret._year, ret._month);
		}

		return ret;
	}*/
	Date operator-(int day)
	{
		Date ret(*this);
		ret -= day;
		return ret;
	}

	// 日期-=天数

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

		return *this;
	}
	
	// 前置++

	Date& operator++()
	{
		*this += 1;
		return *this;
	}
	// 后置++

	Date operator++(int)//后置++中的整形只是为了和前置++区分但是不使用
	{
		Date ret(*this);
		*this += 1;
		return ret;

	}
	// 后置--

	Date operator--(int)
	{
		Date ret(*this);
		*this -= 1;
		return ret;
	}



	// 前置--

	Date& operator--()
	{
		*this -= 1;
		return *this;
	}
	
	// >运算符重载

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


	}



	// ==运算符重载

	bool operator==(const Date& d)
	{
		return (_year == d._year && _month == d._month && _day == d._day);
	}



	// >=运算符重载

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



	// <运算符重载

	bool operator < (const Date& d)
	{
		return !(*this >= d);
	}



	// <=运算符重载

	bool operator <= (const Date& d)
	{
		return *this < d || *this == d;
	}



	// !=运算符重载

	bool operator != (const Date& d)
	{
		return !(*this == d);
	}
	
	// 析构函数
	~Date()
	{
		cout << "析构函数" << endl;

	}


	// 日期-日期 返回天数

	int operator-(const Date& d)
	{
		//日期减日期有可能是小日期减去大日期
		Date Min = d;
		Date MAx = *this;
		int flag = 1;
		if (*this < d)
		{
			flag = -1;
			Min = *this;
			MAx = d;
		}
		int n = 0;
		while (Min != MAx)
		{
			n++;
			Min++;
		}
		return n * flag;
	}
private:
	int _year;
	int _month;
	int _day;

};

理解拷贝构造和赋值

用一个对象初始化一个对象那么便是拷贝构造,两个已经存在的对象是赋值

eg:
拷贝构造:
Date d1;
Date d1(d2);
Date d3 = d1;都是拷贝构造
但是
如果后面再加上d3 = d2那么此时便是赋值。

理解类中的const

类中的函数第一个参数是*this我们都知道,但是实际上它是Date *const this表示this指向可以被改变但是其本身不能被改变,但是如果我们在函数后面加上const,此时表示this指向的也不能被改变,此时第一个参数表示的就是const Date*const this
下面以一个函数为例
在这里插入图片描述
规则调用函数时,权限只能缩小不能放大,例如:如果一个函数的形参是char const ch
但是此时我们传进去的参数是字符串常量那么便会报错,因为此时函数的参事其指向的内容可以改变,但是我们传进去的是一个常量,具有常性,无法改变,那么此时便是权限的放大,但是权限只能缩小不能放大,所以会报错,但是如果函数的形参是const char
const ch,那么此时传字符串常量也是可以的,所以为了避免出现此类错误,在类中写函数时,如果不涉及到成员变量的改变,最好在后面加上const
在这里插入图片描述

>>与<<运算符重载

:流提取
<<流插入
流:流是一个类型的对象,这个对象完成提取和插入

在这里插入图片描述
如果按照之前一样,那么写出的函数

ostream& operator<<(ostream& out)
	{
		out << _year << "-" << _month << "-" << _day << endl;
		return out;
	}

但是我们在程序中调用之后便会发现,此时cout<<d会报编译错误,但是d<<cout运行正确,为什么!
当一个运算符是双操作数的时候,对一个运算符是左操作数,第二个运算符是右操作数,所以cout<<i-------->化成cout,operator<<(i)
但是我们在上面写的第一个操作数默认是this指针,违背的第一个操作数得是ostream对象,但是函数只要在类中,那么第一个参数就是this,为了改变这个,我们只能用全局函数并且改成友元函数,只有这样才能访问类中的成员变量。
所以之后便是

声明:
friend std::ostream& operator<<(std::ostream& out, const Date& d);
定义:
std::ostream& operator<<(std::ostream& out, const Date& d)
{
	out << d._year << "-" << d._month << "-" << d._day << endl;
	return out;
}

**注意:**全局函数不能在.h中的定义,因为如果在.h中定义,那么当其被包含时,展开后有可能会造成链接错误.

初始化列表

先看一个构造函数

class Date
{
public:


	Date(int year = 1, int month = 2, int day = 3)
	{
		_year = year;
		_month = month;
		_day = day;
		_year = month;
	}
	void Print()
	{
		cout << _year << " " << _month << " " << _day << endl;



	}

private:
	int _year;
	int _month;
	int _day;


};

虽然调用完上述的构造函数之后,对象中的已经有了一个初始值,但是不能将其称为对象的初始化,构造函数的语句只能将其称作为赋初值,不能将其称作为初始化,因为在构造函数中是可以多次赋值。
初始化列表只能初始化一次!
初始化列表:以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个"成员变量"后面跟一个放在括号中的初始值或表达式。

Date(int year = 1, int month = 2, int day = 3)
		:_year(year)
		,_month(month)
	{
		cout << _month << endl;
		_day = day;
	}

先走初始化列表,再走构造函数,且初始化列表中只能初始化一次,但是构造函数里面可以多次赋值
其中有三类成员变量必须放在初始化列表中进行初始化,而不能放在构造函数进行初始化。

引用成员变量,const成员变量,自定义类型成员(该类没有默认构造函数)
内置类型无所谓是在初始化列表中初始化还是在构造函数中初始化是因为,内置类型可以先定义在在初始化,但是上述三种类型,也就是引用必须定义后直接初始化,他们不能在构造函数中赋值,否则会报错。

class A
{
public:
	//默认构造函数是不用传参就可以调用的构造函数,有三种:
	//1,无参默认构造函数
	// 2,全缺省的默认构造函数
	//3,我们不写编译器自动生成的默认构造函数
	A(int x)

	{
		_x = x;

	}

private:
	int _x;


};
class Date
{
public:


	Date(int year = 1, int month = 2, int day = 3)
		:_year(year)
		,_month(month)
		,_a(8)
	{
		cout << _month << endl;
		_day = day;
	}
	void Print()
	{
		cout << _year << " " << _month << " " << _day << endl;



	}

private:
	int _year;
	int _month;
	int _day;
	A _a;

};

尽量使用初始化列表初始化,因为不管你是否使用初始化列表,对于自定义类型成员变量,一定会先使用初始化列表初始化

class A
{
public:
	//默认构造函数是不用传参就可以调用的构造函数,有三种:
	//1,无参默认构造函数
	// 2,全缺省的默认构造函数
	//3,我们不写编译器自动生成的默认构造函数
	A(int x = 8)
		:_x(x)
	{
		//_x = x;
		cout << x << endl;
	}

private:
	int _x;


};
class Date
{
public:


	Date(int year = 1, int month = 2, int day = 3,int hour = 6)
		:_year(year)
		,_month(month)
	
	{
		A a(hour);//先调用a的拷贝构造,在调用赋值运算符重载
		_a = a;
		_day = day;
	}
	Date(int year = 1, int month = 2, int day = 3,int hour = 6)
		:_year(year)
		, _month(month)
		,_a(hour)
	{
		_day = day;
	}
	void Print()
	{
		cout << _year << " " << _month << " " << _day << endl;



	}

private:
	int _year;
	int _month;
	int _day;
	A _a;

};

可以看到如果我们在构造函数中初始化自定义类型,我们还要先拷贝构造,然后再调用赋值运算符重载,比较麻烦,但是如果我们用初始化列表,就可以让其直接调用该自定义类型的初始化列表或者构造函数了。

成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关

class Q
{
public:
	Q(int x)
		:_x1(x)
		, _x2(_x1)
	{}
	void Print()
	{
		cout << _x1 << "---" << _x2 << endl;
	}
private:
	int _x2;
	int _x1;

};
int main()
{
	Q d(2);
	d.Print();
	return 0;

}

上述代码的最终显示值是“2 和随机值”虽然看上去是先初始化x1,但实际上是初始化x2,然而刚开始x1是随机值,所以最终结果是2,随机值。

explicit关键字

构造函数不仅可以构造与初始化对象,对于单个参数的构造函数,还具有类型转换的作用。

在这里插入图片描述

int a = 2;
	a = 3.5;
	cout << a << endl;//输出结果是3

在构造函数中其实也是一样的。
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

每天少点debug

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

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

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

打赏作者

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

抵扣说明:

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

余额充值