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

C++:特殊类设计和四种类型转换

一、特殊类设计

1.1 不能被拷贝的类

          拷贝只会放生在两个场景中:拷贝构造函数以及赋值运算符重载,因此想要让一个类禁止拷贝,只需让该类不能调用拷贝构造函数以及赋值运算符重载即可。

C++98:

1、将拷贝构造函数与赋值运算符重载只声明不定义。(防自己人)

        不定义是因为该函数根本不会调用,定义了其实也没有什么意义,不写反而还简单,而且如果定义了就可能导致成员函数进行内部拷贝了。

2、并且将其访问权限设置为私有即可。(防外人)

        如果只声明没有设置成private,用户自己如果在类外定义了,就可以不能禁止拷贝了

class CopyBan
{
// ...
private:
CopyBan(const CopyBan& c);
CopyBan& operator=(const CopyBan& c);
//...
};

C++11:

       C++11扩展delete的用法,delete除了释放new申请的资源外,如果在默认成员函数后跟=delete,表示让编译器删除掉该默认成员函数。

class CopyBan
{
// ...
CopyBan(const CopyBan& c)=delete;
CopyBan& operator=(const CopyBan& c)=delete;
//...
};

1.2 只能在堆上创建对象的类

思路1:

1. 将类的构造函数私有,拷贝构造声明成私有(可以直接delete掉)。防止别人调用拷贝在栈上生成对象。

      注意:拷贝构造可以直接delete掉,但是构造函数不行!!因为我们还需要利用构造函数在堆上创建对象。
2. 提供一个静态的成员函数,在该静态成员函数中完成堆对象的创建。

     注意:这里涉及到的是先有鸡还是先有蛋的问题,因为如果不去创建这个对象就没有办法去调用他的构造函数,但是没有调用构造函数就没有办法创建对象。所以这里必须通过静态成员函数的返回值去构造堆对象。

class HeapOnly
{
public:static HeapOnly* CreateObject(){return new HeapOnly;}
private:HeapOnly(){};HeapOnly(const HeapOnly&) = delete;
};int main()
{HeapOnly*p = HeapOnly::CreateObject();return 0;
}

思路2:

1、相比较于上一种思路把构造函数私有、拷贝构造delete,也可以选择将析构函数给设成私有。

      析构函数设置成私有同样会导致对象无法在栈上进行创建。因为自定义类型在栈帧中销毁的时候会去自动调用他的析构函数,但是因为调不到所以会报错。

2、封装一个destory的成员函数,这样我们可以手动释放堆空间的资源。

      在堆上创建对象是用指针去接受,所以并不影响,但是内存需要我们去手动释放,因此我们需要封装destory的成员函数去调用delete,这样delete可以对应调用到析构函数。 这里有两个方案:一种是设置能静态成员函数(类域调用),一种是设置成普通成员函数(对象自己调用)。

class HeapOnly
{
public://方案1 static void Destroy(HeapOnly* hp){delete hp;}//方案2void Destroy(){delete this;}
private:~HeapOnly() {};
};int main()
{//HeapOnly H; 调不到析构函数,无法创建//静态成员函数释放HeapOnly* ptr = new HeapOnly;HeapOnly::Destroy(ptr);//普通成员函数释放HeapOnly* ptr2 = new HeapOnly;ptr2->Destroy();return 0;
}

1.3 只能在栈上创建对象的类

