单列模式和C++中的类型转换 1.设计模式单列模式设计模式Design Pattern是一套被反复使用、多数人知晓的、经过分类的、代码设计经验的总结。使用设计模式的目的为了代码可重用性、让代码更容易被他人理解、保证代码可靠性。 设计模式使代码编写真正工程化设计模式是软件工程的基石脉络如同大厦的结构一样。单列模式一个类只能创建一个对象即单例模式该模式可以保证系统中该类只有一个实例并提供一个访问它的全局访问点该实例被所有程序模块共享。要想控制一个类的创建一定要封装一下构造函数。而我们没有办法通过构造函数创建对象随意只能将封装的构造函数属性设为 static。类外不能直接通过new或构造函数创建对象这里实现一个 mapstringstring 类型的字典单列模式有两种实现模式1.1 饿汉模式就是说不管你将来用不用程序启动时就创建一个唯一的实例对象。全局变量在main 函数之前就可以创建好但是有缺陷不能直接创建构造函数私有。如果封装的构造返回的是 new 的指针用指针能创建一个就能创建多个。如果返回 static new 会违背饿汉模式的初衷饿汉模式的核心优势是在静态存储区数据段编译期就分配好内存访问速度极快且绝对安全。class A { public: //就是全局函数只是受类域限制 static A* GetInstance() //static 才能类外调用也可以返回引用 { return _inst; } void Add(const string key, const string value) { _dict[key] value; } void Point() { 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;//禁掉赋值一般一块禁掉 mapstring, string _dict; int _n; //就是全局变量只是受类域限制 static A _inst;//不是自己创建自己静态不在对象里面存在静态区。 //这是声明,可以调用类内的构造 }; //类外定义main函数启动前创建好 A A::_inst; ////instance_inst 是全局静态变量程序一启动、进入 main 之前就已经构造好了不是你调用 GetInstance() 才创建。 int main() { A::GetInstance()-Add(sort, 排序); A::GetInstance()-Add(left, 左边); A::GetInstance()-Add(right, 右边); A* p A::GetInstance();//获取的都是同一个 p-Add(right, wdz); A::GetInstance()-Point(); A dict *A::GetInstance();//引用 //A copy(*A::GetInstance()); //所以要禁掉拷贝构造函数 //*A::GetInstance() *A::GetInstance(); //禁掉 赋值运算符重载 return 0; }成员类型类内声明类外定义强制原因静态成员变量必须是C17前强制除非加inline需要分配独立的内存空间静态成员函数必须声明否直接在类内实现最常用函数是代码指令不占对象内存构造函数不能在 public中对象在自己类域内 static 创建使用时调用 static 成员函数获得对象饿汉模式只用全局对象不行因为构造函数时需要私有无法在外部调用。饿汉模式的优点实现简单。饿汉模式的缺点可能会导致线程启动慢。如何两个单例有启动先后顺序那么饿汉无法控制因为可能在不同文件被其他全局对象依赖这样两个对象之间便有了依赖关系1.2 懒汉模式如果单例对象启动 构造十分耗时或者占用很多资源比如加载插件啊 初始化网络连接啊读取文件啊等等而有可能该对象程序运行时不会用到那么也要在程序一开始就进行初始化就会导致程序启动时非常的缓慢。 所以这种情况使用懒汉模式延迟加载更好。从饿汉的方式开始改开始只创建对象的空指针第一次调用的时候如果再申请空间class B { public: //就是全局函数只是受类域限制 static B* GetInstance() //static 才能类外调用也可以返回引用 { if (_inst nullptr) _inst new B; return _inst; } void Add(const string key, const string value) { _dict[key] value; } void Point() { for (auto kv : _dict) { cout kv.first : kv.second endl; } cout endl; } ~B()//程序执行完会自己 释放所以空间这里是测试 { //持久化操作比如将数据写到文件 cout ~B endl; } static void DeIInstance()//别人想提前析构调也可以调 { if (_inst ! nullptr) { delete _inst; _inst nullptr; } } private: B(){ }; B(const B aa) delete;//禁掉拷贝构造 B operator(const B aa) delete;//禁掉赋值一般一块禁掉 mapstring, string _dict; int _n; //就是全局变量只是受类域限制 static B* _inst; //内部类不是外部类的 友元因此默认不能访问外部类的私有成员 //除非外部类显式声明 friend class B; class gc { public: ~gc() { DeIInstance();// 内部类只能访问外部类的静态 还是相互独立只是作用域变化 } }; static gc _gc; }; B* B::_inst nullptr; //gc类空类大小1字节如果 B 的 析构函数 私有时 可以调用释放B B::gc B::_gc;//全局对象main/程序执行完 期望会主动析构_inst, //最后一道 释放 B::_inst的闸口没有B::_inst也会释放 int main() { B::GetInstance()-Add(sort, 排序); B::GetInstance()-Add(left, 左边); B::GetInstance()-Add(right, 右边); B* p B::GetInstance();//获取的都是同一个 p-Add(right, wdz); B::GetInstance()-Point(); p-DeIInstance();自己提前显示释放 //A copy(*A::GetInstance()); //*A::GetInstance() *A::GetInstance(); delete p;//期望main函数结束前自动调用 return 0; }懒汉模式有一个问题线程安全如果多个同时new _inst时会出现问题我们后面文章会做讲解new 的懒汉对象一般是不需要释放因为它是单例全局作用进程正常结束会释放如果想要显示释放可以使用 delete 或 全局内部类对象的方法外部类也可以释放。关于 B 的释放gc类空类大小1字节如果 B 的 析构函数 私有时 可以调用释放BB::gc B::_gc 也是声明定义分离是全局对象main/程序执行完可以主动析构 _inst 是最后一道 释放 B::_inst 的闸口没有B::_inst 程序执行完也会释放技术上可以业务上不行。优点第一次使用实例对象时创建对象。进程启动无负载。多个单例实例启动顺序自由控制先执行的会首先构造。缺点复杂现代 C 懒汉单例class B { public: static B GetInstance() { static B instance; // 静态区对象自动析构无需 _gc return instance; } // ... 业务函数 ... private: B() default; ~B() { /* 持久化操作程序退出自动执行 */ } B(const B) delete; B operator(const B) delete; };2. C中的类型转换2.1 C语言中的类型转换在C语言中如果赋值运算符左右两侧类型不同或者形参与实参类型不匹配或者返回值类型与接收返回值类型不一致时就需要发生类型转化C语言中总共有两种形式的类型转换隐式类型转换和显式类型转换。隐式类型转化编译器在编译阶段自动进行能转就转不能转就编译失败显式类型转化需要用户自己处理void Test () { int i 1; // 隐式类型转换 double d i; printf(%d, %.2lf\n , i, d); int* p i; // 显示的强制类型转换 int address (int) p; printf(%x, %d\n , p, address); }缺陷转换的可视性比较差所有的转换形式都是以一种相同形式书写难以跟踪错误的转换。2.2 为什么C需要四种类型转换C风格的转换格式很简单但是有不少缺点的隐式类型转化有些情况下可能会出问题比如数据精度丢失显式类型转换将所有情况混合在一起代码不够清晰因此C提出了自己的类型转化风格注意因为C要兼容C语言所以C中还可以使用C语言的转化风格2.3 C强制类型转换标准C为了加强类型转换的可视性引入了四种命名的强制类型转换操作符static_cast、reinterpret_cast、const_cast、dynamic_cast2.3.1 static_caststatic_cast用于非多态类型的转换静态转换编译器隐式执行的任何类型转换都可用static_cast但它不能用于两个不相关的类型进行转换。size_t 和 int 会隐式类型转换小于 0 时会出现问题。显示类型转换容易被人注意到。int main() { //相近类型用static_cast-意义相近 的类型 double d 12.34; int a static_castint(d);//对应c语言隐式类型转换 coutaendl; return 0; }2.3.2 reinterpret_castreinterpret_cast操作符通常为操作数的位模式提供较低层次的重新解释用于将一种类型转换为另一种不同的类型指针和整型不是相似类型需要使用reinterpret_cast。int main() { double d 12.34; int a static_castint(d); cout a endl; // 这里使用static_cast会报错应该使用reinterpret_cast //int *p static_castint*(a); int *p reinterpret_castint*(a); return 0; }2.3.3 const_castconst_cast最常用的用途就是删除变量的const属性但绝对不能用来修改原本就是 const 的对象。int main() { const int a 2; int* p const_castint*(a); *p 3; cout a endl;//结果是2 cout *p endl; cout a endl; cout p endl; return 0; }在vs环境下运行发现输出a的值还是2没有被修改因为这里编译器发生了优化认为 const 变量不会被修改直接放到寄存器中或者define为常量所以有可能以后直接从编译器中读2不存在内存中但他们地址却相同。我们可以使用 volatile 表示被修饰的变量的值可能会在程序的控制流之外被改变但结果发现a的地址是1。所以不要试图为这些诡异数字找一个精确的“编译器优化原理”因为它们本身就是“无原理可讲”的未定义行为垃圾结果C认为const变量不能被修改。2.3.4 dynamic_castdynamic_cast 是 C 中专门用于多态类型的运行时类型识别RTTI转换操作符。它允许在类的继承层次结构中安全地进行向下转换从基类到派生类或横向转换到兄弟类。核心行为向下转换基类 → 派生类如果expression实际指向/引用的对象是TargetType类型或由其公有派生的类型转换成功否则失败。横向转换在多重继承中可将基类指针转换为另一个基类指针兄弟类只要实际对象同时派生自两者。转换为void*dynamic_castvoid*(expression)返回指向实际对象最派生对象的指针。失败处理指针版本失败返回nullptr。引用版本失败抛出std::bad_cast异常。注意dynamic_cast只能用于父类含有虚函数的类dynamic_cast会先检查是否能转换成功能成功则转换不能则返回0首先复习一下子类对象/指针赋值给父类对象/指针/引用中间不产生临时变量。向上转型子类对象指针/引用-父类指针/引用(不需要转换赋值兼容规则不产生临时变量)向下转型父类对象指针/引用-子类指针/引用(需要使用dynamic_cast转型是安全的)class A { public: virtual void f() {} int _a 0; }; class B : public A { public: virtual void f() {} int _b 1; }; int main() { A obja; B objb; obja objb;//不会产生临时变量 A ra objb;//不会产生临时变量 double d 1.1;//会产生临时变量 const int i d; return 0; }应用场景由于A*是父类指针传入子类也可以调用但传入父类之后强制类型转换为子类是不安全的比如 fun1 函数void fun1(A* pa) //子类父类都可以调用 { //直接转换是不安全的 B* ptr (B*)pa; ptr-_a; ptr-_b;//如果是父类调用 是没有 _b } void fun(A* pa) { B* ptr dynamic_castB*(pa);//pa 必须支持多态 if (ptr)//可以间接识别出 pa指向父类还是子类 { ptr-_a; ptr-_b; } else { cout 转换失败 endl; } }RTTIRun-time Type identification的简称即运行时类型识别。C通过以下方式来支持RTTItypeid运算符dynamic_cast运算符decltype本篇结束