目录

1.构造函数

1.1 构造函数的重载

1.2 不写构造函数是否会调用?

1.3 默认无参构造的消失

1.4 全缺省构造函数

2 析构函数

3 拷贝构造函数

3.1 拷贝构造死循环

4 运算符重载

4.1 比较运算符重载

4.2 +-运算符重载

5. 完整代码


        本章内容介绍类的六个默认成员函数中的构造函数、析构函数、拷贝构造函数、运算符重载函数

1.构造函数

        回顾一下之前的Date函数,我们书写了一个Init的初始化函数,我们试想一下,如果在调用初始化函数之前,就使用对象中的值,那么就会出现未初始化的情况如下图所示:

我们来介绍一下类的构造函数:在类的对象构造时,自动调用的函数,这个函数完成初始化工作

注意构造函数,没有返回值,调用的时候在创建类的对象的时候后面加上括号即可。

class Date 
{
public:
	// 构造函数
	Date(int year,int month,int day) 
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void PrintDate() 
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main() 
{
	Date d1(2025,9,1);
	d1.PrintDate();
}

1.1 构造函数的重载

        我们可以拥有多个构造函数,可以使用上一节内容提到的函数名重载来实现,我们可以写一个无参构造函数。

class Date 
{
public:
	// 构造函数
	Date(int year,int month,int day) 
	{
		_year = year;
		_month = month;
		_day = day;
	}
	// 无参构造函数
	Date() 
	{
		_year = 0;
		_month = 0;
		_day = 0;
	}
	void PrintDate() 
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main() 
{
	Date d1(2025,9,1);
	Date d2;
	d1.PrintDate();
	d2.PrintDate();
}

1.2 不写构造函数是否会调用?

        如果不写构造函数,系统其实默认会有一个无参的构造函数,当创建对象的时候会自动调用。

        我们惊奇地发现,即使调用了默认的无参构造函数,这里对象的成员变量仍然是随机值,这是为什么呢?这个默认的构造函数似乎什么都没干。

        这里我们再定义一个类,时间类,在Date类中定义一个时间类的对象,再来调试一下;

class Time 
{
private:
	int hour;
	int miniute;
	int second;

public:
	Time() 
	{
		hour = 0;
		miniute = 0;
		second = 0;
	}
};

class Date
{
public:
	// 没有构造函数
	void PrintDate()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
	Time _time;
};
int main()
{
	Date d1;
	d1.PrintDate();
}

我们这里打了一个断点进行调试,发现time类已经被初始化完毕了,也就是说,当我们使用了默认的构造函数,这个对象建立的时候,默认调用无参的初始化函数:

1.针对内置类型的成员变量,不做处理;

2.针对自定义类型的成员变量,调用该类型的构造函数进行初始化该对象

1.3 默认无参构造的消失

        当自己写了一个构造函数,那么原来类自带的默认无参构造就会消失。

        如果还是想要一个无参构造函数,可以自己再继续写一个,类似于函数的重载一样,这里不再赘述。

1.4 全缺省构造函数

        我们能够以一个更加简洁的方式,只定义一个构造函数,就能完成指定传参、无参就使用默认参数,那就是全缺省构造函数。

class Date
{
public:
	// 构造函数
	Date(int year = 2001 ,int month = 8,int day = 23)
	{
		this->_year = year;
		this->_month = month;
		this->_day = day;
	}	
	void PrintDate()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d1;
	Date d2(2023,6,25);
	d1.PrintDate();
	d2.PrintDate();
}

        这里全缺省和无参构造函数不能同时存在,这里是因为如果不传入任何参数,那就不知道调用的全缺省构造函数还是无参构造函数。

        默认构造函数的定义:

        ①自己定义的无参构造函数;

        ②自己没有定义,使用的是系统默认的无参构造函数(没啥用);

        ③自己定义的全缺省的无参构造函数。

        上面三种都有一个共同点,那就是不需要传入任何的参数。                

        当直接使用Date d1;创建对象的时候,这里三个都会被调用,所以自己定义的两个构造函数不能同时存在,否则会产生歧义;一旦自己定义完毕构造函数,系统默认的构造函数会被覆盖。

2 析构函数

        定义方式就是在函数名的前面加一个~,表示析构函数;当一个对象声明周期结束之后,自动调用,没有任何返回值和形参,如果没有显式定义,系统会调用默认的析构函数

class Date
{
public:
	// 构造函数
	Date(int year = 2001 ,int month = 8,int day = 23)
	{
		this->_year = year;
		this->_month = month;
		this->_day = day;
	}	
	~Date() 
	{
		cout << "析构函数调用" << endl;
	}

