C++——类型转换与特殊类设计

news2024/12/23 10:50:56
我们在C语言中经常会使用到强制类型转换,例如指针和整形之间的转换是最为常见的,但是
在C++中,C++设计师认为这种强制类型转换是不安全的,所以在C++标准中加入了四种强制
类型转换风格,这就是我将要介绍的强制类型转换。
在某些场景中,我们可能需要一些特殊的类来让我们的代码能够更加符合场景,比如只能在栈
上创建对象,或者只能在堆上常见对象等等场景,而其中尤为出名的一种特殊类,也被纳入设计
模式中,那就是单例模式。

1. 特殊类设计

a. 不能能被拷贝的类

拷贝只会放生在两个场景中:拷贝构造函数以及赋值运算符重载,因此想要让一个类禁止拷贝,只需让该类不能调用拷贝构造函数以及赋值运算符重载即可。
由于这两个函数是默认成员函数,你不写它会自动生成,所以我们需要显式地写出来,并且是具有私有属性:

class A
{
public:
	A(int a = 0)
		:_a(a)
	{}
private:
	A(const A& a);
	A& operator=(const A& a);

	int _a;
};

在这里插入图片描述
而在C++11中delete作用于函数有着新的作用,那就是不生成该函数,所以:

class A
{
public:
	A(int a = 0)
		:_a(a)
	{}

	A(const A& a) = delete;
	A& operator=(const A& a) = delete;
private:


	int _a;
};

这个的话私有共有无所谓了。
在这里插入图片描述

b. 只能在堆上创建的对象

我们想要创建一个对象无非就这两种方式:

int main()
{
	A aa;//栈上
	A* paa = new(A);//堆上

	return 0;
}

现在要让我们不能在栈上创建对象,一定是从构造函数找出发点,首先构造函数不能直接地被调用,说明他应该是私有成员,但是他是私有成员后,我们new一个对象的时候也需要构造函数,这样也不能在堆上创建对象了,这个时候我们就需要在类中再写一个函数,由于类内是没有私有公有一说的,所以类内成员函数可以随意的调用类内的任意成员函数,所以:

class A
{
public:
	static A* getObj(int a = 0)
	{
		A* pa = new A(a);
		return pa;
	}
private:
	A(int a = 0) :_a(a){};

	int _a;
};

这样写完之后我们发现,我们要使用这个函数先得需要一个对象,但是我们又创建不了对象,自相矛盾了,所以我们需要将这个函数设置为静态的:

class A
{
public:
	static A* getObj(int a = 0)
	{
		A* pa = new A(a);
		return pa;
	}
private:
	A(int a = 0) :_a(a){};

	int _a;
};

在这里插入图片描述
还有问题,如果是这样呢:
在这里插入图片描述
所以还需要禁用拷贝构造和赋值重载:

class A
{
public:
	static A* getObj(int a = 0)
	{
		A* pa = new A(a);
		return pa;
	}
private:
	A(int a = 0) :_a(a){};
	A(const A& a) = delete;
	A& operator=(const A& a) = delete;
	int _a;
};

c. 只能在栈上创建对象

我们肯定还是在构造函数上找突破口:

class A 
{
public:
	static A getObj()
	{
		A a;
		return a;
	}


private:
	A(int a = 0) :_a(a) {};
	int _a;
};

而对于这样的方式,有人会说会不会效率变慢了,如果对象需要在堆上开辟空间的话,不要忘了我们可是有右值引用的:
这个现象需要在VS2019或者更低版本的编译器上进行,VS2022优化有点高,看不到这种现象。

#include <iostream>
using namespace std;

class A
{
public:
	static A getObj()
	{
		A a;
		return a;
	}

	A(A&& a)
	{
		cout << "右值拷贝构造" << endl;
		swap(_a, a._a);
	}

private:
	A(int a = 0) :_a(new int[a]) {};
	int* _a;
};

int main()
{
	A a = A::getObj();
	//A* pa = new A;
	return 0;
}

在这里插入图片描述

d. 只能创建一个对象(单例模式)

关于这一个设计,这是一种设计模式,因为它经常出现在各种编程场景中,被人们广泛使用。
设计模式:

