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语言的基本语法和如何处理命令行输入。在这个过…...
KubeSphere 容器平台高可用:环境搭建与可视化操作指南
Linux_k8s篇 欢迎来到Linux的世界,看笔记好好学多敲多打,每个人都是大神! 题目:KubeSphere 容器平台高可用:环境搭建与可视化操作指南 版本号: 1.0,0 作者: 老王要学习 日期: 2025.06.05 适用环境: Ubuntu22 文档说…...
Python爬虫实战:研究MechanicalSoup库相关技术
一、MechanicalSoup 库概述 1.1 库简介 MechanicalSoup 是一个 Python 库,专为自动化交互网站而设计。它结合了 requests 的 HTTP 请求能力和 BeautifulSoup 的 HTML 解析能力,提供了直观的 API,让我们可以像人类用户一样浏览网页、填写表单和提交请求。 1.2 主要功能特点…...
React Native 开发环境搭建(全平台详解)
React Native 开发环境搭建(全平台详解) 在开始使用 React Native 开发移动应用之前,正确设置开发环境是至关重要的一步。本文将为你提供一份全面的指南,涵盖 macOS 和 Windows 平台的配置步骤,如何在 Android 和 iOS…...

【入坑系列】TiDB 强制索引在不同库下不生效问题
文章目录 背景SQL 优化情况线上SQL运行情况分析怀疑1:执行计划绑定问题?尝试:SHOW WARNINGS 查看警告探索 TiDB 的 USE_INDEX 写法Hint 不生效问题排查解决参考背景 项目中使用 TiDB 数据库,并对 SQL 进行优化了,添加了强制索引。 UAT 环境已经生效,但 PROD 环境强制索…...

Debian系统简介
目录 Debian系统介绍 Debian版本介绍 Debian软件源介绍 软件包管理工具dpkg dpkg核心指令详解 安装软件包 卸载软件包 查询软件包状态 验证软件包完整性 手动处理依赖关系 dpkg vs apt Debian系统介绍 Debian 和 Ubuntu 都是基于 Debian内核 的 Linux 发行版ÿ…...
系统设计 --- MongoDB亿级数据查询优化策略
系统设计 --- MongoDB亿级数据查询分表策略 背景Solution --- 分表 背景 使用audit log实现Audi Trail功能 Audit Trail范围: 六个月数据量: 每秒5-7条audi log,共计7千万 – 1亿条数据需要实现全文检索按照时间倒序因为license问题,不能使用ELK只能使用…...
Golang dig框架与GraphQL的完美结合
将 Go 的 Dig 依赖注入框架与 GraphQL 结合使用,可以显著提升应用程序的可维护性、可测试性以及灵活性。 Dig 是一个强大的依赖注入容器,能够帮助开发者更好地管理复杂的依赖关系,而 GraphQL 则是一种用于 API 的查询语言,能够提…...
Python如何给视频添加音频和字幕
在Python中,给视频添加音频和字幕可以使用电影文件处理库MoviePy和字幕处理库Subtitles。下面将详细介绍如何使用这些库来实现视频的音频和字幕添加,包括必要的代码示例和详细解释。 环境准备 在开始之前,需要安装以下Python库:…...

使用 SymPy 进行向量和矩阵的高级操作
在科学计算和工程领域,向量和矩阵操作是解决问题的核心技能之一。Python 的 SymPy 库提供了强大的符号计算功能,能够高效地处理向量和矩阵的各种操作。本文将深入探讨如何使用 SymPy 进行向量和矩阵的创建、合并以及维度拓展等操作,并通过具体…...
Hive 存储格式深度解析:从 TextFile 到 ORC,如何选对数据存储方案?
在大数据处理领域,Hive 作为 Hadoop 生态中重要的数据仓库工具,其存储格式的选择直接影响数据存储成本、查询效率和计算资源消耗。面对 TextFile、SequenceFile、Parquet、RCFile、ORC 等多种存储格式,很多开发者常常陷入选择困境。本文将从底…...