当前位置: 首页 > news >正文

【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++】特殊类设计、单例模式与类型转换

目录 一、设计一个类不能被拷贝 &#xff08;一&#xff09;C98 &#xff08;二&#xff09;C11 二、设计一个类只能在堆上创建对象 &#xff08;一&#xff09;将构造函数私有化&#xff0c;对外提供接口 &#xff08;二&#xff09;将析构函数私有化 三、设计一个类只…...

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&#xff1a;另新建一个数组 法2&#xff1a;将结果集合转为数组 ▲ 3.数组…...

Synology 群辉NAS安装(6)安装mssql

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

2025年美赛B题-结合Logistic阻滞增长模型和SIR传染病模型研究旅游可持续性-成品论文

模型设计思路与创新点&#xff1a; 建模的时候应该先确定我们需要建立什么类的模型&#xff1f;优化类还是统计类&#xff1f;这个题需要大量的数据分析&#xff0c;因此我们可以建立一个统计学模型。 统计学建模思路&#xff1a;观察规律&#xff0c;建立模型&#xff0c;参…...

Hook 函数

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

蓝桥杯模拟算法:蛇形方阵

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

DeepSeek-R1解读:纯强化学习,模型推理能力提升的新范式?

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

深度解析:基于Vue 3的教育管理系统架构设计与优化实践

一、项目架构分析 1. 技术栈全景 项目采用 Vue 3 TypeScript Tailwind CSS 技术组合&#xff0c;体现了现代前端开发的三大趋势&#xff1a; 响应式编程&#xff1a;通过Vue 3的Composition API实现细粒度响应 类型安全&#xff1a;约60%的组件采用TypeScript编写 原子化…...

【PyTorch】3.张量类型转换

个人主页&#xff1a;Icomi 在深度学习蓬勃发展的当下&#xff0c;PyTorch 是不可或缺的工具。它作为强大的深度学习框架&#xff0c;为构建和训练神经网络提供了高效且灵活的平台。神经网络作为人工智能的核心技术&#xff0c;能够处理复杂的数据模式。通过 PyTorch&#xff0…...

Spring Boot整合JavaMail实现邮件发送

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

字节跳动发布UI-TARS,超越GPT-4o和Claude,能接管电脑完成复杂任务

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…...

数据的秘密:如何用大数据分析挖掘商业价值

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

OAuth1和OAuth2授权协议

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

AI学习(vscode+deepseek+cline)

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

04-机器学习-网页数据抓取

网络爬取&#xff08;Web Scraping&#xff09;深度指南 1. 网络爬取全流程设计 一个完整的网络爬取项目通常包含以下步骤&#xff1a; 目标分析&#xff1a; 明确需求&#xff1a;需要哪些数据&#xff08;如商品价格、评论、图片&#xff09;&#xff1f;网站结构分析&…...

计网week1+2

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

重定向与缓冲区

4种重定向 我们有如下的代码&#xff1a; #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 是一款由微软开发且跨平台的免费源代码编辑器&#xff1b;该软件支持语法高亮、代码自动补全、代码重构、查看定义功能&#xff0c;并且内置了命令行工具和Git版本控制系统。 下载链接&#xff1a;https://pan.quark.cn/s/3a90aef4b645 提取码&#xff1a;NFy5 通过上面…...

别再手动点菜单了!用这招让Cadence Virtuoso Schematic效率翻倍(附Net高亮快捷键配置)

电路设计效率革命&#xff1a;Cadence Virtuoso Schematic高阶快捷键配置指南 在集成电路设计的浩瀚宇宙中&#xff0c;Cadence Virtuoso如同设计师手中的光刻机&#xff0c;每一次精准操作都直接影响最终芯片的性能与可靠性。然而&#xff0c;当面对数百个晶体管组成的复杂模…...

用STM32CubeMX和HAL库快速上手WS2812B:告别手动计算延时,一键生成驱动框架

