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

第二十三讲:特殊类和类型转换

目录

1、特殊类设计

1.1、不能被拷贝

1.2、只能在堆上创建

1.3、只能在栈上创建

1.4、不能被继承

1.5、单例模式

1.5.1、饿汉模式

1.5.2、懒汉模式

2、类型转换

2.1、C语言中的类型转换

2.2、C++四种类型转换

2.2.1、static_cast

2.2.2、reinterpret_cast

2.2.3、const_cast

2.2.4、dynamic_cast

3、RTTI


1、特殊类设计

1.1、不能被拷贝

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

C++98:将拷贝构造函数与赋值运算符重载只声明不定义,并且将其访问权限设置为私有即可。原因:

1、设置成私有:如果只声明没有设置成private,用户自己如果在类外定义了,就可以拷贝了。

2、只声明不定义:不定义是因为该函数根本不会调用,定义了其实也没有什么意义,不写反而还简单,而且如果声明了就不会生成默认的拷贝和赋值重载函数了。

C++11:在默认成员函数后跟上=delete,表示让编译器删除掉该默认成员函数。

不再举例,因为在智能指针这一部分已经举例了。

1.2、只能在堆上创建

第一种方式是:将析构函数私有化。例如:

class HeapOnly
{
private:~HeapOnly(){cout << "~HeapOnly" << endl;}
};int main()
{HeapOnly hp1; // 会报错static HeapOnly hp2; // 会报错return 0;
}

这样就只能在堆上创建对象了,如果要释放在堆上创建的该对象,直接使用delete是不行的,可以写一个销毁函数来解决这个问题,如下:

class HeapOnly
{
public:static void Destory(HeapOnly* ptr){delete ptr;}
private:~HeapOnly(){cout << "~HeapOnly" << endl;}
};int main()
{	HeapOnly* ptr = new HeapOnly;HeapOnly::Destory(ptr);return 0;
}

或者,也可以像下面这样写:

class HeapOnly
{
public:void Destory(){delete this;}
private:~HeapOnly(){cout << "~HeapOnly" << endl;}
};int main()
{	HeapOnly* ptr = new HeapOnly;ptr->Destory();return 0;
}

第二种方式:将类的构造函数私有,拷贝构造声明成私有。防止别人调用拷贝构造在栈上生成对象。例如:

class HeapOnly
{
private:HeapOnly(){cout << "HeapOnly" << endl;}HeapOnly(const HeapOnly& hp){cout << "HeapOnly(const HeapOnly& hp)" << endl;}
};int main()
{// 下面的这些都会报错HeapOnly hp1;static HeapOnly hp2;HeapOnly* ptr = new HeapOnly;HeapOnly copy(ptr);return 0;
}

如果我们此时想要创建对象的话,就可以像下面这样做:

class HeapOnly
{
public:static HeapOnly* CreateObj(){return new HeapOnly;}
private:HeapOnly(){cout << "HeapOnly" << endl;}HeapOnly(const HeapOnly& hp){cout << "HeapOnly(const HeapOnly& hp)" << endl;}
};int main()
{HeapOnly* ptr = HeapOnly::CreateObj();return 0;
}

1.3、只能在栈上创建

可以将构造函数私有化,并禁掉new和delete函数。例如:

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:StackOnly(){cout << "StackOnly" << endl;}
};int main()
{StackOnly obj = StackOnly::CreateObj();StackOnly* ptr = new StackOnly; // 报错StackOnly* ptr = new StackOnly(obj); // 报错return 0;
}

1.4、不能被继承

C++98:构造函数私有化,派生类中调不到基类的构造函数。则无法继承。

C++11:使用final关键字,final修饰类,表示该类不能被继承。

这个在之前就已经提到过了,不再举例。

1.5、单例模式

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

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

注:设计模式有很多,常见的设计模式就几个,下面的单例模式就是常见的一种设计模式。

