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

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

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

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 2

    const A& aa2 = 1;  // 隐式将 int 1 转换为 A 对象,并绑定到常量引用

    A aa3 = {2, 2};  // 使用 C++11 的列表初始化语法
    aa3.Print();     // 输出: 2 2

    B 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;  // 初始对象数量为 0
    A a1, a2;  // 创建两个对象,计数加2
    A a3(a1);  // 拷贝构造,计数加1
    cout << "当前对象数量: " << A::GetACount() << endl;  // 输出 3

    return 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. 构造函数调用顺序
  • 先全局变量,再局部变量,最后静态局部变量。
  1. 析构函数调用顺序
  • 先局部变量,顺序与构造相反;
  • 然后静态局部变量;
  • 最后全局变量。

3.7static成员函数与变量总结

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

写在最后

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


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

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

目录
相关文章
|
编译器 C++ 容器
【c++11】c++11新特性(上)(列表初始化、右值引用和移动语义、类的新默认成员函数、lambda表达式)
C++11为C++带来了革命性变化,引入了列表初始化、右值引用、移动语义、类的新默认成员函数和lambda表达式等特性。列表初始化统一了对象初始化方式,initializer_list简化了容器多元素初始化;右值引用和移动语义优化了资源管理,减少拷贝开销;类新增移动构造和移动赋值函数提升性能;lambda表达式提供匿名函数对象,增强代码简洁性和灵活性。这些特性共同推动了现代C++编程的发展,提升了开发效率与程序性能。
492 12
|
11月前
|
人工智能 机器人 编译器
c++模板初阶----函数模板与类模板
class 类模板名private://类内成员声明class Apublic:A(T val):a(val){}private:T a;return 0;运行结果:注意:类模板中的成员函数若是放在类外定义时,需要加模板参数列表。return 0;
264 0
|
11月前
|
存储 编译器 程序员
c++的类(附含explicit关键字,友元,内部类)
本文介绍了C++中类的核心概念与用法,涵盖封装、继承、多态三大特性。重点讲解了类的定义(`class`与`struct`)、访问限定符(`private`、`public`、`protected`)、类的作用域及成员函数的声明与定义分离。同时深入探讨了类的大小计算、`this`指针、默认成员函数(构造函数、析构函数、拷贝构造、赋值重载)以及运算符重载等内容。 文章还详细分析了`explicit`关键字的作用、静态成员(变量与函数)、友元(友元函数与友元类)的概念及其使用场景,并简要介绍了内部类的特性。
419 0
|
存储 监控 算法
基于 C++ 哈希表算法的局域网如何监控电脑技术解析
当代数字化办公与生活环境中,局域网的广泛应用极大地提升了信息交互的效率与便捷性。然而,出于网络安全管理、资源合理分配以及合规性要求等多方面的考量,对局域网内计算机进行有效监控成为一项至关重要的任务。实现局域网内计算机监控,涉及多种数据结构与算法的运用。本文聚焦于 C++ 编程语言中的哈希表算法,深入探讨其在局域网计算机监控场景中的应用,并通过详尽的代码示例进行阐释。
265 4
|
设计模式 安全 C++
【C++进阶】特殊类设计 && 单例模式
通过对特殊类设计和单例模式的深入探讨,我们可以更好地设计和实现复杂的C++程序。特殊类设计提高了代码的安全性和可维护性,而单例模式则确保类的唯一实例性和全局访问性。理解并掌握这些高级设计技巧,对于提升C++编程水平至关重要。
251 16
|
编译器 C++
类和对象(中 )C++
本文详细讲解了C++中的默认成员函数,包括构造函数、析构函数、拷贝构造函数、赋值运算符重载和取地址运算符重载等内容。重点分析了各函数的特点、使用场景及相互关系,如构造函数的主要任务是初始化对象,而非创建空间;析构函数用于清理资源;拷贝构造与赋值运算符的区别在于前者用于创建新对象,后者用于已存在的对象赋值。同时,文章还探讨了运算符重载的规则及其应用场景,并通过实例加深理解。最后强调,若类中存在资源管理,需显式定义拷贝构造和赋值运算符以避免浅拷贝问题。
|
存储 编译器 C++
类和对象(上)(C++)
本篇内容主要讲解了C++中类的相关知识,包括类的定义、实例化及this指针的作用。详细说明了类的定义格式、成员函数默认为inline、访问限定符(public、protected、private)的使用规则,以及class与struct的区别。同时分析了类实例化的概念,对象大小的计算规则和内存对齐原则。最后介绍了this指针的工作机制,解释了成员函数如何通过隐含的this指针区分不同对象的数据。这些知识点帮助我们更好地理解C++中类的封装性和对象的实现原理。
|
编译器 C++
类和对象(下)C++
本内容主要讲解C++中的初始化列表、类型转换、静态成员、友元、内部类、匿名对象及对象拷贝时的编译器优化。初始化列表用于成员变量定义初始化,尤其对引用、const及无默认构造函数的类类型变量至关重要。类型转换中,`explicit`可禁用隐式转换。静态成员属类而非对象,受访问限定符约束。内部类是独立类,可增强封装性。匿名对象生命周期短,常用于临时场景。编译器会优化对象拷贝以提高效率。最后,鼓励大家通过重复练习提升技能!
|
编译器 C++ 开发者
【C++篇】深度解析类与对象(下)
在上一篇博客中,我们学习了C++的基础类与对象概念,包括类的定义、对象的使用和构造函数的作用。在这一篇,我们将深入探讨C++类的一些重要特性,如构造函数的高级用法、类型转换、static成员、友元、内部类、匿名对象,以及对象拷贝优化等。这些内容可以帮助你更好地理解和应用面向对象编程的核心理念,提升代码的健壮性、灵活性和可维护性。
|
编译器 C语言 C++
类和对象的简述(c++篇)
类和对象的简述(c++篇)

推荐镜像

更多
  • DNS