设计模式(Design Pattern)是一套被反复使用、多数人知晓的、经过分类的、代码设计
经验的总结。为什么会产生设计模式这样的东西呢?就像人类历史发展会产生兵法。最开
始部落之间打仗时都是人拼人的对砍。后来春秋战国时期,七国之间经常打仗,就发现打
仗也是有套路的,后来孙子就总结出了《孙子兵法》。孙子兵法也是类似。

使用设计模式的目的:

为了代码可重用性、让代码更容易被他人理解、保证代码可靠性。 设计模式使代码编写真
正工程化;设计模式是软件工程的基石脉络,如同大厦的结构一样。

单例模式:

一个类只能创建一个对象,即单例模式,该模式可以保证系统中该类只有一个实例,并提
供一个访问它的全局访问点,该实例被所有程序模块共享。比如在某个服务器程序中,该
服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务
进程中的其他对象再通过这个单例对象获取这些配置信息,这种方式简化了在复杂环境下
的配置管理。

而关于单例模式的设计有两种方法应对于不同的场景:

1). 饿汉模式

饿汉模式,他的意思就是在进入main函数之前,这个对象就已经被创建好了。
我们首先要想想,什么变量是在进入main函数之前就已经被创建好了呢?那肯定是全局变量啊,但是全局变量你要知道,它使用起来是有风险的,在多文件的时候尤为明显,可能会产生重定义的风险。所以我们是这样设计的:

class Singleton
{
public:
	static Singleton& getExa()
	{
		return sl;
	}

	void Add(const pair<string, int> kv)
	{
		sl._hash.insert(kv);
	}

	void Print()
	{
		for (auto& e : sl._hash)
		{
			cout << e.first << " : " << e.second << endl;
		}
		cout << endl;
	}
private:
	map<string, int> _hash;

	Singleton() {};
	Singleton(const Singleton& sl) = delete;
	Singleton& operator=(const Singleton& sl) = delete;

	static Singleton sl;
};

Singleton Singleton::sl;

int main()
{
	Singleton::getExa().Add({ "hello", 2 });
	Singleton::getExa().Add({ "world", 1 });
	Singleton::getExa().Add({ "xxx", 1 });
	Singleton::getExa().Add({ "yyy", 10 });


	Singleton::getExa().Print();

	return 0;
}

2). 懒汉模式

这种设计模式就是在对象被调用的时候再创建:

class Singleton
{
public:
	static Singleton* getExa()
	{
		if (sl == nullptr)
			sl = new Singleton;
		return sl;
	}

	void Add(const pair<string, int> kv)
	{
		sl->_hash.insert(kv);
	}

	void Print()
	{
		for (auto& e : sl->_hash)
		{
			cout << e.first << " : " << e.second << endl;
		}
		cout << endl;
	}
private:
	map<string, int> _hash;

	Singleton() {};
	Singleton(const Singleton& sl) = delete;
	Singleton& operator=(const Singleton& sl) = delete;

	static Singleton* sl;
};

Singleton* Singleton::sl = nullptr;

int main()
{
	Singleton::getExa()->Add({ "hello", 2 });
	Singleton::getExa()->Add({ "world", 1 });
	Singleton::getExa()->Add({ "xxx", 1 });
	Singleton::getExa()->Add({ "yyy", 10 });


	Singleton::getExa()->Print();

	return 0;
}

在饿汉模式中,对象的销毁在进程结束之后会自动销毁,并且由于是在栈上,所以无法手动销毁对象,但是在懒汉模式中该对象是从堆上开辟而来,有人就会想到,那这个对象究竟什么时候释放呢?程序也不会自动调用析构函数释放啊,其实我们不需要担心它内存泄露的问题,因为进程如果是正常退出的话,操作系统会帮我们做这一件事的,如果进程是异常退出的话,那需要担心的地方也不是这里了,而是为什么进程异常退出了。但是我们非要设计进程结束,释放资源的功能也是可以的:

class Singleton
{
public:
	static Singleton* getExa()
	{
		if (sl == nullptr)
			sl = new Singleton;
		return sl;
	}

