【C++篇】C++类与对象深度解析(四):初始化列表、类型转换与static成员详解

文章目录

  • C++类与对象超详细入门指南
  • 前言
    • 1. 初始化列表——再谈构造函数
      • 1.1 初始化成员变量的方式
        • 1.1.1 构造函数内部赋值 vs 初始化列表
        • 1.1.2 两者的区别
        • 1.1.3 为什么要使用初始化列表
        • 1.1.4 示例
      • 1.2 初始化列表的语法
          • 1.2.1 示例:
      • 1.3 引用成员变量、const成员变量的初始化
        • 1.3.1 引用类型成员的初始化
        • 1.3.2 const成员变量的初始化
      • 1.4 没有默认构造函数的类类型变量
        • 1.4.1 示例
      • 1.5 成员变量默认值的使用 (C++11)
      • 1.6 初始化顺序
        • 1.6.1 示例
      • 1.7 初始化列表总结
    • 2. 类型转换详解
      • 2.1 内置类型转换为类类型
        • 2.1.1 隐式类型转换
        • 2.1.2 `explicit` 防止隐式转换
      • 2.2 类类型之间的转换
        • 2.2.1 类类型之间的隐式转换
        • 2.2.2 阻止类类型的隐式转换
      • 2.3 类型转换的实践
        • 2.3.1 示例代码
        • 2.3.2 解析
      • 2.4 C++11中的多参数类型转换
      • 2.5 编译器的优化:直接构造
      • 2.6 类型转换总结
    • 3. `static` 成员详解——静态成员变量与静态成员函数
      • 3.1. `static` 成员变量
        • 3.1.1 静态成员变量的特性
      • 3.2. `static` 成员函数
        • 3.2.1 静态成员函数的特性
      • 3.3 静态成员的访问
        • 3.3.1 通过类名访问
        • 3.3.2 通过对象访问
      • 3.4 静态成员变量的初始化
        • 3.4.1 为什么静态成员变量不能在类内初始化?
      • 3.5 访问控制与静态成员
        • 3.5.1 `public` 静态成员
        • 3.5.2 `private` 静态成员
      • 3.6 `static` 成员的实际应用
        • 3.6.1 实例:求 1 + 2 + 3 + ... + n
          • (1)题目代码
          • (2)解析
          • (3)示例
        • 3.6.2 构造函数和析构函数的调用顺序
          • (1)题目分析
      • 3.7`static`成员函数与变量总结
  • 写在最后

C++类与对象超详细入门指南

前言

💬 欢迎讨论:如果你在学习过程中有任何问题或想法,欢迎在评论区留言,我们一起交流学习。你的支持是我继续创作的动力!

👍 点赞、收藏与分享:觉得这篇文章对你有帮助吗?别忘了点赞、收藏并分享给更多的小伙伴哦!你们的支持是我不断进步的动力!
🚀 分享给更多人:如果你觉得这篇文章对你有帮助,欢迎分享给更多对C++感兴趣的朋友,让我们一起进步!

1. 初始化列表——再谈构造函数


1.1 初始化成员变量的方式

初始化列表 是构造函数中用于初始化类成员变量的一种特殊机制。与在构造函数体中直接赋值不同,初始化列表可以提高效率,尤其是对于某些特定类型的成员变量,它是唯一可行的初始化方式。

1.1.1 构造函数内部赋值 vs 初始化列表

