C++ 进阶:类相关特性的深入探讨

news2024/11/26 2:32:15

⭐在对C++ 中类的6个默认成员函数有了初步了解之后,现在我们进行对类相关特性的深入探讨!

🔥🔥🔥【C++】类的默认成员函数:深入剖析与应用(上)

               【C++】类的默认成员函数:深入剖析与应用(下)


目录

💯前言

💯再谈构造函数

(一)构造函数体赋值

(二)初始化列表

(三)explicit关键字

💯static成员

(一)静态成员变量

(二)静态成员函数

💯友元

(一)友元函数

(二)友元类

💯内部类

(一)定义与访问

(二)内部类的用途

💯总结


💯前言

在 C++ 编程中,类是构建复杂程序的基石。🌟它提供了一种将数据和操作数据的方法进行封装的机制,使得程序更加模块化、可维护和可扩展。在之前对类的学习中,我们已经了解了一些基本概念,如构造函数、拷贝构造函数和析构函数等。然而,类还有许多其他重要的特性,这些特性对于深入理解和掌握 C++ 编程至关重要。🎦本文将进一步探讨构造函数的更多细节,以及 Static 成员、友元、内部类等特性,并再次深入理解封装的概念。

 


💯再谈构造函数

 构造函数在 C++ 类中扮演着至关重要的角色。它主要用于对象的初始化操作。当创建一个类的对象时,构造函数会被自动调用


(一)构造函数体赋值

在创建对象时,编译器通过调用构造函数,给对象中各个成员变量一个合适的初始值。

class Date {
public:
    Date(int year, int month, int day) {
        _year = year;
        _month = month;
        _day = day;
    }

private:
    int _year;
    int _month;
    int _day;
};

❓问题如下: 

虽然在构造函数中通过赋值操作给对象的成员变量赋予了初始值,但严格意义上来说这只是在构造函数体中进行的赋初值操作,而不是真正的初始化。

真正的初始化是在对象创建时就确定下来,并且只能进行一次。而在构造函数体内部,可以进行多次赋值操作,这就与初始化的概念有所不同。

 🔵现在我们引出一个概念,来解决这一问题——初始化列表 


(二)初始化列表

 

为了实现真正的初始化,可以使用初始化列表。

初始化列表是在构造函数的参数列表之后,函数体之前,以冒号开头,后面跟着一系列成员变量的初始化表达式。

👇代码如下 :

class Date {
public:
    Date(int year, int month, int day) : _year(year), _month(month), _day(day) {
        // 构造函数体,可以进行其他操作,但这里不能再对成员变量进行初始化
    }
private:
    int _year;
    int _month;
    int _day;
};

 ❗注意:

  1. 每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)
  2. 类中包含以下成员,必须放在初始化列表位置进行初始化:
    🌠引用成员变量
    🌠const成员变量
    🌠自定义类型成员(且该类没有默认构造函数时)
    #include <iostream>
    
    // 自定义类 CustomClass 的定义
    class CustomClass {
    public:
        // 构造函数,接收一个整数值进行初始化
        CustomClass(int val) : value(val) {
            std::cout << "CustomClass constructor called with value: " << value << std::endl;
        }
    private:
        int value;
    };
    
    // 主类 MyClass 的定义
    class MyClass {
    public:
        int& ref; // 引用成员变量
        const int constVal; // const 成员变量
        CustomClass custom; // 自定义类型成员变量
    
        // 构造函数,接收一个引用、一个整数和一个整数作为参数
        MyClass(int& r, int v, int customVal) : ref(r), constVal(v), custom(customVal) {
            std::cout << "MyClass constructor called." << std::endl;
        }
    };
    
    int main() {
        int num = 10;
        // 创建 MyClass 对象,传入相应参数
        MyClass obj(num, 20, 30);
        return 0;
    }

    MyClass包含了引用成员变量、const成员变量和一个自定义类型的成员变量。在构造函数中,必须使用初始化列表来正确初始化这些特殊类型的成员变量。


  3. 尽量使用初始化列表初始化,因为不管你是否使用初始化列表,✅对于自定义类型成员变量,一定会先使用初始化列表初始化
    #include <iostream>
    
    class CustomType {
    public:
        CustomType(int val) : data(val) {
            std::cout << "CustomType constructor called with value: " << data << std::endl;
        }
    private:
        int data;
    };
    
    class MyClass {
    public:
        MyClass(int customVal) : customMember(customVal) {
            std::cout << "MyClass constructor called." << std::endl;
        }
    private:
        CustomType customMember;
    };
    
    int main() {
        MyClass obj(42);
        return 0;
    }

    在这个例子中,MyClass有一个自定义类型CustomType的成员变量customMember。当创建MyClass的对象时,即使在MyClass的构造函数中没有显式地写出初始化列表,但实际上编译器会先尝试使用初始化列表来初始化customMember,这就调用了CustomType的构造函数并输出相应信息。如果CustomType没有默认构造函数,那么就必须在MyClass的构造函数中显式地使用初始化列表来正确初始化customMember


  4. 成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关

    #include <iostream>
    
    class MyClass {
    public:
        MyClass(int a, int b, int c) : c_member(c), b_member(b), a_member(a) {
            std::cout << "Constructor called." << std::endl;
        }
    
        void printMembers() {
            std::cout << "a_member: " << a_member << std::endl;
            std::cout << "b_member: " << b_member << std::endl;
            std::cout << "c_member: " << c_member << std::endl;
        }
    
    private:
        int a_member;
        int b_member;
        int c_member;
    };
    
    int main() {
        MyClass obj(1, 2, 3);
        obj.printMembers();
        return 0;
    }


    在这个例子中,构造函数的初始化列表中成员变量的初始化顺序看起来是 c_memberb_membera_member💐但实际上成员变量的初始化顺序是由它们在类中的声明顺序决定的即先初始化 a_member,再初始化 b_member,最后初始化 c_member如果在初始化列表中打乱顺序,初始化的结果仍然是按照声明顺序进行的。


