C++ ——— 类的 6 个默认成员函数之 构造函数
目录
何为默认成员函数
一、构造函数
构造函数的概念
构造函数的特性
日期类的构造函数
栈的构造函数
编译器自动生成的构造函数
总结
何为默认成员函数
默认成员函数就是用户没有显示实现,但是编译器会自动生成的成员函数称为默认成员函数
一、构造函数
构造函数的概念
构造函数时特殊的成员函数,函数的名字与类名相同,创建类的类型对象时由编译器自动调用
以保证每个数据成员都有一个合适的初始值,并且再对象整个生命周期内只调用一次
构造函数的特性
构造函数虽然名为构造,但是构造函数的主要任务并不是开空间创建对象,而是初始化对象
其特征如下:
1. 构造函数的函数名与类名相同
2. 无返回值(不需要写返回值)
3. 对象实例化时编译器自动调用对应的构造函数
4. 构造函数可以重载(我们可以手动写多个构造函数,提供不同的初始化方式)
日期类的构造函数
代码演示:
class Data
{
public:// 无参构造函数Data(){_year = 1;_month = 1;_day = 1;}// 打印void Print(){cout << _year << "/" << _month << "/" << _day << endl;}private:int _year;int _month;int _day;
};
以上代码是一个简单的日期类,其中有一个无参的构造函数,并且给了初始值,那么是否实例化对象后,就能打印出初始化的值呢
代码验证:
可以发现,构造函数,确确实实初始化了成员变量
在特征中说了构造函数可以重载,那么如何传参进行初始化?
代码演示:
// 带参构造函数
Data(int yrar, int month, int day)
{_year = yrar;_month = month;_day = day;
}
在以上的日期类中增加了一个带参数的构造函数
那么该如何传递参数进行初始化呢?
代码验证:
直接在实例化对象的后面初始化即可
合并以上两个构造函数:
// 全缺省构造函数
Data(int yrar = 1, int month = 1, int day = 1)
{_year = yrar;_month = month;_day = day;
}
全缺省的构造函数就可以合并以上两个构造函数,并且初始化时会更灵活
代码验证:
栈的构造函数
代码演示:
class Stack
{
public:// 无参构造函数Stack(){_a = nullptr;_top = _capacity = 0;}// 入栈void Push(int x){// 先判断是否需要扩容if (_top == _capacity){// 扩容int new_capacity = _capacity == 0 ? 4 : _capacity * 2;int* tmp = (int*)realloc(_a, sizeof(int) * new_capacity);// 判断是否扩容成功if (tmp == nullptr){perror("realloc fail");return;}_a = tmp;_capacity = new_capacity;}// 入数据_a[_top++] = x;}// 访问栈顶元素int Top(){assert(_top >= 0);return _a[_top - 1];}// 出栈void Pop(){assert(_top > 0);_top--;}// 判断是否为空bool Empty(){return _top == 0;}// 打印void Print(){while (!Empty()){// 访问栈顶元素cout << Top() << " ";// 弹出栈顶元素Pop();}cout << endl;;}// 释放void Destroy(){free(_a);_a = nullptr;_top = _capacity = 0;}private:int* _a;int _top;int _capacity;
};
以上是一个栈的简单实现,并且封装到类里面
其中有一个无参的构造函数,完成了对栈的初始化
代码验证:
但是这样的无参的构造函数有一个缺点,就是如果最开始就知道要开辟 1000 个空间
那么还是这个无参的构造函数的话,就会导致异地扩容的次数加大,降低效率
所以可以写成全缺省的构造函数
代码演示:
Stack(int n = 4)
{if (n == 0){_a = nullptr;_top = _capacity = 0;}else{_a = (int*)malloc(sizeof(int) * n);_top = 0;_capacity = n;}
}
这个构造函数的意思就是如果实参部分传递了 0 ,那么就初始化为 nullptr
如果实参部分没有传递值,或者传递了其他非 0 的正数值,那么就开辟多少空间
代码验证:
// 不开辟空间
Stack s1(0);// 开辟4个空间
Stack s2;// 开辟100个空间
Stack s3(100);
编译器自动生成的构造函数
在概念中提到过,构造函数是默认成员函数,如果不手动添加,编译器也会自动生成
编译器生成的默认构造函数的特点:
1. 不手动添加构造函数,编译器才会自动生成,手动添加了,编译器就不会生成了
2. 内置类型的成员不会做处理(int、char、int*……这些类型称为内置类型)
3. 对于自定义类型的成员才会处理(结构体、类……这些类型称为自定义类型),会去调用这个自定义类型成员的构造函数
证明第1、2点:
当我把日期类中的构造函数去掉后,如果实例化对象后直接打印,就会出现随机值
这就证明了编译器是自动生成了构造函数的,但只是这个构造函数对内置类型的成员不会做处理
所以打印出来才会是随机值
证明第3点:
代码演示:
class Stack
{
public:// 全缺省构造函数Stack(int n = 4){cout << "Stack(int n = 4)" << endl;if (n == 0){_a = nullptr;_top = _capacity = 0;}else{_a = (int*)malloc(sizeof(int) * n);_top = 0;_capacity = n;}}// ……………………
}class MyQueue
{
private:Stack _PushSt;Stack _PopSt;
};
MyQueue 类中有两个自定义的成员,是两个栈对象的成员,而且我在栈这个类中加上了一句打印,只要实例化一个 MyQueue 类的对象就能证明第 3 点
代码验证:
可以看到,确实调用了栈这个类的构造函数,所以证明了第3点
总结
一般最好是自己手动写上构造函数
只有在成员是自定义类型的时候可以不用写,因为会去调用这些自定义成员的构造函数,