【C++】特殊类设计、单例模式与类型转换
目录
一、设计一个类不能被拷贝
(一)C++98
(二)C++11
二、设计一个类只能在堆上创建对象
(一)将构造函数私有化,对外提供接口
(二)将析构函数私有化
三、设计一个类只能在栈上创建对象
四、设计一个类不能被继承
(一)C++98
(二)C++11
五、设计一个类只能创建一个对象(单例模式)
(一)饿汉模式
(二)懒汉模式
六、类型转换
(一)C语言类型转换
(二)C++新增四种强制类型转换
1、static_cast
2、reinterpret_cast
3、const_cast
4、dynamic_cast
一、设计一个类不能被拷贝
(一)C++98
class banCopy
{
private:banCopy(const banCopy& bc);banCopy& operator=(const banCopy& bc);
};
通过将拷贝构造以及赋值重载私有化使得外部不能调用拷贝构造以及赋值重载。但是对于内部而言仍可以进行类的拷贝。
(二)C++11
class banCopy
{banCopy(const banCopy& bc) = delete;banCopy& operator=(const banCopy& bc) = delete;
};
通过使用关键字直接删除相关函数。
二、设计一个类只能在堆上创建对象
(一)将构造函数私有化,对外提供接口
class heapOnly
{
public:static heapOnly* createObj(){return new heapOnly;}
private:heapOnly() {};heapOnly(const heapOnly& h) = delete;heapOnly& operator = (const heapOnly& h) = delete;
};
首先将构造函数私有化,可以禁止在栈帧以及静态区创建对象,专门提供创建堆上对象的接口。同时也需要将接口设置为静态成员函数,使得无需对象也可以直接调用该接口。
heapOnly* ph = heapOnly::createObj();
需要注意的是,也要将拷贝构造与赋值重载删除掉,如果不进行删除,仍然可以通过拷贝对象生成在栈帧或者静态区的对象。
//若不禁用拷贝函数,即可通过拷贝仍在栈帧上生成对象
heapOnly* ph = heapOnly::createObj();
heapOnly h(*ph);
(二)将析构函数私有化
class heapOnly
{
public:heapOnly() { };void destoy(){this->~heapOnly();}
private:~heapOnly() { };
};
利用类与对象的特性:局部对象出作用域会自动调用析构函数释放资源的特性,将析构函数私有化使得在栈上或静态区上开辟的对象会因此导致编译错误,只能在堆上开辟对象。
因为在堆上开辟的对象需要手动 delete 释放资源,而使用 delete 关键字仍然会调用析构函数(私有化)而导致编译报错,因此可以提供释放资源的接口来实现资源的释放。
三、设计一个类只能在栈上创建对象
class stackOnly
{public:static stackOnly createObj(){return stackOnly();}
private:stackOnly() { }
};
通过构造函数私有化并提供创建对象接口实现只在栈帧上开辟空间。因此使用 new 生成对象会自动调用构造函数,又因构造函数被私有化了因此使用 new 关键字生成对象会编译报错,故禁止了在堆上开辟空间。
但上述方法并不能禁用从静态区生成对象,仍然可以通过拷贝在静态区生成对象。
static stackOnly so = stackOnly::createObj();
如果想彻底解决以上问题则需要禁用拷贝构造,但是如果禁用了拷贝构造,就不能从 createObj() 返回生成对象了,只能生成临时对象或是临时对象的引用,但是不能修改。
stackOnly::createObj(); //临时对象
const stackOnly& so = stackOnly::createObj(); //临时对象的引用
四、设计一个类不能被继承
(一)C++98
class finalClass
{
public:static finalClass CreateObj(){return finalClass();}
private:finalClass() { }
};
因为子类构造函数会自动调用父类构造函数完成对父类变量的初始化,通过将父类构造函数私有化,使得子类无法调用父类的构造函数,因此使得该类无法被继承。
(二)C++11
class FinalClass final
{
};
C++11直接使用 final 关键字使得该类无法被继承。
五、设计一个类只能创建一个对象(单例模式)
(一)饿汉模式
class InfoSingleton
{
public://返回私有静态成员static InfoSingleton& GetInstance(){return _sins;}void insert(string name, int salary){_um[name] = salary;}void Print(){for (auto& e : _um){cout << e.first << ":" << e.second << endl;}cout << endl;}
private://构造函数私有化InfoSingleton(){}//删除拷贝构造以防新建对象InfoSingleton(const InfoSingleton& sins) = delete;InfoSingleton& operator=(const InfoSingleton& sins) = delete;//示例功能 无特殊含义unordered_map<string, int> _um;//定义静态类型成员static InfoSingleton _sins;
};InfoSingleton InfoSingleton::_sins; //初始化静态变量int main()
{//下列代码可证明返回的是同一个对象InfoSingleton& sins1 = InfoSingleton::GetInstance();sins1.insert("张三", 15000);sins1.insert("李四", 20000);sins1.insert("王五", 10000);sins1.insert("赵六", 30000);sins1.Print();InfoSingleton& sins2 = InfoSingleton::GetInstance();sins2.insert("赵六", 20000);sins2.Print();return 0;
}
以上是利用所有对象公用一个类静态成员的特性,通过私有构造函数以及删除拷贝函数给出静态类型成员变量的静态接口函数,使得该类只能通过该接口返回静态成员对象。
因为静态成员变量需要在类外进行初始化,而在初始化的同时会自动调用构造函数进行初始化。因此该静态成员对象的初始化,也就是调用构造函数会在main函数之前。
InfoSingleton InfoSingleton::_sins; //初始化静态变量
饿汉模式的特点:
1、 单例对象初始化时,数据太多会导致启动慢;
2、如果多个单例类有初始化的依赖关系,饿汉模式无法控制。例如A和B都是单例类,因为B的启动依赖A,所以需要先初始化A,再初始化B,但是饿汉模式无法控制对象的初始化顺序;
3、饿汉模式创建的对象不会有线程安全问题,因为该模式的对象在main函数之前已经被创建好了,而mian函数之前线程都没启动。
(二)懒汉模式
class InfoSingleton
{public://返回私有静态成员static InfoSingleton& GetInstance(){if (_sins == nullptr){_sins = new InfoSingleton;}return *_sins;}void insert(string name, int salary){_um[name] = salary;}void Print(){for (auto& e : _um){cout << e.first << ":" << e.second << endl;}cout << endl;}
private://构造函数私有化InfoSingleton(){}//删除拷贝构造以防新建对象InfoSingleton(const InfoSingleton& sins) = delete;InfoSingleton& operator=(const InfoSingleton& sins) = delete;//示例功能 无特殊含义unordered_map<string, int> _um;//定义静态类型成员static InfoSingleton* _sins;
};
InfoSingleton* InfoSingleton::_sins = nullptr; //初始化静态变量int main()
{//下列代码可证明返回的是同一个对象InfoSingleton& sins1 = InfoSingleton::GetInstance();sins1.insert("张三", 15000);sins1.insert("李四", 20000);sins1.insert("王五", 10000);sins1.insert("赵六", 30000);sins1.Print();InfoSingleton& sins2 = InfoSingleton::GetInstance();sins2.insert("赵六", 20000);sins2.Print();return 0;
}
同样的,私有构造函数以及删除拷贝函数并提供生成对象接口都是为了单例模式做准备,与饿汉模式类同。
与饿汉模式不同的是,懒汉模式封装的是静态对象指针,也就是在 createObj() 函数第一次进入时会在堆区新建一个对象,而之后的进入则是返回该对象,以此实现只能生成一个对象。
但是以上的代码存在线程安全问题,多个线程进入 createObj() 函数可能会导致多个线程都会在堆上新建对象导致内存泄漏,因此需要对以上接口进行修改。
class InfoSingleton
{public://返回私有静态成员static InfoSingleton& GetInstance(){if (_sins == nullptr){_mutex.lock();if (_sins == nullptr){_sins = new InfoSingleton;}_mutex.unlock();}return *_sins;}
private:static mutex _mutex;
};
将 _sins 的检查以及赋值进行加锁保护,这里进行了两层 if 进行条件判断。
如果只有内部一层 if 判断,会导致每个线程进入该函数都会先加锁,进行判断操作后再解决,降低了系统性能。
在外层再加一个 if 判断,当第一个线程进入时正常加锁判断操作,但是当之后的线程进入以后,再遇到第一个 if 判断后因为此时 _sins 不为空而无需进行加锁判断了,直接返回 _sins 即可,提高了性能效率。
懒汉模式的特点:
1、对象在main函数之后才会创建;
2、可以主动控制对象的创建时机。
3、创建对象时存在线程安全问题,因此需要用户进行保护控制。
六、类型转换
(一)C语言类型转换
在C语言中,如果赋值运算符两边类型不同,或是函数形参实参类型不同,或是函数返回值和接收值类型不同时等情况都会发生类型转换。C语言中共有两种形式的类型转换:显式类型转换或是隐式类型转换。
int main()
{int a = 0;//隐式类型转换double b = a;int* pa = &a;//显示类型转换int c = (int)pa;return 0;
}
C语言的类型转换可能会存在一些问题,例如精度丢失或是隐式转换可能带来一些隐藏的bug等,因此C++提出了新的类型转换方式。
(二)C++新增四种强制类型转换
1、static_cast
static_cast 常用于非多态类型的转换,在C语言中能隐式转换的都可以使用 static_cast 进行转换,其主要用于相近类型转换。
int main()
{double a = 1.1;int b = static_cast<int>(a);std::cout << b << std::endl;return 0;
}
2、reinterpret_cast
reinterpret_cast 用于将一种类型转换为另一种不同的类型,常用于不相关类型的转换。
int main()
{int a = 1;int* pa = &a;//这里如果使用 static_cast 会编译报错int b = reinterpret_cast<int>(pa);std::cout << b << std::endl;return 0;
}
3、const_cast
const_cast 用于对具有 const 属性的变量进行转换,常用于删除变量的const属性,方便赋值。
int main()
{//volatile const int a = 1;const int a = 1;int* b = const_cast<int*>(&a);++(*b);std::cout << a << std::endl << *b << std::endl;return 0;
}
以上代码运行结果为 1 2,使用const_cast 取消了变量 a 的常量属性,但指针 b 仍然指向变量 a 在内存中的位置,因此可以通过指针 b 修改内存中 a 的值。
对于最后的运行结果在不同平台下的运行结果可能不同,因为编译器对 const 变量会有优化,认为 const 变量在运行中不会改变,因此将 const 变量存入寄存器或告诉缓存中,方便访问读取。而通过指针 b 修改的值实际是修改了于内存中的值,因此打印结果不同。
可以使用 volatile 关键字保持内存可见性,编译器将取消以上优化仍从内存读取 const 变量,因此加入关键字后打印输出的值相同。
4、dynamic_cast
以上三种类型转换实际在C语言中已经存在,只是将其更加规范化。
dynamic_cast 用于将一个父类的指针或引用转换为子类对象的指针或引用。
向上转型:子类对象指针或引用 -> 父类对象指针或引用 (无需进行转换,发生切片)
向下转型:父类对象指针或引用 -> 子类对象指针或引用 (使用 dynamic_cast 进行类型转换)
需要注意的是:dynamic_cast 只能用于父类含有虚函数的类,如果转换失败会返回0。
class A
{
public:virtual void func() { }
};
class B : public A
{
};
void function(A* pa)
{B* pb1 = static_cast<B*>(pa);B* pb2 = dynamic_cast<B*>(pa);std::cout << "pb1:" << pb1 << std::endl;std::cout << "pb2:" << pb2 << std::endl;
}
int main()
{A a;B b;function(&a);function(&b);return 0;
}
以下是上述代码的运行结果,我们来简要分析以下:
实际上 function() 函数是一个多态调用。如果传入的是类型为 B 的对象,无论是 static_cast 还是 dynamic_cast 转换都可以成功,因为函数参数指针 pa 本身指向的就是类型为 B 的对象,因此可以正常使用转换后的指针。
但是当传入的是类型为 A 的对象, 我们可以从图可知, static_cast 转换成功了但是 dynamic_cast 转换失败指向为0,而指向为0这也符合 dynamic_cast 的特性。虽然 static_cast 转换成功了,但是在使用该转换后的指针存在着极大的风险会造成未定义行为。
在以上场景中,相较于 static_cast, dynamic_cast 相当于多了一层检查,如果不能确保能够安全转换,那不建议使用 static_cast 进行强制类型转换。
相关文章:

【C++】特殊类设计、单例模式与类型转换
目录 一、设计一个类不能被拷贝 (一)C98 (二)C11 二、设计一个类只能在堆上创建对象 (一)将构造函数私有化,对外提供接口 (二)将析构函数私有化 三、设计一个类只…...

scratch七彩六边形 2024年12月scratch三级真题 中国电子学会 图形化编程 scratch三级真题和答案解析
目录 scratch七彩六边形 一、题目要求 1、准备工作 2、功能实现 二、案例分析 1、角色分析 2、背景分析 3、前期准备 三、解题思路 1、思路分析 2、详细过程 四、程序编写 五、考点分析 六、推荐资料 1、入门基础 2、蓝桥杯比赛 3、考级资料 4、视频课程 5、…...

代码随想录刷题day16|(哈希表篇)349.两个数组的交集
目录 一、哈希表理论基础 二、集合set在哈希法中的应用 三、相关算法题目 四、相关知识点 1.set集合特点和常用方法 1.1 set集合概述 1.2 set集合特点 1.3 常用方法 2.set集合转换成数组 法1:另新建一个数组 法2:将结果集合转为数组 ▲ 3.数组…...

Synology 群辉NAS安装(6)安装mssql
Synology 群辉NAS安装(6)安装mssql 写在前面mssql 2019:成功安装说明,这个最终成功了 mssql 2022没有成功1. pull image2.启动mssql docker container 远程连接 写在前面 mssq是一个重要节点。 这是因为我对mysql没有一丝好感。虽然接触了许…...