	void Add(const pair<string, int> kv)
	{
		sl->_hash.insert(kv);
	}

	void Print()
	{
		for (auto& e : sl->_hash)
		{
			cout << e.first << " : " << e.second << endl;
		}
		cout << endl;
	}

	//创建一个内嵌的自定义类,并且创建该自定义类的栈上的对象,那么在进程结束的
	//时候该对象会调用它的析构函数,我们在它的析构函数中释放sl即可。
	class GC
	{
	public:
		~GC()
		{
			if (sl)
			{
				delete sl;
				sl = nullptr;
			}
		}
	};

private:
	map<string, int> _hash;

	Singleton() {};
	Singleton(const Singleton& sl) = delete;
	Singleton& operator=(const Singleton& sl) = delete;


	static Singleton* sl;
	static GC gc;
};

Singleton* Singleton::sl = nullptr;
Singleton::GC Singleton::gc;

在这里插入图片描述

要注意:自定义类型的静态成员变量在类内声明,类外定义

3). 两者的场景

饿汉模式

如果这个单例对象在多线程高并发环境下频繁使用,性能要求较高,那么显然使用饿汉
模式来避免资源竞争,提高响应速度更好。

懒汉模式

如果单例对象构造十分耗时或者占用很多资源,比如加载插件啊, 初始化网络连接啊,
读取文件啊等等,而有可能该对象程序运行时不会用到,那么也要在程序一开始就进行
初始化,就会导致程序启动时非常的缓慢。 所以这种情况使用懒汉模式(延迟加载)更
好。

在这里要说明一点,饿汉模式不需要担心线程安全问题,而懒汉模式需要,需要加锁,关于这一方面的问题,日后再谈(因为我也不会…)。

1. C语言类型转换

我们在编写C/C++代码的过程中肯定会不可避免地使用到强制类型转换,例如自己计算结构体某一成员偏移量,指针之间,指针与整形之间转换的场景我们都是这么写:

// 偏移量
#define offsetof( type,member) (char *)&((type *)0->member)

它的类型转换分为两种,一种是隐式的:

double j = 1.1;
int a = j;

但是很明显这种转换会丢失精度,还有就是显式的:

int main()
{
	int a = 0;
	int* pa = &a;
	int b = (int)pa;

	return 0;
}

对于以上类型转换会存在一些很明显的缺陷:

1. 转换的可视性比较差,所有的转换形式都是以一种相同形式书写,难以跟踪错误的转换
2.  隐式类型转化有些情况下可能会出问题:比如数据精度丢失
3. 显式类型转换将所有情况混合在一起,代码不够清晰
4. 隐式类型的转换在出现错误后一般在编译时才出现

所以基于这种情况C++标准提出了四种类型转换格式。

2. C++类型转换

a. static_cast

static_cast用于非多态类型的转换(静态转换),编译器隐式执行的任何类型转换都可用static_cast,但它不能用于两个不相关的类型进行转换:

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

b. 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;
}

c. const_cast

const_cast最常用的用途就是删除变量的const属性,方便赋值:

void Test ()
{
  const int a = 2;
  int* p = const_cast< int*>(&a );
  *p = 3;
  cout<<a <<endl;
}

d. dynamic_cast

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

  1. dynamic_cast只能用于父类含有虚函数的类
    1. dynamic_cast会先检查是否能转换成功,能成功则转换,不能则返回0
