【C++基础】构造函数和他的初始化列表

构造函数部分

C++的构造函数是类的一种特殊成员函数,当对象被创建时自动调用,用于初始化对象的成员变量。构造函数的名称必须与类名相同,并且没有返回类型(包括void)。它是实现对象初始化的关键部分。以下是构造函数的一些用法和概念:

1. 默认构造函数(Default Constructor)

如果你没有定义任何构造函数,C++编译器会自动为你生成一个默认构造函数。默认构造函数不带参数,用于默认初始化对象的成员变量。

class MyClass {
public:
    MyClass() {  // 默认构造函数
        // 初始化代码
    }
};

2. 参数化构造函数(Parameterized Constructor)

参数化构造函数允许你在创建对象时传递参数,并使用这些参数初始化成员变量。

class MyClass {
public:
    int x;
    MyClass(int val) {  // 参数化构造函数
        x = val;
    }
};

使用参数化构造函数创建对象:

MyClass obj(10);  // x 被初始化为 10

3. 拷贝构造函数(Copy Constructor)

拷贝构造函数用于创建一个新对象,并将已有对象的值拷贝到新对象中。它的参数通常是传递一个同类的对象引用。

class MyClass {
public:
    int x;
    MyClass(int val) : x(val) {}  // 参数化构造函数

    MyClass(const MyClass &obj) {  // 拷贝构造函数
        x = obj.x;
    }
};

使用拷贝构造函数创建对象:

MyClass obj1(10);
MyClass obj2 = obj1;  // obj2 使用 obj1 的值初始化

4. 委托构造函数(Delegating Constructor)

C++11 引入了委托构造函数,允许一个构造函数调用同一个类中的另一个构造函数来实现初始化。这可以减少代码重复。

class MyClass {
public:
    int x, y;
    
    MyClass(int val) : MyClass(val, 0) {}  // 委托给另一个构造函数
    
    MyClass(int val1, int val2) : x(val1), y(val2) {}
};

5. 初始化列表(Initializer List)

初始化列表用于在构造函数的函数体执行之前初始化成员变量,尤其是在成员变量是常量或引用时。

class MyClass {
public:
    const int x;
    
    MyClass(int val) : x(val) {  // 使用初始化列表初始化 x
        // 构造函数体
    }
};

6. 显式构造函数(Explicit Constructor)

如果构造函数前面加上explicit关键字,C++编译器将不会允许通过隐式类型转换来调用该构造函数。这可以避免意外的类型转换。

class MyClass {
public:
    explicit MyClass(int val) {
        // 构造函数体
    }
};

MyClass obj = 10;  // 错误,不能进行隐式转换
MyClass obj(10);   // 正确,显式调用构造函数

7. 析构函数与构造函数配对

与构造函数相对应的是析构函数,它在对象的生命周期结束时调用,用于释放资源。构造函数初始化对象,析构函数负责清理对象。

class MyClass {
public:
    MyClass() {
        // 构造函数体
    }
    ~MyClass() {
        // 析构函数体
    }
};

总结

C++构造函数在对象的生命周期管理中扮演了重要角色,通过不同类型的构造函数(如默认、参数化、拷贝构造函数等)可以灵活地控制对象的初始化行为,确保对象在使用前处于有效状态。

构造函数初始化列表部分

构造函数初始化列表,是一种用于在构造函数中直接初始化成员变量的语法:

MyOpenGLWidget::MyOpenGLWidget(QWidget* parent): QOpenGLWidget(parent)
{
    // 构造函数的初始化代码
}

咋一看,还以为是类继承呢(可以包含 父类的实例化,所以开起来像继承一样)。 其实是构造函数后面带个冒号,冒号后面的就是列表。

