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;
};
❗注意:
- 每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)
- 类中包含以下成员,必须放在初始化列表位置进行初始化:
🌠引用成员变量
🌠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
成员变量和一个自定义类型的成员变量。在构造函数中,必须使用初始化列表来正确初始化这些特殊类型的成员变量。
- 尽量使用初始化列表初始化,因为不管你是否使用初始化列表,✅对于自定义类型成员变量,一定会先使用初始化列表初始化。
#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
。
成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关
#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_member
、b_member
、a_member
,💐但实际上成员变量的初始化顺序是由它们在类中的声明顺序决定的,即先初始化a_member
,再初始化b_member
,最后初始化c_member
。如果在初始化列表中打乱顺序,初始化的结果仍然是按照声明顺序进行的。
(三)explicit关键字
构造函数不仅可以构造与初始化对象,对于接收单个参数的构造函数,还具有类型转换的作用。
接收单个参数的构造函数具体表现🌞:
- 构造函数只有一个参数
- 构造函数有多个参数,除第一个参数没有默认值外,其余参数都有默认值
- 全缺省构造函数
#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的privateVarstd::cout << obj.privateVar << std::endl; }
❗说明:
- 友元函数可访问类的私有和保护成员,但不是类的成员函数
- 友元函数不能用const修饰
- 友元函数可以在类定义的任何地方声明,不受类访问限定符限制
- 一个函数可以是多个类的友元函数
- 友元函数的调用与普通函数的调用原理相同
(二)友元类
1.定义
- 友元类是在一个类中声明为友元的另一个类。友元类的所有成员函数都可以访问声明它为友元的类的私有成员。例如:
class MyClass { private:int privateVar; public:friend class FriendClass; }; class FriendClass { public:void accessPrivate(MyClass obj) {// 可以访问obj的privateVarstd::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】
相关文章:

C++ 进阶:类相关特性的深入探讨
⭐在对C 中类的6个默认成员函数有了初步了解之后,现在我们进行对类相关特性的深入探讨! 🔥🔥🔥【C】类的默认成员函数:深入剖析与应用(上) 【C】类的默认成员函数:深入剖…...

C++ 多态、虚析构、模板类、常函数、虚继承、虚函数和纯虚函数相关知识和问题总结
1. C 中的多态 多态(Polymorphism)是面向对象编程中的一个重要特性,它允许使用相同的接口来表示不同的类型。由于派生类重写基类方法,然后用基类引用指向派生类对象,调用方法时候会进行动态绑定,这就是多态…...

计算机组成原理一句话
文章目录 计算机系统概述存储系统指令系统 计算机系统概述 指令和数据以同等地位存储在存储器中,形式上没有差别,但计算机应能区分他们。通过指令周期的不同阶段。 完整的计算机系统包括,1)软件系统:程序、文档和数据&…...

【Linux】僵尸进程和孤儿进程
一、僵尸进程 何为僵尸进程? 在 Unix/Linux 系统中,正常情况下,子进程是通过父进程创建的,且两者的运行是相互独立的,父进程永远无法预测子进程到底什么时候结束。当一个进程调用 exit 命令结束自己的生命时ÿ…...

Patchcore运行过程
论文github地址:https://github.com/amazon-science/patchcore-inspection 平台:autodl云服务器 1.将下载的代码上传到autodl-tmp/PatchCore里面解压,将数据集上传path_to_mvtec_folder/mvtec里,目录结构如图 2.安装依赖 cd au…...

一小时快速入门Android GPU Inspector
本文介绍如何使用 Android GPU Inspector (AGI) 对Android 应用进行系统性能分析和帧性能分析 。面向熟悉Android图形的开发者。 待分析应用需要的前置条件 (1) 将应用设置为可调试状态 <application [...] android:debuggable"true">(2)…...

二叉树展开为链表
二叉树展开为链表 给你二叉树的根结点 root ,请你将它展开为一个单链表: 展开后的单链表应该同样使用 TreeNode ,其中 right 子指针指向链表中下一个结点,而左子指针始终为 null 。展开后的单链表应该与二叉树 先序遍历 顺序相同…...

基于SpringBoot+Vue+uniapp微信小程序的教学质量评价系统的详细设计和实现
项目运行截图 技术框架 后端采用SpringBoot框架 Spring Boot 是一个用于快速开发基于 Spring 框架的应用程序的开源框架。它采用约定大于配置的理念,提供了一套默认的配置,让开发者可以更专注于业务逻辑而不是配置文件。Spring Boot 通过自动化配置和约…...

【二刷hot100】day 4
终于有时间刷刷力扣,求实习中。。。。 目录 1.最大子数组和 2.合并区间 3.轮转数组 4.除自身以外数组的乘积 1.最大子数组和 class Solution {public int maxSubArray(int[] nums) {//就是说可以转换为计算左边的最大值,加上中间的值,…...

10.22学习
1.求余 在C语言中,求余操作是通过取模运算符 % 来实现的。取模运算符会返回两个数相除后的余数。对于正数和负数的除法,求余的结果会有所不同,但 % 运算符总是返回被除数的符号。 下面是一个简单的例子,展示如何使用 % 运…...

【不要离开你的舒适圈】:猛兽才希望你落单,亲人总让你回家,4个维度全面构建舒适圈矩阵
单打独斗的英雄时代已经落幕 抱团取暖才是社会寒冬的良策 自然界中,每个物种都占据着自己的领地和生存空间。 生态位的差异决定了它们的生存方式,一旦离开领地,失去群体的庇护,就会沦为野兽的美餐。 人类社会同样存在隐形圈层…...

OpenIPC开源FPV之Channel配置
OpenIPC开源FPV之Channel配置 1. 源由2. 现象3. 硬件3.1 模拟频点3.2 数字频点2.4GHz频段频点表格 (802.11b/g/n):5GHz频段频点表格 (802.11a/n/ac): 4. 分析5. 实验6. 参考资料 1. 源由 无线信号,传输过程中不可避免都会受到干扰。同时,由于在一个开放…...

UG NX12.0建模入门笔记:1.0 UG NX12.0安装教程
一、如何关闭防火墙? 提示:安装软件之前,建议先 关闭防火墙和杀毒软件!!! 文章目录 一、如何关闭防火墙?二、UG NX12.0安装包三、UG NX12.0安装教程1.新建文件夹2.安装JAVA环境3.安装许可证管理…...

【C++】踏上C++学习之旅(三):“我“ 与 “引用“ 的浪漫邂逅
文章目录 前言1. "引用"的概念1.1 "引用"的语法 2. "引用"的特性3. "引用"的使用场景3.1 "引用"做参数3. 2 "引用"做返回值3.2.1 "引用"做返回值时需要注意的点 4. 常引用5. "引用"在底层的实…...

中间件之Seata
一、引言 在微服务架构日益盛行的今天,分布式事务成为了一个必须面对和解决的问题。传统的本地事务已经无法满足分布式环境下的数据一致性需求,因此分布式事务解决方案应运而生。Seata作为一款开源的分布式事务中间件,以其高性能、易用性和灵…...

MySQL 异常: “Host ‘xxx‘ is not allowed to connect to this MySQL server“
update user set host % where user root; FLUSH PRIVILEGES; 这两行代码就行...

c语言中字符串函数strlen,strcmp,strcpy,srtcat,strncpy,strncat,strncmp
1.strlen的使用和模拟实现 strlen 用来求字符串的长度,统计\0之前字符的个数。 模拟实现1:计数参数法 #include<stdio.h> #include<assert.h> size_t my_strlen(char* str) {int count0;assert(str);//assert断言是判断是字符串不能为空w…...

携程线下一面,面试内容:
面试时间:2024/9/12 • 实例方法和静态方法有什么不一样? • Java中的异常有哪几类?分别怎么使用? • 常用的集合类有哪些?比如List如何排序? • ArrayList和LinkedList内部的实现大致是怎样的?他们之间的区别和各自适应的场景是什么? • 内存溢出是怎么…...

DeepL翻译:全世界最准确的翻译
DeepL翻译是一款高质量的机器翻译工具,以下从产品描述、产品特色、适用人群、适用场景四个方面对其进行介绍: 体验地址:DeepL翻译:全世界最准确的翻译 产品描述 DeepL是一家德国公司,以其高质量的机器翻译服务而闻名…...

15分钟学Go 实战项目一:命令行工具
实战项目一:命令行工具 1. 引言 命令行工具是开发者常用的工具之一,它可以帮助用户通过命令行界面对程序进行控制和交互。在这节中,我们将创建一个简单的命令行工具,以帮助你理解Go语言的基本语法和如何处理命令行输入。在这个过…...

lesson02 作业
lesson02-01作业 小红的体重是 m 千克,她想知道自己的体重在磅(1 千克约等于 2.20462 磅)是多少 输入描述 输入一个整数表示小红的标准体重m(kg) 输出描述 输出一个整数表示转换后的磅值n 磅 示例 输入: 50 输出:…...

港大和字节提出长视频生成模型Loong,可生成具有一致外观、大运动动态和自然场景过渡的分钟级长视频。
HKU, ByteDance|⭐️ 港大和字节联合提出长视频生成模型Loong,该模型可以生成外观一致、运动动态大、场景过渡自然的分钟级长视频。选择以统一的顺序对文本标记和视频标记进行建模,并使用渐进式短到长训练方案和损失重新加权来克服长视频训练…...

RabbitMQ进阶_可靠性
文章目录 一、 发送者的可靠性1.1、 生产者重试机制1.2、 生产者确认机制1.2.1、确认机制理论1.2.2、确认机制实现1.2.2.1、定义ReturnCallback1.2.2.2、定义ConfirmCallback 二、 MQ的可靠性2.1、 数据持久化2.1.1、 交换机持久化2.1.2、 队列持久化2.1.3、 消息持久化 2.2、 …...

JavaScript字符串的常用方法有哪些?
1.1操作方法 归纳为增删查改 1.1.1增 这里不是直接增添内容,而是创建字符串的一个副本,再进行操作 处理用以及${}进行字符串拼接外,还可以通过concat 1.1.1.1concat 用于将一个或多个字符串拼接为一个新字符串(浅拷贝&#…...

jmeter发送post请求
在jmeter中,有两种常用的请求方式,get和post.它们两者的区别在于get请求的参数一般是放在路径中,可以使用用户自定义变量和函数助手等方式进行参数化,而post请求的参数不能随url发送,而是作为请求体提交给服务器。而在…...

图文深入理解Oracle Total Recall
List item 题记:本文图文深入理解Oracle Total Recall技术。 1. Oracle Total Recall 概述 Oracle Total Recall(也称为 Flashback Data Archive - 闪回数据归档)提供了一种用于跟踪数据库更改的机制,可自动跟踪数据库历史更改…...

腾讯云控制台URL刷新URL预热 使用接口刷新
如图所示的俩个控制台功能,调用腾讯云的接口执行这俩个动作 (代码可以优化)nodejs框架是express, 这里粘贴调用成功的代码示例,做个记录。 app.get(/PurgeUrlsCache, async function (req, res, next) {// Depends on tencentclo…...

构建后端为etcd的CoreDNS的容器集群(二)、下载最新的etcd容器镜像
在尝试获取etcd的容器的最新版本镜像时,使用latest作为tag取到的并非最新版本,本文尝试用实际最新版本的版本号进行pull,从而取到想的最新版etcd容器镜像。 一、用latest作为tag尝试下载最新etcd的镜像 1、下载镜像 [rootlocalhost opt]# …...

libaom-all-intra参数说明
part_sf.less_rectangular_check_level 1; 这个设置可能控制编码器在分割画面时使用非矩形分区的检查级别。part_sf.ml_prune_partition 1; 这个设置可能用于基于机器学习(ML)的分区修剪,以减少不必要的计算。part_sf.prune_ext_partition_…...

应用假死?
有个客户10月18日应用接口都访问慢,nginx层面error显示连接拒绝,当时实施同学重启了java应用运行正常,但今天又卡死了,后台登录也登录不上去,看日志没异常,最终找到了数据库层面。 查看数据库相关日志&…...