一、类的默认成员函数
默认成员函数是指用户没有显式实现,编译器会自动生成的成员函数。它可以进行自动调用,无需使用者手动调用。例如构造函数用于给对象进行初始化,之前我们都要给一个类写 Init 函数然后调用它,但如果有了默认构造函数,在创建对象时就会自动调用,非常方便。
一个类在不写的情况下,编译器会默认生成以下 6 个默认成员函数。需要注意的是这 6 个中最重要的是前 4 个,最后两个取地址重载不重要,稍微了解一下即可。其次 C++11 标准还增加了两个默认成员函数:移动构造和移动赋值,后续再讲解。现在来学习这 6 个默认成员函数,它们很重要也比较复杂,要从两个方面去学习:
- 不写时,编译器默认生成的函数行为是什么,是否满足需求。
- 编译器默认生成的函数不满足需求,需要自己实现,那么如何实现?
以下是这几个默认成员函数的基本作用,接下来的文章将一一剖析讲解。
二、构造函数
构造函数是特殊的成员函数。需要注意的是,构造函数虽然名称叫构造,但是主要任务并不是开空间创建对象(局部对象是栈帧创建时开的空间),而是对象实例化时初始化对象。
构造函数的本质是要替代以前 Stack 和 Date 类中写的 Init 函数的功能,利用默认构造函数能够自动调用的特点完美替代 Init 函数。接着从编译器默认生成的构造函数能做什么,以及如果默认生成的构造函数不够用该怎么写构造函数两个方面展开学习。
1. 默认生成的构造函数能干什么?
-
自定义类型:当不写构造函数时,编译器会默认生成一个构造函数。虽然我们看不到,但编译器确实会生成。这个构造对内置类型成员变量的初始化没有要求,比如 int、double 等类型,默认生成的构造函数对它们进行初始化时没有要求,可能是随机值,也可能是 0,要看编译器的具体实现。
-
对于自定义类型的成员变量,即类类型或者结构体类型的成员变量。例如使用两个栈实现队列的基本操作,在这个队列中就包含了自定义类型的两个成员变量栈:
class Stack {
// 栈的实现...
};
class Queue {
public:
// 使用栈实现队列的各种方法放这里...
private:
// 这里的 st1 和 st2 就是自定义类型的成员变量
Stack st1;
Stack st2;
};
上面演示的伪代码是两个栈实现队列,可以看到队列的两个成员变量不是 int 等内置类型,而是一个类类型,也就是自定义类型。那么对于自定义类型,编译器生成的构造函数会如何进行处理呢?
编译器会调用这些自定义类型成员变量的默认构造函数,用它们自己的默认构造函数对这些自定义类型的成员变量进行初始化。默认构造函数不止有编译器默认生成的构造函数,还有全缺省构造函数以及无参构造函数都属于默认构造函数。
如果这个自定义类型的成员变量没有自己的默认构造函数,那么编译器就会报错。要解决这个问题需要使用初始化列表,初始化列表稍后讲解。
总结一下:如果是内置类型的成员变量,默认生成的构造函数的处理是由编译器决定,有可能是随机值,也可能是 0;如果是自定义类型的成员变量,默认生成的构造函数会调用这个自定义类型的默认构造,如果这个自定义类型没有默认构造编译器就会报错。
那么很显然编译器默认生成的构造函数肯定不够我们使用,所以大部分情况还是需要自己来写构造函数。
2. 怎么写构造函数
在写构造函数之前,要先了解手写构造函数的特点以及一些注意事项:
- 构造函数的函数名与类名相同,比如类名为 Date,那么构造函数的函数名为 Date()。
- 构造函数无返回值,也就是它的返回值啥都不需要给,也不需要写 void,C++ 规定如此。
- 对象实例化时编译器会自动调用对应的默认构造函数,也就是当创建一个对象时,编译器会自动调用默认构造函数,不需要手动进行初始化操作,非常方便。
- 构造函数可以重载,只要参数不同,可以有多个构造函数,它们的函数名都是类名,在调用时会根据参数自动确认调用哪个构造函数。
- 如果类中没有显式定义构造函数,则 C++ 编译器会自动生成一个无参的默认构造函数,一旦用户显式写了构造函数那么编译器将不再生成。
根据上面的规则可以实现构造函数。下面写一个日期类的无参构造函数,该构造函数不需要任何参数,默认全部初始化为 2025 年 1 月 1 日:


