一、构造函数的基本语法
1. 定义构造函数
构造函数是类的特殊成员函数,用于初始化对象的状态。它的名称必须与类名相同,且没有返回类型(连void也没有)。
基本形式:
class MyClass {
public:
// 构造函数声明
MyClass(参数列表);
};
// 构造函数定义(类外实现)
MyClass::MyClass(参数列表) {
// 构造函数体(初始化代码)
}
2. 默认构造函数
如果类没有定义任何构造函数,编译器会自动生成一个默认构造函数(无参数)。如果自定义了构造函数,但仍需默认构造函数,可以显式定义:
class MyClass {
public:
MyClass() = default; // 显式声明默认构造函数
};
二、成员初始化列表(冒号:的作用)
1. 什么是成员初始化列表?
构造函数后的冒号 : 用于启动成员初始化列表,允许在构造函数体执行前直接初始化成员变量(尤其是那些必须在创建时初始化的成员,如const成员、引用成员、类类型成员等)。
语法:
ClassName::ClassName(参数列表)
: member1(value1), member2(value2), ... { // 初始化列表
// 构造函数体
}
2. 为什么必须用初始化列表?
某些成员变量必须在初始化列表中初始化,而不能在构造函数体内赋值:
- 常量成员(
const成员):const变量必须在创建时初始化。 - 引用成员:引用必须在初始化时绑定到某个对象。
- 类类型成员(无默认构造函数):如果成员是另一个类的对象,且该类没有默认构造函数,必须通过初始化列表显式调用其构造函数。
示例:
class Example {
private:
const int maxValue; // const成员
int& ref; // 引用成员
std::string name; // 类类型成员(有默认构造函数)
public:
// 必须通过初始化列表初始化const和引用成员
Example(int value, int& r)
: maxValue(value), ref(r), name("Unknown") {
// 构造函数体(name可以在体内赋值,但效率更低)
}
};
3. 初始化列表的优势
- 效率更高:对于类类型成员(如
std::string、其他自定义类),初始化列表直接调用成员的构造函数,而构造函数体内赋值会先调用默认构造函数,再调用赋值运算符。 - 避免重复初始化:初始化列表直接初始化,而构造函数体内是先默认初始化再赋值。
- 解决依赖问题:成员的初始化顺序由它们在类中的声明顺序决定,与初始化列表中的顺序无关(但建议保持顺序一致)。
示例对比:
class Point {
private:
int x;
int y;
public:
// 低效方式(先默认初始化x,y,再赋值)
Point(int a, int b) {
x = a;
y = b;
}
// 高效方式(直接初始化)
Point(int a, int b) : x(a), y(b) {}
};
三、构造函数的其他特性
1. 委托构造函数(C++11)
一个构造函数可以调用同一个类的另一个构造函数(减少重复代码):
class Rectangle {
private:
int width, height;
public:
// 委托给另一个构造函数
Rectangle() : Rectangle(0, 0) {} // 调用下面的构造函数
Rectangle(int w, int h) : width(w), height(h) {}
};
2. 拷贝构造函数
一种特殊的构造函数,用于通过同类型对象初始化新对象:
class MyClass {
public:
MyClass(const MyClass& other)
: member1(other.member1), member2(other.member2) {}
};
3. 移动构造函数(C++11)
用于“窃取”临时对象的资源(提高性能):
class MyClass {
public:
MyClass(MyClass&& other) noexcept
: member1(std::move(other.member1)) {}
};
四、构造函数的常见问题
1. 为什么类类型成员需要在初始化列表中初始化?
如果类类型成员没有默认构造函数(即必须通过参数构造),则必须使用初始化列表。例如:
class A {
public:
A(int value) {} // 没有默认构造函数
};
class B {
private:
A a; // 必须通过初始化列表构造
public:
B(int x) : a(x) {} // 正确:调用A(int)
// B(int x) { a = A(x); } // 错误:A没有默认构造函数
};
2. 初始化列表的顺序问题
成员的初始化顺序由它们在类中的声明顺序决定,而不是初始化列表中的顺序。如果顺序不一致,可能导致未定义行为:
class C {
private:
int a;
int b;
public:
C(int x) : b(x), a(b) {} // 危险!a先于b初始化,此时b未初始化
};
五、总结
- 构造函数用于初始化对象,名称与类名相同。
- 成员初始化列表(
:)是高效初始化的关键,尤其对const成员、引用成员和类类型成员。 - 初始化列表的执行顺序由成员声明顺序决定,而非初始化列表中的顺序。
- 需要初始化列表的场景:
const、引用、无默认构造函数的类成员。