2025年美赛B题-结合Logistic阻滞增长模型和SIR传染病模型研究旅游可持续性-成品论文
模型设计思路与创新点: 建模的时候应该先确定我们需要建立什么类的模型?优化类还是统计类?这个题需要大量的数据分析,因此我们可以建立一个统计学模型。 统计学建模思路:观察规律,建立模型,参…...

Hook 函数
什么是hook函数? 在计算机编程中,hook函数是指在特定的事件发生时被调用的函数,用于在事件发生前或后进行一些特定的操作。通常,hook函数作为回调函数被注册到事件处理器中,当事件发生时,事件处理器会自动…...

蓝桥杯模拟算法:蛇形方阵
P5731 【深基5.习6】蛇形方阵 - 洛谷 | 计算机科学教育新生态 我们只要定义两个方向向量数组,这种问题就可以迎刃而解了 比如我们是4的话,我们从左向右开始存,1,2,3,4 到5的时候y就大于4了就是越界了&…...

DeepSeek-R1解读:纯强化学习,模型推理能力提升的新范式?
DeepSeek-R1解读:纯强化学习,模型推理能力提升的新范式? 1. Impressive Points2. 纯强化学习,LLM推理能力提升新范式?2.1 DeepSeek-R1-Zero2.2 DeepSeek-R1 3. 端侧模型能力提升:蒸馏>强化学习 1. Impre…...

