从C语言到C++_37(特殊类设计和C++类型转换)单例模式(中)

简介: 从C语言到C++_37(特殊类设计和C++类型转换)单例模式

从C语言到C++_37(特殊类设计和C++类型转换)单例模式(上):https://developer.aliyun.com/article/1522499

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

单例模式是一种设计模式。

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


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


面试会有时会问到什么是单例模式:        


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


       比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息,这种方式简化了在复杂环境下的配置管理。


单例模式有两种实现模式:饿汉模式和懒汉模式

1.5.1 饿汉模式

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

class Singleton
{
public:
  static Singleton* GetInstance() // 获取单例对象接口
  {
    return &m_instance;
  }
 
private:
  Singleton() // 构造函数私有化
  {}
  Singleton(const Singleton& s) = delete; // 禁止使用拷贝构造
  Singleton& operator=(const Singleton& s) = delete; // 禁止使用赋值运算符重载
 
  //保证单例对象在静态区且只有一个
  static Singleton m_instance;//单例对象
};
 
Singleton Singleton::m_instance;//在程序入口之前就完成单例对象初始化
 
int main()
{
  Singleton* s = Singleton::GetInstance();//获取单例对象
 
  return 0;
}

       静态成员变量只能在类域外进行定义初始化。所以在main函数之前就将单例对象定义初始化,此时该单例对象创建在静态区上,而且仅有一个,后面就无法再创建。


       想要获取该单例对象只能通过静态成员函数GetInstance()来获取。


静态成员函数可以直接访问静态成员变量m_instance。


       为什么称之为饿汉模式呢?不管将来会不会使用到这个单例对象,但是在程序一启动还没有进入main函数之前就创建一个唯一的实例对象。就像一个饿汉一样,一上来就先吃(创建单例对象)。


缺点:


可能会导致进程启动较慢,如过实例对象很复杂,在创建实例对象时就会花费很多时间。

实例顺序不确定,如果有多个单例对象,并且对象之间存在互相依赖关系,由于对象的实例对象不确定(和代码顺序无关,由编译器决定),此时就会发生错误。

优点:简单、没有线程安全问题。


       如果这个单例对象在多线程高并发环境下频繁使用,性能要求较高,那么显然使用饿汉模式来避 免资源竞争,提高响应速度更好。

1.5.2 懒汉模式

懒汉模式:

class Singleton
{
public:
  static Singleton* GetInstance()
  {
    // 如果单例对象没有创建则在堆区创建
    if (m_pInstance == nullptr)
    {
      m_pInstance = new Singleton;
    }
 
    return m_pInstance;
  }
 
private:
  Singleton() // 构造函数
  {}
  Singleton(const Singleton& s) = delete; // 禁止拷贝
  Singleton& operator=(const Singleton& s) = delete; // 禁止赋值
 
  // 静态单例对象指针
  static Singleton* m_pInstance; // 单例对象指针
};
 
Singleton* Singleton::m_pInstance = nullptr; // 初始化为空
 
int main()
{
  Singleton* ps = Singleton::GetInstance();//获取单例对象
 
  return 0;
}

       同样将构造函数私有,拷贝构造和赋值运算符重载函数禁止调用,用来保证单例模式的唯一性。


       增加静态单例对象指针成员变量。在类外实例化静态指针变量的时候,并没有创建单例对象,而是将其初始化为空。


       在获取单例对象的时候,如果是第一次使用,那么就会在堆区上new一个单例对象,并且将所在地址赋值给静态的成员指针变量。


为什么称之为懒汉模式呢?

       懒汉模式又叫做延时加载模式,如果单例对象构造十分耗时或者占用很多资源,比如加载插件啊, 初始化网络连接啊,读取文件啊等等,而有可能该对象程序运行时不会用到,那么也要在程序一开始就进行初始化,就会导致程序启动时非常的缓慢。 所以这种情况使用懒汉模式(延迟加载)更好。


优点:


① 第一次使用单例对象时才创建对象,进程启动过程中无负载。


② 多个互相依赖的单例可以控制启动顺序(通过代码顺序)。


缺点:


① 相对复杂。(线程安全问题没讲,后面学了Linux系统期间再讲)


② 线程安全问题要处理好


单例对象释放问题:


① 一般情况下,单例对象不需要释放的。因为一般整个程序运行期间都可能会用它。


单例对象在进程正常结束后,也会资源释放。


② 有些特殊场景需要释放,比如单例对象析构时,要进行一些持久化(往文件、数据库写)操作。

2. 类型转换

       在C语言中,如果赋值运算符(=)两边的类型不同,或者形参和实参类型不匹配,或者返回值类型和接收值类型不一致,就需要发生类型转换。

C语言中有两种类型转换:

  • 隐式类型转换:编译器在编译阶段自动进行,能转就转,不能转就编译失败。
  • 显式类型转换:需要用户自己处理。

double d = i是发生了隐式类型转换,将整形转换成了double类型。

       隐式类型转换只发生在相近类型,比如整形家族直接,或者这些int,double等表示数值直接的变量类型。


int address = (int)p是发生了显式类型转换,将int * 类型的变量转换为int类型。

       显式类型需要用户自己维护,在两种类型没有任何关系的时候需要进行显式类型转换,比如将指针类型转换成普通类型等等。


C语言的类型转换存在缺陷:


隐式类型转换有些情况下会出现问题,比如数据精度发生丢失(整形提升等)。

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


       所以C++提出了自己的类型转换风格,但是仍然可以使用C语言的转换风格,因为要兼容C语言。标准C++为了加强类型转换的可视性,引入了四种命名的强制类型转换操作符:


static_cast、reinterpret_cast、const_cast、dynamic_cast。下面我们一一了解。

2.1 static_cast

       C语言的隐式类型转换(两个相近的类型转换)在C++中就可以使用static_cast来转换,但是不能用于两个不相关的类型进行转换。

int main()
{
  double a = 7.14;
  int b = static_cast<int>(a);
 
  cout << a << " " << b << endl;
 
  return 0;
}

       double类型转int类型,在C语言中是隐式类型转换,在C++中为了更加明确使用了static_cast。

static_cast后的<>里放要转换的类型,()里放被转换的类型。

如果将static_cast看成是类模板,就是在实例化,(d)就是在拷贝构造,siatic_cast(d)就是在用d构建一个匿名对象。


2.2 reinterpret_cast

       reinterpret_cast操作符通常为操作数的位模式提供较低层次的重新解释用于将一种类型转换为另一种不同的类型。C语言的显式类型转换在C++中就可以reinterpret_cast。

int main()
{
  int a = 7;
  int* pa = &a;
  int address = reinterpret_cast<int>(pa);
 
  cout << a << " " << pa << " " << address << endl;
 
  return 0;
}

int*类型转换为int类型,在C语言中是显式类型转换,在C++中为了不混乱使用了reinterpret_cast。


这里如果使用static_cast进行类型转换的话会报错,必须使用reinterpret_cast:


2.1反过来用也会报错:


2.3 const_cast

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

int main()
{
  const int a = 2;
  int* p = const_cast<int*>(&a);
  *p = 3;
  cout << a << " " << *p << endl;
 
  return 0;
}

       变量a原本是const属性的,不能被修改,使用了const_cast以后去除了常量属性,可以修改了,如*p = 3。

F11:

       在调试窗口中可以看到,成功修改了原本是const属性的变量a。

为什么a在调试窗口看到的是3,打印出来的是2?这就涉及到了以前提到的汇编里的寄存器的内容

       此时代码已经被优化了,不同编译器的优化不一样,有些编译器(VS)会把a = 2放到寄存器(认为const类型的变量不会被修改,每次去内存取的话很慢),那么读的时候去寄存器取就好了,打印的时候不会去内存取,所以打印出来的是3,监视窗口去内存取的,是2。