这里看上去其实挺容易迷惑的,由于C++申明一般写在头文件里面,所以头文件里面一般是一个class。 而实现过程写到cpp文件里面。比如上面这段代码。
MyOpenGLWidget::MyOpenGLWidget 前面这个MyOpenGLWidget,表面后面函数是归属哪个类的。是的,C++的函数可以 放到 class外面(C# 表示震惊!),只要用类名限定一下即可。
而后面这个::MyOpenGLWidget 是构造函数,由于是构造函数,所以和类名相同。紧接着后面又有冒号,跟着一个父类。
感觉很像是一个类继承了另一个类!(特别是C#写习惯的同学,会这么认为。。。)但其实这是个构造函数!注意这里并没有class关键字!

初始化列表(Initializer List)

在C++中,初始化列表是一种用于在构造函数中直接初始化成员变量的语法。它允许在构造函数的函数体执行之前初始化成员变量,尤其适用于以下情况:

  1. 常量成员变量:常量必须在初始化时赋值,不能在构造函数体内赋值。
  2. 引用成员变量:引用必须在初始化时绑定到对象上。
  3. 没有默认构造函数的类类型成员变量:如果一个成员变量的类型没有默认构造函数,必须通过参数进行初始化。

初始化列表的语法如下:

class MyClass {
public:
    int a;
    const int b;
    int &c;
    
    MyClass(int x, int y, int &z) : a(x), b(y), c(z) {
        // 构造函数体
    }
};

在这个例子中,abc都是通过初始化列表进行初始化的。这意味着在进入构造函数体之前,它们已经被赋予了初值。

为什么使用初始化列表?
  • 效率:通过初始化列表,成员变量在对象创建时就被初始化,不会先被默认初始化然后再赋值,从而避免了不必要的开销。
  • 必须:某些成员(如const引用)必须在对象创建时初始化,否则会导致编译错误。

构造时继承父类

在继承时,派生类的构造函数可以调用基类的构造函数以初始化基类的部分。C++中,基类的构造函数是在派生类的构造函数之前执行的,因此,初始化列表中调用基类构造函数是一种常见的做法。

语法

在派生类的构造函数的初始化列表中调用基类的构造函数:

class Base {
public:
    int base_val;
    Base(int val) : base_val(val) {
        // 基类构造函数体
    }
};

class Derived : public Base {
public:
    int derived_val;
    
    Derived(int baseVal, int derivedVal) : Base(baseVal), derived_val(derivedVal) {
        // 派生类构造函数体
    }
};

在这个例子中,Derived类继承自Base类,并且在Derived类的构造函数中,通过初始化列表调用了Base类的构造函数来初始化基类成员base_val

基类的构造顺序
  • 基类构造函数先执行:无论在初始化列表中是否调用基类构造函数,基类的构造函数总是在派生类的构造函数之前执行。这是因为派生类的成员变量可能依赖于基类的成员变量。

  • 成员初始化顺序:即使在初始化列表中成员变量的顺序不同,成员变量的初始化顺序依然按照它们在类定义中出现的顺序执行。

示例:父类与子类的构造

以下是一个示例,展示了父类与子类的构造顺序:

class Base {
public:
    int x;
    
    Base(int a) : x(a) {
        std::cout << "Base Constructor" << std::endl;
    }
};

class Derived : public Base {
public:
    int y;
    
    Derived(int a, int b) : Base(a), y(b) {
        std::cout << "Derived Constructor" << std::endl;
    }
};

int main() {
    Derived obj(5, 10);
    return 0;
}

输出

Base Constructor
Derived Constructor

在这个示例中,创建Derived类对象时,首先调用了Base类的构造函数,然后才调用Derived类的构造函数。通过这种方式,派生类可以确保其基类部分在使用之前已经正确初始化。

总结

初始化列表是一种高效且必需的初始化方式,特别是当你需要初始化const成员、引用成员或者必须提供参数初始化的成员时。在继承结构中,基类的构造在派生类之前执行,派生类可以通过初始化列表来控制基类如何初始化。这种机制确保了对象创建过程中的正确性和效率。

关于顺序

构造函数初始化列表,里面可以构建父类,可以对类成员赋值,或构建类成员。
能构建父类,原因是,父类也可以看作是子类的成员之一,和其他成员并没有什么不同

所以这么写,也是可以的:

MyOpenGLWidget::MyOpenGLWidget(QWidget* parent): a(1),QOpenGLWidget(parent)
{
    // 构造函数的初始化代码
}

在C++中,当构造派生类对象时,基类的构造函数必须在派生类的构造函数中调用,而这个调用是在初始化列表中指定的。然而,基类的构造函数在初始化列表中的位置并不影响它的执行顺序。

构造顺序规则

  1. 基类先于派生类初始化:无论基类构造函数在初始化列表中的位置如何,基类对象总是先于派生类对象进行初始化。这是C++标准规定的。

  2. 成员初始化顺序与声明顺序一致:派生类中的成员变量初始化顺序与它们在类中声明的顺序一致,而不是它们在初始化列表中的顺序。这意味着即使在初始化列表中按照不同顺序列出成员变量,编译器仍然会按照它们的声明顺序进行初始化。

我们来举一个例子,演示基类与派生类的构造顺序以及类成员变量的初始化顺序。这个例子中不涉及引用变量,只涉及普通的类类型成员变量。

示例:基类和派生类的构造顺序

#include <iostream>

class Base {
public:
    int base_val;

    Base(int val) : base_val(val) {
        std::cout << "Base Constructor: base_val = " << base_val << std::endl;
    }
};

class Member1 {
public:
    int member_val1;

    Member1(int val) : member_val1(val) {
        std::cout << "Member1 Constructor: member_val1 = " << member_val1 << std::endl;
    }
};

class Member2 {
public:
    int member_val2;

    Member2(int val) : member_val2(val) {
        std::cout << "Member2 Constructor: member_val2 = " << member_val2 << std::endl;
    }
};

class Derived : public Base {
public:
    Member1 m1;
    Member2 m2;

    // 初始化列表中的顺序与成员初始化的顺序无关
    Derived(int baseVal, int val1, int val2) : m2(val2), m1(val1), Base(baseVal) {
        std::cout << "Derived Constructor" << std::endl;
    }
};

int main() {
    Derived obj(10, 20, 30);
    return 0;
}

输出

Base Constructor: base_val = 10
Member1 Constructor: member_val1 = 20
Member2 Constructor: member_val2 = 30
Derived Constructor

解释

  • 基类构造顺序:虽然在Derived类的初始化列表中,Base构造函数被放在最后调用,但它仍然是最先执行的。基类Base的构造函数在派生类的成员变量之前被调用。这是C++标准规定的顺序。

  • 成员变量初始化顺序:派生类的成员变量m1m2的初始化顺序不是由初始化列表中的顺序决定的,而是由它们在类定义中的顺序决定的。在Derived类中,m1先被声明,因此它先于m2初始化,即使在初始化列表中m2被列在m1之前。

  • 派生类构造函数的执行:在基类构造函数和所有成员变量构造函数都完成之后,派生类的构造函数体才会执行。

结论

  • 基类构造函数始终优先执行:无论在初始化列表中基类构造函数的位置如何,它总是在派生类成员变量初始化之前执行。

  • 成员变量按照声明顺序初始化:派生类的成员变量初始化顺序与它们在类中的声明顺序一致,与初始化列表中的顺序无关。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

code bean

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值