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

⭐在对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】

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Linux基础知识和常用基础命令

家目录 每个用户账户的专用目录。家目录的概念为用户提供了一个独立的工作空间&#xff0c;它是用户在文件系统中的主要工作区域&#xff0c;包含了用户的个人文件、配置文件和其他数据。 家目录通常位于 /home/用户名 路径下。例如&#xff0c;如果用户名为 1&#xff0c;那…

[Windows] 很火的开源桌面美化工具 Seelen UI v2.0.2

最近&#xff0c;一款来自Github的开源桌面美化工具突然在网上火了起来&#xff0c;引发了大家的关注&#xff0c;不少小伙伴纷纷开始折腾了起来。而折腾的目的&#xff0c;无非是为了一点点乐趣而已&#xff0c;至于结果如何&#xff0c;并不是最要紧的&#xff0c;反倒是体验…

音频声音怎么调大?将音频声音调大的几个简单方法

音频声音怎么调大&#xff1f;在现代生活中&#xff0c;音频内容无处不在&#xff0c;从在线课程和播客到音乐和电影&#xff0c;音频已经成为我们获取信息和娱乐的重要方式。然而&#xff0c;许多人在使用音频时可能会遇到一个常见问题&#xff1a;音频声音太小&#xff0c;无…

组件通信八种方式(vue3)

一、父传子&#xff08;props&#xff09; 关于Props的相关内容可以参考&#xff1a;Props-CSDN博客 父组件通过 props 向子组件传递数据。适合简单的单向数据流。 <!-- Parent.vue --> <template><Child :message"parentMessage" /> </temp…

2018年-2020年 计算机技术专业 程序设计题(算法题)实战_数组回溯法记录图的路径

阶段性总结&#xff1a; 树的DFS存储一条路径采用定义一个栈的形式 图的DFS和BFS&#xff0c;存储一条路径 采用数组回溯法 文章目录 2018年1.c语言程序设计部分2. 数据结构程序设计部分 2019年1.c语言程序设计部分2. 数据结构程序设计部分 2020年1.c语言程序设计部分2. 数据结…

基于微信小程序的智能校园社区服务推荐系统

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、Vue项目源码、SSM项目源码、微信小程序源码 精品专栏&#xff1a;…

Kimi 自带的费曼学习器,妈妈再也不用担心我的学习了

大家好&#xff0c;我是Shelly&#xff0c;一个专注于输出AI工具和科技前沿内容的AI应用教练&#xff0c;体验过300款以上的AI应用工具。关注科技及大模型领域对社会的影响10年。关注我一起驾驭AI工具&#xff0c;拥抱AI时代的到来。 AI工具集1&#xff1a;大厂AI工具【共23款…

【经管】比特币与以太坊历史价格数据集(2014.1-2024.5)

一、数据介绍 数据名称&#xff1a;比特币与以太坊历史价格数据集 频率&#xff1a;逐日 时间范围&#xff1a; BTC&#xff1a;2014/9/18-2024/5/1 ETH&#xff1a;2017/11/10-2024/5/1 数据格式&#xff1a;面板数据 二、指标说明 共计7个指标&#xff1a;Date、Open…

安装vue发生异常: idealTree:nodejs: sill idealTree buildDeps

一、异常 C:\>npm install vue -g npm ERR! code CERT_HAS_EXPIRED npm ERR! errno CERT_HAS_EXPIREDnpm ERR! request to https://registry.npm.taobao.org/vue failed, reason: certificate has expired 二、原因 请求 https://registry.npm.taobao.org 失败&#xff0c;证…

通义灵码:融合创新玩法与探索,重塑LeetCode解题策略

文章目录 关于通义灵码安装指南 通义灵码与LeetCode的结合通义灵码给出优化建议通义灵码给出修改建议通义灵码给出自己的思路 总结 大家好&#xff0c;欢迎大家来到工程师令狐小哥的频道。都说现在的时代是AI程序员的时代。AI程序员标志着程序员的生产力工具已经由原来的搜索式…