(三)explicit关键字

构造函数不仅可以构造与初始化对象,对于接收单个参数的构造函数,还具有类型转换的作用。 

接收单个参数的构造函数具体表现🌞: 

  1. 构造函数只有一个参数
  2. 构造函数有多个参数,除第一个参数没有默认值外,其余参数都有默认值
  3. 全缺省构造函数 
#include <iostream>

class Date
{
public:
    // 1. 单参构造函数,没有使用 explicit 修饰,具有类型转换作用
    // explicit 修饰构造函数,禁止类型转换---explicit 去掉之后,代码可以通过编译
    explicit Date(int year)
        : _year(year)
    {
    }

    /*
    // 2. 虽然有多个参数,但是创建对象时后两个参数可以不传递,没有使用 explicit 修饰,具有类型转换作用
    // explicit 修饰构造函数,禁止类型转换
    explicit Date(int year, int month = 1, int day = 1)
        : _year(year)
       , _month(month)
       , _day(day)
    {
    }
    */

    Date& operator=(const Date& d)
    {
        if (this!= &d)
        {
            _year = d._year;
            _month = d._month;
            _day = d._day;
        }
        return *this;
    }

private:
    int _year;
    int _month;
    int _day;
};

void Test()
{
    Date d1(2022);
    // 用一个整形变量给日期类型对象赋值
    // 实际编译器背后会用 2023 构造一个无名对象,最后用无名对象给 d1 对象进行赋值
    // 将 1 屏蔽掉,2 放开时则编译失败,因为 explicit 修饰构造函数,禁止了单参构造函数类型转换的作用
}

 上述代码可读性不是很好,用explicit修饰构造函数,将会禁止构造函数的隐式转换。

 有一个接受单个整数参数的构造函数Date(int year),用于初始化日期对象的年份部分。这个构造函数使用了explicit关键字修饰,这意味着它不能进行隐式类型转换。如果没有这个关键字,编译器可能会在某些情况下自动使用这个构造函数进行隐式的类型转换。

 


💯static成员

 

(一)静态成员变量

😃静态成员变量是属于类的变量,而不是属于某个具体的对象。它在整个类的所有对象之间共享。

  1.定义与初始化

  • 静态成员变量需要在类内声明,但不能在类内初始化(除了一些特殊的静态常量整数类型可以在类内初始化)。例如:
    class MyClass {
    public:
        static int staticVar;
    };
    int MyClass::staticVar = 0; // 在类外初始化

  2.访问方式

  • 可以通过类名直接访问静态成员变量,也可以通过对象访问,但通常建议通过类名访问,以体现其类属性。例如:
    MyClass obj;
    MyClass::staticVar = 10; // 通过类名访问
    obj.staticVar = 20; // 通过对象访问也是允许的,但不规范

(二)静态成员函数

🍎静态成员函数也是属于类的函数,它不依赖于具体的对象实例。

1.特点

  • 它只能访问静态成员变量和其他静态成员函数,因为它没有this指针指向具体的对象。例如:
    class MyClass {
    public:
        static int staticVar;
        static void staticFunction() {
            staticVar++; // 可以访问静态成员变量
            // 不能访问非静态成员变量,因为没有this指针
        }
    };

2.调用方式

  • 与静态成员变量类似,可以通过类名直接调用静态成员函数。例如:MyClass::staticFunction();

 


💯友元

友元机制允许一个类或函数访问另一个类的私有成员。它打破了类的封装性,但在某些特定情况下是非常有用的。

(一)友元函数

1.定义

  • 友元函数是在一个类中声明为友元的普通函数。它不是类的成员函数,但可以访问该类的私有成员。例如:
    class MyClass {
    private:
        int privateVar;
    public:
        friend void friendFunction(MyClass obj);
    };
    void friendFunction(MyClass obj) {
        // 可以访问obj的privateVar
        std::cout << obj.privateVar << std::endl;
    }

❗说明:

  1. 友元函数可访问类的私有和保护成员,但不是类的成员函数
  2. 友元函数不能用const修饰
  3. 友元函数可以在类定义的任何地方声明,不受类访问限定符限制
  4. 一个函数可以是多个类的友元函数
  5. 友元函数的调用与普通函数的调用原理相同

(二)友元类

1.定义

  • 友元类是在一个类中声明为友元的另一个类。友元类的所有成员函数都可以访问声明它为友元的类的私有成员。例如:
    class MyClass {
    private:
        int privateVar;
    public:
        friend class FriendClass;
    };
    class FriendClass {
    public:
        void accessPrivate(MyClass obj) {
            // 可以访问obj的privateVar
            std::cout << obj.privateVar << std::endl;
        }
    };

2.使用场景

  • 当两个类之间存在紧密的合作关系,需要相互访问对方的私有成员时,可以使用友元类。例如,一个图形绘制类和一个图形变换类可能需要相互访问对方的私有数据来实现复杂的图形操作。

💯内部类

🍄内部类是定义在另一个类内部的类。它具有一些特殊的性质和用途。

(一)定义与访问

1.定义

  • 内部类可以在一个外部类的任何部分定义,包括私有部分、保护部分和公共部分。例如:
    class OuterClass {
    private:
        int outerVar;
        class InnerClass {
        public:
            void innerFunction() {
                // 可以访问OuterClass的成员吗?这取决于具体情况
            }
        };
    };

2.访问

  • 外部类可以通过创建内部类的对象来访问内部类的成员。内部类也可以访问外部类的成员,但需要注意访问权限。如果内部类定义在外部类的公共部分,那么它可以像普通类一样被外部访问和使用。如果定义在私有部分,只有外部类的成员函数可以创建内部类的对象并访问其的成员。例如:
    OuterClass outerObj;
    OuterClass::InnerClass innerObj; // 创建内部类对象(如果InnerClass是公共的)
    outerObj.innerObj.innerFunction(); // 通过外部区
    outerObj.innerObj.innerFunction(); // 通过外部类对象访问内部类对象的成员(如果InnerClass是公共的且有合适的访问路径)

(二)内部类的用途

1.隐藏实现细节

  • 内部类可以将一些与外部类相关但又不想暴露给外部的实现细节封装起来。例如,一个复杂的容器类可能使用内部类来实现其内部的数据结构,如链表节点类可以作为容器类的内部类。

2.实现辅助功能

  • 可以利用内部类来实现一些辅助功能,这些功能与外部类紧密相关但又不适合作为外部类的直接成员函数。例如,一个文件读取类可能有一个内部类用于处理文件的缓冲和读取位置等细节。

💯总结

C++ 中类的这些特性为我们提供了强大的编程工具,让我们能够更好地组织和管理代码。希望本文能够帮助你更深入地理解 C++ 类的相关特性,提升你的编程能力。🚩🚩🚩


以后我将深入研究继承、多态、模板等特性,并将默认成员函数与这些特性结合,以解决更复杂编程问题!欢迎关注我👉【A Charmer】

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

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

相关文章

【凸优化】Legendre变换、共轭函数、对偶

推荐文章 [凸优化-凸共轭]Legendre and Legendre-Fenchel transforms - 知乎 (zhihu.com) 一、Legendre变换 1、几何解释1 Legendre 变换通过选择斜率 作为新的自变量&#xff0c;将函数描述为它的斜率与相应的 x 的函数值之间的关系。几何上&#xff0c;它可以理解为用函…

Vert.x,认证与授权 - HTTP基本认证

几乎所有的线上系统都离不开认证和授权&#xff0c;Vert.x auth相关组件提供了丰富(Session&#xff0c;JTW, OAuth&#xff0c;…)&#xff0c;便捷的认证和授权支持。 当前&#xff0c;使用最多是Web应用&#xff0c;所以在后续讨论中&#xff0c;都是关于Vert.x auth在Web应…

kernel32.dll下载地址:如何安全地恢复系统文件

关于从网络上寻找kernel32.dll的下载地址&#xff0c;这通常不是一个安全的做法&#xff0c;而且可能涉及到多种风险。kernel32.dll是Windows操作系统的核心组件之一&#xff0c;负责内存管理、进程和线程管理以及其他关键系统功能。因为kernel32.dll是系统的基础文件&#xff…

IDEA如何给debug断点加上筛选条件判断

前言 我们在使用IDEA开发Java应用时&#xff0c;经常是需要进行代码调试的&#xff0c;这就需要打断点进行操作。但有些时候&#xff0c;我们只希望在符合某种条件的情况下&#xff0c;才去到这个断点&#xff0c;不符合的情况下&#xff0c;直接跳过断点&#xff0c;这其实也…

【linux】线程 (三)

13. 常见锁概念 &#xff08;一&#xff09;了解死锁 死锁是指在一组进程中的各个进程均占有不会释放的资源&#xff0c;但因互相申请被其他进程占有的&#xff0c;且不释放的资源&#xff0c;而处于的一种永久等待状态 &#xff08;二&#xff09;死锁四个必要条件 互斥条件…

深度学习(一)基础:神经网络、训练过程与激活函数(1/10)

深度学习基础&#xff1a;神经网络、训练过程与激活函数 引言&#xff1a; 深度学习作为机器学习的一个子领域&#xff0c;近年来在人工智能的发展中扮演了举足轻重的角色。它通过模仿人脑的神经网络结构&#xff0c;使得计算机能够从数据中学习复杂的模式和特征&#xff0c;…

COLA架构生成DDD项目

1.下载代码 https://github.com/alibaba/COLA 2.执行命令 2.1 shell Linux mvn archetype:generate \-DgroupIdcom.xxc \-DartifactIdcola-springboot-demo \-Dversion0.0.1 \-Dpackagecom.xxc.demo \-DarchetypeArtifactIdcola-framework-archetype-web \-DarchetypeGrou…

我是类(最终版)

文章目录 再看构造函数类型转换static静态成员友元内部类匿名对象对象拷贝时的编译器优化 再看构造函数 本标题的目的是解决如下问题&#xff1a;当实现MyQueue时&#xff0c;我们不需要写默认构造函数&#xff0c;因为编译器会调用Stack的默认构造&#xff0c;但是&#xff0…

01 设计模式-创造型模式-工厂模式

工厂模式&#xff08;Factory Pattern&#xff09;是 Java 中最常用的设计模式之一&#xff0c;它提供了一种创建对象的方式&#xff0c;使得创建对象的过程与使用对象的过程分离。 工厂模式提供了一种创建对象的方式&#xff0c;而无需指定要创建的具体类。 通过使用工厂模式…

vue 页面导出gif图片 img 导出gif 超简单~

1.首先需要新建一个文件件 新建gif文件夹。这两个文件在文章最后面需要可自提 2.出gif分为两种情况 第一种情况 页面是img标签&#xff0c;直接导出图片作为gif 第二种情况 页面是div标签&#xff0c;需要导出div里面的图片作为gif 2.1页面是img标签&#xff0c;直接导出图…

电脑异常情况总结

文章目录 笔记本无症状息屏黑屏 笔记本无症状息屏黑屏 &#x1f34e; 问题描述&#xff1a; 息屏导致黑屏&#xff1b;依次操作计算机--》右键--》管理--》事件查看器--》Windows日志--》系统&#xff1b;从息屏到异常黑屏之间出现了很多错误&#xff0c;如下&#xff1a;事件…

使用CMake生成动态链接库(.dll和.so)和静态链接库(.lib和.a)的方法

&#xff08;一&#xff09;简介 对于大型软件开发&#xff0c;动态连接库是必不可少的。不仅可以实现模块的封装&#xff0c;而且可以实现软件的热更新&#xff08;即替换windows下的.dll或Linux下的.so文件后直接实现软件更新&#xff0c;无需重新编译&#xff09;。有时也需…

力扣 困难 52.N皇后II

文章目录 题目介绍题解 题目介绍 题解 法一&#xff1a;返回51题N皇后List的长度 法二&#xff1a; class Solution {private int n, ans;private boolean[] onPath, diag1, diag2;public int totalNQueens(int n) {this.n n;onPath new boolean[n];diag1 new boolean[n * …

Standard IO

为了提高可移植性&#xff0c;将通用IO接口经过再封装就形成了标准IO&#xff0c;标准IO不仅适用于Unix环境&#xff0c;也兼容非Unix环境&#xff0c;这也是为什么说我们应该尽可能的使用标准IO&#xff0c;通用IO通过文件描述符fd来与文件交互&#xff0c;为了以示区分&#…

element plus e-table表格中使用多选,当翻页时已选中的数据丢失

摘要&#xff1a; 点击第一页选中两个&#xff0c;再选择第二页&#xff0c;选中&#xff0c;回到第一页&#xff0c;之前选中的要保留&#xff01; element ui table 解决办法&#xff1a; :row-key“getRowKeys” &#xff08;写在el-table中&#xff09; methods中声明 ge…

多一DY4100数字式接地电阻测试仪使用测量方法

接地电阻二线法测量步骤 1、找出一套简易测试线&#xff0c;一根红色两插头线和一根短绿线&#xff1b; 2、找出一根辅助地钉&#xff0c;距离被测接地桩/扁铁约五米远整根打下去&#xff1b; 3、绿线插头接仪器E孔&#xff08;绿色孔&#xff09;&#xff0c;夹子夹需测的接…

ArcGIS002:软件自定义设置

摘要&#xff1a;本文详细介绍安装arcgis10.2后软件自定义设置内容&#xff0c;包括工具条的启用、扩展模块的启用、如何加载项管理器、快捷键设置、样式管理器的使用以及软件常规设置。 一、工具条的启用 依次点击菜单栏【自定义】->【工具条】&#xff0c;根据工作需求勾…

医院信息化与智能化系统(5)

医院信息化与智能化系统(5) 这里只描述对应过程&#xff0c;和可能遇到的问题及解决办法以及对应的参考链接&#xff0c;并不会直接每一步详细配置 如果你想通过文字描述或代码画流程图&#xff0c;可以试试PlantUML&#xff0c;告诉GPT你的文件结构&#xff0c;让他给你对应的…

【漏洞修复/安全补丁】5.4之前版本通过api/products接口进行sql注入的问题

5.4之前版本&#xff0c;会使用api/products接口的selectId参数进行sql注入 修复办法 1、修改crmeb/app/api/controller/v1/store/StoreProductController.php文件中的lst方法里面的接收参数&#xff0c;修改成下图 如果你的版本没有coupon_category_id参数的话&#xff0c;…

No.19 笔记 | WEB安全 - 任意文件操作详解 part 1

1. 任意文件上传漏洞基础 什么是文件上传功能? 在网站和应用中,我们经常会看到允许用户上传文件的功能,比如: 更换头像:让用户上传自己的照片作为头像发布图片:在社交媒体或论坛上传图片提交文档:在办公系统中上传Word、Excel等文档 这些都是常见的文件上传功能。 任意文…