目录
- 一.类的默认成员函数
- 二.构造函数
- 三.析构函数。
- 四.拷贝构造函数
- 五.赋值运算符重载
一.类的默认成员函数
类的默认成员函数就是定义一个类后,类会自动生成的成员函数,若我们显示定义则类不会自动生成。
二.构造函数
在数据结构学习阶段我们手撕过栈、链表、队列等数据结构,当时我们是用C语言来实现的,每一种数据结构都有自己的初始化和销毁函数。这两个函数对数据结构的使用非常重要,但是我们又常常忘记使用,所以C++在类中设定了默认成员函数——构造函数,它的作用是初始化函数。
他有以下的特征:
- 函数名与类名相同。
- 无返回值(连void都不用写)
- 函数可以重载
- 若无显示定义则在类成员实例化的时候自动调用
还有以下特点要注意,这里我们使用经典的日期类作讲解:
class Date
{
public:
Date()//无参的构造函数
{
_year = 2023;
_month = 5;
_day = 1;
}
// 全缺省的构造函数
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
如上代码,我们已经显示定义了构造函数——一个无参的,一个全缺省的,显然我们要调用时就会有分歧,所以我们规定,调用无参的函数时连括号都不用写:
int mian()
{
Date d1;//调用无参的构造函数
Date d2();//调用全缺省的构造函数
Date d3(2023,4,5);//调用全缺省的构造函数
return 0;
}
如果类中未显示定义构造函数,编译器则会生成一个无参的默认构造函数。但是自动生成的默认构造函数只会初始化自定义类型不会初始化内置类型(在不同的编译器上有所区别)。内置类型就是语言提供的数据类型如:int/char等,自定义类型就是我们自己定义的类型如:struct/class/union等。
这样问题就来了,日期类中如果我们忘记定义构造函数,因为日期类成员全是内置类型,则编译器自动生成的构造函数无法完成对类成员的初始化,所以在C++11中规定了,内置类型成员变量在类中声明的时候可以给默认值。还有要注意的:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为是默认构造函数。
那么我们什么时候需要自己写构造函数,什么时候用编译器生成的呢:
- 一般来说,有内置类型并且内置类型未赋符合要求的缺省值时,就需要我们自己编写构造函数。
- 如果成员全是自定义类型的则可以使用编译器生成的默认成员函数
三.析构函数。
析构函数就是销毁清理函数,完成对象资源的清理工作,他有以下特征:
- 析构函数名是类名前面加上~。
- 析构函数无返回值 无参数
- 析构函数无法重载,一个类只能有一个析构函数,若未显式定义则编译器自动生成。
- 对象生命周期结束时,系统自动调用析构函数。
析构函数与构造函数相似:内置类型不会做处理,自定义类型会调用它的析构函数。
由此判断:
- 一般情况下,有动态申请资源,就需要显示写析构函数释放资源
- 没有动态申请的资源,不需要写析构
- 需要释放资源的成员都是自定义类型,不需要写析构
四.拷贝构造函数
当我们想创建两个成员相同的对象时,可以采用用已经存在的对象初始化,这就要使用到拷贝构造函数。他只有一个形参是对本类型对象的引用,拷贝构造函数时构造函数的一个重载,形参如果不使用引用而采用传值调用的话会引发无穷递归调用——因为内置类型的传值调用需要中间变量拷贝来完成而自定义类型传参的值拷贝需要拷贝构造函数完成。
若未显式定义,编译器会自动生成一个默认拷贝构造函数:内置类型会完成值拷贝/浅拷贝,而自定义类型会调用他的拷贝构造函数。
注意:类中如果没有涉及资源申请时,拷贝构造函数是否写都可以,一旦涉及到资源申请时,则拷贝构造函数是一定要写的,否则就是浅拷贝。
指针无论是什么类型的都是内置类型。
五.赋值运算符重载
运算符重载是具有特殊函数名的函数。
函数名字为:关键字operator后面接需要重载的运算符符号。
函数原型:返回值类型 operator操作符(参数列表)
运算符重载需要注意一下几点:
- 重载操作符必须有一个类类型参数
- 用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不 能改变其含义
- 不能通过连接其他符号来创建新的操作符:比如operator@
- 作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this
.* sizeof :: ?: .
注意以上5个运算符不能重载。
诸如下面的代码就是运算符重载:
// >运算符重载
bool operator>(const Date& d)
{
return !(*this <= d);
}
// ==运算符重载
bool operator==(const Date& d)
{
return _year == d._year
&& _month == d._month
&& _day == d._day;
}
// >=运算符重载
bool operator >= (const Date& d)
{
return !(*this < d);
}
// <运算符重载
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 !(*this > d);
}
// !=运算符重载
bool operator != (const Date& d)
{
return !(*this == d);
}
赋值运算符重载需要注意:
- 参数类型:const T&,传递引用可以提高传参效率
- 返回值类型:T&,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值
- 检测是否自己给自己赋值
- 返回*this :要复合连续赋值的含义
class Date
{
public :
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
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;
}
private:
int _year ;
int _month ;
int _day ;
};
还有几点需要注意的!
赋值重载不能写成全局(默认成员函数都不能写成全局函数)但是可以声明和定义分离(也不一定还要注意访问类成员私有共有问题)
C++规定: ++a 前缀运算符 a.operator() 不需要加参数a++后缀运算符 a.operator(int) 需要加参数。
当一元运算符作为成员函数重载时参数表中没有参数,那个惟一的操作数以this指针的形式隐藏在参数表中;当作为非成员函数重载时,那个惟一的操作数必须出现在参数表中。