JavaSE之String类

文章目录 一、String类常用的构造方法二、常见的四种String对象的比较1.使用比较2.使用equals()方法比较3.使用compareTo()方法比较4.使用compareToIgnoreCase()方法比较 三、字符串的查找四、字符串的转化1.数字和字符串间的转化2.大小写转化3.字符串和数组间的转化 五、字符串…

grafana 配置prometheus

安装prometheus 【linux】麒麟v10安装prometheus监控&#xff08;ARM架构&#xff09;-CSDN博客 登录grafana 访问地址&#xff1a;http://ip:port/login 可以进行 Grafana 相关设置&#xff08;默认账号密码均为 admin&#xff09;。 输入账户密码 添加 Prometheus 数据源…

Codeforces Round 979 (Div. 2)

A. A Gift From Orangutan 题意&#xff1a; 思路&#xff1a; 贪心 模拟 重新排列的数组 -> 最大的元素放第一个位置 &#xff0c;最小的元素放第二个位置 #include<bits/stdc.h> using namespace std; #define lowbit(x) ( x & -x )#define int long long ty…

人类末日?Hinton预言AI恐将夺取地球控制权!

图片来源&#xff1a;Youtube Z Highlights&#xff1a; AI会变得比人类更聪明。我们必须担心它们会想从我们手中夺取控制权&#xff0c;这是我们应该认真思考的问题。 使用AI制造自动化致命武器的风险并不取决于AI是否比我们聪明。这与AI本身可能失控并试图接管的风险是完全…

[论文笔记]HERMES 3 TECHNICAL REPORT

引言 今天带来论文HERMES 3 TECHNICAL REPORT&#xff0c;这篇论文提出了一个强大的工具调用模型&#xff0c;包含了训练方案介绍。同时提出了一个函数调用标准。 为了简单&#xff0c;下文中以翻译的口吻记录&#xff0c;比如替换"作者"为"我们"。 聊天模…

嵌套div导致子区域margin失效问题解决

嵌套div导致子区域margin失效问题解决 现象原因解决方法 现象 <div class"prev"></div> <div class"parent"><div class"child"></div><div class"child"></div> </div> <div cl…

HCIP到底需要考哪几门?821和831都要考吗?

相对于华为认证中的HCIE&#xff0c;HCIP难度较低比较容易获得。 对于许多准备考HCIP认证的朋友来说&#xff0c;了解考试要求和内容是成功的关键第一步。 经常问的问题上次刚梳理了一波价格&#xff0c;还没看的看这里→《HCIP考证多少钱&#xff1f;HCIP认证深度解析》 今天再…

效果不错的论文介绍:Im2Flow2Act:-跨领域机器人操控技术

Im2Flow2Act: 跨领域机器人操控技术 简介 今天介绍一个比较惊艳的论文&#xff0c;Im2Flow2Act&#xff0c;可以预测应该怎么移动图象中的物体预测移动方法完成需要执行的动作任务。 Im2Flow2Act 是一个基于学习的机器人操控框架&#xff0c;旨在通过多种数据源为机器人提供操…

《深度学习》OpenCV EigenFaces算法 人脸识别

目录 一、EigenFaces算法 1、什么是EigenFaces算法 2、原理 3、实现步骤 1&#xff09;数据预处理 2&#xff09;特征提取 3&#xff09;构建模型 4&#xff09;识别 4、优缺点 1&#xff09;优点 2&#xff09;缺点 二、案例实现 1、完整代码 运行结果&#xff…

Star Tower:智能合约的安全基石与未来引领者

在区块链技术的快速发展中&#xff0c;智能合约作为新兴的应用形式&#xff0c;正逐渐成为区块链领域的重要组成部分。然而&#xff0c;智能合约的可靠性问题一直是用户最为关心的焦点之一。为此&#xff0c;Star Tower以其强大的技术实力和全面的安全保障措施&#xff0c;为智…