从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

目录
相关文章
|
16天前
|
存储 编译器 C语言
C语言存储类详解
在 C 语言中,存储类定义了变量的生命周期、作用域和可见性。主要包括:`auto`(默认存储类,块级作用域),`register`(建议存储在寄存器中,作用域同 `auto`,不可取地址),`static`(生命周期贯穿整个程序,局部静态变量在函数间保持值,全局静态变量限于本文件),`extern`(声明变量在其他文件中定义,允许跨文件访问)。此外,`typedef` 用于定义新数据类型名称,提升代码可读性。 示例代码展示了不同存储类变量的使用方式,通过两次调用 `function()` 函数,观察静态变量 `b` 的变化。合理选择存储类可以优化程序性能和内存使用。
135 82
|
10天前
|
编译器 C++
C++ 类构造函数初始化列表
构造函数初始化列表以一个冒号开始,接着是以逗号分隔的数据成员列表,每个数据成员后面跟一个放在括号中的初始化式。
56 30
|
10天前
|
算法 机器人 C语言
ROS仿真支持C++和C语言
ROS仿真支持C++和C语言
24 1
|
24天前
|
存储 编译器 C++
C ++初阶:类和对象(中)
C ++初阶:类和对象(中)
|
24天前
|
C++
C++(十六)类之间转化
在C++中,类之间的转换可以通过转换构造函数和操作符函数实现。转换构造函数是一种单参数构造函数,用于将其他类型转换为本类类型。为了防止不必要的隐式转换,可以使用`explicit`关键字来禁止这种自动转换。此外,还可以通过定义`operator`函数来进行类型转换,该函数无参数且无返回值。下面展示了如何使用这两种方式实现自定义类型的相互转换,并通过示例代码说明了`explicit`关键字的作用。
|
24天前
|
存储 设计模式 编译器
C++(十三) 类的扩展
本文详细介绍了C++中类的各种扩展特性,包括类成员存储、`sizeof`操作符的应用、类成员函数的存储方式及其背后的`this`指针机制。此外,还探讨了`const`修饰符在成员变量和函数中的作用,以及如何通过`static`关键字实现类中的资源共享。文章还介绍了单例模式的设计思路,并讨论了指向类成员(数据成员和函数成员)的指针的使用方法。最后,还讲解了指向静态成员的指针的相关概念和应用示例。通过这些内容,帮助读者更好地理解和掌握C++面向对象编程的核心概念和技术细节。
|
24天前
|
存储 C++
C++(五)String 字符串类
本文档详细介绍了C++中的`string`类,包括定义、初始化、字符串比较及数值与字符串之间的转换方法。`string`类简化了字符串处理,提供了丰富的功能如字符串查找、比较、拼接和替换等。文档通过示例代码展示了如何使用这些功能,并介绍了如何将数值转换为字符串以及反之亦然的方法。此外,还展示了如何使用`string`数组存储和遍历多个字符串。
|
24天前
|
编译器 C语言 C++
从C语言到C++
本文档详细介绍了C++相较于C语言的一些改进和新特性,包括类型检查、逻辑类型 `bool`、枚举类型、可赋值的表达式等。同时,文档还讲解了C++中的标准输入输出流 `cin` 和 `cout` 的使用方法及格式化输出技巧。此外,还介绍了函数重载、运算符重载、默认参数等高级特性,并探讨了引用的概念及其应用,包括常引用和引用的本质分析。以下是简要概述: 本文档适合有一定C语言基础的学习者深入了解C++的新特性及其应用。
|
1月前
|
存储 安全 编译器
【C++】类和对象(下)
【C++】类和对象(下)
【C++】类和对象(下)
|
1月前
|
存储 算法 编译器
c++--类(上)
c++--类(上)