在学习类的默认成员函数之前,先带大家复习一下什么是类,类是在C++中引进的新的类型,是一种自定义类型,实际上跟C语言中的结构体类似,但是是对结构体的升级,可以在类里面添加函数,也有对应的访问限定符private,public和protect;在C++中依旧可以使用结构体struct,因为C++要兼容C,只不过我们可以直接使用类名来实例化对象,还有一点就是class在不写访问限定符的时候,默认为private私有,而struct不写默认的是public,这也跟C++要兼容C有关,因为我们在C语言中struct的成员都是可以直接访问的。
其实我们在实现一个日期类的时候,我们肯定是需求在实例化对象的时候就初始化好我们的日期,比如:2023年11月20日
在没有学习构造函数之前我们会怎么把日期传给我们的成员变量呢?来看下面的代码:
#include
using namespace std;
class Date
{
public:
void Init(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
void Print()
{
cout << _year << "年" << _month << "月" << _day << "日" << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date date;
date.Init(2023, 11, 20);
date.Print();
return 0;
}
所以我们在不知道构造函数的存在之前,我们只会这样初始化日期,其实准确来说并不是初始化,我们只是简单的赋值,而真正的初始化是在初始化列表中实现的,因为初始化列表只能允许成员变量出现一次,所以也就赋值一次,这才是初始化!我们回到正题,如果每次写类的时候,都要这样实现初始化,神都会觉得麻烦,基于这样的理念,我们的构造函数就诞生了
构造函数是一种特殊的成员函数,跟我们之前所有的函数模样都不一样。
- 构造函数不用写返回值
- 构造函数的函数名是类名
- 构造函数允许重载
- 构造函数是在实例化对象时自动调用的
- 构造函数的参数可以给缺省值
下面就是构造函数的写法:
class Date
{
public:
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
上面就是构造函数的一种简单的写法,构造函数就是用来给成员变量赋值的,记住是赋值不是初始化,构造函数体内可以进行多次赋值,这跟初始化的概念截然不同,在后面讲的初始化列表才是初始化;还有一点就是我们显式地去写了构造函数,编译器就不会生成了,这也就是说明了,默认成员函数是编译器自己会生成的。既然我们可以写构造函数,编译器可以生成构造函数,那构造函数的分类是什么呢?
构造函数分为下面两类:
- 显式构造函数:必须传参的,如半缺省,无缺省;
- 默认构造函数:不需要传参的,如全缺省,无参和编译器默认生成的;
注意事项:
- 默认构造函数只允许存在一个;
- 写了显式构造函数,编译器就不会生成默认构造函数;
- 对于自定义类型的成员变量,当前类的构造函数会调用这个自定义类型的默认构造函数,如果这个自定义类型没有默认构造,就会报错;
其实最常用的并不是构造函数,而是我们的初始化列表,它的存在解决了对于必须在定义的时候初始化的变量,和一些没有默认构造函数的自定义类型。
使用规则:冒号开头,逗号接应,末尾没有分号
class Date
{
public:
Date(int year, int month, int day)
: _year(year)
, _month(month)
, _day(day)
{}
private:
int _year;
int _month;
int _day;
};
注意事项:
- C++11 中针对内置类型成员不初始化的缺陷,又打了补丁。即:内置类型成员变量在类中声明时可以给默认值,其实就是给的初始化列表。
- 成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关
给大家举两个例子:
- 第一个是在日期类,编译器生成的默认构造函数是没办法给我们的日期正常赋值的,会赋值,但是是给随机值,所以不满足我们的需求,我们会写构造函数,给正确的值;
- 第二个是栈,顺序表,链表,二叉树等需要申请资源,开辟空间的,我们的编译器生成的默认构造函数,是没办法进行malloc和realloc等操作的,所以我们要自己写一个构造函数来开辟空间;
- 综上所述,我们写构造函数的原因就是基于编译器生成的默认构造函数没办法提供我们正常的需求。
大家在这里,要知道凡是类里面有自定义类型,无论是默认构造函数还是显式构造函数,都会调用这个自定义类型自己的默认构造函数。如果这个自定义类型没有默认构造函数,编译器会报错。
区别:
- 我们构造函数准确的来说,不是给成员变量初始化,因为初始化只有一次,而我们的构造函数的内部可以多次给一个成员变量赋值,所以构造函数不是初始化,是赋值;
- 而初始化列表中的成员变量只会出现一次,所以就只会初始化一次。
关系:
- 可以在初始化列表和构造函数体内部同时使用,当我们需要一些变量初始化时需要条件判断,就要放在构造函数体内部。
- 能用初始化列表都用初始化列表,除非必须用构造函数。
因为我们在没有初始化列表的时候,我们写了构造函数,但是会发现这样一个现象,当没有进入构造函数的时候,成员变量都是随机值,那这个随机值是在哪里给的呢?答案就是初始化列表,所以初始化列表是一直存在的,只不过在没有显式地写的情况下,是默认给随机值的。
- 了解这些之后,我们再来谈谈const修饰的成员变量,因为const修饰的变量只能初始化一次,不可以被修改,所以在我们不写初始化列表时,默认的初始化列表就会初始化一次const,那接下来我们又在构造函数体内部赋值是肯定错误的;
- 那对于引用这样的变量,必须是在定义的时候初始化,如果我们不使用初始化列表,引用类型的成员变量就是默认初始化列表的随机值的别名,因为引用是改变不了指向的对象的,所以就会出错;基于这个原因,引用才必须在初始化列表中初始化;