class A
{
public :
virtual void f(){}
};
class B : public A
{};
void fun (A* pa)
{
// dynamic_cast会先检查是否能转换成功,能成功则转换,不能则返回0
B* pb1 = static_cast<B*>(pa);
B* pb2 = dynamic_cast<B*>(pa);
cout<<"pb1:" <<pb1<< endl;
cout<<"pb2:" <<pb2<< endl;
}
int main ()
{
  A a;
  B b;
  fun(&a);
  fun(&b);
  return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1409445.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

《Windows核心编程》若干知识点实战应用分享

目录 1、进程的虚拟内存分区与小于0x10000的小地址内存区 1.1、进程的虚拟内存分区 1.2、小于0x10000的小地址内存区 2、保存线程上下文的CONTEXT结构体 3、从汇编代码角度去理解多线程运行过程的典型实例 4、调用TerminateThread强制结束线程会导致线程中的资源没有释放…

『OpenCV-Python|鼠标作画笔』

Opencv-Python教程链接&#xff1a;https://opencv-python-tutorials.readthedocs.io/ 本文主要介绍OpenCV-Python如何将鼠标作画笔绘制圆或者矩形。 示例一&#xff1a;图片上双击的位置绘制一个圆圈 首先创建一个鼠标事件回调函数&#xff0c;鼠标事件发生时就会被执行。鼠标…

Git学习笔记(第9章):国内代码托管中心Gitee

目录 9.1 简介 9.1.1 Gitee概述 9.1.2 Gitee帐号注册和登录 9.2 VSCode登录Gitee账号 9.3 创建远程库 9.4 本地库推送到远程库(push) 9.5 导入GitHub项目 9.6 删除远程库 9.1 简介 9.1.1 Gitee概述 众所周知&#xff0c;GitHub服务器在国外&#xff0c;使用GitHub作为…

51-15 视频理解串讲—TimeSformer论文精读

今天读的论文题目是Is Space-Time Attention All You Need for Video Understanding? Facebook AI提出了一种称为TimeSformer视频理解的新架构&#xff0c;这个架构完全基于transformer&#xff0c;不使用卷积层。它通过分别对视频的时间和空间维度应用自注意力机制&#xff…

山西电力市场日前价格预测【2024-01-26】

日前价格预测 预测说明&#xff1a; 如上图所示&#xff0c;预测明日&#xff08;2024-01-26&#xff09;山西电力市场全天平均日前电价为309.45元/MWh。其中&#xff0c;最高日前电价为587.20元/MWh&#xff0c;预计出现在18:15。最低日前电价为0.00元/MWh&#xff0c;预计出…

2016年认证杯SPSSPRO杯数学建模A题(第一阶段)洗衣机全过程文档及程序

2016年认证杯SPSSPRO杯数学建模 A题 洗衣机 原题再现&#xff1a; 洗衣机是普及率极高的家用电器&#xff0c;它给人们的生活带来了很大的方便。家用洗衣机从工作方式来看&#xff0c;有波轮式、滚筒式、搅拌式等若干种类。在此基础上&#xff0c;各厂商也推出了多种具体方案…

mac电脑安卓文件传输工具:Android File Transfer直装版

Android File Transfer&#xff08;AFT&#xff09;是一款用于在Mac操作系统上与Android设备之间传输文件。它允许用户将照片、音乐、视频和其他文件从他们的Android手机或平板电脑传输到Mac电脑&#xff0c;以及将文件从Mac上传到Android设备。 下载地址&#xff1a;https://w…

Cesium介绍及3DTiles数据加载时添加光照效果对比

一、Cesium简介 Cesium原意是化学元素铯&#xff0c;铯是制造原子钟的关键元素&#xff0c;通过命名强调了Cesium产品专注于基于时空数据的实时可视化应用。熟悉GIS开发领域的读者都知道&#xff0c;Cesium是一个用于创建3D地理空间应用程序的开源JavaScript库&#xff0c;它允…

文件包含技术总结

开发人员一般会把重复使用的函数写到单个文件中&#xff0c;需要使用某个函数时直接调用此文件&#xff0c;而无需再次编写&#xff0c;这中文件调用的过程一般被称为文件包含。 allow_url_fopen On&#xff08;是否允许打开远程文件&#xff09; allow_url_include On&…

书生·浦语大模型--第五节课笔记作业--LMDeploy 大模型量化部署实践

文章目录 大模型部署背景LMDeploy简介动手实践创建环境服务部署在线转换离线转换TurboMind推理API服务Gradio 作为前端 Demo演示TurboMind 服务作为后端TurboMind 推理作为后端 作业 大模型部署背景 部署&#xff1a;将训练好的模型在特定软硬件环境中启动的过程 挑战&#x…

BurpSuite Pro 2023.12.1.2下载与破解-最新版BurpSuite Pro

本文在我的博客地址是&#xff1a;https://h4cker.zip/post/f05ae2e66da503f6383dffe48cdf5bac 上一次BurpSuite的分享还是在2020年 由于CSDN有防盗链&#xff0c;我自己的博客都无法访问这篇博文的图片了 至于为什么再写一次&#xff0c;是因为我看到群里这张图&#xff1a;…

前端开发提高效率的两大工具

一、浏览器中的开发者工具 怎么启动开发者工具&#xff1f; 在浏览器中按下F12或者鼠标右键点击检查 怎么利用&#xff08;常用的几点&#xff09;&#xff1f; 1、元素 点击标红的图标可以用于在页面选择元素&#xff0c;同时右侧会找到元素在前端代码中的位置 点击下方红…

飞驰云联与中兴新支点、中科红旗完成兼容认证 助力国产信创落地

近日&#xff0c;Ftrans飞驰云联自主研发的“Ftrans文件数据交换平台软件产品”、“Ftrans文件大数据传输管控平台软件产品”、“Ftrans信创文件安全传输软件产品”等&#xff0c;分别与广东中兴新支点技术有限公司的新支点电信级服务器操作系统V5、V6&#xff0c;以及中科红旗…

从零开始:Git 上传与使用指南

Git 是一种非常强大的版本控制系统&#xff0c;它可以帮助您在多人协作开发项目中更好地管理代码版本&#xff0c;并确保每个团队成员都能及时地获取最新的代码更改。在使用 Git 进行版本控制之前&#xff0c;您需要先进行一些设置&#xff0c;以确保您的代码能够顺利地与远程仓…

Linux之快速入门

一、Linux目录结构 从Windows转到Linux最不习惯的是什么&#xff1a; 目录结构 Windows会分盘&#xff0c;想怎么放东西就怎么放东西&#xff0c;好处自由&#xff0c;缺点容易乱 Linux有自己的目录结构&#xff0c;不能随随便便放东西 /&#xff1a;根目录/bin:二进制文件&…

蓝桥杯备战——3.定时器前后台

1.STC15F2k61S2的定时器 阅读STC15系列的手册&#xff0c;我们可以看到跟STC89C52RC的定时器还是有不同之处的&#xff1a; 由上图可以看到我们可以通过AUXR寄存器直接设置定时器的1T/12T模式了 在定时器0/1模式上也可以设置为16位自动重装载。 另外需要注意IAP15F2K61S2只有…

【11.PWM捕获】蓝桥杯嵌入式一周拿奖速成系列

系列文章目录 蓝桥杯嵌入式系列文章目录(更多此系列文章可见) PWM捕获 系列文章目录一、STM32CUBEMX配置二、项目代码1.mian.c --> HAL_TIM_IC_CatureCallback 总结 一、STM32CUBEMX配置 STM32CUBEMX PA15 ->TIM2_CH1; PB4-> TIM3_CH1 预分频设置为79,自动重装载设置…

9.STM32F40x PWM简单介绍

一、PWM概念 脉冲宽度调制(PWM)&#xff0c;是英文“Pulse Width Modulation”的缩写&#xff0c;简称脉宽调制&#xff0c;是利用微处理器的数字输出来对模拟电路进行控制的一种非常有效的技术。简单一点&#xff0c;就是对脉冲宽度的控制&#xff0c;PWM原理如图1所示&#x…

2859. 计算 K 置位下标对应元素的和

本题的关键在于如何去求一个二进制数中含有多少个1的问题。 如果一个整数不为0&#xff0c;那么这个整数至少有一位是1。如果我们把这个整数减1&#xff0c;那么原来处在整数最右边的1就会变为0&#xff0c;原来在1后面的所有的0都会变成1(如果最右边的1后面还有0的话)。其余所…

Redis(秒杀活动、持久化之RDB、AOF)

目录 秒杀活动 一、测压工具jmete的使用 二、java实现秒杀活动 1、myseckillcontroller 2、先启动pos请求添加商品&#xff0c;再启动jmeter进行压测 Redis持久化 一 、Redis持久化之RDB 1.RDB是什么 2. 备份是如何执行的 3.Fork 4. RDB持久化流程 5. dump.rdb文件 6…