深度解析:基于Vue 3的教育管理系统架构设计与优化实践
一、项目架构分析 1. 技术栈全景 项目采用 Vue 3 TypeScript Tailwind CSS 技术组合,体现了现代前端开发的三大趋势: 响应式编程:通过Vue 3的Composition API实现细粒度响应 类型安全:约60%的组件采用TypeScript编写 原子化…...

【PyTorch】3.张量类型转换
个人主页:Icomi 在深度学习蓬勃发展的当下,PyTorch 是不可或缺的工具。它作为强大的深度学习框架,为构建和训练神经网络提供了高效且灵活的平台。神经网络作为人工智能的核心技术,能够处理复杂的数据模式。通过 PyTorch࿰…...

Spring Boot整合JavaMail实现邮件发送
一. 发送邮件原理 发件人【设置授权码】 - SMTP协议【Simple Mail TransferProtocol - 是一种提供可靠且有效的电子邮件传输的协议】 - 收件人 二. 获取授权码 开通POP3/SMTP,获取授权码 授权码是QQ邮箱推出的,用于登录第三方客户端的专用密码。适用…...

字节跳动发布UI-TARS,超越GPT-4o和Claude,能接管电脑完成复杂任务
每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗?订阅我们的简报,深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同,从行业内部的深度分析和实用指南中受益。不要错过这个机会,成为AI领…...

数据的秘密:如何用大数据分析挖掘商业价值
数据的秘密:如何用大数据分析挖掘商业价值 在这个数据爆炸的时代,我们每天都在产生、存储和处理着海量的数据。然而,仅仅拥有数据并不等于拥有价值。就像拥有一座金矿,不开采和提炼,最终只是一堆毫无用处的石头。如何…...

