1.构造函数
2.拷贝构造函数
3.析构函数、
4.运算符重载---实现一个日期计算器
c++类有6个默认成员函数:构造函数,拷贝构造函数,析构函数,赋值操作符重载。取地址操作符重载,const修饰的取地址操作符重载。其中前四个默认成员函数是我们重点研究对象。
1.构造函数
成员变量为私有的,要对他们进行初始化,必须用一个公有成员函数来进行。同时这个函数有且仅在定义对象时自动执行一次,这时调用的函数称为构造函数。 构造函数是特殊的成员函数,其特征如下:
1.1函数名与类名相同。
1.2无返回值
1.3对象构造函数(对象实例化)时系统自动调用对应的构造函数。
1.4构造函数可以重载。
1.5构造函数可以在类中定义也可以在类外定义。
1.6如果类定义中没有给出构造函数,则c++编译器会默认生成一个缺省的构造函数,但只要我们定义了一个构造函数,系统就不会自动生成缺省的构造函数。
1.7无参的构造函数和全缺省值的构造函数都认为是缺省的构造函数,并且缺省的构造函数只能有一个。
无参的构造函数和带参的构造函数
class Date
{
public:
Date() //无参的构造函数
{}
Date(int year,int month,int day) //带参的构造函数
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
}
void test
{
Date d1; //调用无参的构造函数
Date d2(2018,9,24); //调用带参的构造函数
Date d3(); //注意这里没有调用对d3的构造函数定义出d3
}
构造函数最好定义成全缺省的,如下:
Date(int year = 1999,int month = 1,int day = 1)//最好定义为全缺省
{
//检查日期是否合法
if(year < 1900
|| month < 1 || month > 12
|| day < 1 || day > GetMonthDay(year,month))
{
cout<<"非法日期"<<endl;
}
_year = year;
_month = month;
_day = day;
}
GetMonthDay()是一个获取一个月有多少天的函数,实现如下:
int GetMonthDay(int year,int month)
{
static int MonthDay[13] = {0,31,28,31,30,31,30,31,31,30,31,30,31};
int day = MonthDay[month];
if(month == 2
&& ((year%400 == 0) || (year%4 == 0 && year%100 != 0)))
{
day += 1;
}
return day;
}
2.拷贝构造函数
创建对象时使用同类对象来进行初始化,这时所用的函数称为拷贝构造函数,拷贝构造函数是特殊的构造函数。
特征如下:
2.1拷贝构造函数其实是一个构造函数的重载。
2.2拷贝构造函数的参数必须使用引用传参,使用传值方式会引发无穷递归。(why?会在下面讲解)
2.3若未显示定义,系统会默认生成一个缺省的拷贝构造函数。缺省的拷贝构造函数会依次拷贝类成员及进行初始化。
为什么传值方式会引发无穷递归?
//为什么传值会引发无穷递归
//Date d2(d1)
Date(Date d)
{
_year = d._year;
}
假如我们要拿d1拷贝构造一个d2,用如上传值的拷贝构造函数来实现的话是有如下实现模型:把d1传给d,d这个对象不存在,实际上就是拿d1拷贝构造一个d;拷贝构造的时候又要传参。。拷贝传参拷贝传参这样一直下去就是一个无穷递归调用。
我们这里使用日期类的例子来讲解类的默认成员函数,日期类可以不用自己定义拷贝构造函数,但是有的类就必须自己实现一个拷贝构造函数,比如顺序表。顺序表系统自动生成的用不了,因为会产生内存泄漏,这其实是一个浅拷贝的问题,这个问题暂时就不进行详细说明。
Date(const Date& d) //拷贝构造函数
//参数为了防止在函数内发生意外的改变,最好加上const
{
_year = d._year;
_month = d._month;
_day = d._day;
} 为什么这里对象可以直接访问私有的成员变量?1.在类的成员函数中可以直接访问同类对象的私有/保护成员。。。。2.c++的访问限定符是以类为单位的,也就是说在这个单元内的成员可以互相访问。
Date d1;
Date d2 = d1; //调用拷贝构造函数
Date d3(d1); //调用拷贝构造函数
3.析构函数
当一个对象生命周期结束时,c++编译器会自动调用一个成员函数,这个特殊的成员函数叫析构函数。
特征:
3.1析构函数在类名前面加~。
3.2析构函数无参数无返回值
3.3一个类有且只有一个析构函数,若未显示定义,系统会自动生成缺省的析构函数。
3.4对象生命周期结束时,c++编译器会自动调用析构函数。
3.5注意析构函数函数体内并不是删除对象,而是做一些清理工作。这里的日期类可以不清理,但有的类必须及逆行清理,比如顺序表vector,不然会内存泄露,清不清理主要看有没有动态的资源。
4.运算符重载
运算符为了增强程序的可读性。
4.1特征:
4.1.1operator+合法的运算符 构成函数名(重载<运算符函数名:operator<)
4.1.2重载运算符以后,不能改变运算符的优先级/结合性/操作数个数。
4.2考点:
5个c++不能重载的运算符:* :: sizeof ?: .
4.3赋值运算符重载
拷贝构造函数是创建的对象,使用一个已有对象来初始化这个准备创建的对象。赋值运算符的重载是对一个已存在的对象进行拷贝赋值。
//d2 = d3 => d2.operator=(&d2,d3)
Date& operator=(const Date& d) //有俩个参数,但只写一个,因为还有一个参数是隐含的this指针。
{
if(this != &d) //是否是自己给自己赋值。没有什么很坏的影响,只是白做了而已。
{
this->_year = d._year; //this可以显示的写出来,也可以不写,写着this在这里方便于观察。
this->_month = d._month;
this->_day = d._day;
}
return *this; //赋值操作已经完成,为什么有个返回值?因为赋值运算符的重载支持三个数,i=j=k;k先赋给j后有一个返回值j,将这个返回值赋给i,返回的是同类型对象,所以类型为Date
//但此时如果没有给函数类型加引用,就是传值返回,不会直接返回,会创建一个临时对象。。会多一次拷贝构造,拷贝一个临时对象,再拿这个临时对象做返回
//传值返回:返回的是一个临时对象
//传引用返回:返回的对象出了作用域还在
}
Date d1;
Date d2(d1); //调用拷贝构造函数
Date d3;
d3 = d1; //调用赋值运算符的重载
传值返回和传引用返回:传值返回:返回的是一个临时对象
传引用返回:返回的对象出了作用域还在
通过运算符的重载我们可以实现一个更好玩的东西:如下图
要完成这个,我们得实现以下的函数
Date& operator+=(int day)
Date& operator+(int day)
Date& operator-=(int day)
Date& operator-(int day)
int operator-(const Date& d)
实现如下:
Date& operator+=(int day)
{
if(day < 0)
{
return *this -= -day;
}
_day += day;
while(_day > GetMonthDay(_year,_month))
{
_day -= GetMonthDay(_year,_month);
_month++;
if(_month == 13)
{
_year++;
_month = 1;
}
}
return *this;
}
//d+10
Date operator+(int day)
{
Date ret(*this);//*this 是d
ret += day;
return ret;
}
Date& operator-=(int day)
{
if(day < 0)
{
return *this += -day;
}
_day -= day;
while(_day <= 0)
{
--_month;
if(_month == 0)
{
_year--;
_month = 12;
}
_day+=GetMonthDay(_year,_month);
}
return *this;
}
Date operator-(int day)
{
Date ret(*this);
ret -= day;
return ret;
}
int operator-(const Date& d) //不加const d2会被改
{
int flag = 1;
Date max = *this;
Date min = d;
if(*this < d)
{
max = d;
min = *this;
flag = -1;
}
int day = 0;
while(min < max)
{
++(min);
++day;
}
return day*flag;
}
//++d => d.operator++(&d)
Date& operator++() //前置 返回值是++后的值
{
*this += 1;
return *this;
}
//d++ => operator++(&d,0)
Date operator++(int) //后置 int只是为了与前置做一个区分 返回的是++前的值
{
Date ret(*this);
*this += 1;
return ret;
}
Date& operator--()
{
*this -= 1;
return *this;
}
Date operator--(int)
{
Date ret(*this);
*this -= 1;
return ret;
}
bool operator>(const Date& d)
{
if(_year > d._year)
{
return true;
}
else if(_year == d._year)
{
if(_month > d._month)
{
return true;
}
else if(_month == d._month)
{
if(_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);
}
bool operator!=(const Date& d)
{
return !(*this == d);
}
void Display()
{
cout<<_year<<"-"<<_month<<"-"<<_day<<endl;
}
日期类的完整实现在我的下一个博客https://blog.youkuaiyun.com/hgelin/article/details/82799757中,可以直接拷贝到编译器进行编译。