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

C++ 之 【特殊类设计 与 类型转换】

目录

1. 特殊类设计

1.1 不能被拷贝的类

1.2 只能在堆上创建对象的类

1.3 只能在栈上创建对象的类

1.4 不能被继承的类

1.5 只能创建一个对象的类(单例模式)

饿汉模式

懒汉模式

2. 类型转换

2.1 C语言中的类型转换

2.2 为什么C++需要四种类型转换

2.3 C++强制类型转换

static_cast

reinterpret_cast

const_cast

dynamic_cast

3. RTTI(了解)


1. 特殊类设计

1.1 不能被拷贝的类

不能被拷贝,则不能被拷贝构造或赋值

class A
{    //...A(const A& aa) = delete;A& operator=(const A& aa) = delete;//...
};class A
{    //...
private:A(const A& aa);A& operator=(const A& aa);//...
};

为了禁用 拷贝构造和赋值重载函数

C++11 使用delete让编译器删除掉该默认成员函数

C++98 将这两个成员函数声明为私有(不定义可以防止成员函数内部进行拷贝操作)

1.2 只能在堆上创建对象的类

//只能再堆上创建对象
class HeapOnly
{
public:static HeapOnly* CreateObj(){return new HeapOnly;}private:HeapOnly(){  // ...}HeapOnly(const HeapOnly& ho){ // ... }
};

(1) 私有构造函数、拷贝构造函数,使得外界不能直接创建对象

(2) 提供一个静态成员函数返回在堆上创建对象的指针,外界再用指针接收即可

不能使用成员函数返回在堆上创建对象的指针,

因为成员函数需要被对象调用,而此时还没有对象

1.3 只能在栈上创建对象的类


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;
private:StackOnly(){}
};

(1) 私有构造函数,使得外界不能直接创建对象

(2) 提供一个静态成员函数返回在栈上创建的对象 

不能传指针或引用返回,因为函数结束对象就销毁了 ;不能使用成员函数返回在栈上创建的对象 ,因为成员函数需要被对象调用,而此时还没有对象

(3) 删除全局 operator new,禁止调用拷贝构造函数,详见 内存管理 new的原理

1.4 不能被继承的类

//设计一个不能被继承的类
class C final
{};
class B 
{
public:static B GetInstance(){return B();}private:B(){}
};

C++11中 用 final 修饰,则该类不能被继承

C++98中 将构造函数私有,派生类不能调用基类的构造函数,就不能实现继承

1.5 只能创建一个对象的类(单例模式)

设计模式是一套被反复使用、多数人知晓的、经过分类的、代码设计经验的 总结

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

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

单例模式有 饿汉模式和懒汉模式 之分

  • 饿汉模式

namespace hungry
{class Singleton{public:static Singleton* CreateObj(){return &_sl;}private:Singleton() {}//私有构造函数,外界不能创建Singleton(const Singleton& sl);//私有拷贝构造函数,外界不能创建Singleton& operator=(const Singleton& sl);//防止拷贝static Singleton _sl;};Singleton Singleton::_sl;//在程序入口之前就完成单例对象的初始化
}

(1) 私有构造函数,使得外界不能直接创建对象

(2) 私有拷贝构造函数、赋值重载函数,且不实现,防止外界与成员函数内部进行拷贝

(3) 声明一个静态成员对象,整个类只有这一个对象,并且是静态的

(4) 提供一个静态成员函数返回静态成员对象的地址

 饿汉模式

优点:简单,程序启动时就创建一个唯一的实例对象

缺点:可能会导致进程启动慢,且如果有多个单例类对象实例启动顺序不确定