基于STM32CubeMX的WS2812B智能灯光控制&#xff1a;从零构建现代化驱动方案在智能硬件和物联网设备快速发展的今天&#xff0c;WS2812B可编程LED灯带因其丰富的色彩表现和简单的单线控制方式&#xff0c;成为创客和工程师们最喜爱的显示组件之一。然而&#xff0c;传统的寄存器…...

ThinkPad开机报错0183/0253?别慌,手把手教你搞定EFI变量错误(附BIOS重置教程)

ThinkPad开机报错0183/0253&#xff1f;EFI变量错误全面解决方案当你按下ThinkPad的电源键&#xff0c;期待熟悉的开机画面时&#xff0c;屏幕上却突然跳出一串神秘代码——"0183: Bad CRC of Security Settings in EFI Variable"或"0253: EFI Variable Block D…...

解决Claude Code访问不稳定与Token不足的痛点

&#x1f680; 告别海外账号与网络限制&#xff01;稳定直连全球优质大模型&#xff0c;限时半价接入中。 &#x1f449; 点击领取海量免费额度 解决Claude Code访问不稳定与Token不足的痛点 许多开发者将Claude Code作为日常编程的得力助手&#xff0c;用于代码生成、问题调试…...

《我看见的世界:李飞飞自传》第1-6章阅读笔记:从移民少女到AI教母的“看见“之旅

前言 当我们谈论人工智能时&#xff0c;我们谈论的是算法、数据、算力&#xff0c;是那些冰冷的代码和复杂的模型。但在《我看见的世界&#xff1a;李飞飞自传》中&#xff0c;李飞飞用她独特的视角告诉我们&#xff1a;AI的本质&#xff0c;是人类对"看见"世界的渴望…...

关联规则挖掘在Calabi-Yau流形Hodge数分析中的应用与复现

1. 项目概述&#xff1a;当数据挖掘遇见高维几何在理论物理和代数几何的交叉领域&#xff0c;Calabi-Yau流形一直扮演着核心角色。这些具有特殊拓扑结构的空间&#xff0c;不仅是弦理论中额外维度紧化的关键候选者&#xff0c;其本身丰富的数学性质也吸引着无数研究者。然而&am…...

Keil µVision链接器错误204解决方案

1. 问题现象与背景解析最近在使用Keil Vision进行嵌入式开发时&#xff0c;不少工程师遇到了一个令人头疼的链接器错误。具体表现为编译时出现"FATAL ERROR 204: INVALID KEYWORD"的致命错误&#xff0c;错误位置指向链接器控制文件中的特定行。这个问题在C166和C51两…...

Unity iOS构建报错SDK version is 0的根因与精准修复

1. 这个报错不是Unity在“发脾气”&#xff0c;而是工程配置在“装死”刚接手一个老项目&#xff0c;打开Unity编辑器&#xff0c;点Build Settings准备打包iOS&#xff0c;结果弹出一行红字&#xff1a;“SDK version is 0, cannot build”。我第一反应是——这什么鬼&#xf…...

终极Chrome画中画扩展:如何在浏览器中实现高效视频多任务处理

终极Chrome画中画扩展&#xff1a;如何在浏览器中实现高效视频多任务处理 【免费下载链接】picture-in-picture-chrome-extension 项目地址: https://gitcode.com/gh_mirrors/pi/picture-in-picture-chrome-extension 想要在浏览网页、处理文档的同时继续观看视频内容吗…...

uWSGI目录穿越漏洞CVE-2018-7490深度利用与防御实战

1. 这不是“读文件”那么简单&#xff1a;uWSGI目录穿越在真实攻防链中的定位与误判代价你刚在Vulfocus靶场里跑通了CVE-2018-7490的PoC&#xff0c;用curl "http://target:8080/?p../../../../etc/passwd"成功读出了root:x:0:0:root:/root:/bin/bash&#xff0c;截…...