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

简介: 【C++篇】C++类与对象深度解析(四):初始化列表、类型转换与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的构造函数。

【C++篇】C++类与对象深度解析(四):初始化列表、类型转换与static成员详解2:https://developer.aliyun.com/article/1617497

目录
相关文章
|
12月前
|
Java 开发者
重学Java基础篇—Java类加载顺序深度解析
本文全面解析Java类的生命周期与加载顺序,涵盖从加载到卸载的七个阶段,并深入探讨初始化阶段的执行规则。通过单类、继承体系的实例分析,明确静态与实例初始化的顺序。同时,列举六种触发初始化的场景及特殊场景处理(如接口初始化)。提供类加载完整流程图与记忆口诀,助于理解复杂初始化逻辑。此外,针对空指针异常等问题提出排查方案,并给出最佳实践建议,帮助开发者优化程序设计、定位BUG及理解框架机制。最后扩展讲解类加载器层次与双亲委派机制,为深入研究奠定基础。
452 0
|
编译器 C++ 开发者
【C++篇】深度解析类与对象(下)
在上一篇博客中,我们学习了C++的基础类与对象概念,包括类的定义、对象的使用和构造函数的作用。在这一篇,我们将深入探讨C++类的一些重要特性,如构造函数的高级用法、类型转换、static成员、友元、内部类、匿名对象,以及对象拷贝优化等。这些内容可以帮助你更好地理解和应用面向对象编程的核心理念,提升代码的健壮性、灵活性和可维护性。
|
11月前
|
存储 监控 算法
基于 C++ 哈希表算法的局域网如何监控电脑技术解析
当代数字化办公与生活环境中,局域网的广泛应用极大地提升了信息交互的效率与便捷性。然而,出于网络安全管理、资源合理分配以及合规性要求等多方面的考量,对局域网内计算机进行有效监控成为一项至关重要的任务。实现局域网内计算机监控,涉及多种数据结构与算法的运用。本文聚焦于 C++ 编程语言中的哈希表算法,深入探讨其在局域网计算机监控场景中的应用,并通过详尽的代码示例进行阐释。
223 4
|
12月前
|
存储 监控 安全
重学Java基础篇—类的生命周期深度解析
本文全面解析了Java类的生命周期,涵盖加载、验证、准备、解析、初始化、使用及卸载七个关键阶段。通过分阶段执行机制详解(如加载阶段的触发条件与技术实现),结合方法调用机制、内存回收保护等使用阶段特性,以及卸载条件和特殊场景处理,帮助开发者深入理解JVM运作原理。同时,文章探讨了性能优化建议、典型异常处理及新一代JVM特性(如元空间与模块化系统)。总结中强调安全优先、延迟加载与动态扩展的设计思想,并提供开发建议与进阶方向,助力解决性能调优、内存泄漏排查及框架设计等问题。
509 5
|
12月前
|
安全 IDE Java
重学Java基础篇—Java Object类常用方法深度解析
Java中,Object类作为所有类的超类,提供了多个核心方法以支持对象的基本行为。其中,`toString()`用于对象的字符串表示,重写时应包含关键信息;`equals()`与`hashCode()`需成对重写,确保对象等价判断的一致性;`getClass()`用于运行时类型识别;`clone()`实现对象复制,需区分浅拷贝与深拷贝;`wait()/notify()`支持线程协作。此外,`finalize()`已过时,建议使用更安全的资源管理方式。合理运用这些方法,并遵循最佳实践,可提升代码质量与健壮性。
375 1
|
Java 数据库 开发者
详细介绍SpringBoot启动流程及配置类解析原理
通过对 Spring Boot 启动流程及配置类解析原理的深入分析,我们可以看到 Spring Boot 在启动时的灵活性和可扩展性。理解这些机制不仅有助于开发者更好地使用 Spring Boot 进行应用开发,还能够在面对问题时,迅速定位和解决问题。希望本文能为您在 Spring Boot 开发过程中提供有效的指导和帮助。
1716 12
|
传感器 监控 Java
Java代码结构解析:类、方法、主函数(1分钟解剖室)
### Java代码结构简介 掌握Java代码结构如同拥有程序世界的建筑蓝图,类、方法和主函数构成“黄金三角”。类是独立的容器,承载成员变量和方法;方法实现特定功能,参数控制输入环境;主函数是程序入口。常见错误包括类名与文件名不匹配、忘记static修饰符和花括号未闭合。通过实战案例学习电商系统、游戏角色控制和物联网设备监控,理解类的作用、方法类型和主函数任务,避免典型错误,逐步提升编程能力。 **脑图速记法**:类如太空站,方法即舱段;main是发射台,static不能换;文件名对仗,括号要成双;参数是坐标,void不返航。
479 5
|
机器学习/深度学习 人工智能 监控
鸿蒙赋能智慧物流:AI类目标签技术深度解析与实践
在数字化浪潮下,物流行业面临变革,传统模式的局限性凸显。AI技术为物流转型升级注入动力。本文聚焦HarmonyOS NEXT API 12及以上版本,探讨如何利用AI类目标签技术提升智慧物流效率、准确性和成本控制。通过高效数据处理、实时监控和动态调整,AI技术显著优于传统方式。鸿蒙系统的分布式软总线技术和隐私保护机制为智慧物流提供了坚实基础。从仓储管理到运输监控再到配送优化,AI类目标签技术助力物流全流程智能化,提高客户满意度并降低成本。开发者可借助深度学习框架和鸿蒙系统特性,开发创新应用,推动物流行业智能化升级。
411 1
|
算法 测试技术 C语言
深入理解HTTP/2:nghttp2库源码解析及客户端实现示例
通过解析nghttp2库的源码和实现一个简单的HTTP/2客户端示例,本文详细介绍了HTTP/2的关键特性和nghttp2的核心实现。了解这些内容可以帮助开发者更好地理解HTTP/2协议,提高Web应用的性能和用户体验。对于实际开发中的应用,可以根据需要进一步优化和扩展代码,以满足具体需求。
1134 29
|
12月前
|
前端开发 数据安全/隐私保护 CDN
二次元聚合短视频解析去水印系统源码
二次元聚合短视频解析去水印系统源码
482 4

推荐镜像

更多
  • DNS