OAuth1和OAuth2授权协议
OAuth 1 授权协议 1. 概述 OAuth1 是 OAuth 标准的第一个正式版本,它通过 签名和令牌 的方式,实现用户授权第三方访问其资源的功能。在 OAuth1 中,安全性依赖于签名机制,无需传递用户密码。 2. 核心特性 使用 签名(…...

AI学习(vscode+deepseek+cline)
1、网页生成不成功时,直接根据提示让模型替你解决问题 2、http://localhost:3000 拒绝链接时,cmd输入命令InetMgr,网站右键新建-配置你的网页代码物理地址,这里我还输入本机登录名及密码了,并把端口地址由默认80修改为…...

04-机器学习-网页数据抓取
网络爬取(Web Scraping)深度指南 1. 网络爬取全流程设计 一个完整的网络爬取项目通常包含以下步骤: 目标分析: 明确需求:需要哪些数据(如商品价格、评论、图片)?网站结构分析&…...

计网week1+2
计网 一.概念 1.什么是Internet 节点:主机及其运行的应用程序、路由器、交换机 边:通信链路,接入网链路主机连接到互联网的链路,光纤、网输电缆 协议:对等层的实体之间通信要遵守的标准,规定了语法、语义…...

重定向与缓冲区
4种重定向 我们有如下的代码: #include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <string.h>#define FILE_NAME "log.txt"int main() {close(1)…...

练习题 - Django 4.x File 文件上传使用示例和配置方法
在现代的 web 应用开发中,文件上传是一个常见的功能,无论是用户上传头像、上传文档,还是其他类型的文件,处理文件上传都是开发者必须掌握的技能之一。Django 作为一个流行的 Python web 框架,提供了便捷的文件上传功能和配置方法。学习如何在 Django 中实现文件上传,不仅…...

[VSCode] vscode下载安装及安装中文插件详解(附下载链接)
VSCode 是一款由微软开发且跨平台的免费源代码编辑器;该软件支持语法高亮、代码自动补全、代码重构、查看定义功能,并且内置了命令行工具和Git版本控制系统。 下载链接:https://pan.quark.cn/s/3a90aef4b645 提取码:NFy5 通过上面…...

JVM常见知识点
在《深入理解Java虚拟机》一书中,介绍了JVM的相关特性。 1、JVM的内存区域划分 在真实的操作系统中,对于地址空间进行了分区域的设计,由于JVM是仿照真实的机器进行设计的,那么也进行了分区域的设计。核心区域有四个,…...

深入探索 Vue 3 Markdown 编辑器:高级功能与实现
目录 1. 为什么选择 Markdown 编辑器?2. 选择合适的 Markdown 编辑器3. 安装与基本配置安装 配置 Markdown 编辑器代码说明 4. 高级功能实现4.1 实时预览与双向绑定4.2 插入图片和图像上传安装图像上传插件配置图像上传插件 4.3 数学公式支持安装 KaTeX配置 KaTeX 插…...

vscode无法格式化go代码的问题
CTRLshiftp 点击Go:Install/Update Tools 点击全选,OK!...

《Java程序设计》课程考核试卷
一、单项选择题(本大题共10个小题,每小题2分,共20分) 1.下列用来编译Java源文件为字节码文件的工具是( )。 A.java B.javadoc C.jar D.javac 2…...
one-hot (独热编码)
一、目的 假设我们现在需要对猫、 狗、 人这三个类别进行分类。 若以 0 代表猫, 以 1 代表狗, 以 2 代表人,会发现那么猫和狗之间距离为 1, 狗和人之间距离为 1, 而猫和人之间距离为 2。 假设真实标签是猫࿰…...

寒假1.23
题解 web:[极客大挑战 2019]Secret File(文件包含漏洞) 打开链接是一个普通的文字界面 查看一下源代码 发现一个链接,点进去看看 再点一次看看,没什么用 仔细看,有一个问题,当点击./action.ph…...

unity 粒子系统设置触发
1、勾选Triggers选项 2、将作为触发器的物体拉入队列当中,物体上必须挂载collider 3、将想要触发的方式(Inide、Outside、Enter和Exit)选择为”Callback“,其他默认为”Ignore“ 4、Collider Query Mode 设置为All:…...

【C++】类和对象(五)
1、初始化列表 作用:C提供了初始化列表语法,用来初始化属性。 语法: 构造函数():属性1(值1),属性2(值2)...{}示例: #include<i…...

超分辨率体积重建实现术前前列腺MRI和大病理切片组织病理学图像的3D配准
摘要: 磁共振成像(MRI)在前列腺癌诊断和治疗中的应用正在迅速增加。然而,在MRI上识别癌症的存在和范围仍然具有挑战性,导致即使是专家放射科医生在检测结果上也存在高度变异性。提高MRI上的癌症检测能力对于减少这种变异性并最大化MRI的临床效用至关重要。迄今为止,这种改…...

第13章 深入volatile关键字(Java高并发编程详解:多线程与系统设计)
1.并发编程的三个重要特性 并发编程有三个至关重要的特性,分别是原子性、有序性和可见性 1.1 原子性 所谓原子性是指在一次的操作或者多次操作中,要么所有的操作全部都得到了执行并 且不会受到任何因素的干扰而中断,要么所有的操作都不执行…...