【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++支持将内置类型(如int、double等)隐式地转换为自定义的类类型。这是通过定义带有内置类型参数的构造函数来实现的。

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

目录
相关文章
|
编译器 C++ 开发者
【C++篇】深度解析类与对象(下)
在上一篇博客中,我们学习了C++的基础类与对象概念,包括类的定义、对象的使用和构造函数的作用。在这一篇,我们将深入探讨C++类的一些重要特性,如构造函数的高级用法、类型转换、static成员、友元、内部类、匿名对象,以及对象拷贝优化等。这些内容可以帮助你更好地理解和应用面向对象编程的核心理念,提升代码的健壮性、灵活性和可维护性。
|
存储 监控 算法
基于 C++ 哈希表算法的局域网如何监控电脑技术解析
当代数字化办公与生活环境中,局域网的广泛应用极大地提升了信息交互的效率与便捷性。然而,出于网络安全管理、资源合理分配以及合规性要求等多方面的考量,对局域网内计算机进行有效监控成为一项至关重要的任务。实现局域网内计算机监控,涉及多种数据结构与算法的运用。本文聚焦于 C++ 编程语言中的哈希表算法,深入探讨其在局域网计算机监控场景中的应用,并通过详尽的代码示例进行阐释。
361 4
|
索引
【Flutter 开发必备】AzListView 组件全解析,打造丝滑索引列表!
在 Flutter 开发中,AzListView 是实现字母索引分类列表的理想选择。它支持 A-Z 快速跳转、悬浮分组标题、自定义 UI 和高效性能,适用于通讯录、城市选择等场景。本文将详细解析 AzListView 的核心参数和实战示例,助你轻松实现流畅的索引列表。
870 7
|
存储 算法 安全
基于红黑树的局域网上网行为控制C++ 算法解析
在当今网络环境中,局域网上网行为控制对企业和学校至关重要。本文探讨了一种基于红黑树数据结构的高效算法,用于管理用户的上网行为,如IP地址、上网时长、访问网站类别和流量使用情况。通过红黑树的自平衡特性,确保了高效的查找、插入和删除操作。文中提供了C++代码示例,展示了如何实现该算法,并强调其在网络管理中的应用价值。
|
安全 编译器 C++
C++ `noexcept` 关键字的深入解析
`noexcept` 关键字在 C++ 中用于指示函数不会抛出异常,有助于编译器优化和提高程序的可靠性。它可以减少代码大小、提高执行效率,并增强程序的稳定性和可预测性。`noexcept` 还可以影响函数重载和模板特化的决策。使用时需谨慎,确保函数确实不会抛出异常,否则可能导致程序崩溃。通过合理使用 `noexcept`,开发者可以编写出更高效、更可靠的 C++ 代码。
|
存储 程序员 C++
深入解析C++中的函数指针与`typedef`的妙用
本文深入解析了C++中的函数指针及其与`typedef`的结合使用。通过图示和代码示例,详细介绍了函数指针的基本概念、声明和使用方法,并展示了如何利用`typedef`简化复杂的函数指针声明,提升代码的可读性和可维护性。
|
安全 编译器 C语言
【C++篇】深度解析类与对象(中)
在上一篇博客中,我们学习了C++类与对象的基础内容。这一次,我们将深入探讨C++类的关键特性,包括构造函数、析构函数、拷贝构造函数、赋值运算符重载、以及取地址运算符的重载。这些内容是理解面向对象编程的关键,也帮助我们更好地掌握C++内存管理的细节和编码的高级技巧。
|
存储 程序员 C语言
【C++篇】深度解析类与对象(上)
在C++中,类和对象是面向对象编程的基础组成部分。通过类,程序员可以对现实世界的实体进行模拟和抽象。类的基本概念包括成员变量、成员函数、访问控制等。本篇博客将介绍C++类与对象的基础知识,为后续学习打下良好的基础。
|
编译器 C++ 容器
【c++11】c++11新特性(上)(列表初始化、右值引用和移动语义、类的新默认成员函数、lambda表达式)
C++11为C++带来了革命性变化,引入了列表初始化、右值引用、移动语义、类的新默认成员函数和lambda表达式等特性。列表初始化统一了对象初始化方式,initializer_list简化了容器多元素初始化;右值引用和移动语义优化了资源管理,减少拷贝开销;类新增移动构造和移动赋值函数提升性能;lambda表达式提供匿名函数对象,增强代码简洁性和灵活性。这些特性共同推动了现代C++编程的发展,提升了开发效率与程序性能。
686 12
|
编译器 C语言 C++
类和对象的简述(c++篇)
类和对象的简述(c++篇)

推荐镜像

更多
  • DNS