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/897275.html

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

相关文章

SpringBoot使用RestTemplate实现发送HTTP请求

Java 实现发送 HTTP 请求&#xff0c;系列文章&#xff1a; 《Java使用原生HttpURLConnection实现发送HTTP请求》 《Java使用HttpClient5实现发送HTTP请求》 《SpringBoot使用RestTemplate实现发送HTTP请求》 1、RestTemplate 的介绍 RestTemplate 是 Spring 框架提供的一个用…

【java】抽象类和接口(了解,进阶,到全部掌握)

各位看官早安午安晚安呀 如果您觉得这篇文章对您有帮助的话 欢迎您一键三连&#xff0c;小编尽全力做到更好 欢迎您分享给更多人哦 大家好我们今天来学习Java面向对象的的抽象类和接口&#xff0c;我们大家庭已经来啦~ 一&#xff1a;抽象类 1.1:抽象类概念 在面向对象的概念中…

12寸FAB厂试产到量产实现无纸化要素之软硬件

在12寸先进封装半导体车间从试产到量产的过程中&#xff0c;实现生产过程无纸化&#xff0c;需要综合考虑软硬件的配置。以下是一些关键的规划建议&#xff1a; 1、生产文档管理系统&#xff08;PDM&#xff09;&#xff1a; 采用基于SOLIDWORKS PDM开发的无纸化方案&#xf…

uniapp 整合 OpenLayers - 加载Geojson数据(在线、离线)

Geojson数据是矢量数据&#xff0c;主要是点、线、面数据集合 Geojson数据获取&#xff1a;DataV.GeoAtlas地理小工具系列 实现代码如下&#xff1a; <template><!-- 监听变量 operation 的变化&#xff0c;operation 发生改变时&#xff0c;调用 openlayers 模块的…

Java面试场景题(1)---如何使用redis记录上亿用户连续登陆天数

感谢uu们的观看&#xff0c;话不多说开始~ 对于这个问题&#xff0c;我们需要先来了解一下~ 海量数据都可以用bitmap来存储&#xff0c;因为占得内存小&#xff0c;速度也很快 我大概计算了一下~ 完全够&#xff1a;String类型512M 1byte 8个bit位 8个状态 512M1024byt…

数据库性能测试报告总结模板

1计划概述 目的&#xff1a;找出系统潜在的性能缺陷 目标&#xff1a;从安全&#xff0c;可靠&#xff0c;稳定的角度出发&#xff0c;找出性能缺陷&#xff0c;并且找出系统最佳承受并发用户数&#xff0c;以及并发用户数下长时间运行的负载情况&#xff0c;如要并发100用户&a…

CTFHUB技能树之SQL——字符型注入

开启靶场&#xff0c;打开链接&#xff1a; 直接指明是SQL字符型注入&#xff0c;但还是来判断一下 &#xff08;1&#xff09;检查是否存在注入点 1 and 11# 返回正确 1 and 12# 返回错误 说明存在SQL字符型注入 &#xff08;2&#xff09;猜字段数 1 order by 2# 1 order…

颠覆编程!通义灵码、包阅AI、CodeGeeX三大AI助手解锁无限潜力!

随着科技的疾速前行&#xff0c;人工智能&#xff08;AI&#xff09;辅助编程工具已跃然成为软件开发领域及编程爱好者群体中不可或缺的得力助手。这些融入了尖端智能化算法的工具&#xff0c;不仅深刻改变了编程工作的面貌&#xff0c;通过自动化和优化手段显著提升了编程效率…

GJS-WCP

不懂的就问&#xff0c;但我也是二把手......哭死 web GJS-ezssti 很常规的ssti模板注入&#xff0c;只过滤了"/","flag"。 过滤了/,flag 可以利用bash的特性绕过&#xff0c;如字符串截取&#xff0c;环境变量等等。payload1: {{url_for.__globals__[…

柔性数组的使用

//柔性数组的使用 #include<stdio.h> #include<stdlib.h> #include<errno.h> struct s {int i;int a[]; }; int main() {struct s* ps (struct s*)malloc(sizeof(struct s) 20 * sizeof(int));if (ps NULL){perror("malloc");return 1;}//使用这…