1、new和delete是全局的运算符重载函数,因此我们只要将这两个给禁用掉,就不会在堆上创建对象。

      需了解具体声明:void* operator new(size_t size) 和 void operator delete(void* p) 。实现类专属的operator new和delete 这样new这个类对象时,operator new就会调用这个,不会调全局的。

	class StackOnly{public:static StackOnly CreateObj(){return StackOnly();}// 禁掉operator new可以把下面用new 调用拷贝构造申请对象给禁掉// StackOnly obj = StackOnly::CreateObj();// StackOnly* ptr3 = new StackOnly(obj);void* operator new(size_t size) = delete;void operator delete(void* p) = delete;private:int _a;};int main(){StackOnly obj;//StackOnly* s = new StackOnly;}

但是没有办法去禁用C语言的相关函数。 

1.4 不能被继承的类

C++98:

构造函数私有化,这样子类调用不到父类的构造函数,无法实现继承

class NonInherit
{
public:
static NonInherit GetInstance()
{
return NonInherit();
}
private:
NonInherit()
{}
};

C++11:

直接用final关键字,可以使得该类无法被继承

class A final
{
// ....
};

1.5 控制可创建对象数量的类

设置一个静态int变量count,每创建一个对象就--count

class singleclass
{
public:static singleclass* getsingleclass(){if (count > 0){count--;return new singleclass();}else{cout << "实例化失败" << endl;return nullptr;}}
private:static int count;singleclass() {};
};
int singleclass::count = 1;int main()
{singleclass* s1 = singleclass::getsingleclass();singleclass* s2 = singleclass::getsingleclass();
}

二、类型转换

2.1 C语言中的类型转换

        在C语言中,如果赋值运算符左右两侧类型不同,或者形参与实参类型不匹配,或者返回值类型与接收返回值类型不一致时,就需要发生类型转化,C语言中总共有两种形式的类型转换:隐式类型转换和显式类型转换。


1、隐式类型转换:编译器在编译阶段自动进行,能转就转,不能转就编译失败。相近类型才可以进行隐式类型转换 比如int和double 他们本质上都是表示数据的大小。

int i = 1;
// 隐式类型转换
double d = i;

 double d=i :首先会产生一个double类型的临时对象接收i,然后将临时对象拷贝给d。

2、显式类型转化:需要用户自己处理。需要有一定的关联性。比如int和int*

int i = 1;
int* p = &i;
// 显示的强制类型转换
int address = (int)p;
printf("%x, %d\n", p, address);
return 0;

缺陷:转换的可视性比较差,所有的转换形式都是以一种相同形式书写,难以跟踪错误的转换

2.2 C++中的类型转换

标准C++为了加强类型转换的可视性,引入了四种命名的强制类型转换操作符

2.2.1 static_cast

       static_cast用于非多态类型的转换(静态转换),编译器隐式执行的任何类型转换都可用(相近类型用static_cast->意义相似的类型)

int main()
{double d = 12.34;int a = static_cast<int>(d);cout << a << endl;return 0;
}

 2.2.2 reinterpret_cast

        reinterpret_cast操作符通常为操作数的位模式提供较低层次的重新解释,用于将一种类型转换为另一种不同的类型(一定的关联,但是意义不相似的的类型用reinterpret_cast

int main()
{double d = 12.34;int a = static_cast<int>(d);cout << a << endl;// 这里使用static_cast会报错,应该使用reinterpret_cast//int *p = static_cast<int*>(a);int* p = reinterpret_cast<int*>(a);return 0;
}

2.2.3 const_cast

       const_cast最常用的用途就是删除变量的const属性,方便做赋值操作(const_cast的类型必须是对象的指针或者引用)

int main()
{const int a = 2;int* p = const_cast<int*>(&a);*p = 3;cout << a << endl;cout << *p << endl;cout << &a << endl;cout << p << endl;//cout的缺陷char ch = 'x';cout << &ch << endl;return 0;
}

思考:为什么*p被修改了,a却没有被修改??

       因为常量被存到寄存器中了,所以其实改变的是内存中的a,但是不是寄存器中的a。这其实是一种优化,如果我们想要去掉这种优化,用volatile关键字(告诉编译器不要优化,直接从内存中读取) 

2.2.4 dynamic_cast(针对父类指针或引用的向下转型)

dynamic_cast用于将一个父类对象的指针/引用转换为子类对象的指针或引用(动态转换)
向上转型:子类对象指针/引用->父类指针/引用(不需要转换,赋值兼容规则)
向下转型:父类对象指针/引用->子类指针/引用(用dynamic_cast转型是安全的)
注意:
1. dynamic_cast只能用于父类含有虚函数的类

2. dynamic_cast会先检查是否能转换成功,能成功则转换,不能则返回nullptr。

思考:

1、为什么父类和子类明明是两种类型,但是向上转型不需要转换呢??

class A
{
public:virtual void f() {}int _a = 0;
};class B : public A
{public:int _b = 1;
};int main()
{B objb;A obja = objb;A& ra = objb;double d = 1.1;const int& i = d;return 0;
}

2、为什么向下转型是不安全的??

      因为子类的指针或引用如果是父类对象,那么会存在一部分的越界!!!

3、为什么 dynamic_cast只能用于父类含有虚函数的类

        dynamic_cast转换是在运行时进行转换,因为只有对于这种类层次结构,才应该将派生类的地址赋给基类指针。运行时转换就需要知道类对象的信息(继承关系等)。C++对象模型中,对象实例最前面的就是虚函数表指针,通过这个指针可以获取到该类对象的所有虚函数,包括父类的。因为派生类会继承基类的虚函数表,所以通过这个虚函数表,我们就可以知道该类对象的父类,在转换的时候就可以用来判断对象有无继承关系。

  所以虚函数对于正确的基类指针转换为子类指针是非常重要的。

4、dynamic_cast的使用原理

       父类指针或引用如果本来指向的是子类的对象,那么类型转化是安全的,如果原本指向的是父类的对象,那么转化是不安全的(有越界,但是编译器检查不出来),所以dynamic_cast可以帮助我们去判断这种情况,如果不符合安全转化的条件,就会返回nullptr。

void fun(A* pa)
{//  向下转换:直接转换是不安全的// 如果pa是指向父类A对象,存在越界问题B* ptr = dynamic_cast<B*>(pa);if (ptr!=nullptr){ptr->_a++;ptr->_b++;}else{cout << "转换失败" << endl;}
}int main()
{// 向下转换规则:父类对象不能转换成子类对象,但是父类指针和引用可以转换子类指针和引用B b1;A a;B b;fun(&a);fun(&b);return 0;
}

2.3 为什么C++需要四种类型转换

C风格的转换格式很简单,但是有不少缺点的:
1、隐式类型转化有些情况下可能会出问题:比如数据精度丢失

2、显式类型转换将所有情况混合在一起,代码不够清晰

3、为了提供更安全、更明确的类型转换,使得代码意图更为清晰

       因此C++提出了自己的类型转化风格,注意因为C++要兼容C语言,所以C++中还可以使用C语言的转化风格。因此C++的转化风格并非强制性的,只不过是程序员之间的一种规范。

2.4 RTTI

RTTI:Run-time Type identification的简称,即:运行时类型识别。RTTI只适用于包含虚函数的类
C++通过以下方式来支持RTTI:
1. typeid运算符(返回指出对象类型的值)
2. dynamic_cast运算符(根据虚基表判断有无继承关系,并判断是否可以转化)
3. decltype(推断表达式返回值的类型)

2.5 相关面试题

1. C++中的4中类型转化分别是:_________、_________、_________、________

2.描述四种类型转化各自的应用场景。


相关文章:

C++:特殊类设计和四种类型转换

一、特殊类设计 1.1 不能被拷贝的类 拷贝只会放生在两个场景中&#xff1a;拷贝构造函数以及赋值运算符重载&#xff0c;因此想要让一个类禁止拷贝&#xff0c;只需让该类不能调用拷贝构造函数以及赋值运算符重载即可。 C98&#xff1a; 1、将拷贝构造函数与赋值运算符重载只…...

(南京观海微电子)——屏幕材质及优缺点对比

LED/LCD LCD&#xff08;Liquid Crystal Ddisplay&#xff09;即“液晶显示器”&#xff0c;由两块偏光镜、两块薄膜晶体管以及彩色滤光片、光源&#xff08;荧光灯&#xff09;、显示面板组成的成像元器件。 LED&#xff08;Light Emitting Diode&#xff09;即“发光二极管…...

uniapp uni.showModal 出现点击没有反应

uni.showModal 里面有好些参数 点击后不弹出 是因为 出现了 null 或者undifind 字符 特别是content 里面 title: 提示, cancelColor: #000000, editable: true,//是否显示输入框 content: item.text?item.te…...

Vue3-VueRouter

客户端 vs. 服务端路由​ 服务端路由指的是服务器根据用户访问的 URL 路径返回不同的响应结果。当我们在一个传统的服务端渲染的 web 应用中点击一个链接时&#xff0c;浏览器会从服务端获得全新的 HTML&#xff0c;然后重新加载整个页面。 然而&#xff0c;在单页面应用中&a…...

【图像处理与机器视觉】频率域滤波

知识铺垫 复数 CRjI 可以看作复平面上的点&#xff0c;则该复数的坐标为&#xff08;R&#xff0c;I&#xff09; 欧拉公式 e j θ c o s θ j s i n θ e^{j\theta} cos \theta j sin \theta ejθcosθjsinθ 极坐标系中复数可以表示为&#xff1a; C ∣ C ∣ ( c o s…...

python第五次作业

1.请实现一个装饰器&#xff0c;每次调用函数时&#xff0c;将函数名字以及调用此函数的时间点写入文件中 # 导入datetime模块&#xff0c;用于获取当前时间并格式化输出 import datetime# 定义一个装饰器工厂函数log_funcName_time&#xff0c;它接受一个参数time def log_fu…...

JS面向对象编程

目录 实例对象与new命令this关键字对象的原型和继承Object对象的相关方法浅拷贝和深拷贝严格模式实例对象与new命令 构造函数 构造函数的特点有两个: 函数体内部使用了this关键字,代表了所要生成的对象实例。生成对象的时候,必须使用new命令。var Vehicle...

kotlin1.8.10问题导致gson报错TypeToken type argument must not contain a type variable

书接上回&#xff0c;https://blog.csdn.net/jzlhll123/article/details/139302991。 之前我发现gson报错后&#xff1a; gson在2.11.0给我的kotlin项目代码报错了。 IllegalArgumentException: TypeToken type argument must not contain a type variable 上次解释原因是因为&…...

数据库漫谈-国产数据库

国产数据库突然大量出现&#xff0c;下表列出&#xff08;按首字母排序 &#xff09; AISWare AntDB&#xff0c;亚信科技。 AliSQL&#xff0c;阿里云。 Analyticdb&#xff0c;阿里云。 ArkDB&#xff0c;北京极数云舟科技有限公司。 CynosDB&#xff0c;腾讯云 DM&…...

小白跟做江科大32单片机之光敏传感器控制蜂鸣器

代码部分 1.思路 通过光敏电阻&#xff0c;控制蜂鸣器的发声 2.butter.h代码 #ifndef _BUTTER__H #define _BUTTER__H void butter_Init(void); void butter_on(void); void butter_off(void); #endif 3.butter.c代码 #include "stm32f10x.h" void butter…...

使用 Django Channels 构建实时聊天应用(包含用户认证和消息持久化)

文章目录 准备工作创建 Django 项目创建应用程序配置项目编写 Consumer编写路由创建 URL 路由运行应用用户认证消息持久化显示历史消息结论 Django Channels 是 Django 的一个扩展&#xff0c;允许在 Web 应用中添加实时功能&#xff0c;例如 Websockets、HTTP2 和其他协议。本…...

【Elasticsearch】es基础入门-03.RestClient操作文档

RestClient操作文档 示例&#xff1a; 一.初始化JavaRestClient &#xff08;一&#xff09;引入es的RestHighLevelClient依赖 <!--elasticsearch--> <dependency><groupId>org.elasticsearch.client</groupId><artifactId>elasticsearch-rest…...

LeetCode - 二分查找(Binary Search)算法集合(Python)[左右边界|旋转数组|双列表]

欢迎关注我的CSDN&#xff1a;https://spike.blog.csdn.net/ 本文地址&#xff1a;https://spike.blog.csdn.net/article/details/139419653 二分查找&#xff0c;也称为折半查找&#xff0c;是一种在有序数组中查找特定元素的高效算法。其基本原理是将待搜索的区间分成两半&am…...

android睡眠分期图

一、效果图 做医疗类项目&#xff0c;经常会遇到做各种图表&#xff0c;本文做的睡眠分期图。 二、代码 引入用到的库 api joda-time:joda-time:2.10.1 调用代码 /*** 睡眠* 分期*/private SleepChartAdapter mAdapter;private SleepChartAttrs mAttrs;private List<SleepI…...

2023年信息素养大赛小学组C++智能算法复赛真题

今天给大家分享2023年全国青少年信息素养大赛小学组C智能算法挑战赛复赛里面的一套真题&#xff0c;希望有助于大家了解复赛的难度及备考。 其他真题下载&#xff1a;网盘-真题-信息素养大赛...

独立游戏开发的 6 个步骤

&#x1f482; 个人网站:【 摸鱼游戏】【神级代码资源网站】【工具大全】&#x1f91f; 一站式轻松构建小程序、Web网站、移动应用&#xff1a;&#x1f449;注册地址&#x1f91f; 基于Web端打造的&#xff1a;&#x1f449;轻量化工具创作平台&#x1f485; 想寻找共同学习交…...

Stable Diffusion AI绘画:从创意词汇到艺术图画的魔法之旅

文章目录 一、Stable Diffusion的工作原理二、从提示词到模型出图的过程三、Stable Diffusion在艺术创作中的应用《Stable Diffusion AI绘画从提示词到模型出图》内容简介作者简介楚天 目录前言/序言本书特色特别提示 获取方式 在科技的飞速发展中&#xff0c;Stable Diffusion…...

使用C++实现高效的套接字连接池

在现代网络应用中&#xff0c;高效管理网络连接是实现高并发和低延迟的重要因素。下面将详细介绍如何使用C实现一个高效的套接字连接池&#xff0c;以便在需要时快速复用连接&#xff0c;从而提高系统性能和资源利用率。 一、什么是连接池&#xff1f; 连接池是一种管理网络连…...

个人百度百科怎么创建

编辑百度词条是一个相对简单的流程&#xff0c;但需要注意的是&#xff0c;并不是所有的词条都可以编辑&#xff0c;部分锁定的词条是无法编辑的&#xff0c;但可以通过官方平台申请解封。以下百科优化网yajje分享是详细的步骤&#xff1a; 注册百度账号 首先&#xff0c;用户…...

Nvidia Jetson/Orin +FPGA+AI大算力边缘计算盒子:潍柴雷沃智慧农业无人驾驶

潍柴雷沃智慧农业科技股份有限公司&#xff0c;是潍柴集团重要的战略业务单元&#xff0c;旗下收获机械、拖拉机等业务连续多年保持行业领先&#xff0c;是国内少数可以为现代农业提供全程机械化整体解决方案的品牌之一。潍柴集团完成对潍柴雷沃智慧农业战略重组后&#xff0c;…...

RestClient

什么是RestClient RestClient 是 Elasticsearch 官方提供的 Java 低级 REST 客户端&#xff0c;它允许HTTP与Elasticsearch 集群通信&#xff0c;而无需处理 JSON 序列化/反序列化等底层细节。它是 Elasticsearch Java API 客户端的基础。 RestClient 主要特点 轻量级&#xff…...

8k长序列建模,蛋白质语言模型Prot42仅利用目标蛋白序列即可生成高亲和力结合剂

蛋白质结合剂&#xff08;如抗体、抑制肽&#xff09;在疾病诊断、成像分析及靶向药物递送等关键场景中发挥着不可替代的作用。传统上&#xff0c;高特异性蛋白质结合剂的开发高度依赖噬菌体展示、定向进化等实验技术&#xff0c;但这类方法普遍面临资源消耗巨大、研发周期冗长…...

Linux相关概念和易错知识点(42)(TCP的连接管理、可靠性、面临复杂网络的处理)

目录 1.TCP的连接管理机制&#xff08;1&#xff09;三次握手①握手过程②对握手过程的理解 &#xff08;2&#xff09;四次挥手&#xff08;3&#xff09;握手和挥手的触发&#xff08;4&#xff09;状态切换①挥手过程中状态的切换②握手过程中状态的切换 2.TCP的可靠性&…...

高频面试之3Zookeeper

高频面试之3Zookeeper 文章目录 高频面试之3Zookeeper3.1 常用命令3.2 选举机制3.3 Zookeeper符合法则中哪两个&#xff1f;3.4 Zookeeper脑裂3.5 Zookeeper用来干嘛了 3.1 常用命令 ls、get、create、delete、deleteall3.2 选举机制 半数机制&#xff08;过半机制&#xff0…...

Frozen-Flask :将 Flask 应用“冻结”为静态文件

Frozen-Flask 是一个用于将 Flask 应用“冻结”为静态文件的 Python 扩展。它的核心用途是&#xff1a;将一个 Flask Web 应用生成成纯静态 HTML 文件&#xff0c;从而可以部署到静态网站托管服务上&#xff0c;如 GitHub Pages、Netlify 或任何支持静态文件的网站服务器。 &am…...

令牌桶 滑动窗口->限流 分布式信号量->限并发的原理 lua脚本分析介绍

文章目录 前言限流限制并发的实际理解限流令牌桶代码实现结果分析令牌桶lua的模拟实现原理总结&#xff1a; 滑动窗口代码实现结果分析lua脚本原理解析 限并发分布式信号量代码实现结果分析lua脚本实现原理 双注解去实现限流 并发结果分析&#xff1a; 实际业务去理解体会统一注…...

什么是Ansible Jinja2

理解 Ansible Jinja2 模板 Ansible 是一款功能强大的开源自动化工具&#xff0c;可让您无缝地管理和配置系统。Ansible 的一大亮点是它使用 Jinja2 模板&#xff0c;允许您根据变量数据动态生成文件、配置设置和脚本。本文将向您介绍 Ansible 中的 Jinja2 模板&#xff0c;并通…...

【Java学习笔记】BigInteger 和 BigDecimal 类

BigInteger 和 BigDecimal 类 二者共有的常见方法 方法功能add加subtract减multiply乘divide除 注意点&#xff1a;传参类型必须是类对象 一、BigInteger 1. 作用&#xff1a;适合保存比较大的整型数 2. 使用说明 创建BigInteger对象 传入字符串 3. 代码示例 import j…...

【电力电子】基于STM32F103C8T6单片机双极性SPWM逆变(硬件篇)

本项目是基于 STM32F103C8T6 微控制器的 SPWM(正弦脉宽调制)电源模块,能够生成可调频率和幅值的正弦波交流电源输出。该项目适用于逆变器、UPS电源、变频器等应用场景。 供电电源 输入电压采集 上图为本设计的电源电路,图中 D1 为二极管, 其目的是防止正负极电源反接, …...

Golang——9、反射和文件操作

反射和文件操作 1、反射1.1、reflect.TypeOf()获取任意值的类型对象1.2、reflect.ValueOf()1.3、结构体反射 2、文件操作2.1、os.Open()打开文件2.2、方式一&#xff1a;使用Read()读取文件2.3、方式二&#xff1a;bufio读取文件2.4、方式三&#xff1a;os.ReadFile读取2.5、写…...