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

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

相关文章:

  • 记录一次QT乱码问题
  • GMT学习记录
  • HBuilder X中uView UI框架的安装及使用
  • Linux 命令大全 CentOS常用运维命令
  • AI对比:ChatGPT与文心一言的异同与未来
  • React的合成事件
  • vant组件库的简单使用
  • Docker(四)操作容器
  • 庞加莱猜想:从三维空间到数学的顶峰
  • 2023年总结我所经历的技术大变革
  • 【大数据】流处理基础概念(一):Dataflow 编程基础、并行流处理
  • 【JavaScript】事件监听:表单事件(上篇)
  • electron源码下载及编译
  • 如何有效清理您的Python环境:清除Pip缓存
  • Qt事件过滤
  • “寒冬”下的金三银四跳槽季来了,帮你客观分析一下局面
  • GDB 调试 Mysql 实战(三)优先队列排序算法中的行记录长度统计是怎么来的(上)...
  • java B2B2C 源码多租户电子商城系统-Kafka基本使用介绍
  • JavaScript函数式编程(一)
  • Shadow DOM 内部构造及如何构建独立组件
  • 多线程事务回滚
  • 聊聊directory traversal attack
  • 那些年我们用过的显示性能指标
  • 如何借助 NoSQL 提高 JPA 应用性能
  • 删除表内多余的重复数据
  • 一个JAVA程序员成长之路分享
  • 原创:新手布局福音!微信小程序使用flex的一些基础样式属性(一)
  • 在Unity中实现一个简单的消息管理器
  • 做一名精致的JavaScripter 01:JavaScript简介
  • [Shell 脚本] 备份网站文件至OSS服务(纯shell脚本无sdk) ...
  • ​软考-高级-系统架构设计师教程(清华第2版)【第12章 信息系统架构设计理论与实践(P420~465)-思维导图】​
  • #基础#使用Jupyter进行Notebook的转换 .ipynb文件导出为.md文件
  • (Arcgis)Python编程批量将HDF5文件转换为TIFF格式并应用地理转换和投影信息
  • (第一天)包装对象、作用域、创建对象
  • (附源码)springboot助农电商系统 毕业设计 081919
  • (附源码)计算机毕业设计ssm电影分享网站
  • (免费领源码)Java#ssm#MySQL 创意商城03663-计算机毕业设计项目选题推荐
  • (十一)图像的罗伯特梯度锐化
  • (转)LINQ之路
  • (转)清华学霸演讲稿:永远不要说你已经尽力了
  • ****** 二十三 ******、软设笔记【数据库】-数据操作-常用关系操作、关系运算
  • .Net IOC框架入门之一 Unity
  • .net 使用$.ajax实现从前台调用后台方法(包含静态方法和非静态方法调用)
  • .NetCore Flurl.Http 升级到4.0后 https 无法建立SSL连接
  • .NET设计模式(2):单件模式(Singleton Pattern)
  • .Net通用分页类(存储过程分页版,可以选择页码的显示样式,且有中英选择)
  • .sh文件怎么运行_创建优化的Go镜像文件以及踩过的坑
  • .vimrc php,修改home目录下的.vimrc文件,vim配置php高亮显示
  • ??在JSP中,java和JavaScript如何交互?
  • @Resource和@Autowired的区别
  • [.NET 即时通信SignalR] 认识SignalR (一)
  • [<MySQL优化总结>]
  • [Angularjs]ng-select和ng-options
  • [autojs]autojs开关按钮的简单使用
  • [C++]运行时,如何确保一个对象是只读的