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

特殊类的设计和类型转换

    • 特殊类的设计
      • 1.设计一个类,不能被拷贝
      • 2.设计一个类,只能在堆上创建对象
      • 3.设计一个类,只能在栈上创建对象
      • 4.设计一个类,不能被继承
      • 5.单例模式
    • C++的类型转换
      • 1. C语言中的类型转换
      • 2.C语言类型转换的缺点
      • 3.C++的强制类型转换
    • C++中const引用做参数的特殊机制
    • RTTI(扩展)

特殊类的设计

1.设计一个类,不能被拷贝

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

//(1)C++98:将拷贝构造函数与赋值运算符重载只声明不定义并且将其访问权限设置为私有即可。
class A 
{
public:A(){}
private:A(A&);  A& operator=(const A&);int x;
};//(2)C++11:扩展delete的用法,delete除了释放new申请的资源外,如果在默认成员函数后跟上
//= delete,表示让编译器删除掉该默认成员函数。
class B
{
public:B() {}B(B&) = delete;B& operator=(const B&) = delete;int x;
};

2.设计一个类,只能在堆上创建对象

两种实现方式:

  1. 将类的构造函数私有拷贝构造声明成私有。防止别人调用拷贝在栈上生成对象。提供一个静态的成员函数,在该静态成员函数中完成堆对象的创建
  2. 析构函数私有化,提供destory接口释放空间。
//只能在堆上开空间
// 第一种方案:构造、拷贝构造私有化,提高static返回创建对象指针
class A {
public:static A* get(){return new A;}
private:A(A&){}A(){}
};//第二种方案:析构函数私有化,提供destory接口释放空间
class B {
public:void Destory(){delete this;}
private:~B()  //栈上变量函数调用结束前调不动析构{cout << "~B" << endl;}
};

3.设计一个类,只能在栈上创建对象

构造函数私有化,然后设计静态方法创建对象返回即可

//设计一个类,只能在栈上面开空间
//禁用new,设计static方法返回局部对象
class C {
public:static C get(){return C();}
private:C(){}void* operator new(size_t s) = delete;
};

4.设计一个类,不能被继承

//不能被继承
// (1)C++98中构造函数私有化,派生类中调不到基类的构造函数。则无法继承
class A
{
public:static A GetInstance(){return A();}
private:A(){}
};//(2)C++11方法可用final关键字,final修饰类,表示该类不能被继承。
class B final
{///
};

5.单例模式

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


两种设计:

  1. 饿汉模式:在main函数执行前就创建好
//单例化模式的设计
//饿汉模式:在main函数前创建好
//要点:(1)只能右一个实例,把构造和拷贝构造私有
//(2)要在main函数前就创建好,我们可以设计成静态成员,类似与全局变量
//(3)提供全局函数返回对象引用或指针
//优点:简单
//缺点:可能会导致进程启动慢,且如果有多个单例类对象实例启动顺序不确定。
class A {
public:static A& get(){return a;}//需要什么变量和方法自己添加
private:A() {};A(A&) {};A& operator=(A&) = delete;//赋值最好禁止掉,但自己给自己赋值也影响不大static A a;
};
A A::a;  //类外定义
  1. 懒汉模式:需要使用的使用才创建