一个类只能创建一个对象,即单例模式,该模式可以保证系统中该类只有一个实例,并提供一个 访问它的全局访问点,该实例被所有程序模块共享。单例模式有两种实现模式: 饿汉模式和懒汉模式。

1.5.1、饿汉模式

饿汉模式就是说不管你将来用不用,程序启动时就创建一个唯一的实例对象。

例如:

// 饿汉模式
class A
{
public:static A* GetInstance(){return &_inst;}void Add(const string& key, const string& value){_dict[key] = value;}void Print(){for (auto kv : _dict){cout << kv.first << " : " << kv.second << endl;}cout << endl;}private:A(){}A(const A& aa) = delete;A& operator=(const A& aa) = delete;map<string, string> _dict;int _n = 0;static A _inst;
};
A A::_inst; // 在执行main函数前就会被创建好。int main()
{A::GetInstance()->Add("sort", "分类、排序");A::GetInstance()->Add("left", "左边");A::GetInstance()->Add("right", "右边");A::GetInstance()->Print();return 0;
}

优点:相比下面的懒汉模式实现会更加简单。

缺点:可能会导致进程启动变慢(指从程序启动到开始执行main函数);另外一个就是如果一个项目中有多个单例,这多个单例类对象实例启动顺序不确定,饿汉模式是无法控制实例化的顺序的,此时就需要考虑下面的懒汉模式了。

1.5.2、懒汉模式

如果单例对象构造十分耗时或者占用很多资源,而有可能该对象程序运行时不会用到,那么也要在程序一开始就进行初始化,就会导致程序启动时非常的缓慢;此外,如果有顺序的要求,也不能使用饿汉模式。所以在这些情况下使用懒汉模式更好。

例如:

// 懒汉模式
class B
{
public:static B* GetInstance(){if (_inst == nullptr){_inst = new B; // 懒汉对象一般不需要释放,因为程序运行过程中,还需要使用它(这种不属于内存泄漏)。// 等到进程正常结束,它也就自然而然的释放了。}return _inst;}void Add(const string& key, const string& value){_dict[key] = value;}void Print(){for (auto kv : _dict){cout << kv.first << " : " << kv.second << endl;}cout << endl;}static void DelInstance() // 如果有需要的话,可以调用该函数销毁懒汉对象。{if (_inst != nullptr){delete _inst;_inst = nullptr;}}private:B(){}~B(){// 如果需要做一些动作的话,比如:持久化(将一些数据写到文件)cout << "数据写到文件" << endl;}B(const B& aa) = delete;B& operator=(const B& aa) = delete;map<string, string> _dict;int _n = 0;static B* _inst;class gc{public:~gc(){DelInstance();}};static gc _gc;
};
B* B::_inst = nullptr; // 在执行main函数前就会被创建好。
B::gc B::_gc;int main()
{B::GetInstance()->Add("sort", "分类、排序");B::GetInstance()->Add("left", "左边");B::GetInstance()->Add("right", "右边");B::GetInstance()->Print();B::GetInstance()->Add("right", "xxx");B::GetInstance()->Print();return 0;
}

优点:第一次使用实例对象时,创建对象,进程启动无负载。多个单例实例启动顺序可自由控制。

缺点:实现比较复杂,懒汉模式是有线程安全问题的,等后面的文章讲线程时再说。

2、类型转换

2.1、C语言中的类型转换

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

1、隐式类型转化:编译器在编译阶段自动进行,能转就转,不能转就编译失败。

2、显式类型转化:需要用户自己处理。

例如:

void Test()
{int i = 1;// 隐式类型转换double d = i; // 因为int和double都是用来表示数据大小的,所以可以进行隐式类型转换。int* p = &i;// 显示的强制类型转换int address = (int)p; // 因为int和int*的含义是不一样的,一个表示地址,一个表示数据大小,因此不能隐式类型转换。// 之所以可以强制类型转化是因为两者之间还有一定的关联性。// 注:完全不相关的类型之间是无法进行互相转换的。
}

C风格的转换格式很简单,但是有不少缺点的:

1、隐式类型转化有些情况下可能会出问题:比如数据精度丢失。

2、显式类型转换代码不够清晰。

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

2.2、C++四种类型转换

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

2.2.1、static_cast

static_cast是静态转换,编译器隐式执行的任何类型转换都可用 static_cast,但它不能用于两个不相关的类型进行转换。对应的就是C语言的隐式类型转换。例如:

int main()
{double d = 12.34;int a = static_cast<int>(d);cout << a << endl;int* ptr = static_cast<int*>(a); // 会报错return 0;
}
2.2.2、reinterpret_cast

reinterpret_cast操作符通常用于将一种类型转换为另一种不同的类型。对应C语言的强制类型转换。例如:

int main()
{int a = 0;int* ptr = reinterpret_cast<int*>(a); return 0;
}
2.2.3、const_cast

const_cast最常用的用途就是删除变量的const属性,方便赋值。对应与C语言的强制类型转换,例如:

int main()
{volatile const int a = 2;int* p = const_cast<int*>(&a);*p = 3;cout << a << endl; // 上面不加volatile最终会导致这里的a的值的打印为2。cout << *p << endl;cout << (void*)&a << endl; // 之所以进行了转换,是因为cout无法正确识别该类型。cout << p << endl;return 0;
}
2.2.4、dynamic_cast

dynamic_cast用于将一个父类对象的指针或者引用转换为子类对象的指针或引用。这里的dynamic_cast针对的是向下转型。

向上转型:子类对象、指针或者引用->父类对象、指针或者引用(不需要转换,赋值兼容规则)。

向下转型:父类对象指针或者引用->子类指针或者引用(用dynamic_cast转型是安全的)。

注意:

1、dynamic_cast只能用于父类含有虚函数的类。

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

例如:

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

运行结果为:

3、RTTI

RTTI:运行时类型识别。C++通过以下方式来支持RTTI:

1、typeid运算符。

2、dynamic_cast运算符。

3、decltype。

上面的这三个在之前的文章中都讲过,不再举例说明。

http://www.dtcms.com/a/438320.html

相关文章:

  • 如何区分数学中的定理、引理、命题?
  • 森东网站建设南昌网站排名优化软件
  • 深圳网站建设制作开发公司开发公司app
  • 《强化学习数学原理》学习笔记8——贝尔曼最优公式小结
  • discuz网站开发深圳建设网站首页
  • Linux信号处理的相关数据结构和操作函数
  • 分类信息网站手机企业网站开发
  • 做杂志的网站有哪些织梦网站系统
  • 我的网站百度怎么搜索不到了文山网站建设代理
  • 小程序推广网站免费wordpress模板下载地址
  • 第66篇:AI+交通:智能驾驶、交通流优化与智慧物流
  • 苏州自学网站建设平台做外国美食的视频网站
  • 黄冈app下载推广平台优化视频
  • 学习日记20:GraphGPT
  • 做网站加班多吗蛋糕店网站建设方案
  • 从餐馆迎客看 accept4:更灵活的“接客“高手
  • Metasploit基础(MSF)
  • 浅析物理层过程
  • 总结 IP 协议的相关特性
  • 网球馆自动预约系统的反调试
  • PyQt5 QLineEdit组件详解:单行文本输入控件的完整指南
  • 网站建设的毕业报告公司名称变更流程及需材料
  • OSPF 多区域实验 概念及题目
  • 网站建设要经历哪些步骤丝芭传媒有限公司
  • 东莞微信网站建设怎样ceo是什么职位什么工作
  • model.fit(train_X, train_y)
  • 数据结构之队列:初始化、入队、出队与源码全解析
  • 国内外优秀网站网站建设江苏百拓
  • hive、spark任务报错或者异常怎么排查以及定位哪段sql
  • 南昌商城网站设计洛阳青峰网络做网站