从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

目录
相关文章
|
2月前
|
安全 C语言 C++
比较C++的内存分配与管理方式new/delete与C语言中的malloc/realloc/calloc/free。
在实用性方面,C++的内存管理方式提供了面向对象的特性,它是处理构造和析构、需要类型安全和异常处理的首选方案。而C语言的内存管理函数适用于简单的内存分配,例如分配原始内存块或复杂性较低的数据结构,没有构造和析构的要求。当从C迁移到C++,或在C++中使用C代码时,了解两种内存管理方式的差异非常重要。
126 26
|
3月前
|
人工智能 机器人 编译器
c++模板初阶----函数模板与类模板
class 类模板名private://类内成员声明class Apublic:A(T val):a(val){}private:T a;return 0;运行结果:注意:类模板中的成员函数若是放在类外定义时,需要加模板参数列表。return 0;
91 0
|
3月前
|
存储 编译器 程序员
c++的类(附含explicit关键字,友元,内部类)
本文介绍了C++中类的核心概念与用法,涵盖封装、继承、多态三大特性。重点讲解了类的定义(`class`与`struct`)、访问限定符(`private`、`public`、`protected`)、类的作用域及成员函数的声明与定义分离。同时深入探讨了类的大小计算、`this`指针、默认成员函数(构造函数、析构函数、拷贝构造、赋值重载)以及运算符重载等内容。 文章还详细分析了`explicit`关键字的作用、静态成员(变量与函数)、友元(友元函数与友元类)的概念及其使用场景,并简要介绍了内部类的特性。
169 0
|
5月前
|
编译器 C++ 容器
【c++11】c++11新特性(上)(列表初始化、右值引用和移动语义、类的新默认成员函数、lambda表达式)
C++11为C++带来了革命性变化,引入了列表初始化、右值引用、移动语义、类的新默认成员函数和lambda表达式等特性。列表初始化统一了对象初始化方式,initializer_list简化了容器多元素初始化;右值引用和移动语义优化了资源管理,减少拷贝开销;类新增移动构造和移动赋值函数提升性能;lambda表达式提供匿名函数对象,增强代码简洁性和灵活性。这些特性共同推动了现代C++编程的发展,提升了开发效率与程序性能。
177 12
|
6月前
|
编译器 C++
类和对象(中 )C++
本文详细讲解了C++中的默认成员函数,包括构造函数、析构函数、拷贝构造函数、赋值运算符重载和取地址运算符重载等内容。重点分析了各函数的特点、使用场景及相互关系,如构造函数的主要任务是初始化对象,而非创建空间;析构函数用于清理资源;拷贝构造与赋值运算符的区别在于前者用于创建新对象,后者用于已存在的对象赋值。同时,文章还探讨了运算符重载的规则及其应用场景,并通过实例加深理解。最后强调,若类中存在资源管理,需显式定义拷贝构造和赋值运算符以避免浅拷贝问题。
|
6月前
|
编译器 C++
类和对象(下)C++
本内容主要讲解C++中的初始化列表、类型转换、静态成员、友元、内部类、匿名对象及对象拷贝时的编译器优化。初始化列表用于成员变量定义初始化,尤其对引用、const及无默认构造函数的类类型变量至关重要。类型转换中,`explicit`可禁用隐式转换。静态成员属类而非对象,受访问限定符约束。内部类是独立类,可增强封装性。匿名对象生命周期短,常用于临时场景。编译器会优化对象拷贝以提高效率。最后,鼓励大家通过重复练习提升技能!
|
存储 算法 C语言
从C语言到C++_39(C++笔试面试题)next_permutation刷力扣
从C语言到C++_39(C++笔试面试题)next_permutation刷力扣
186 5
|
存储 编译器 C语言
从C语言到C++_23(多态)抽象类+虚函数表VTBL+多态的面试题(下)
从C语言到C++_23(多态)抽象类+虚函数表VTBL+多态的面试题
155 1
|
存储 编译器 Linux
从C语言到C++_23(多态)抽象类+虚函数表VTBL+多态的面试题(中)
从C语言到C++_23(多态)抽象类+虚函数表VTBL+多态的面试题
134 1
|
编译器 C语言 C++
从C语言到C++_23(多态)抽象类+虚函数表VTBL+多态的面试题(上)
从C语言到C++_23(多态)抽象类+虚函数表VTBL+多态的面试题
149 0