【C++入门】面向对象(2)
目录
本章内容介绍类的六个默认成员函数中的构造函数、析构函数、拷贝构造函数、运算符重载函数
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();
}
更多推荐



所有评论(0)