	void PrintDate()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d1;
	Date d2(2023,6,25);
}

        析构函数本质完成的是类中的资源清理,例如在某些数据结构中,有动态开辟的空间,就可以使用析构函数将数据进行清理,如下图所示:

        析构的顺序,我们先构造d1,再构造d2,由于对象是在栈帧中的,所以先析构d2,再析构d1;

总结:不论是编译器自动生成的构造函数或者析构函数,不会对基本类型做出任何初始化、析构动作,但是会对自定义类型进行调用构造函数或者析构函数

3 拷贝构造函数

        如果我需要两个不同的对象,但是对象的值必须相同,构造对象的时候每一次都需要将值输入两边,这就显得非常的冗余,我们能不能直接在创建对象的时候,直接在形参内传入同类型的对象作为实参。

3.1 拷贝构造死循环

         传参的过程其实就是一个调用拷贝构造函数的过程,循环往复,最后就会造成死循环。

       

class Date
{
public:
	int _year;
	int _month;
	int _day;
	Date(int year,int month,int day) 
	{
		this->_year = year;
		this->_month = month;
		this->_day = day;
	}
	
};


int main() 
{
	Date d1(2001,8,23);
	Date d2(d1); // 拷贝构造
	Date d3 = d1; // 调用拷贝构造
}

        如果我们将形参换成了引用,传参的过程,就会发生改变,这里就会将d1的数据全部给d2

总结:传入参数fuc(Date d)类似于Date d1 = d2;会调用拷贝构造函数,并且产生递归,如果是fuc(Date& d ),就代表d的别名,就不会调用拷贝构造函数。

        所以正常的拷贝构造函数的形参需要带上引用,为了避免传进来的引用被修改,所以最好还要加上const修饰。

class Date
{
public:
	int _year;
	int _month;
	int _day;
	Date(int year,int month,int day) 
	{
		this->_year = year;
		this->_month = month;
		this->_day = day;
	}
	Date(const Date& d)  // 拷贝构造函数
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
};

4 运算符重载

4.1 比较运算符重载

        自定义类型是没有办法通过运算符比较的,例如上面写的Date日期类,的两个对象d1 和d2,想要比较这两个日期的大小,并不能使用d1 > d2 这种方式,在c语言中,我们可以写一个函数进行比较类似于:

这样看起来可读性不强,能不能使用最简单的运算符,来直接表示自定义类型的大小。

        使用运算符重载,形参的个数表示这个操作数的个数,例如==,一定有两个操作数,++就只需要一个操作数,所以只需要一个形参,由于比较大小,所以返回类型是bool值。

bool operator==(const Date& d1, const Date& d2)
{
	return d1._day == d2._day && d1._year == d2._year && d1._month == d2._month;
}
class Date
{
public:
	int _year;
	int _month;
	int _day;
	Date(int year, int month, int day)
	{
		this->_year = year;
		this->_month = month;
		this->_day = day;
	}
	Date(const Date& d)  // 拷贝构造函数
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
};

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

int main()
{
	Date d1(2001, 8, 23);
	Date d2(d1); // 拷贝构造
	Date d3(2002, 9, 23); // 调用拷贝构造
	if (d1 == d2)
	{
		cout << " 日期相等" << endl;
	}
	else
	{
		cout << "日期不等" << endl;
	}
}

d1 == d2;// 等价
operator==(d1,d2);

如果这么写,那么就必须要求成员变量是公有的,不符合实际开发规范。

我们可以将这个方法作为成员方法,此时形参只需要传入一个即可,因为已经包含this指针了。

class Date
{
public:
	int _year;
	int _month;
	int _day;
	Date(int year, int month, int day)
	{
		this->_year = year;
		this->_month = month;
		this->_day = day;
	}
	Date(const Date& d)  // 拷贝构造函数
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
	// 作为成员函数重载运算符
	bool operator==(const Date& d) 
	{
		return _year == d._year && _month == d._month && _day == d._day;
	}
};

此时d1 == d2 ,实际上是d1.operator==(d2)

再写一个>运算符重载练练手:

// > 重载
bool operator>(const Date& d)
{
	if (_year > d._year)
	{
		return true;
	}
	else if (_year == d._year && _month > d._month)
	{
		return true;
	}
	else if (_year == d._year && _month == d._month && _day > d._day)
	{
		return true;
	}
	else
	{
		return false;
	}
}
class Date
{
public:
	int _year;
	int _month;
	int _day;
	Date(int year, int month, int day)
	{
		this->_year = year;
		this->_month = month;
		this->_day = day;
	}
	Date(const Date& d)  // 拷贝构造函数
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
	// 作为成员函数重载运算符
	bool operator==(const Date& d)
	{
		return _year == d._year && _month == d._month && _day == d._day;
	}

