构造函数

175 阅读4分钟

一、构造函数的基本语法

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、引用、无默认构造函数的类成员。