在C++中,我们有两种主要方式来初始化类的成员变量:

  • 构造函数内部赋值:在构造函数体内给成员变量赋值。

    例如:

    class A {
    public:A(int x) {this->_x = x;  // 在构造函数体内赋值}
    private:int _x;
    };
    
  • 初始化列表赋值:在构造函数的初始化列表中直接对成员变量进行初始化。

    例如:

    class A {
    public:A(int x) : _x(x) {  // 使用初始化列表赋值}
    private:int _x;
    };
    
1.1.2 两者的区别
  • 内置类型(如 int:对于内置类型,使用初始化列表和在构造函数体内赋值在效率上几乎没有差别。内置类型没有构造函数,也不会进行隐式初始化(即它们可能持有垃圾值)。构造函数体内赋值或初始化列表赋值都只进行一次操作。因此,选择哪种方式主要是基于代码的清晰性和一致性。

  • 类类型:对于类类型的成员变量,如果没有使用初始化列表,成员变量会先调用默认构造函数进行初始化,然后在构造函数体内再赋值。这样就相当于进行了两次操作:一次初始化,一次赋值。而使用初始化列表时,成员变量只会被初始化一次,效率更高。

    • 例如,考虑如下代码:

      class Member {
      public:Member(int value = 5) : _value(value) {}
      private:int _value;
      };class A {
      public:A(int x) {_member = Member(x);  // 先默认构造后再赋值}
      private:Member _member;
      };
      

      上面代码中,_member 会首先调用 Member 类的默认构造函数,然后再在构造函数体内通过赋值进行重新初始化。

      而如果使用初始化列表:

      class A {
      public:A(int x) : _member(x) {  // 直接通过初始化列表初始化}
      private:Member _member;
      };
      

      _member 只会被初始化一次,避免了不必要的性能开销。

  • 特殊情况:某些成员变量,例如常量 (const)、引用类型 (reference) 或没有默认构造函数的对象,必须通过初始化列表进行初始化,否则编译器会报错。

1.1.3 为什么要使用初始化列表
  • 效率:如前所述,初始化列表避免了成员变量的二次初始化,特别是在类类型成员中,性能优势更为明显。
  • 必要性某些类型的成员变量,如 const、引用类型,或没有默认构造函数的类成员,必须通过初始化列表进行初始化,否则编译器无法自动处理这些成员的初始化。
1.1.4 示例
class Time {
public:Time(int hour) : _hour(hour) {cout << "Time() called" << endl;}
private:int _hour;
};

在这个例子中,Time 类的构造函数使用了初始化列表,将传入的参数 hour 直接赋值给成员变量 _hour。这样,_hour 在对象构造时就被初始化,而不需要在构造函数体内赋值。


1.2 初始化列表的语法

语法结构:初始化列表的使用方式是在构造函数名后跟一个冒号,接着是一个以逗号分隔的成员变量列表,每个成员变量后面紧跟括号中的初始值或表达式。

基本语法格式

ClassName(参数列表) : 成员变量1(初始值), 成员变量2(初始值), ... {// 构造函数体
}
1.2.1 示例:
class MyClass {
public:MyClass(int a, int b) : _a(a), _b(b) {// 构造函数体}
private:int _a;int _b;
};

在这里,_a 被初始化为 a_b 被初始化为 b


1.3 引用成员变量、const成员变量的初始化

有些成员变量,比如引用类型常量类型,只能通过初始化列表进行初始化。

1.3.1 引用类型成员的初始化

引用类型成员变量在 C++ 中必须在声明时被初始化,不能在构造函数体内赋值,必须使用初始化列表。

class MyClass {
public:MyClass(int& ref) : _ref(ref) {// _ref 是引用类型,必须在初始化列表中初始化}
private:int& _ref;
};
1.3.2 const成员变量的初始化

常量成员变量 (const) 也必须在对象创建时初始化,之后不能修改。因此也必须在初始化列表中进行初始化。

class MyClass {
public:MyClass(int n) : _n(n) {// _n 是 const 类型,必须在初始化列表中初始化}
private:const int _n;
};

1.4 没有默认构造函数的类类型变量

如果一个类的成员变量是另一个没有默认构造函数的类类型变量,它也必须在初始化列表中进行初始化。

1.4.1 示例
class Time {
public:Time(int hour) : _hour(hour) {}
private:int _hour;
};class Date {
public:Date(int year, int month, int day) : _year(year), _month(month), _day(day), _t(12) {// _t 是 Time 类型,必须在初始化列表中调用 Time 的构造函数}
private:int _year;int _month;int _day;Time _t;  // Time 没有默认构造函数
};

1.5 成员变量默认值的使用 (C++11)

C++11 引入了成员变量默认值的概念。可以在类的声明中为成员变量提供默认值,这些默认值将在没有通过初始化列表显式初始化时使用。

class MyClass {
public:MyClass() : _b(2) {  // _a 使用默认值1// 构造函数体}
private:int _a = 1;  // 默认值int _b;
};

1.6 初始化顺序

尽管初始化列表中的成员可以按任何顺序出现,但成员变量的初始化顺序是按照它们在类中声明的顺序进行的,而不是它们在初始化列表中的顺序。

1.6.1 示例
class MyClass {
public:MyClass(int a, int b) : _b(b), _a(a) {// 尽管 _b 在初始化列表中先出现,但 _a 会首先被初始化}
private:int _a;int _b;
};

为了保持代码的一致性和可读性,建议初始化列表的顺序和成员变量声明的顺序一致。


1.7 初始化列表总结

  1. 每个构造函数都有初始化列表,即使你没有显式地写出它。
  2. 每个成员变量都必须被初始化,即使它没有在初始化列表中显式地被初始化。
  3. 对于引用类型常量没有默认构造函数的类类型成员,必须在初始化列表中进行初始化。
  4. C++11 允许在成员变量声明时提供默认值,这些默认值会在初始化列表中未显式初始化时使用。
  5. 初始化顺序取决于成员变量在类中的声明顺序,而不是它们在初始化列表中的顺序。

在这里插入图片描述


2. 类型转换详解

在C++中,类型转换(Type Conversion)是指将一种数据类型转换为另一种数据类型的过程。对于类而言,C++允许将内置类型类类型转换为其他类类型,这一功能在面向对象编程中非常有用。类型转换可以是显式的(explicit)或隐式的(implicit),并且它们涉及构造函数、转换运算符和explicit关键字。


2.1 内置类型转换为类类型

C++支持将内置类型(如intdouble等)隐式地转换为自定义的类类型。这是通过定义带有内置类型参数的构造函数来实现的。

2.1.1 隐式类型转换

在没有explicit关键字修饰构造函数的情况下,编译器会自动将符合构造函数参数类型的内置类型值隐式转换为类对象。

示例

class A {
public:A(int a1) : _a1(a1) {}void Print() {cout << _a1 << endl;}private:int _a1;
};int main() {A obj = 10;  // 隐式将 int 10 转换为 A 类型对象obj.Print();  // 输出: 10
}

在上面的代码中,整数 10 被隐式地转换为类 A 的对象,编译器自动调用了A的构造函数。可以直接通过A obj = 10;来创建对象,这是隐式类型转换的常见形式。

2.1.2 explicit 防止隐式转换

有时候,隐式类型转换会引发意想不到的错误或逻辑问题。为了防止这些错误,C++允许我们使用explicit关键字修饰构造函数,这样可以禁止该构造函数参与隐式转换。

示例

class A {
public:explicit A(int a1) : _a1(a1) {}void Print() {cout << _a1 << endl;}private:int _a1;
};int main() {// A obj = 10;  // 错误:explicit 阻止了隐式转换A obj(10);      // 正确:必须显式调用构造函数obj.Print();    // 输出: 10
}

在这个例子中,explicit关键字阻止了A obj = 10;的隐式类型转换,必须使用A obj(10);进行显式调用构造函数来创建对象。这种方式避免了潜在的类型转换混淆问题。


2.2 类类型之间的转换

C++也允许将一个类类型的对象隐式转换为另一个类类型。这通常通过类的构造函数来实现。例如,一个类可以通过接受另一个类类型对象的构造函数进行隐式转换。

2.2.1 类类型之间的隐式转换

在下面的例子中,B类通过构造函数接受一个A类对象,这样当我们将A类对象赋值给B类时,C++会自动进行隐式转换。

示例

class A {
public:A(int a1) : _a1(a1) {}int Get() const {return _a1;}private:int _a1;
};class B {
public:B(const A& a) : _b(a.Get()) {}void Print() {cout << _b << endl;}private:int _b;
};int main() {A objA(10);B objB = objA;  // A 类型对象隐式转换为 B 类型对象objB.Print();   // 输出: 10
}

在这里,B类的构造函数接受一个A类对象,因此当我们将objA赋值给objB时,C++会隐式调用B的构造函数将A对象转换为B对象。

2.2.2 阻止类类型的隐式转换

与内置类型的隐式转换类似,我们也可以使用explicit关键字来防止类类型之间的隐式转换。如下所示:

class B {
public:explicit B(const A& a) : _b(a.Get()) {}void Print() {cout << _b << endl;}private:int _b;
};int main() {A objA(10);// B objB = objA;  // 错误:explicit 阻止了隐式转换B objB(objA);      // 正确:显式调用构造函数objB.Print();      // 输出: 10
}

在这个例子中,explicit关键字阻止了A对象隐式转换为B对象,必须显式调用B的构造函数。


2.3 类型转换的实践

为了更好地理解类型转换,下面我们结合一个稍复杂的例子来展示如何利用类型转换优化代码中的对象构造和赋值操作。

2.3.1 示例代码
#include<iostream>
using namespace std;class A {
public:// 构造函数,支持隐式类型转换A(int a1) : _a1(a1) {}A(int a1, int a2) : _a1(a1), _a2(a2) {}void Print() {cout << _a1 << " " << _a2 << endl;}int Get() const {return _a1 + _a2;}private:int _a1 = 1;int _a2 = 2;
};class B {
public:// 接受 A 类型的对象,进行隐式类型转换B(const A& a) : _b(a.Get()) {}private:int _b = 0;
};int main() {A aa1 = 1;      // 隐式将 int 1 转换为 A 对象aa1.Print();    // 输出: 1 2const A& aa2 = 1;  // 隐式将 int 1 转换为 A 对象,并绑定到常量引用A aa3 = {2, 2};  // 使用 C++11 的列表初始化语法aa3.Print();     // 输出: 2 2B b = aa3;       // A 对象隐式转换为 B 对象const B& rb = aa3;  // 隐式转换时绑定常量引用return 0;
}
2.3.2 解析
  • A aa1 = 1;:这里 1 是一个 int 类型的值,编译器会通过调用 A(int a1) 构造函数将其隐式转换为 A 类型的对象。
    -const A& aa2 = 1;:同样,int 类型的值 1 会隐式转换为 A 类型的临时对象,然后这个临时对象必须通过常量引用绑定到 aa2。这种做法就会延长临时对象的生命周期,直到出作用域
  • A aa3 = {2, 2};:这使用了 C++11 的列表初始化,将 2, 2 传递给 A 类的构造函数。
  • B b = aa3;:这里,aa3 是一个 A 类型的对象,它通过 B 类的构造函数进行隐式转换。

通过这种类型转换机制,C++允许我们使用内置类型或其他类类型轻松构造对象,并在对象之间传递数据。


2.4 C++11中的多参数类型转换

在C++11之前,类型转换通常只能支持单参数的构造函数。但从C++11开始,C++标准引入了列表初始化(也称为统一初始化),使得我们能够更灵活地传递多个参数来进行类型转换。

例如:

A aa3 = {2, 2};  // 使用列表初始化,传递两个参数

在这个例子中,{2, 2} 是一个初始化列表,C++11允许我们通过这种方式为类的构造函数传递多个参数。这种形式的类型转换比传统的单参数转换更加灵活,可以处理更复杂的初始化场景。


2.5 编译器的优化:直接构造

在之后会详细讲解

在对象的构造过程中,如果出现了临时对象的构造和拷贝构造的连续过程,编译器通常会进行优化。特别是在C++11中,编译器可以省略临时对象的构造和拷贝,直接构造最终对象。

示例:上述的代码其实是这样的:

A aa3 = A(2, 2);  //然后编译器优化为直接构造 aa3,而不是先构造临时对象再拷贝

在这段代码中,编译器遇到A(2, 2)的构造操作后,通常会先构造一个临时对象,再通过拷贝构造函数将其赋值给aa3。然而,现代编译器通常会进行优化,直接将aa3构造出来,而不需要先构造临时对象。这种优化被称为返回值优化(RVO)或拷贝省略


2.6 类型转换总结

C++中的类型转换是一个强大且灵活的机制,通过构造函数,我们可以轻松地在内置类型和类类型之间进行隐式或显式的转换。同时,explicit关键字让我们能够严格控制类型转换,避免不必要的隐式转换带来的潜在问题。C++11的引入进一步增强了类型转换的灵活性,特别是列表初始化使得我们能够传递多个参数进行转换。理解并合理使用这些机制,可以让我们编写出更加简洁、灵活的代码。


3. static 成员详解——静态成员变量与静态成员函数

在C++中,static成员既可以用于修饰类的变量,也可以用于修饰类的函数。通过static,我们可以实现成员共享、函数无依赖等功能,特别适用于一些类级别的操作,而不依赖于具体的对象实例。静态成员具有一些特殊的属性和行为。


3.1. static 成员变量

static成员变量,称为静态成员变量,它是类的所有对象共享的变量,而不是每个对象独立拥有的。静态成员变量存储在静态存储区(也称为全局区),并且只能在类外初始化

3.1.1 静态成员变量的特性
  • 共享性:静态成员变量是所有类的对象共享的,不属于某个具体的对象。
  • 独立性:静态成员变量存储在静态存储区中,不会随着对象的创建或销毁而重新分配内存。
  • 类外初始化:静态成员变量必须在类外初始化,不能在类内的声明位置给它赋值。

示例:通过静态成员变量计算类对象的数量。

#include<iostream>
using namespace std;class A {
public:A() {++_scount;  // 每创建一个对象,计数加1}A(const A& t) {++_scount;  // 每调用拷贝构造函数,计数加1}~A() {--_scount;  // 每销毁一个对象,计数减1}static int GetACount() {return _scount;  // 返回当前对象的数量}private:// 声明静态成员变量static int _scount;
};// 类外初始化静态成员变量
int A::_scount = 0;int main() {cout << "初始对象数量: " << A::GetACount() << endl;  // 初始对象数量为 0A a1, a2;  // 创建两个对象,计数加2A a3(a1);  // 拷贝构造,计数加1cout << "当前对象数量: " << A::GetACount() << endl;  // 输出 3return 0;
}

输出

初始对象数量: 0
当前对象数量: 3

解释

  • _scount 是静态成员变量,它被所有 A 类对象共享。无论创建多少个对象,所有对象共享这一个计数器。
  • 静态成员变量在类外进行初始化:int A::_scount = 0;,这是强制要求的,不能在类内部直接赋值。
  • 通过类名 A::GetACount() 或对象 a1.GetACount() 来访问静态成员函数 GetACount(),输出当前对象的数量。

3.2. static 成员函数

静态成员函数是类的成员函数,但是它与普通的成员函数不同,它不依赖于具体的对象实例,可以通过类名直接调用。静态成员函数没有this指针,因此它只能访问类的静态成员变量静态成员函数,不能访问非静态成员。

3.2.1 静态成员函数的特性
  • 没有this指针:静态成员函数没有 this 指针,因此不能访问非静态成员。
  • 只能访问静态成员静态成员函数只能访问静态成员变量或静态成员函数,不能访问类的非静态成员。
  • 通过类名调用:静态成员函数可以通过类名直接调用,而不需要依赖于对象实例。

示例:静态成员函数的使用。

#include<iostream>
using namespace std;class A {
public:A() {++_scount;}A(const A& t) {++_scount;}~A() {--_scount;}static int GetACount() {return _scount;  // 静态成员函数访问静态成员变量}private:static int _scount;
};int A::_scount = 0;int main() {A a1, a2;cout << "当前对象数量: " << A::GetACount() << endl;  // 通过类名访问静态成员函数return 0;
}

输出

当前对象数量: 2

解释

  • 静态成员函数 GetACount() 可以通过类名 A::GetACount() 调用,而不依赖于具体的对象。
  • 静态成员函数没有 this 指针,因此它不能访问非静态成员变量或函数。
  • 通过类名直接访问静态成员函数是它的主要特性之一。

3.3 静态成员的访问

静态成员既可以通过类名来访问,也可以通过对象来访问。

3.3.1 通过类名访问

静态成员不属于某个具体的对象,而是属于整个类,因此它们可以通过类名来访问。例如,A::GetACount() 是通过类 A 的名字直接访问静态成员函数 GetACount()

3.3.2 通过对象访问

虽然静态成员不属于对象,但仍然可以通过对象来访问静态成员。例如,a1.GetACount() 也可以调用静态成员函数,尽管底层实现实际上仍然是通过类来访问的。

示例

int main() {A a1, a2;cout << A::GetACount() << endl;  // 通过类名访问cout << a1.GetACount() << endl;  // 通过对象访问return 0;
}

无论是通过类名 A::GetACount(),还是通过对象 a1.GetACount(),最终结果都是一样的。


3.4 静态成员变量的初始化

静态成员变量不能在类内初始化,必须在类外进行初始化。这是因为静态成员变量存储在静态存储区中,它们不属于某个对象实例,因此不能在类的构造函数或初始化列表中进行初始化。

3.4.1 为什么静态成员变量不能在类内初始化?
  • 静态成员变量存储在静态存储区,而不是对象中。构造函数的初始化列表是为每个对象实例服务的,因此静态成员变量不能通过初始化列表进行初始化
  • 由于静态成员变量的共享性,它们只在整个程序中存在一份,因此必须在类外进行初始化,以确保所有对象访问的都是同一份数据。

示例

class A {
public:static int _count;
};// 类外初始化
int A::_count = 0;

在上面的例子中,A::_count 在类外初始化为 0,所有对象共享这个静态变量。


3.5 访问控制与静态成员

静态成员与普通成员一样,也受访问控制修饰符publicprotectedprivate)的限制。即使静态成员属于类,而不是对象,但它们仍然需要遵守访问控制规则。

3.5.1 public 静态成员

public 静态成员可以被类的任何对象或函数访问,包括类外代码。

class A {
public:static int _public_count;
};int A::_public_count = 0;int main() {A::_public_count = 10;  // 可以在类外访问cout << A::_public_count << endl;return 0;
}
3.5.2 private 静态成员

private 静态成员只能被类的成员函数访问,不能被类外代码直接访问。

class A {
private:static int _private_count;public:static int GetPrivateCount() {return _private_count;  // 只能通过成员函数访问}
};int A::_private_count = 0;int main() {// cout << A::_private_count;  // 错误,无法访问 private 静态成员cout << A::GetPrivateCount() << endl;  // 通过静态成员函数访问return 0;
}

3.6 static 成员的实际应用

静态成员通常用于实现某些类级别的操作,例如计算对象的数量、跟踪全局状态等。通过静态成员,我们可以方便地在类内部管理全局信息,而无需创建对象实例。

3.6.1 实例:求 1 + 2 + 3 + … + n

求1+2+3+…+n——牛客网

这道题要求我们使用静态成员来求 1 + 2 + 3 + ... + n 的和。通过构造函数和静态成员变量,我们可以在创建对象时动态更新累加值,进而得到最终结果。

(1)题目代码
class Sum {
public:Sum() {_ret += _i;  // 每次调用构造函数时,将当前 i 的值加到 _ret 中++_i;        // 自增 _i}static int GetRet() {return _ret;  // 返回累加结果}private:static int _i;    // 用于计数的静态变量static int _ret;  // 用于存储结果的静态变量
};// 初始化静态成员变量
int Sum::_i = 1;
int Sum::_ret = 0;class Solution {
public:int Sum_Solution(int n) {Sum arr[n];  // 创建 n 个 Sum 对象,触发构造函数进行累加return Sum::GetRet();  // 返回累加的结果}
};
(2)解析
  • 每次创建 Sum 对象时,都会调用其构造函数,而构造函数会将 _i 的值累加到 _ret 中。
  • 静态变量 _i 用于记录当前的计数,随着每次对象创建自增。
  • 静态变量 _ret 保存累加的结果。
  • 通过创建 nSum 对象,我们能够计算出 1 + 2 + 3 + ... + n 的结果。
(3)示例
int main() {Solution solution;cout << solution.Sum_Solution(5) << endl;  // 输出: 15 (1+2+3+4+5)return 0;
}

输出结果为:

15

3.6.2 构造函数和析构函数的调用顺序

在C++中,构造函数和析构函数的调用顺序遵循一定的规则,尤其是在全局变量和静态对象的情况下,了解它们的调用顺序非常重要。

(1)题目分析

设已经有 ABCD 四个类的定义,程序中涉及构造函数和析构函数的调用顺序,解析如下。

class A {
public:A() { cout << "A Constructor" << endl; }~A() { cout << "A Destructor" << endl; }
};class B {
public:B() { cout << "B Constructor" << endl; }~B() { cout << "B Destructor" << endl; }
};class C {
public:C() { cout << "C Constructor" << endl; }~C() { cout << "C Destructor" << endl; }
};class D {
public:D() { cout << "D Constructor" << endl; }~D() { cout << "D Destructor" << endl; }
};C c;  // 全局变量int main() {A a;B b;static D d;  // 静态局部变量return 0;
}

构造函数的调用顺序

构造函数的调用顺序是先全局变量,再局部变量,最后静态局部变量。在上面的代码中:

  • C 是全局变量,因此它的构造函数 C()main 函数执行之前被调用。
  • AB 是局部变量,它们的构造函数按照声明的顺序,在 main 函数中依次调用。
  • D 是静态局部变量,它的构造函数在 main 函数的执行中调用,但只会在程序的第一次运行时调用一次。

构造函数的调用顺序为:

C Constructor  // 全局变量,最先调用
A Constructor  // 局部变量,按声明顺序
B Constructor  // 局部变量,按声明顺序
D Constructor  // 静态局部变量,最后调用

析构函数的调用顺序

  • 局部变量的析构函数首先调用,按与声明相反的顺序。
  • 静态局部变量的析构函数在 main 函数执行结束后调用。
  • 全局变量的析构函数在程序退出时调用。

析构函数的调用顺序为:

B Destructor  // 局部变量,先析构,顺序与构造相反
A Destructor  // 局部变量,顺序与构造相反
D Destructor  // 静态局部变量,函数结束后调用
C Destructor  // 全局变量,最后析构

总结:

  1. 构造函数调用顺序

    • 先全局变量,再局部变量,最后静态局部变量。
  2. 析构函数调用顺序

    • 先局部变量,顺序与构造相反;
    • 然后静态局部变量;
    • 最后全局变量。

3.7static成员函数与变量总结

在C++中,static成员为类提供了管理全局数据和类级别操作的强大机制。静态成员变量被所有对象共享,存储在静态存储区中,而静态成员函数则可以在没有对象的情况下通过类名直接调用。静态成员与普通成员一样,受访问控制修饰符的限制,可以是public、private或protected。同时,静态成员变量不能在类内初始化,必须在类外进行初始化。通过静态成员,我们可以方便地实现对象计数、全局状态管理等功能,这让类在不依赖对象实例的情况下,依然能够提供有用的功能。


写在最后

在本篇文章中,我们深入探讨了C++中一些关键的高级特性,帮助你在编程实践中更好地理解和运用这些工具。我们首先讲解了构造函数中的初始化列表,它不仅提高了代码的效率,还为常量、引用类型及没有默认构造函数的类类型变量提供了必要的初始化方式。接下来,我们探索了C++中内置类型和类类型之间的类型转换,包括隐式和显式转换,并通过explicit关键字展示了如何避免因不当类型转换引发的潜在问题。最后,我们详细分析了静态成员变量和静态成员函数的特性及应用。通过这些知识的学习,希望你不仅能优化代码的性能,还能在设计复杂系统时更好地管理类级别的数据和操作。

这些高级特性虽然可能在初学者眼中显得复杂,但一旦理解透彻,将会极大地提升你的C++编程能力,使你能够编写出更具扩展性、可维护性的代码。在实际开发中,善用这些特性将帮助你应对各种编程挑战,让你的C++代码更具表现力和效率。


以上就是关于【C++篇】C++类与对象深度解析(四):初始化列表、类型转换与static成员详解的内容啦,各位大佬有什么问题欢迎在评论区指正,或者私信我也是可以的啦,您的支持是我创作的最大动力!❤️

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/146824.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

【图灵完备 Turing Complete】游戏经验攻略分享 Part.4 处理器架构

比较有难度的一个部分。 运算单元ALU&#xff0c;其实就是通过OP选择计算方式&#xff0c;然后选通某个计算&#xff0c;之后输出。每个计算逐个实现就行了。 下面是一个优化占地面积的ALU&#xff0c;变得紧凑了一点。 下面是一个简单的OP选通原理线路。判断是立即数寻址&…

【C++】关键字auto详解

&#x1f984;个人主页:小米里的大麦-CSDN博客 &#x1f38f;所属专栏:C_小米里的大麦的博客-CSDN博客 &#x1f381;代码托管:C: 探索C编程精髓&#xff0c;打造高效代码仓库 (gitee.com) ⚙️操作环境:Visual Studio 2022 目录 一、前言 二、类型别名思考 三、auto简介 四…

学习笔记——RegNet:Designing Network Design Spaces

RegNet&#xff1a;Designing Network Design Spaces RegNet&#xff1a;设计一个网络设计空间 论文地址&#xff1a; https://arxiv.org/pdf/2003.13678 1、前言 在这项工作中&#xff0c;作者提出了一种新的网络设计范例。 作者的目标是帮助增进对网络设计的理解并发现跨设置…

2024年华为杯数学建模研赛(C题) 建模解析| 磁芯损耗建模 | 小鹿学长带队指引全代码文章与思路

我是鹿鹿学长&#xff0c;就读于上海交通大学&#xff0c;截至目前已经帮2000人完成了建模与思路的构建的处理了&#xff5e; 本篇文章是鹿鹿学长经过深度思考&#xff0c;独辟蹊径&#xff0c;实现综合建模。独创复杂系统视角&#xff0c;帮助你解决研赛的难关呀。 完整内容可…

C语言中易混淆概念的关键字

最快的关键字---- register register&#xff1a; 这个关键字请求编译器尽可能的将变量存在 CPU 内部寄存器中而不是通过内 存寻址访问以提高效率。注意是尽可能&#xff0c;不是绝对。你想想&#xff0c;一个 CPU 的寄存器也就那么 几个或几十个&#xff0c;你要是定义了很多很…

Kyutai开源实时语音对话模型Moshi

新闻 法国人工智能实验室Kyutai在巴黎举行的一次活动上推出了能够进行自然交互的对话式人工智能助手Moshi&#xff0c;并计划将其作为开源技术发布。Kyutai表示&#xff0c;Moshi是首款可公开访问的人工智能助手&#xff0c;可实现实时对话&#xff0c;有别于OpenAI的GPT-4o&a…

互联网广告产品基础知识

一 计价与效果 广告产品如何估算收入&#xff1f; 一种是从需求侧计算&#xff1a;按照广告主数量进行拟合&#xff1b;一种是从供给侧计算&#xff1a;按照曝光量和千次曝光单价进行拟合。 需求侧 从需求侧&#xff0c;也就是广告主侧&#xff0c;来计算广告产品的总收入&…

构建高可用和高防御力的云服务架构:从DDoS高防到PolarDB

引言 随着互联网技术的飞速发展&#xff0c;网络环境已经成为我们日常生活和商业活动中不可或缺的一部分。然而&#xff0c;这种依赖也带来了新的挑战&#xff0c;尤其是在网络安全领域。其中&#xff0c;分布式拒绝服务&#xff08;DDoS&#xff09;攻击因其破坏性强、难以防…

vite 使用飞行器仪表示例

这里写自定义目录标题 环境vue代码效果图 环境 jquery npm install -S jqueryjQuery-Flight-Indicators 将img、css、js拷贝到vite工程目录中 打开 jquery.flightindicators.js&#xff0c;在文件开头加上import jQuery from "jquery"; vue代码 <template>&…

深度学习经典模型之BERT(上)

BERT(Bidirectional Encoder Representations from Transformers)是一个双向transformer编码器的言表示模型。来自论文&#xff1a;BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding 。由Google公司的研发&#xff0c;BERT的出现使得我们能够…

MySQL篇(SQL优化)(持续更新迭代)

目录 一、插入数据&#xff1a;Insert 1. 优化方案一&#xff1a;批量插入数据 2. 优化方案二&#xff1a;手动控制事务 3. 优化方案三&#xff1a;主键顺序插入&#xff0c;性能要高于乱序插入 4. 大批量插入数据 5. 案例 5.1. 创建表结构 5.2. 设置参数 5.3. load加载…

IDAE中Quarkus框架(3.13版本)开发、调试、部署、打包等

code-with-quarkus code-with-quarkus 是使用官网生成的demo项目 这个项目使用Quarkus&#xff08;使用3.13.0版本&#xff0c;该版本支持JDK21&#xff09;&#xff0c;超音速亚原子Java框架。官网地址: https://quarkus.io/. 环境要求 OS: Windows 10.0 jdk 11 maven 3.9…

单元测试、集成测试、系统测试有什么不同?

单元测试、集成测试和系统测试是软件测试开发中不可或缺的部分。 单元测试&#xff1a; 范围&#xff1a;单元测试是对软件中最小的可测试单元的测试&#xff0c;通常是函数、方法或类。 目的&#xff1a;它的目标是验证每个单独的单元是否按照预期工作&#xff0c;以增加代码…

数据转换器——佛朗哥Chater 1

【注:本文基于《数据转换器》一书进行学习、总结编撰,适合新手小白进行学习】 目录 1.1 理想的数据转换器 1.2 采样 1.2.1 欠采样 1.2.2 采样时间的抖动(A/D转换的第一个精度限制) 1.3 幅度的量化 1.3.1 量化噪声(基本限制) 1.3.2 量化噪声的性质 1.4 KT/C噪声(…

Qt (19)【Qt 线程安全 | 互斥锁QMutex QMutexLocker | 条件变量 | 信号量】

阅读导航 引言一、互斥锁1. QMutex&#xff08;1&#xff09;基本概念&#xff08;2&#xff09;使用示例基本需求⭕thread.h⭕thread.cpp⭕widget.h⭕widget.cpp 2. QMutexLocker&#xff08;1&#xff09;基本概念&#xff08;2&#xff09;使用示例 3. QReadWriteLocker、QR…

【Linux】简易日志系统

目录 一、概念 二、可变参数 三、日志系统 一、概念 一个正在运行的程序或系统就像一个哑巴&#xff0c;一旦开始运行我们很难知晓其内部的运行状态。 但有时在程序运行过程中&#xff0c;我们想知道其内部不同时刻的运行结果如何&#xff0c;这时一个日志系统可以有效的帮…

软考无损连接判断

如何判断是否为无损连接&#xff0c;要看能否还原回最开始的关系模式 最开始的关系模式 U{A&#xff0c;B&#xff0c;C} 函数连接 F{A -> B}&#xff0c;这个函数连接的意思就是A可以推导出B 首先从P1开始判断&#xff0c;{ AB&#xff0c;BC } C不能通过函数依赖推导出来…

数据结构之线性表——LeetCode:328. 奇偶链表,86. 分隔链表,24. 两两交换链表中的节点

328. 奇偶链表 题目描述 328. 奇偶链表 给定单链表的头节点 head &#xff0c;将所有索引为奇数的节点和索引为偶数的节点分别组合在一起&#xff0c;然后返回重新排序的列表。 第一个节点的索引被认为是 奇数 &#xff0c; 第二个节点的索引为 偶数 &#xff0c;以此类推。…

头条|司法部公法局局长访谈:推进高水平公立鉴定机构建设!加快推进司法鉴定立法!

主持人&#xff1a;大家好&#xff0c;我是司法部AI主播司政轩。为切实做好党的二十届三中全会精神学习宣传贯彻&#xff0c;积极反映司法部及地方司法行政机关学习全会精神的体会收获和贯彻落实举措&#xff0c;我们推出了“学习宣传贯彻党的二十届三中全会精神--司法行政微访…

Elasticsearch 检索优化:停用词的应用

Elasticsearch 检索优化&#xff1a;停用词的应用 场景描述 目前在 Elasticsearch 集群中存储约 1.5 亿篇文章数据&#xff0c;随着数据量的增加&#xff0c;检索性能问题逐渐显现。在列表检索和聚合操作中&#xff0c;CPU 消耗飙升至 100%&#xff0c;并且检索耗时较长&…