如果加上一个volatile关键字就告诉编译器不要优化:

int main()
{
  //const int a = 2;
  //int* p = const_cast<int*>(&a);
  //*p = 3;
  //cout << a << " " << *p << endl; // 2 3
  volatile const int a = 2;
  int* p = const_cast<int*>(&a);
  *p = 3;
  cout << a << " " << *p << endl; // 3 3
 
  return 0;
}

可以用C语言的强制类型转化:

这里用reinterpret_cast就不行:


const_cast更多的是一种警示,表示去除了const属性,要谨慎操作。

已经讲了三种C++的类型转化,应该也知道了是不能混着用的,

       static_cast对标的是C语言的隐式类型转化reinterpret_cast和const_cast对标的都是C语言的强制类型转化,增加就是为了建议你规范的用。

下面的dynamic_cast是C++特有的。

从C语言到C++_37(特殊类设计和C++类型转换)单例模式(下):https://developer.aliyun.com/article/1522503?spm=a2c6h.13148508.setting.20.50c04f0ef94tTt

目录
相关文章
|
1天前
|
存储 编译器 C++
【c++】类和对象(中)(构造函数、析构函数、拷贝构造、赋值重载)
本文深入探讨了C++类的默认成员函数,包括构造函数、析构函数、拷贝构造函数和赋值重载。构造函数用于对象的初始化,析构函数用于对象销毁时的资源清理,拷贝构造函数用于对象的拷贝,赋值重载用于已存在对象的赋值。文章详细介绍了每个函数的特点、使用方法及注意事项,并提供了代码示例。这些默认成员函数确保了资源的正确管理和对象状态的维护。
12 4
|
2天前
|
存储 编译器 Linux
【c++】类和对象(上)(类的定义格式、访问限定符、类域、类的实例化、对象的内存大小、this指针)
本文介绍了C++中的类和对象,包括类的概念、定义格式、访问限定符、类域、对象的创建及内存大小、以及this指针。通过示例代码详细解释了类的定义、成员函数和成员变量的作用,以及如何使用访问限定符控制成员的访问权限。此外,还讨论了对象的内存分配规则和this指针的使用场景,帮助读者深入理解面向对象编程的核心概念。
12 4
|
25天前
|
存储 编译器 对象存储
【C++打怪之路Lv5】-- 类和对象(下)
【C++打怪之路Lv5】-- 类和对象(下)
22 4
|
25天前
|
编译器 C语言 C++
【C++打怪之路Lv4】-- 类和对象(中)
【C++打怪之路Lv4】-- 类和对象(中)
20 4
|
25天前
|
存储 安全 C++
【C++打怪之路Lv8】-- string类
【C++打怪之路Lv8】-- string类
18 1
|
6月前
|
存储 算法 C语言
从C语言到C++_39(C++笔试面试题)next_permutation刷力扣
从C语言到C++_39(C++笔试面试题)next_permutation刷力扣
59 5
|
6月前
|
存储 编译器 C语言
从C语言到C++_23(多态)抽象类+虚函数表VTBL+多态的面试题(下)
从C语言到C++_23(多态)抽象类+虚函数表VTBL+多态的面试题
59 1
|
6月前
|
存储 编译器 Linux
从C语言到C++_23(多态)抽象类+虚函数表VTBL+多态的面试题(中)
从C语言到C++_23(多态)抽象类+虚函数表VTBL+多态的面试题
66 1
|
6月前
|
编译器 C语言 C++
从C语言到C++_23(多态)抽象类+虚函数表VTBL+多态的面试题(上)
从C语言到C++_23(多态)抽象类+虚函数表VTBL+多态的面试题
46 0
|
6月前
|
编译器 程序员 C语言
从C语言到C++⑨(第三章_C&C++内存管理)详解new和delete+面试题笔试题(下)
从C语言到C++⑨(第三章_C&C++内存管理)详解new和delete+面试题笔试题
43 0