目录
类的默认成员函数
我们在定义类的时候,类里面其实默认生成六个函数,在涉及到使用它们的情况下就会默认调用这些函数。(即使是空类也会有默认成员函数)
六种成员函数:
构造函数、析构函数、拷贝构造函数、
运算符重载、默认拷贝构造与赋值运算符重载、const成员函数
我们以日期类来进行以下函数的说明
1.构造函数
我们在实例化对象时,需要给对象进行初始化,而每次创建时都初始化未免显得太麻烦,有没有可能在不传参数的情况下直接进行初始化呢?要实现这个功能就要使用构造函数了。
问题来了:既然是默认的构造函数,我们还要去写吗?
我们先来看一下不写会怎么样:
#include<iostream>
using std::cout;
using std::cin;
using std::endl;
class Date
{
public:
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
d1.Print();
return 0;
}
可以看出是成员数据都是随机数,也就是说默认的构造函数对内置类型数据并不进行处理。(下面会讨论关于自定义类型数据的情况)
再来看如果自己写构造函数呢?
#include<iostream>
using std::cout;
using std::cin;
using std::endl;
class Date
{
public:
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
//构造函数
Date()
{
_year = 2022;
_month = 5;
_day = 19;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
d1.Print();
return 0;
}
首先,构造函数的函数名和类名相同,函数可以不传参数(默认传this指针),然后对内置类型数据进行赋值。
那能不能整成缺省参数呢?这样一来就相当方便了,想要传参就传参,想要不传参数就缺省赋值。
下面来尝试一下:
#include<iostream>
using std::cout;
using std::cin;
using std::endl;
class Date
{
public:
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
//也可以写成半缺省参数,但是要进行对应参数传参
Date(int year = 2022, int month = 5, int day = 19)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
d1.Print();
Date d2(1999, 9, 9);
d2.Print();
return 0;
}
小结:
默认构造函数:默认生成的构造函数、无参自定义构造函数、全缺省自定义构造函数
来看一个特殊情况:(自定义类型数据的处理)
#include<iostream>
using std::cout;
using std::cin;
using std::endl;
class Dates
{
public:
void Print()
{
cout << _num << endl;
}
Dates()
{
_num = 6;
}
private:
int _num;
};
class Date
{
public:
void Print()
{
cout << _year << "-" << _month << "-" << _day <<endl;
_dates.Print();
}
/*Date(int year = 2022, int month = 5, int day = 19)
{
_year = year;
_month = month;
_day = day;
}*/
private:
int _year = 2022;//这里相当于缺省
int _month = 5;
int _day = 19;
Dates _dates;
};
编译器生成默认的构造函数会对自定类型成员_dates调用的它的默认成员函数(上文提到的默认构造函数的分类在这里就用到了)
2.析构函数
首先强调一点:析构函数是特殊的成员函数
特性:
1. 析构函数名是在类名前加上字符 ~。
2. 无参数无返回值。
3. 一个类有且只有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。
4. 对象生命周期结束时,C++编译系统系统自动调用析构函数。
析构函数实际上是和构造函数对应着的一个函数,它在处理当函数结束时将对象里的资源进行清理。当然,像日期这样的类里不涉及到内存的动态开辟一类的问题是不需要我们自己去写析构函数的。当然如果涉及到了,我们还是要动手去写的。
例:
class Date
{
public:
void Print()
{
cout << _year << "-" << _month << "-" << _day <<endl;
}
Date(int year = 2022, int month = 5, int day = 19)
{
_year = year;
_month = month;
_day = day;
_num = (int*)malloc(sizeof(int) * 4);
}
//析构函数
~Date()
{
free(_num);
_num = nullptr;
_year = _month = _day = 0;
}
private:
int _year = 2022;
int _month = 5;
int _day = 19;
int* _num;
};
在对象要被销毁时编译器会自动调用析构函数,这就避免了我们忘记释放空间而造成内存泄露问题了。
和构造函数一样,编译器生成的默认析构函数,会对自定类型成员调用它的析构函数。如下:
class Dates
{
public:
Dates()
{
_num=(int*)malloc(sizeof(int)*10);
}
~Dates()
{
free(_num);
_num = nullptr;
}
privite:
int* _num;
};
class Date
{
public:
void Print()
{
cout << _year << "-" << _month << "-" << _day <<endl;
}
Date(int year = 2022, int month = 5, int day = 19)
{
_year = year;
_month = month;
_day = day;
_num = (int*)malloc(sizeof(int) * 4);
}
//默认生成的析构函数会调用Dates的析构函数
private:
int _year = 2022;
int _month = 5;
int _day = 19;
Dates d;
};
3.拷贝构造函数
当我们在创建一个新的对象时,能不能创建一个和已有的对象一摸一样的呢?
解决这个问题就需要用到拷贝构造函数了。
首先,拷贝构造函数也是一个特殊的构造函数。
特性:
1. 拷贝构造函数是构造函数的一个重载形式。
同样是和类同一个名字,只不过参数发生了变化,变为:const Date& d
2. 拷贝构造函数的参数只有一个且必须使用引用传参,使用传值方式会引发无穷递归调用。
Date(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
如果写成 Date(const Date d)的话,首先传参会进行一次拷贝,这个拷贝用到的也是拷贝构造函数,如果不是引用传参的话,在传参拷贝时又会调用拷贝函数... 这样就会形成无限循环。
用法: 在实例化对象时传 要拷贝的对象。
Date d1(1999,9,9);
d1.Print();
Date d2(d1);//拷贝d1
d2.Print();
3. 若未显示定义,系统生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝我们叫做浅拷贝,或者值拷贝。
如果类里面有动态空间类的成员数据,简单的值拷贝肯定是不行的。简单的值拷贝会使两个或多个指针指向同一个内存空间,而在销毁类对象时会调用析构函数,因此导致多次free同一个内存空间,这时就需要深度拷贝了,这个是需要自己去写的。
4.运算符重载
我们熟知的 加、减、乘、除这些基本运算符可以直接对内置类型数据进行运算,但是对于自定义类型数据编译器不可能也没有能力去自动识别类里的内容并进行相应的运算操作。这时只能依靠我们自己去写这些功能的函数了——运算符重载
首先介绍一个简单的也是基础的运算符 “ = ”
1.“ = ”
Date& operator=(const Date& d)
{
if (this != &d) //防止进行无意的自赋值
{
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this;
}
返回值之所以设置成 Date& 是因为要应对连等的情况:d1 = d2 = d3;
2.“ +=” “ -= ”
#include<iostream>
using std::cout;
using std::cin;
using std::endl;
bool IsLeapYear(int year)
{
return (year % 400 == 0 || (year % 100 != 0 && year % 4 == 0));
}
int Date::GetMonthDay(int year, int month)
{
const static int a[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
if (month == 2 && IsLeapYear(year))
{
return 29;
}
else
{
return a[month];
}
}
class Date
{
public:
void Print()
{
cout << _year << "-" << _month << "-" << _day <<endl;
}
Date(int year = 2022, int month = 5, int day = 19)
{
_year = year;
_month = month;
_day = day;
_num = (int*)malloc(sizeof(int) * 4);
}
~Date()
{
free(_num);
_num = nullptr;
_year = _month = _day = 0;
}
Date(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
Date& operator=(const Date& d)
{
if (this != &d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this;
}
int GetMonthDay(int year, int month);
Date& operator+=(const int day)
{
if (day < 0)
{
return (*this -= -day);
}
int x = GetMonthDay(_year, _month);
_day += day;
while (_day > x)
{
_month++;
if (_month > 12)
{
_year++;
_month = 1;
}
_day -= x;
x = GetMonthDay(_year, _month);
}
return *this;
}
Date& operator-=(int day)
{
if (day < 0)
{
return *this += -day;
}
_day -= day;
while (_day <= 0)
{
--_month;
if (_month < 1)
{
_month = 12;
--_year;
}
_day += GetMonthDay(_year, _month);
}
return *this;
}
private:
int _year;
int _month;
int _day;
int* _num;
};
上面的函数在涉及到day的正负的时候,里其实就用到了+= 和 -= 的复用。
注:
不能通过连接其他符号来创建新的操作符:比如operator@
重载操作符必须有一个类类型或者枚举类型的操作数
用于内置类型的操作符,其含义不能改变,例如:内置的整型+,不 能改变其含义
作为类成员的重载函数时,其形参看起来比操作数数目少1成员函数的 操作符有一个默认的形参this,限定为第一个形参
.* 、 :: 、 sizeof 、 ?: 、. 注意以上5个运算符不能重载。
5.默认 拷贝函数与赋值运算符重载函数
这两个函数在上面我们就稍微提了几句,特点就是只会进行浅拷贝,而赋值运算符的话就是按字节拷贝。编译器默认生成的拷贝函数与赋值运算符重载会对自定类型成员调用它的拷贝函数和赋值运算符重载函数。
6.const成员函数
将const修饰的类成员函数称之为const成员函数,const修饰类成员函数,实际修饰该成员函数隐含的this 指针,表明在该成员函数中不能对类的任何成员进行修改。
例如:
void Print() const
{
cout << _year << "-" << _month << "-" << _day <<endl;
}
相当于:
void Print(const Date* this)
{
cout << _year << "-" << _month << "-" << _day <<endl;
}
取地址及const取地址操作符重载:
class Date
{
public :
Date* operator&()
{
return this ;
}
const Date* operator&()const
{
return this ;
}
private :
int _year ;
int _month ;
int _day ;
};
这两个默认成员函数一般不用重新定义 ,编译器默认会生成。这两个运算符一般不需要重载,使用编译器生成的默认取地址的重载即可,只有特殊情况,才需要重载,比 如想让别人获取到指定的内容!