目录
[1. 关于构造函数](#1. 关于构造函数)
[2. 初始化列表](#2. 初始化列表)
[3. 初始化顺序](#3. 初始化顺序)
**《Effective C++:55 Specific Ways to Improve Your Programs and Designs》**一书中指出,理解C++默认生成并调用的函数至关重要。若需禁用编译器自动生成的函数,必须显式拒绝。
在类与对象部分,存在6种特殊的成员函数 ,它们不需要我们手动调用,是由编译器在特定的时机自动调用的。
这些函数可以交由编译器默认生成,当生成的函数不满足我们的需求时,我们需要自己显式实现。
本文重点介绍C++98标准中的第一个默认成员函数:构造函数。
1. 关于构造函数
- 功能: 类通过一个或多个默认构造函数来初始化成员对象
- 特点: 函数名与类名相同,无返回值,可以重载。编译器自动生成的构造函数是无参的,一旦我们显式定义,编译器就不会再生成了。
- 默认构造函数: 分为无参构造函数,全缺省构造函数,编译器默认生成的构造函数三种。总结一下:不传实参就可以调用的构造就是默认构造。
注意:这三种默认构造函数有且只能有一个存在,不能同时存在。否则就会产生歧义,编译器不知道该调用哪个函数。默认构造函数可以和有参构造同时 存在**。**
使用示例:
cpp
//全缺省构造
String(const char* str = "")
{
_size = strlen(str);
_capacity = _size;
_str = new char[_capacity + 1];
strcpy(_str, str);
}
//有参构造
String(char* str)
{
_size = strlen(str);
_capacity = _size;
_str = new char[_capacity + 1];
strcpy(_str, str);
}
在string的模拟实现中,这两种构造函数是可以同时存在的。
总结:
没有显式实现构造函数时,编译器默认生成的构造函数,对于内置类型 成员变量的初始化是不统一 的,因编译器而异;对于自定义类型 的成员变量,会调用这个成员变量的构造函数进行初始化,没有构造函数则报错。
需要自己实现的情况:
-
自定义类型的构造函数
-
内置类型开辟了空间(eg:指针成员指向的空间需要深拷贝)
2. 初始化列表
- 功能: 初始化操作,使成员在创建出来的同时进行初始化
- **特点:**以一个冒号开始,用逗号分隔的数据成员列表,每个成员变量后面跟一个放在括号里的表达式或者初始值。
使用示例:
cpp
//初始化列表
String()
:_str(new char[1]{'\0'})
,_size(0)
,_capacity(0)
{}
tips:切忌加任何分号!
注意:
必须通过初始化列表初始化的情况:
- 引用(&)成员变量
- const成员变量
- 没有默认构造的类类型变量
前两种情形必须在对象创建时完成初始化,无法采用先声明后赋值的方式。由于构造函数内部本质上是先创建对象再进行赋值的流程,因此这些初始化操作无法在构造函数中完成。
第三点更详细来说,如果一个类没有默认构造,那么当它作为另一个类的成员时,必须在外部类的构造函数初始化列表里显式构造它:
cpp
class NoDefault
{
public:
// 没有默认构造
NoDefault(int x)
{}
};
class MyClass
{
private:
NoDefault _obj; // 作为外部类的成员变量
public:
// 必须写在初始化列表中
MyClass()
: _obj(42)
{}
};
**建议:**尽量使用初始化列表进行初始化,因为所有变量都会先走初始化列表。
对于没有显式出现在初始化列表的内置类型 成员变量是否初始化由编译器决定,自定义类型则会调用默认构造函数,没有则会编译报错。这一点和构造函数的规则很相似。
初始化顺序: 按照成员变量的声明顺序进行初始化,与列表中的顺序无关。因此建议列表的顺序要和声明顺序保持一致。
总结:
- 无论是否显式写初始化列表,每个构造函数都有初始化列表。
- 无论是否在初始化列表显式初始化了成员变量,每个成员变量都会走初始化列表。
3. 初始化顺序
我们可以把成员变量初始化的顺序想象成一个到站下车的过程,也可以看作是优先级问题。第一站是初始化列表,第二站是声明位置的缺省值,第三站是默认构造函数,第四站则是构造函数体。

具体的初始化流程为:
- 变量先按照声明顺序执行初始化列表:
- 如果初始化列表显式写了变量,则用指定的值
- 未出现在初始化列表的变量,会走声明,有缺省值则使用
- 以上两种都不符,则编译器自动调用默认构造:内置类型随机值,自定义类型调用默认构造
- 最后所有变量进入构造函数体,出现在构造函数体中的变量会被赋值。没出现的变量保持初始化列表结束时的值
举个例子:
cpp
class A
{
public:
A(int x, int y)
: _b(y) // ①
, _c(_a) // ②
{
_a = x; // ③
_b = x + y; // ④
}
void Print() const
{
cout << "_a = " << _a << ", _b = " << _b << ", _c = " << _c << endl;
}
private:
int _a = 10;
int _b = 20;
int _c = 30;
};
int main()
{
A obj(5, 7);
obj.Print();
return 0;
}
Q: 这串代码的输出是什么?
A:
首先看主函数,对象obj是A类的实例化,传入了参数5和7;
随后看初始化列表,此时形参x为5,y为7,按照声明顺序进行初始化:
_a未在初始化列表显式写,因此_a会使用声明的缺省值10进行初始化;_b被初始化为7,_c被初始化为_a的值,即为10;
最后,所有变量进入构造函数内部:_a被赋值为5(x),_b被赋值为12(x+y);
输出结果为:

// 感谢阅读~顺手点个免费的赞吧(●'◡'●)
//封面是特蕾西娅殿下TvT