目录
一、类的6个默认成员函数
二、构造函数
1、定义
2、特征
三、析构函数
1、定义
2、特征
四、默认生成构造&析构
1、定义
2、内置类型
3、自定义类型
4、声明处给默认值
5、总结
下一篇
一、类的6个默认成员函数
如果一个类中没有定义任何成员,我们可以称之为空类。
然而,实际上空类并不是真的什么都没有。当我们没有在类中显式定义任何成员时,编译器会自动生成以下6个默认成员函数:
这些默认成员函数在需要时会被编译器自动生成,以提供类的基本功能。尽管我们没有显式定义任何成员,但这些默认成员函数确保了类的完整性和正确性。
本次主要讲解前前两个默认成员函数。
二、构造函数
之前学习数据结构中的 “栈” 时,对其进行初始化的函数如下:
class Stack
{
public:void Init(int n){a = (int*)malloc(sizeof(int) * n);if (a == nullptr){perror("malloc fail");return;}_capacity = n;_size = 0;}private:int* _a;int _size;int _capacity;
};
对于Stack类,可以通过 Init 公有方法给对象进行初始化,但如果每次创建对象时都调用该方法设置信息,未免有点麻烦,那能否在对象创建时,就将信息设置进去呢?
这时,构造函数登场。
1、定义
- 构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,以保证每个数据成员都有 一个合适的初始值,并且在对象整个生命周期内只调用一次。
- 构造函数是特殊的成员函数,需要注意的是,构造函数虽然名称叫构造,但是构造函数的主要任务并不是开空间创建对象,而是初始化对象。
2、特征
- 函数名与类名相同。
- 无返回值。
- 对象实例化时编译器自动调用对应的构造函数。
- 构造函数可以重载。—多种构造方式
下面通过例子一一解释 :
构造函数可以重载。(本质可以写多个构造函数,提供多种初始化方式)
class Stack
{
public:// 1.无参构造函数Stack(){_a = nullptr;_size = _capacity = 0;}// 2.带参构造函数Stack(int n){_a = (int*)malloc(sizeof(int) * n);if (_a == nullptr){perror("malloc fail");return;}_capacity = n;_size = 0;}private:int* _a;int _size;int _capacity;
};
两个Stack函数都为构造函数,有无参数均可以,他们共同的特点是函数名与类名相等,无返回值,其余部分跟正常函数一样。
接下来看如何在主函数调用构造函数初始化对象。
当我们声明类的对象后,程序运行到这段代码后,编译器会自动调用无参数的构造函数进行初始化。
int main()
{Stack st;return 0;
}
在下图中,我们对Stack st;打断点,在调试中可以看到按F11之后直接跳转到无参数的Stack()进行初始化。
也可以选择选择带参数的构造函数进行初始化,只需在后面加上参数即可。
Stack st(4);
我们再看一个日期类:
class Date
{
public://Date()//{// _year = 1;// _month = 1;// _day = 1;//}////Date(int year, int month, int day)//{// _year = year;// _month = month;// _day = day;//}//缺省构造函数代替上述函数重载Date(int year = 1, int month = 1, int day = 1){_year = year;_month = month;_day = day;}void Print(){cout << _year << "/" << _month << "/" << _day << endl;cout << _year << "年" << _month << "月" << _day << "日" << endl;}private:int _year;int _month;int _day;
};int main()
{Date d1;Date d2(2023, 2, 3);d1.Print();d2.Print();return 0;
}
除了上述的两种构造函数,我们还可以结合缺省参数使其更完善,只需这一个构造函数就可满足各种情况,一般来说很多时候都喜欢使用全缺省或半缺省的构造函数。
注意:这种缺省构造函数不能与无参的构造函数同时存在,编译能通过但有警告,会产生歧义,程序不知道调用哪个。
三、析构函数
通过前面构造函数的学习,我们知道一个对象是怎么来的,那一个对象又是怎么没呢的?
1、定义
2、特征
其特征如下:
- 析构函数名是在类名前加上字符 ~。
- 无参数无返回值类型。
- 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构函数不能重载
- 对象生命周期结束时,C++编译系统系统自动调用析构函数
下面代码中的~Stack()即为析构函数:
class Stack
{
public:Stack(){cout << "Stack()" << endl;_a = nullptr;_size = _capacity = 0;}~Stack(){cout << "~Stack()" << endl;free(_a);_a = nullptr;_size = _capacity = 0;}private:int* _a;int _size;int _capacity;
};int main()
{Stack st;//Stack st(4);//st.Destroy();由析构函数代替return 0;
}
我们使用调试看一下程序如何调用 ~Stack()函数:
在return 0;位置打断点,按F11可以看到直接进入~Stack()函数
程序直接跳转~Stack()函数
如果类中没有申请资源时,析构函数可以不写,直接使用编译器生成的默认析构函数,比如Date类;有资源申请时,一定要写,否则会造成资源泄漏,比如Stack类。
四、默认生成构造&析构
1、定义
如果类中没有显式定义构造或析构函数,则C++编译器会自动生成一个无参的默认构造或析构函数,一旦用户显式定义编译器将不再生成。同理析构函数也一样。
2、内置类型
我们来看下面的例子:
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;
}
通过结果可知,实际上并没有对成员变量自动进行初始化。
如果我们加上自己定义的构造函数呢?
Date(int year = 0, int month = 1, int day = 1)
{_year = year;_month = month;_day = day;
}
结果可以正常初始化。
关于编译器生成的默认成员函数,很多人会有疑惑:不实现构造函数的情况下,编译器会生成默认的构造函数。但是看起来默认构造函数又没什么用?对象调用了编译器生成的默认构造函数,但是d1对象_year/_month/_day,依旧是随机值。
也就说在这里编译器生成的默认构造函数并没有什么用??
- C++规定:默认生成构造和析构函数对内置类型成员不做处理,内置类型就是语言提供的数据类型,如:int/char...。
- 对自定义类型的成员,会去调用它的默认构造(不用传参数的构造),自定义类型就是我们使用class/struct/union等自己定义的类型。
3、自定义类型
下面是用栈实现队列的程序,使用两个栈模拟实现队列,编译器生成默认的构造函数会对自定类型成员MyQueue调用的它的默认成员函数。
class Stack
{
public:Stack(){_a = nullptr;_size = _capacity = 0;}private:int* _a;int _size;int _capacity;
};class MyQueue {
public:void push(int x) {}//....Stack _pushST;Stack _popST;
};int main()
{MyQueue q;return 0;
}
输出结果如下:(使用两个栈模拟实现队列,构造和析构函数各调用两次)
结论:
- 默认生成构造函数,对自定义类型成员,会调用他的默认构造函数。
- 默认生成析构函数,对自定义类型成员,会调用他的析构函数。
4、声明处给默认值
C++11 中针对内置类型成员不初始化的缺陷,又打了补丁,即:内置类型成员变量在类中声明时可以给默认值。
如下图所示:
class Date
{
public:void Print(){cout << _year << "年" << _month << "月" << _day << "日" << endl;}private:int _year = 1;int _month = 1;int _day = 1;
};int main()
{Date d1;d1.Print();return 0;
}
成功初始化:
我们对MyQueue函数中新增一个整型变量size。
class MyQueue {
public:void push(int x) {}//....Stack _pushST;Stack _popST;int _size;
};int main()
{MyQueue q;return 0;
}
在调试中可以看到size未初始化,
这时可以在声明处给默认值0。
int _size = 0;
可以看到size变量成功初始化。
5、总结
- 无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。
- 注意:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为是默认构造函数。
- 一般情况下,都需要我们自己写构造函数,决定初始化方式;成员变量全是自定义类型,可以考虑不写构造函数。
下一篇
C++类与对象(3)—拷贝构造函数&运算符重载