react18中在列表项中如何使用useRef来获取每项的dom对象

在react中获取dom节点都知道用ref&#xff0c;但是在一个列表循环中&#xff0c;这样做是行不通的&#xff0c;需要做进一步的数据处理。 实现效果 需求&#xff1a;点击每张图片&#xff0c;当前图片出现在可视区域。 代码实现 .box{border: 1px solid #000;list-style: …

Math类、System类、Runtime类、Object类、Objects类、BigInteger类、BigDecimal类

课程目标 能够熟练使用Math类中的常见方法 能够熟练使用System类中的常见方法 能够理解Object类的常见方法作用 能够熟练使用Objects类的常见方法 能够熟练使用BigInteger类的常见方法 能够熟练使用BigDecimal类的常见方法 1 Math类 1.1 概述 tips&#xff1a;了解内容…

Java | Leetcode Java题解之第493题翻转对

题目&#xff1a; 题解&#xff1a; class Solution {public int reversePairs(int[] nums) {Set<Long> allNumbers new TreeSet<Long>();for (int x : nums) {allNumbers.add((long) x);allNumbers.add((long) x * 2);}// 利用哈希表进行离散化Map<Long, Int…

【JAVA】第三张_Eclipse下载、安装、汉化

简介 Eclipse是一种流行的集成开发环境&#xff08;IDE&#xff09;&#xff0c;可用于开发各种编程语言&#xff0c;包括Java、C、Python等。它最初由IBM公司开发&#xff0c;后来被Eclipse Foundation接手并成为一个开源项目。 Eclipse提供了一个功能强大的开发平台&#x…

AI 编译器学习笔记之四 -- cann接口使用

1、安装昇腾依赖 # CANN发布件地址 https://cmc.rnd.huawei.com/cmcversion/index/releaseView?deltaId10274626629404288&isSelectSoftware&url_datarun Ascend-cann-toolkit_8.0.T15_linux-aarch64.run Ascend-cann-nnal_8.0.T15_linux-aarch64.run Ascend-cann-ker…

当下大语言模型(LLM)应用的架构介绍

想要构建你的第一个大语言模型应用&#xff1f;这里有你需要了解的一切&#xff0c;以及你今天就能开始探索的问题领域。 LLM 应用架构 我们的目标是让你能够自由地使用大语言模型进行实验、打造自己的应用&#xff0c;并挖掘那些尚未被人注意的问题领域。为此&#xff0c;Git…

数据类型的通用操作

#通用操作有&#xff1a;for语句遍历&#xff0c;len()统计元素个数&#xff0c;是数据类型间的相互转换&#xff0c;元素的排序&#xff08;正反向&#xff09; 1.for语句遍历若遍历字典则 只去字典中的key(即名词) 2.各数据类型间的数据转换&#xff08;若为字典转化为列表…

2024年软件设计师中级(软考中级)详细笔记【7】面向对象技术(上)(分值10+)

目录 前言第7章 面向对象技术 &#xff08;上&#xff09;7.1 面向对象基础(3-4分&#xff09;7.1.1 面向对象的基本概念7.1.2 面向对象分析&#xff08;熟记&#xff09;7.1.3 面向对象设计7.1.4 面向对象程序设计7.1.5 面向对象测试 7.2 UML(3~4分)7.2.1 事务7.2.2 关系7.2.2…

超详细JDK安装+环境配置教程

安装jdk 1.首先在JDK官网进行下载 JDK会默认安装在C盘 program file文件下 2.并且在JDK安装的过程中会提示安装JRE JDK和JRE会安装在同一目录下 JDK通过命令行进行使用 JDK的目录 以下是JDK对应的目录 bin:存放可执行程序 其中包含java javac命令 Include&#xff1a;本地…

013_django基于大数据的高血压人群分析系统2024_dcb7986h_055

目录 系统展示 开发背景 代码实现 项目案例 获取源码 博主介绍&#xff1a;CodeMentor毕业设计领航者、全网关注者30W群落&#xff0c;InfoQ特邀专栏作家、技术博客领航者、InfoQ新星培育计划导师、Web开发领域杰出贡献者&#xff0c;博客领航之星、开发者头条/腾讯云/AW…