  • 懒汉模式

暂时未涉及多线程

namespace lazy
{class Singleton{public:static Singleton* CreateObj(){// 注意这里一定要使用Double-Check的方式加锁,才能保证效率和线程安全if (!_sl)_sl = new Singleton();return _sl;}// 实现一个内嵌垃圾回收类    class CGarbo //类似于智能指针{public:~CGarbo(){if (Singleton::_sl)delete Singleton::_sl;}};static CGarbo Garbo;private:Singleton() {}//私有构造函数,外界不能创建Singleton(const Singleton& sl);//私有拷贝构造函数,外界不能创建Singleton& operator=(const Singleton& sl);//防止拷贝static Singleton* _sl;//单例对象指针};Singleton* Singleton::_sl = nullptr;Singleton::CGarbo Garbo;
}

(1) 私有构造函数,使得外界不能直接创建对象

(2) 私有拷贝构造函数、赋值重载函数,且不实现,防止外界与成员函数内部进行拷贝

(3) 声明一个静态成员指针(整个类共用),初始化为空,后续指向唯一的对象

(4) 提供一个静态成员函数返回单例对象指针

(5) 单例类对象的析构函数不能被直接调用,即使指针调用也有内存泄露的风险,解决方法:

实现一个内部类,并在单例类中声明一个静态内部类对象,当单例类对象生命周期结束时,编译器调用单例类对象的析构函数的同时,也会调用内部类对象的析构函数,进而释放单例类对象,类似于智能指针

多线程实现方式,后续讲解

class Singleton
{
public:static Singleton* GetInstance() {// 注意这里一定要使用Double-Check的方式加锁,才能保证效率和线程安全if (nullptr == m_pInstance) {m_mtx.lock();if (nullptr == m_pInstance) {m_pInstance = new Singleton();}m_mtx.unlock();}return m_pInstance;}// 实现一个内嵌垃圾回收类    class CGarbo {public:~CGarbo() {if (Singleton::m_pInstance)delete Singleton::m_pInstance;}};// 定义一个静态成员变量,程序结束时,系统会自动调用它的析构函数从而释放单例对象static CGarbo Garbo;
private:// 构造函数私有Singleton() {};// 防拷贝Singleton(Singleton const&);Singleton& operator=(Singleton const&);static Singleton* m_pInstance; // 单例对象指针static mutex m_mtx;//互斥锁
};
Singleton* Singleton::m_pInstance = nullptr;
Singleton::CGarbo Garbo;
mutex Singleton::m_mtx;

2. 类型转换

2.1 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.2 为什么C++需要四种类型转换

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

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

2. 显式类型转换将所有情况混合在一起,代码不够清晰

因此C++提出了自己的类型转化风格,

注意因为C++要兼容C语言,所以C++中还可以使用C语言的 转化风格

2.3 C++强制类型转换

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

  • static_cast

static_cast用于非多态类型的转换(静态转换),

编译器隐式执行的任何类型转换都可用 static_cast,即

static_cast用于两个相关的类型进行转换

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

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

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

const_cast 不能直接用于基本类型(如 int)的转换,需通过指针或引用间接操作

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

通过指针p我们确实修改了常变量a在内存中存储的值,

但是,编译器会优化常变量的访问操作,即上述代码打印出 a 的值为 2

要想真正使用常变量内存中存储的值需要使用 volatile 关键字

volatile 是 C++ 中的一个关键字,用于告知编译器某个变量的值可能在程序外部被意外修改

void Test()
{volatile const int a = 2;//常变量int* p = const_cast<int*>(&a);*p = 3;cout << a << endl;
}
  • 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:Run-time Type identification的简称,即:运行时类型识别。

C++通过以下方式来支持RTTI:

1. typeid运算符

2. dynamic_cast运算符

3. decltype

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

相关文章:

  • 第14章 MySQL索引
  • Entities - 遍历与查询
  • TargetGroup 全面优化:从六个维度打造卓越用户体验
  • Proxy与Reflect
  • 浅解Letterbox算法
  • 【Triton 教程】triton_language.permute
  • JavaScript洗牌算法实践
  • 掌握timedatectl命令:Ubuntu 系统时间管理指南
  • 【RT Thread】RTT内核对象机制详解
  • Seata分布式事务
  • 用例图讲解
  • makefile原理
  • AUTOSAR CP开发流程总结
  • 通过VNC实现树莓派远程桌面访问
  • linux信号done
  • BeanUtils.copyProperties 映射规则详解
  • 物联网 frid卡控制
  • LeetCode刷题记录----322.零钱兑换(Medium)
  • 2015/07 JLPT听力原文 问题四
  • Redis集群实验
  • 昇腾生态双支柱:MindSpore 与 CANN 的全栈技术解析
  • YOLO系列——实时屏幕检测
  • 牛客算法基础noob49 上三角矩阵判定
  • autosar 中OS模块理解
  • 通俗范畴论17.2 向量空间的对偶与双对偶
  • huggingface_hub 安装部署问题汇总
  • 在我的Java项目中为什么使用AllArgsConstructor注解注入的方式启动报错了:
  • π0:一个 VLA 流匹配模型用于通用机器人控制(又称 pi0)
  • Information theorem-Entropy
  • 编译原理实验报告——词法分析程序