//懒汉模式:需要的时候创建
//要点:(1)只能有一个实例,把构造和拷贝构造私有
//(2)设计一个静态变量指针,初始化为空
//(3)第一次调用get方法的时候才创建对象
class B {
public:static B* get(){if (b == nullptr){b = new B();}return b;}
private:B() {};B(B&) {};B& operator=(B&) = delete;static B* b;
};
B* B::b = nullptr;//如果需要在退出时进行数据持久化,可以利用析构函数和内部类
//可以手动调用,但不调也会在main结束前自动调用
class B {
public:static B* get(){if (b == nullptr){b = new B();}return b;}static void destory(){if (b) {//数据持久化操作delete b;  b = nullptr;  //懒汉释放空间其实不重要,重要的是可以在这个过程进行数据持久化cout << "destory" << endl;}}
private:B() {};B(B&) {};B& operator=(B&) = delete;static B* b;class C {public:~C(){B::destory();}};static C c;  //在main函数结束前会调用相应的析构函数
};
B* B::b = nullptr;
B::C B::c;



C++的类型转换

1. C语言中的类型转换

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

  1. 隐式类型:编译器在编译阶段自动进行,能转就转,不能转就编译失败
  2. 显式类型转化:需要用户自己处理
void Test()
{int i = 1;// 隐式类型转换(有关联,意义相似)double d = i;printf("%d, %.2f\n", i, d);int* p = &i;// 显示的强制类型转换int address = (int)p;printf("%x, %d\n", p, address);
}

2.C语言类型转换的缺点

  1. 隐式类型转化有些情况下可能会出问题:比如数据精度丢失
  2. 显式类型转换将所有情况混合在一起,代码不够清晰

因此C++提出了自己的类型转化风格,注意因为C++要兼容C语言,所以C++中还可以使用C语言的转化风格


3.C++的强制类型转换

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

  1. static_cast
int main()
{//相近类型(意义也相近)的转化(对应C的int、double、char之间转换)double d = 12.34;int a = static_cast<int>(d);cout<<a<<endl;return 0;
}
  1. reinterpret_cast
int main()
{//reinterpret_cast用于有一定关联,但意义不相似的类型间转换(对应C的int与int*)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;
}
  1. const_cast
void Test()
{//const_cast最常用的用途就是删除变量的const属性,方便赋值//注意:const_cast属于比较危险的转换volatile const int a = 2;int* p = const_cast<int*>(&a);*p = 3;//这里加volatile是因为编译器对const变量有优化,可能会放到寄存器,也有可能是把a直接替换为2//所以有时内存中数据修改了,但是打印发现没有变化,加volatile可以强制每次都去内存取cout << a << endl;
}
  1. dynamic_cast

dynamic_cast用于将一个父类对象的指针/引用转换为子类对象的指针或引用(向下转型,动态转换)

  • 向上转型:子类对象指针 / 引用->父类指针 / 引用(不需要转换,赋值兼容规则)
  • 向下转型:父类对象指针 / 引用->子类指针 / 引用(用dynamic_cast转型是安全的)

使用注意:

  1. dynamic_cast只能用于父类含有虚函数的类(虚函数我在多态那一文讲过)
    为什么:dynamic_cast 的工作原理是基于运行时的类型信息(RTTI)。当一个类包含至少一个虚函数时,编译器会自动为该类生成一个虚函数表,其中包含了所有虚函数的地址。每个该类的对象都会存储一个指向虚函数表的指针。因此,通过检查这个指针,我们可以确定对象的实际类型。但是,如果一个类没有虚函数,那么它就不会有虚函数表,也就无法在运行时确定其实际类型。在这种情况下,使用 dynamic_cast 进行类型转换会导致未定义的行为。 ---- 简而言之就是不知道指针指向的类型,不能确保安全性

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

class A
{
public :virtual void f(){}
};
class B : public A
{};
void fun (A* pa)
{// dynamic_cast会先检查是否能转换成功,能成功则转换,不能则返回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;
}



C++中const引用做参数的特殊机制

先看一种常见情况:

void fun(vector<int> v)
{//不做操作
}int main()
{initializer_list<int> li = { 1, 2, 3 };//这里很明显,也很常见,是隐式类型转换//只要vector<int>支持了initializer_list<int>做参数的构造即可fun(li);
}

再看引用做参数:

void fun(vector<int>& v)
{//不做操作
}int main()
{initializer_list<int> li = { 1, 2, 3 };//这里会报错,也很好理解,引用底层是指针,initializer_list<int>* 和 vector<int>* 不支持隐式转换fun(li);
}

最后看const引用做参数:

void fun(const vector<int>& v)
{//不做操作
}int main()
{initializer_list<int> li = { 1, 2, 3 };//这里不报错,原因是触发了隐式转换(存在对应构造函数),为什么://(1)const修饰后是不支持修改的,这个时候隐式转换是安全的//(2)如果普通引用也支持隐式类型转换的话,可能修改关键数据造成错误fun(li);
}



RTTI(扩展)

RTTI:Run-time Type identification的简称,即运行时类型识别
C++通过以下方式来支持RTTI:

  1. typeid运算符
  2. dynamic_cast运算符(本质是检查虚函数表)
  3. decltype

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

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

相关文章

vue3-模版引用ref

1. 介绍 概念&#xff1a;通过 ref标识 获取真实的 dom对象或者组件实例对象 2. 基本使用 实现步骤&#xff1a; 调用ref函数生成一个ref对象 通过ref标识绑定ref对象到标签 代码如下&#xff1a; 父组件&#xff1a; <script setup> import { onMounted, ref } …

Android Studio 之 菜单 Menu

选项菜单 OptionsMenu 用xml添加&#xff08;更建议使用&#xff09; 创建一个菜单布局 : 在 res文件下新建一个menu 目录&#xff0c;此时的菜单id为&#xff1a;R.menu.option <?xml version"1.0" encoding"utf-8"?> <menu xmlns:android&…

不同开发语言在进程、线程和协程的设计差异

不同开发语言在进程、线程和协程的设计差异 1. 进程、线程和协程上的差异1.1 进程、线程、协程的定义1.2 进程、线程、协程的差异1.3 进程、线程、协程的内存成本1.4 进程、线程、协程的切换成本 2. 线程、协程之间的通信和协作方式2.1 python如何实现线程通信&#xff1f;2.2 …

【Unity】AB包下载

【Unity】AB包下载 1.使用插件打AB包 a.AB包分类 一般地&#xff0c;将预制体作为AB包资源&#xff0c;不仅需要对预制体本身进行归类&#xff0c;还要对其涉及的动画&#xff08;AnimationClip&#xff09;、动画状态机&#xff08;AnimatorController&#xff09;、以及所…

144.二叉树的前序遍历

递归 public List<Integer> preorderTraversal(TreeNode root) {List<Integer> list new ArrayList<>();traversal(root, list);return list;}public void traversal(TreeNode t, List<Integer> list) {if (t null) {return;}list.add(t.val);trave…

《A++ 敏捷开发》- 5 量化管理从个人开始

我&#xff1a;你们管理层和客户都比较关心项目的进度&#xff0c;项目是否能按时完成&#xff1f;请问你们过去的项目如何&#xff1f; 开发&#xff1a;我们现在就是走敏捷开发&#xff0c;两周一个迭代。每次迭代前&#xff0c;我们聚一起开会&#xff0c;把所有用户故事按优…

2024.1.19力扣每日一题——使数组和小于等于 x 的最少时间

2024.1.19 题目来源我的题解方法一 动态规划方法二 动态规划&#xff08;空间优化&#xff09; 题目来源 力扣每日一题&#xff1b;题序&#xff1a;2809 我的题解 题解参考官方题解。 方法一 动态规划 若能找到一个最小的时间t使得数组和小于等于x&#xff0c;则最多在一轮…

Dubbo 3.2版本分析Provider启动时操作

Dubbo 3.2版本分析Provider启动时操作 前言例子分析onStarting 模块doStart 模块 小结 前言 上一篇文章&#xff0c;我们分析了 Dubbo 3.2 版本在 Provider 启动前的操作流程&#xff0c;这次我们具体分析具体它的启动过程&#xff0c;揭开它的神秘面纱。 例子 这里我们还是…

【ZYNQ入门】第八篇、基于Lwip构建TCP服务器

目录 第一部分、基础知识 1、小白入门必看文章 2、什么是Lwip&#xff1f; 3、什么是TCP/IP协议&#xff1f; 4、MAC地址、IP地址、子网掩码、网关 4.1、MAC地址 4.2、IP地址 4.3、子网掩码 4.4、网关 第二部分、硬件搭建 第三部分、软件代码 1、SDK工程的建立 2、…

数据结构与算法-二叉树-从中序与后序遍历序列构造二叉树

从中序与后序遍历序列构造二叉树 给定两个整数数组 inorder 和 postorder &#xff0c;其中 inorder 是二叉树的中序遍历&#xff0c; postorder 是同一棵树的后序遍历&#xff0c;请你构造并返回这颗 二叉树 。 示例 1: 输入&#xff1a;inorder [9,3,15,20,7], postorder …

云盘后端分析

1.验证码 用的是外面找的 2.发送邮箱验证码 配置邮箱的授权码 我们在发送邮箱的时候&#xff0c;需要把那个值传到数据库中&#xff0c;数据库中有它的状态&#xff0c;我们需要根据状态判断它是注册还是找回密码 我们在发送邮箱之前&#xff0c;先从session里面得到我们验证…

Rocky Linux 8.9 安装图解

风险告知 本人及本篇博文不为任何人及任何行为的任何风险承担责任&#xff0c;图解仅供参考&#xff0c;请悉知&#xff01;本次安装图解是在一个全新的演示环境下进行的&#xff0c;演示环境中没有任何有价值的数据&#xff0c;但这并不代表摆在你面前的环境也是如此。生产环境…

某度网盘提取下载链接JS逆向分析(一)

本次目标网址如下&#xff0c;使用base64解码后获得 aHR0cHM6Ly9wYW4uYmFpZHUuY29tL3MvMUZsaDBPeGpZamZJTFVZWUQzTm9fVnc 链接提取码为&#xff1a;ly12 本次逆向分析分为上下两篇文章说明&#xff0c;一为讲解如何从原链接通过逆向拿到下载链接&#xff0c;二为逆向登录拿到co…

flink结合Yarn进行部署

1. 什么是Yarn模式部署Flink 独立&#xff08;Standalone&#xff09;模式由 Flink 自身提供资源&#xff0c;无需其他框架&#xff0c;这种方式降低了和其他第三方资源框架的耦合性&#xff0c;独立性非常强。但我们知道&#xff0c;Flink 是大数据计算框架&#xff0c;不是资…

编程笔记 html5cssjs 044 CSS显示

编程笔记 html5&css&js 044 CSS显示 一、display 属性二、块级元素&#xff08;block element&#xff09;三、行内元素&#xff08;inline element&#xff09;四、disply属性设置&#xff08;一&#xff09;Display: none;&#xff08;二&#xff09;覆盖默认的 Disp…

C++学习笔记——指针

1&#xff0c;指针的基本概念 指针的作用&#xff1a;可以通过指针间接访问内存 内存的编号是从0开始记录的&#xff0c;一般用十六进制数字表示可以利用指针变量保存地址 上图中的p就是a变量的指针&#xff0c;也可以记作*a 2&#xff0c;指针变量的定义和使用 指针变量定…

Linux操作系统——理解文件系统

预备知识 到目前为止&#xff0c;我们所学习到的关于文件的操作&#xff0c;全部都是基于文件被打开&#xff0c;被访问&#xff0c;访问期间比较重要的有重定向&#xff0c;缓冲区&#xff0c;一切皆文件&#xff0c;当我们访问完毕的时候需要将文件关闭&#xff0c;关闭时那…

3.RHCSA脚本配置及通过node2改密码

运行脚本发现node2不成功 脚本破解 选第二个 Ctrl x 换行 破解成功后做node2的改密码题 回到redhat, 发现检测程序检测密码题成功,得了8分.

DBA技术栈MongoDB: 数据增改删除

该博文主要介绍mongoDB对文档数据的增加、更新、删除操作。 1.插入数据 以下案例演示了插入单个文档、多个文档、指定_id、指定多个索引以及插入大量文档的情况。在实际使用中&#xff0c;根据需求选择适合的插入方式。 案例1&#xff1a;插入单个文档 db.visitor.insert({…

集成显卡和普通显卡的区别?

问题描述&#xff1a;集成显卡和普通显卡的区别&#xff1f; 问题解答&#xff1a; 集成显卡和独立显卡&#xff08;普通显卡&#xff09;是两种不同类型的图形处理单元&#xff0c;它们在计算机系统中负责处理图形和视频输出&#xff0c;但它们有一些关键的区别&#xff1a;…