	// > 重载
	bool operator>(const Date& d)
	{
		if (_year > d._year)
		{
			return true;
		}
		else if (_year == d._year && _month > d._month)
		{
			return true;
		}
		else if (_year == d._year && _month == d._month && _day > d._day)
		{
			return true;
		}
		else
		{
			return false;
		}
	}
};

五个运算符不能被重载:.*::sizeof ?:.

        运算符重载能够被复用,我们实现了<和==的运算符重载,我们就能直接复用从而实现<=的运算符重载。

//  < 重载
bool operator<(const Date& d) 
{
	if (_year < d._year)
	{
		return true;
	}
	else if (_year == d._year && _month < d._month) 
	{
		return true;
	}
	else if (_year == d._year && _month == d._month && _day < d._day)
	{
		return true;
	}
	else 
	{
		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;
}

        这是因为,重载运算符作为成员函数,其形参中是(Date* this,const Date& d),也就是第一个形参永远是指向本对象的指针,可以利用这一点。

        那么>号就能直接实现,其实就是<=进行取反。

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

那么同理,!=其实就是==进行复用取反。

4.2 +-运算符重载

        日期+日期是没有意义的,但是日期 + - 天数、日期-日期都是有意义的。

下面以日期+天数为例:不需要改变自身的值,所以我们可以拷贝函数做一个临时的对象;

①先加天数,再判断天数是否合理;

②之前写过通过年份和月份得到此月的天数的函数,如果相加后的天数>本月天数,需要进行修正;

③需要减去本月天数,month + 1;

④如果当前月是12月需要,重置月份,增加年份。

// +重载,日期 + 天 = 日期
Date operator+(int day) 
{
	// +不改变本身
	Date ret = *this;
	ret._day += day;

	// 判断天数是否合法
	while (ret._day > getMonthDay(ret._year,ret._month))
	{
		ret._day -= getMonthDay(ret._year, ret._month);
		ret._month++;
		// 如果刚好是12月
		if (ret._month == 13)
		{
			ret._month = 1;
			ret._year++;
		}
	}
	return ret;
}

+=的实现基本类似,但是这里是直接作用于自己,就不需要临时变量了。返回值由于当前对象的创建并不是在类中创建的,所以不需要担心出了作用于会被销毁的情况,所以这里直接返回引用,这里返回引用的好处就是不需要创建多余的临时变量。

// +重载,日期 + 天 = 日期
Date& operator+=(int day)
{
	// +=改变本身
	_day += day;

	// 判断天数是否合法
	while (_day > getMonthDay(_year, _month))
	{
		_day -= getMonthDay(_year, _month);
		_month++;
		// 如果刚好是12月
		if (_month == 13)
		{
			_month = 1;
			_year++;
		}
	}
	return *this;
}

        -=符号需要注意的是,先-day,当day <=0的时候需要先将month--,如果month为0的时候,此时year也需要--,将month更新为12;最后将上个月的总天数+目前的day,最终返回Date&,因为是在栈外创建。

// +重载,日期 + 天 = 日期
Date& operator-=(int day)
{
	// +=改变本身
	_day -= day;

	// 判断天数是否合法
	while (_day < 1)
	{
		_month--;
		// 如果刚好是12月
		if (_month == 0)
		{
			_month = 12;
			_year--;
		}
		_day += getMonthDay(_year, _month);

	}
	return *this;
}

        date - date最后得到天数,如果单纯的按照这个思路来做,非常复杂,所以我们可以将两个日期分为大日期和小日期,小日期每次增加1天,就计数+1,最后统计计数就能知道这两个日期相差多少。

int operator-(const Date& d)
{
	int flag = 1; // 大 - 小,相差日期是正的
	Date max = *this;
	Date min = d;
	if (max < min)
	{
		max = d;
		min = *this;
		flag = -1; // 小 - 大,相差日期是负的
	}
	int cnt = 0;
	while (min < max)
	{
		min++;
		cnt++;
	}
	return cnt * flag;
}

4.3 ++--单目运算符

        先说++,分为前++和后++,前者是先增加再返回本身;后者是先返回本身的值再增加本身。

	// d1++
	// 其实不需要形参,但是为了构成重载,所以需要加上int
	Date operator++(int)
	{
		Date tmp = *this;
		*this += 1;
		return tmp;
	}

	// --d1 
	Date& operator--()
	{
		*this -= 1;
		return *this;
	}

        注意,形参需要做出区分,虽然都不需要形参,但是还是需要进行区分,使其能够函数重载;返回值也是,如果栈内销毁,直接返回Date,如果栈内没有销毁,直接返回引用。

        --同理:

// --d1 
Date& operator--()
{
	*this -= 1;
	return *this;
}

// d1-- 
Date operator--(int)
{
	Date tmp = *this;
	*this -= 1;
	return tmp;
}

5. 完整代码

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>

using namespace std;

// 实现一个完整的日期类

class Date
{
public:
	// 获取日期的天数
	int getMonthDay(int year, int month)
	{
		static int monthDay[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
		// 闰年处理
		if (month == 2 && (year % 4 == 0 && year % 100 != 0) || year % 400 == 0)// 4年一润,百年不润,400年又润
		{
			return 29;
		}

		return monthDay[month];
	}
	Date(int year = 0, int month = 0, int day = 0)
	{
		if (year >= 0 &&
			month >= 1 && month <= 12 &&
			day >= 1 && day <= getMonthDay(year, month))
		{
			_year = year;
			_day = day;
			_month = month;
		}
		else
		{
			cout << "非法日期" << endl;
		}
	}

	void printDate()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}

	//  < 重载
	bool operator<(const Date& d)
	{
		if (_year < d._year)
		{
			return true;
		}
		else if (_year == d._year && _month < d._month)
		{
			return true;
		}
		else if (_year == d._year && _month == d._month && _day < d._day)
		{
			return true;
		}
		else
		{
			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);
	}

	// +重载,日期 + 天 = 日期
	Date operator+(int day)
	{
		// +不改变本身
		Date ret = *this;
		ret._day += day;

		// 判断天数是否合法
		while (ret._day > getMonthDay(ret._year, ret._month))
		{
			ret._day -= getMonthDay(ret._year, ret._month);
			ret._month++;
			// 如果刚好是12月
			if (ret._month == 13)
			{
				ret._month = 1;
				ret._year++;
			}
		}
		return ret;
	}

	// +重载,日期 + 天 = 日期
	Date& operator+=(int day)
	{
		// +=改变本身
		_day += day;

		// 判断天数是否合法
		while (_day > getMonthDay(_year, _month))
		{
			_day -= getMonthDay(_year, _month);
			_month++;
			// 如果刚好是12月
			if (_month == 13)
			{
				_month = 1;
				_year++;
			}
		}
		return *this;
	}

	// +重载,日期 + 天 = 日期
	Date& operator-=(int day)
	{
		// +=改变本身
		_day -= day;

		// 判断天数是否合法
		while (_day < 1)
		{
			_month--;
			// 如果刚好是12月
			if (_month == 0)
			{
				_month = 12;
				_year--;
			}
			_day += getMonthDay(_year, _month);

		}
		return *this;
	}




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


	// d1++
	// 其实不需要形参,但是为了构成重载,所以需要加上int
	Date operator++(int)
	{
		Date tmp = *this;
		*this += 1;
		return tmp;
	}

	// --d1 
	Date& operator--()
	{
		*this -= 1;
		return *this;
	}

	// d1-- 
	Date operator--(int)
	{
		Date tmp = *this;
		*this -= 1;
		return tmp;
	}

	// date - date
	/*
		可以想象成小的 + 天数 = 大的
	*/
	int operator-(const Date& d)
	{
		int flag = 1; // 大 - 小,相差日期是正的
		Date max = *this;
		Date min = d;
		if (max < min)
		{
			max = d;
			min = *this;
			flag = -1; // 小 - 大,相差日期是负的
		}
		int cnt = 0;
		while (min < max)
		{
			min++;
			cnt++;
		}
		return cnt * flag;
	}


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

int main()
{
	Date d2(2020, 2, 29);
	Date d3(2025, 9, 25);
	d2.printDate();
	(d3 + 1000).printDate();
	d3.printDate();
}

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