【C++篇】深度解析类与对象(下)
引言
在上一篇博客中,我们学习了C++的基础类与对象概念,包括类的定义、对象的使用和构造函数的作用。在这一篇,我们将深入探讨C++类的一些重要特性,如构造函数的高级用法、类型转换、static
成员、友元、内部类、匿名对象,以及对象拷贝优化等。这些内容可以帮助你更好地理解和应用面向对象编程的核心理念,提升代码的健壮性、灵活性和可维护性。
无论你是C++的初学者还是有一定基础的开发者,本文都将为你提供详细的解释和实用的示例代码,帮助你掌握C++中的类与对象的高级用法。
一、再谈构造函数
构造函数是 C++ 中用于初始化对象的特殊函数。每次创建类的实例时,构造函数都会自动调用,用来为对象的成员变量赋初值。本篇将深入探讨构造函数的一些高级用法与技巧。
1.1 构造函数的初始化方式
在 C++ 中,构造函数可以通过两种方式为类的成员变量赋值:构造函数体内赋值和初始化列表。
(1). 构造函数体内赋值
传统方式是通过构造函数体来对成员变量赋值:
class Person {
public:Person(int age) {_age = age; // 在构造函数体内赋值}
private:int _age;
};
这种方式虽然常见,但可能带来效率问题,因为成员变量会经历两次赋值——默认初始化后再赋值。
(2).初始化列表
初始化列表可以更高效地为成员变量赋值。其语法是在构造函数的参数列表后面使用冒号 :
,直接对成员变量进行初始化:
class Person {
public:Person(int age) : _age(age) {} // 使用初始化列表
private:int _age;
};
使用初始化列表可以避免默认初始化后的重复赋值,因而效率更高。
1.2 必须使用初始化列表的场景
在某些情况下,初始化列表是唯一可行的选择:
1.引用成员变量
引用类型必须在初始化时绑定对象,因此需要通过初始化列表来进行初始化,不能在构造函数体内赋值。
2.常量成员变量
常量成员变量一旦被赋值便不能修改,必须在初始化列表中赋值,不能在构造函数体内赋值。
3.没有默认构造函数的类类型成员
如果成员是其他类的对象且没有默认构造函数,则必须通过初始化列表进行初始化。否则,编译器无法为其分配默认初值,导致编译错误。
示例:
class Date {
public:Date(int& year, int month = 1, int day = 1): _year(year), _month(month), _day(day), _constValue(100) {}private:int& _year; // 引用类型成员变量const int _constValue; // 常量成员变量int _month;int _day;
};
在上述代码中,_year
是引用类型,_constValue
是常量,都必须在初始化列表中进行初始化。
1.3 初始化列表的优势
1.提升性能
使用初始化列表避免了构造函数体内赋值的额外开销,尤其对于包含大量成员变量的类,能够显著提升性能。
2.必须使用的场合
某些成员(如引用和常量)只能在初始化列表中进行初始化,这是 C++ 语言规范的要求。
1.4 成员变量的缺省值
在 C++11 中,可以在类内为成员变量赋予缺省值,这样即使构造函数没有显式赋值,也会使用这些默认值:
class Person {
private:int _age = 18; // 缺省值std::string _name = "Unnamed"; // 缺省值
};
这种方式简化了构造函数,提高了代码的灵活性。
1.5 初始化列表的顺序规则
(1).成员变量初始化顺序
尽管初始化列表的书写顺序可以自由选择,但成员变量的初始化顺序是由它们在类中声明的顺序决定的。
示例:
class Example {
private:int _x;int _y;
public:Example(int x, int y) : _y(y), _x(x) {} // _x 仍会先于 _y 初始化
};
(2).成员变量初始化的逻辑
在初始化列表中,成员变量的初始化遵循以下逻辑:
如果成员变量在初始化列表中显式初始化,则按照提供的值进行初始化。
如果成员变量未在初始化列表中显式初始化,则有两种情况:
成员变量在类中声明时有缺省值:按照缺省值初始化。
成员变量在类中声明时没有缺省值:
对于内置类型成员,初始化为随机值,可能是默认值 0 等,具体行为依赖于编译器。
对于自定义类型成员,如果没有默认构造函数且无法自动初始化,将导致编译错误。
此外,引用类型成员、
const
成员以及没有默认构造函数的类类型成员,必须在初始化列表中进行显式初始化,无法在构造函数体内赋值。
建议始终保持初始化列表中的顺序与成员变量声明顺序一致,以避免潜在的未定义行为。
1.6 常见错误
(1).忘记初始化引用或常量成员
引用类型和常量成员必须通过初始化列表进行初始化,否则编译器会报错。
(2).初始化顺序不一致
初始化顺序与成员变量声明顺序不一致时,可能导致未定义行为,尤其在成员变量之间存在依赖关系时更需谨慎。
二、类型转换
2.1 C++ 中的类型转换概述
在C++中,类型转换是一个非常强大的功能。它允许我们在不同类型之间进行转换。在类中,类型转换通常通过构造函数或者类型转换运算符来实现。C++支持隐式和显式类型转换,这在开发复杂系统时非常有用。
2.2 隐式类型转换
如果一个类有一个接受单个参数的构造函数,那么该类的对象可以通过传递该类型的参数进行隐式构造。例如,假设我们有一个类A
,它有一个接受int
类型参数的构造函数。
class A {
public:A(int a) : _a(a) {}void print() {cout << "A: " << _a << endl;}
private:int _a;
};
int main() {A obj = 10; // 隐式类型转换:int 被转换为 Aobj.print(); // 输出 A: 10
}
在上面的代码中,整数10
被隐式地转换为A
类的对象,并调用了A
的构造函数。这个过程称为隐式类型转换。然而,隐式类型转换可能会导致意外的行为,因此我们通常希望控制这种行为。
2.3 使用explicit
关键字阻止隐式类型转换
为了避免隐式类型转换导致的错误,我们可以使用explicit
关键字来标记构造函数,这样该构造函数将不再允许隐式转换。
class A {
public:explicit A(int a) : _a(a) {}void print() {cout << "A: " << _a << endl;}
private:int _a;
};
int main() {// A obj = 10; // 编译错误,因为构造函数是 explicit 的A obj(10); // 必须显式调用构造函数obj.print();
}
在这个例子中,explicit
关键字阻止了int
到A
的隐式转换,因此必须通过显式地调用构造函数来创建对象。
2.4 类型转换运算符
除了构造函数,C++还支持类型转换运算符,它允许我们将类类型的对象转换为其他类型。类型转换运算符的定义形式如下:
operator 类型() {// 转换逻辑
}
例如:class A {
public:A(int a) : _a(a) {}// 定义 int 类型的转换运算符operator int() {return _a;}
private:int _a;
};
int main() {A obj(10);int num = obj; // 隐式调用转换运算符,将 A 对象转换为 intcout << "num: " << num << endl; // 输出 num: 10
}
在上面的代码中,类A
提供了一个将A
对象转换为int
的运算符,因此可以直接将A
对象赋值给一个int
类型的变量。
三、静态成员 (static
)
3.1 静态成员变量
static
成员变量是属于类本身的,而不是类的某个对象。也就是说,所有类的对象都共享同一个static
成员变量。静态成员变量在类的所有对象之间共享,并且必须在类外部进行初始化。
示例代码:
class Counter {
public:Counter() { ++_count; }~Counter() { --_count; }static int getCount() { return _count; }
private:static int _count; // 静态成员变量
};
int Counter::_count = 0; // 必须在类外初始化
int main() {Counter c1, c2;cout << "Current count: " << Counter::getCount() << endl; // 输出 2return 0;
}
在这个例子中,_count
是一个静态成员变量,用来计数创建的对象数量。由于它是静态的,所有Counter
对象共享同一个计数器。
3.2 静态成员函数
静态成员函数与普通成员函数的主要区别在于,它们不能访问非静态的成员变量或成员函数。因为静态成员函数没有this
指针,不能与具体的对象绑定。
示例代码:
class Example {
public:static void staticFunc() {cout << "This is a static function." << endl;}
};int main() {Example::staticFunc(); // 静态成员函数可以通过类名直接调用return 0;
}
在上面的例子中,静态成员函数staticFunc
是通过类名直接调用的,而不需要通过对象。
四、友元(Friend)
4.1 什么是友元?
C++中的友元(friend)是一种特殊机制,它允许一个函数或类访问另一个类的私有成员。通常,类的私有成员只能通过公共接口(如公共成员函数)访问,但有时候我们希望让另一个类或函数直接访问这些私有数据。友元就是为此设计的。
友元函数:允许某个外部函数访问类的私有和保护成员。
友元类:允许另一个类访问当前类的私有和保护成员。
4.2 友元函数的使用
友元函数并不是类的成员函数,但它有权访问类的私有成员。友元函数在类内部通过friend
关键字声明。
class Box {friend void showBox(const Box& b); // 友元函数声明
private:int _length = 10;int _width = 5;
};void showBox(const Box& b) {std::cout << "Box length: " << b._length << ", width: " << b._width << std::endl;
}int main() {Box b;showBox(b); // 输出:Box length: 10, width: 5return 0;
}
4.3 友元类的使用
友元类中的所有成员函数都可以访问另一个类的私有成员。友元类的关系是单向的,即A类是B类的友元,但B类并不能访问A类的私有成员。
class Engine;class Car {friend class Engine; // Engine 是 Car 的友元类
private:int _horsePower = 300;
};class Engine {
public:void showHorsePower(const Car& c) {std::cout << "Car's horsepower: " << c._horsePower << std::endl; // 访问 Car 的私有成员}
};int main() {Car myCar;Engine myEngine;myEngine.showHorsePower(myCar); // 输出:Car's horsepower: 300return 0;
}
4.4 友元的优缺点
友元提供了一种方便的方式来访问类的私有数据,增加了类之间的合作性。然而,它也会破坏类的封装性和数据安全性。因此,使用友元时要慎重,不宜过度使用。
五、内部类
内部类是定义在另一个类中的类。它通常用于封装两者之间的紧密关系,外部类可以控制内部类的行为。内部类在外部类中定义时,默认情况下可以访问外部类的私有成员。
示例代码:
class Outer {
private:int _data = 42;
public:class Inner {public:void show(const Outer& o) {cout << "Outer::_data = " << o._data << endl; // 访问外部类的私有成员}};
};int main() {Outer outer;Outer::Inner inner;inner.show(outer); // 输出 Outer::_data = 42return 0;
}
在这个例子中,Inner
类是Outer
类的内部类,它可以访问Outer
类的私有成员_data
。
六、匿名对象
匿名对象是没有名字的临时对象,它们的生命周期非常短暂,只在当前语句中存在。匿名对象经常用于简化临时操作,不需要为它们显式命名。
示例代码:
class A { public:A() { cout << "A()" << endl; }~A() { cout << "~A()" << endl; } };int main() {A(); // 创建匿名对象return 0; // 匿名对象在这一行结束时自动销毁 }
在上面的代码中,匿名对象在创建后立即被销毁。匿名对象的使用使代码更简洁,但需要注意它们的生命周期。
七、对象拷贝时的编译器优化
在C++中,编译器会进行一些优化,减少对象拷贝次数,从而提高性能。这些优化包括返回值优化(RVO)和拷贝省略。当一个函数返回一个对象时,编译器会尝试直接构造返回值,而不是先构造临时对象再拷贝。
示例代码:
class A {
public:A() { cout << "A()" << endl; }A(const A&) { cout << "A(const A&)" << endl; }~A() { cout << "~A()" << endl; }
};A createObject() {A obj;return obj; // 返回时优化
}int main() {A a = createObject(); // 返回值优化return 0;
}
在这个例子中,编译器会进行优化,避免多次调用拷贝构造函数。
总结
本篇博客详细介绍了C++类与对象的高级特性,包括构造函数的初始化列表、类型转换、静态成员、友元、内部类、匿名对象以及对象拷贝时的编译器优化。这些特性不仅增强了代码的灵活性,也帮助开发者编写出更加高效的程序。理解这些内容将大大提升你对C++面向对象编程的理解能力,同时为编写健壮的应用程序奠定基础。
希望本文的详细讲解对你有帮助!如果有任何疑问或需要进一步的解释,欢迎在评论区留言讨论。
相关文章:

【C++篇】深度解析类与对象(下)
引言 在上一篇博客中,我们学习了C的基础类与对象概念,包括类的定义、对象的使用和构造函数的作用。在这一篇,我们将深入探讨C类的一些重要特性,如构造函数的高级用法、类型转换、static成员、友元、内部类、匿名对象,…...

【gRPC】什么是RPC——介绍一下RPC
说起RPC,博主使用CPP手搓了一个RPC项目,RPC简单来说,就是远程过程调用:我们一般在本地传入数据进行执行函数,然后返回一个结果;当我们使用RPC之后,我们可以将函数的执行过程放到另外一个服务器上…...
谈谈你对AQS的理解
AQS 是多线程同步器,它是 JUC 包中多个组件的底层实现,如 Lock、CountDownLatch、Semaphore等都用到了AQS。 从本质上来说,AQS 提供了两种锁机制,分别是排它锁,和共享锁。 排它锁,就是存在多线程竞争同一…...
Bitcoin全节点搭建
1. wget https://bitcoincore.org/bin/bitcoin-core-0.20.1/bitcoin-0.20.1-x86_64-linux-gnu.tar.gz 2.tar -xzvf bitcoin-0.20.1-x86_64-linux-gnu.tar.gz mv bitcoin-0.20.1 bitcoin 3.创建配置文件(bitcoin.conf) mkdir -p /btc_data mkdir ~/.b…...

【mysql进阶】4-6. InnoDB 磁盘文件
InnoDB 磁盘⽂件 1 InnoDB存储引擎包含哪些磁盘⽂件? 🔍 分析过程 ✅ 解答问题 InnoDB的磁盘⽂件主要是表空间⽂件和其他⽂件,表空间包括:系统表空间、独⽴表空间、通⽤表空间、临时表空间和撤销表空间;其他⽂件有重做…...

HexForge:一款用于扩展安全汇编和十六进制视图的IDA插件
关于HexForge HexForge是一款用于扩展安全汇编和十六进制视图的IDA插件,在该工具的帮助下,广大研究人员可以方便地直接从 IDA Pro 界面数据解码、解密或执行安全数据审计任务。 功能介绍 1、从 IDA 的反汇编或十六进制视图复制原始十六进制;…...

WORFBENCH:一个创新的评估基准,目的是全面测试大型语言模型在生成复杂工作流 方面的性能。
2024-10-10,由浙江大学和阿里巴巴集团联合创建的WORFBENCH,一个用于评估大型语言模型(LLMs)生成工作流能力的基准测试。它包含了一系列的测试和评估协议,用于量化和分析LLMs在处理复杂任务时分解问题和规划执行步骤的能力。WORFBE…...

SpringBoot 集成 Activiti 7 工作流引擎
一. 版本信息 IntelliJ IDEA 2023.3.6JDK 17Activiti 7 二. IDEA依赖插件安装 安装BPM流程图插件,如果IDEA的版本超过2020,则不支持actiBPM插件。我的IDEA是2023版本我装的是 Activiti BPMN visualizer 插件。 在Plugins 搜索 Activiti BPMN visualizer 安装创建…...

UVM初学篇 -(22)UVM field_automation 域的自动化机制
field_automation机制是域的自动化的机制,这个机制的最大的优点是可以对一些变量进行批量的处理,比如对象拷贝、克隆、打印之类的变量。 一、 成员变量的注册 使用field_automation机制首先要用uvm_field 系列宏完成变量的注册,类中的成员变…...
STL二分查找
本课主要介绍容器部分里面的二分查找函数。涉及的函数有 3 个,这 3 个函数的强两个输入参数都和迭代器有关,或者说参数是可以迭代的,而第三个参数则是你要查找的值。 1. binary_search binary_search 的返回结果是 bool 值,如果找…...

啤酒游戏—企业经营决策沙盘
感谢黄浦区文华学院的邀请,今年是为南房集团开展系统思考培训的第二年。我们现在为客户设计的一整年系统思考训练中,会将系统环路结构图与真实议题研讨作为前置内容,让大家在理解整体框架后,再体验麻省理工学院系统动力学著名的“…...

尚硅谷-react教程-求和案例-@redux-devtools/extension 开发者工具使用-笔记
## 7.求和案例_react-redux开发者工具的使用(1).npm install redux-devtools/extension(2).store中进行配置import { composeWithDevTools } from redux-devtools/extension;export default createStore(allReducer,composeWithDevTools(applyMiddleware(thunk))) src/redux/s…...

【动手学强化学习】part2-动态规划算法
阐述、总结【动手学强化学习】章节内容的学习情况,复现并理解代码。 文章目录 一、什么是动态规划?1.1概念1.2适用条件 二、算法示例2.1问题建模2.2策略迭代(policyiteration)算法2.2.1伪代码2.2.2完整代码2.2.3运行结果2.2.4代码…...

【python爬虫实战】爬取全年天气数据并做数据可视化分析!附源码
由于篇幅限制,无法展示完整代码,需要的朋友可在下方获取!100%免费。 一、主题式网络爬虫设计方案 1. 主题式网络爬虫名称:天气预报爬取数据与可视化数据 2. 主题式网络爬虫爬取的内容与数据特征分析: - 爬取内容&am…...

初识Linux · 动静态库(incomplete)
目录 前言: 静态库 动态库 前言: 继上文,我们从磁盘的理解,到了文件系统框架的基本搭建,再到软硬链接部分,我们开始逐渐理解了为什么运行程序需要./a.out了,这个前面的.是什么我们也知道了。…...

华为OD机试 - 匿名信(Java 2024 E卷 100分)
华为OD机试 2024E卷题库疯狂收录中,刷题点这里 专栏导读 本专栏收录于《华为OD机试(JAVA)真题(E卷D卷A卷B卷C卷)》。 刷的越多,抽中的概率越大,私信哪吒,备注华为OD,加…...

通过rancher2.7管理k8s1.24及1.24以上版本的k8s集群
目录 初始化实验环境 安装Rancher 登录Rancher平台 通过Rancher2.7管理已存在的k8s最新版集群 文档中的YAML文件配置直接复制粘贴可能存在格式错误,故实验中所需要的YAML文件以及本地包均打包至网盘. 链接:https://pan.baidu.com/s/1oYX4eGoBtW_R-7i…...

text-align的属性justify
text-align常用的属性是left、center、right,具体的可参考css解释,今天重点记录的对象是justify justify 可以使文本的两端都对齐在两端对齐文本中,文本行的左右两端都放在父元素的内边界上。然后,调整单词和字母间的间隔&#x…...

使用python自制桌面宠物,好玩!——枫原万叶桌宠,可以直接打包成exe去跟朋友炫耀。。。
大家好,我是小黄。 今天我们使用python实现一个桌面宠物。只需要gif动态图片就行。超级简单容易上手。 #完整源代码可在下方图片免费获取 一:下载相关的库文件。 我们本次使用到的库文件为:tkinter和pyautogui 下载命令: pip…...

使用 ASP.NET Core 8.0 创建最小 API
构建最小 API,以创建具有最小依赖项的 HTTP API。 它们非常适合需要在 ASP.NET Core 中仅包括最少文件、功能和依赖项的微服务和应用。 本教程介绍使用 ASP.NET Core 生成最小 API 的基础知识。 在 ASP.NET Core 中创建 API 的另一种方法是使用控制器。 有关在最小 …...

MPNet:旋转机械轻量化故障诊断模型详解python代码复现
目录 一、问题背景与挑战 二、MPNet核心架构 2.1 多分支特征融合模块(MBFM) 2.2 残差注意力金字塔模块(RAPM) 2.2.1 空间金字塔注意力(SPA) 2.2.2 金字塔残差块(PRBlock) 2.3 分类器设计 三、关键技术突破 3.1 多尺度特征融合 3.2 轻量化设计策略 3.3 抗噪声…...

【网络安全产品大调研系列】2. 体验漏洞扫描
前言 2023 年漏洞扫描服务市场规模预计为 3.06(十亿美元)。漏洞扫描服务市场行业预计将从 2024 年的 3.48(十亿美元)增长到 2032 年的 9.54(十亿美元)。预测期内漏洞扫描服务市场 CAGR(增长率&…...
【C++从零实现Json-Rpc框架】第六弹 —— 服务端模块划分
一、项目背景回顾 前五弹完成了Json-Rpc协议解析、请求处理、客户端调用等基础模块搭建。 本弹重点聚焦于服务端的模块划分与架构设计,提升代码结构的可维护性与扩展性。 二、服务端模块设计目标 高内聚低耦合:各模块职责清晰,便于独立开发…...

mysql已经安装,但是通过rpm -q 没有找mysql相关的已安装包
文章目录 现象:mysql已经安装,但是通过rpm -q 没有找mysql相关的已安装包遇到 rpm 命令找不到已经安装的 MySQL 包时,可能是因为以下几个原因:1.MySQL 不是通过 RPM 包安装的2.RPM 数据库损坏3.使用了不同的包名或路径4.使用其他包…...

AI书签管理工具开发全记录(十九):嵌入资源处理
1.前言 📝 在上一篇文章中,我们完成了书签的导入导出功能。本篇文章我们研究如何处理嵌入资源,方便后续将资源打包到一个可执行文件中。 2.embed介绍 🎯 Go 1.16 引入了革命性的 embed 包,彻底改变了静态资源管理的…...
重启Eureka集群中的节点,对已经注册的服务有什么影响
先看答案,如果正确地操作,重启Eureka集群中的节点,对已经注册的服务影响非常小,甚至可以做到无感知。 但如果操作不当,可能会引发短暂的服务发现问题。 下面我们从Eureka的核心工作原理来详细分析这个问题。 Eureka的…...

技术栈RabbitMq的介绍和使用
目录 1. 什么是消息队列?2. 消息队列的优点3. RabbitMQ 消息队列概述4. RabbitMQ 安装5. Exchange 四种类型5.1 direct 精准匹配5.2 fanout 广播5.3 topic 正则匹配 6. RabbitMQ 队列模式6.1 简单队列模式6.2 工作队列模式6.3 发布/订阅模式6.4 路由模式6.5 主题模式…...
【Go语言基础【13】】函数、闭包、方法
文章目录 零、概述一、函数基础1、函数基础概念2、参数传递机制3、返回值特性3.1. 多返回值3.2. 命名返回值3.3. 错误处理 二、函数类型与高阶函数1. 函数类型定义2. 高阶函数(函数作为参数、返回值) 三、匿名函数与闭包1. 匿名函数(Lambda函…...
Java求职者面试指南:计算机基础与源码原理深度解析
Java求职者面试指南:计算机基础与源码原理深度解析 第一轮提问:基础概念问题 1. 请解释什么是进程和线程的区别? 面试官:进程是程序的一次执行过程,是系统进行资源分配和调度的基本单位;而线程是进程中的…...

【从零开始学习JVM | 第四篇】类加载器和双亲委派机制(高频面试题)
前言: 双亲委派机制对于面试这块来说非常重要,在实际开发中也是经常遇见需要打破双亲委派的需求,今天我们一起来探索一下什么是双亲委派机制,在此之前我们先介绍一下类的加载器。 目录 编辑 前言: 类加载器 1. …...