当前位置: 首页 > news >正文

【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之前。

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

结论

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

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


http://www.mrgr.cn/news/19583.html

相关文章:

  • PAT甲级(Advanced Level) 1001 A+B Format
  • jmeter使用——接口测试事知识点
  • 海康二次开发学习笔记13-从Group外部输入图像
  • opencv全面详解教程
  • 浅谈人工智能之基于AutoGen Studio+litellm+ollama构建model
  • 2024年【化工自动化控制仪表】考试资料及化工自动化控制仪表找解析
  • Vue2项目搭建:Vue2.7+Vite4+Pinia+TailwindCSS+Prettier+ESLint
  • 云计算之ECS
  • 一招制胜!掌握 Python 中pip的8个必备命令
  • MSP430F149实现0.96寸OLED显示
  • 编码(曼彻斯特编码,4B/5B 编码,8b/10b编码)
  • 解决Pynput不能在Ubuntu22.04上正常使用问题
  • 【LabVIEW学习篇 - 17】:人机交互界面设计01
  • 轻松上手 | 基于RockyLinux 9.4安装部署Zabbix 7.0
  • 2024年最强图纸加密软件大揭秘!图纸加密软件推荐
  • lvs DR模式调试
  • 【Fastapi】使用APIRouter做路由管理
  • 189.轮转数组
  • HarmonyNext动画大全03-帧动画
  • 数据分析 设备一个月以来的参数变化