【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 通过上面…...

【力扣数据库知识手册笔记】索引
索引 索引的优缺点 优点1. 通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性。2. 可以加快数据的检索速度(创建索引的主要原因)。3. 可以加速表和表之间的连接,实现数据的参考完整性。4. 可以在查询过程中,…...
pam_env.so模块配置解析
在PAM(Pluggable Authentication Modules)配置中, /etc/pam.d/su 文件相关配置含义如下: 配置解析 auth required pam_env.so1. 字段分解 字段值说明模块类型auth认证类模块,负责验证用户身份&am…...

【数据分析】R版IntelliGenes用于生物标志物发现的可解释机器学习
禁止商业或二改转载,仅供自学使用,侵权必究,如需截取部分内容请后台联系作者! 文章目录 介绍流程步骤1. 输入数据2. 特征选择3. 模型训练4. I-Genes 评分计算5. 输出结果 IntelliGenesR 安装包1. 特征选择2. 模型训练和评估3. I-Genes 评分计…...

视觉slam十四讲实践部分记录——ch2、ch3
ch2 一、使用g++编译.cpp为可执行文件并运行(P30) g++ helloSLAM.cpp ./a.out运行 二、使用cmake编译 mkdir build cd build cmake .. makeCMakeCache.txt 文件仍然指向旧的目录。这表明在源代码目录中可能还存在旧的 CMakeCache.txt 文件,或者在构建过程中仍然引用了旧的路…...
IP如何挑?2025年海外专线IP如何购买?
你花了时间和预算买了IP,结果IP质量不佳,项目效率低下不说,还可能带来莫名的网络问题,是不是太闹心了?尤其是在面对海外专线IP时,到底怎么才能买到适合自己的呢?所以,挑IP绝对是个技…...

深度学习水论文:mamba+图像增强
🧀当前视觉领域对高效长序列建模需求激增,对Mamba图像增强这方向的研究自然也逐渐火热。原因在于其高效长程建模,以及动态计算优势,在图像质量提升和细节恢复方面有难以替代的作用。 🧀因此短时间内,就有不…...
ubuntu22.04 安装docker 和docker-compose
首先你要确保没有docker环境或者使用命令删掉docker sudo apt-get remove docker docker-engine docker.io containerd runc安装docker 更新软件环境 sudo apt update sudo apt upgrade下载docker依赖和GPG 密钥 # 依赖 apt-get install ca-certificates curl gnupg lsb-rel…...

java高级——高阶函数、如何定义一个函数式接口类似stream流的filter
java高级——高阶函数、stream流 前情提要文章介绍一、函数伊始1.1 合格的函数1.2 有形的函数2. 函数对象2.1 函数对象——行为参数化2.2 函数对象——延迟执行 二、 函数编程语法1. 函数对象表现形式1.1 Lambda表达式1.2 方法引用(Math::max) 2 函数接口…...
用递归算法解锁「子集」问题 —— LeetCode 78题解析
文章目录 一、题目介绍二、递归思路详解:从决策树开始理解三、解法一:二叉决策树 DFS四、解法二:组合式回溯写法(推荐)五、解法对比 递归算法是编程中一种非常强大且常见的思想,它能够优雅地解决很多复杂的…...

VSCode 使用CMake 构建 Qt 5 窗口程序
首先,目录结构如下图: 运行效果: cmake -B build cmake --build build 运行: windeployqt.exe F:\testQt5\build\Debug\app.exe main.cpp #include "mainwindow.h"#include <QAppli...