【C++】-还在玩普通的类吗,这里面有好几种特殊的类的设计,快进来看看

2023-10-20 17:44

本文主要是介绍【C++】-还在玩普通的类吗,这里面有好几种特殊的类的设计,快进来看看,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

在这里插入图片描述
💖作者:小树苗渴望变成参天大树🎈
🎉作者宣言:认真写好每一篇博客💤
🎊作者gitee:gitee✨
💞作者专栏:C语言,数据结构初阶,Linux,C++ 动态规划算法🎄
如 果 你 喜 欢 作 者 的 文 章 ,就 给 作 者 点 点 关 注 吧!

文章目录

  • 前言
  • 一、请设计一个类,不能被拷贝
  • 二、请设计一个类,只能在堆上创建对象
  • 三、 请设计一个类,只能在栈上创建对象
  • 四、请设计一个类,不能被继承
  • 五、请设计一个类,只能创建一个对象(单例模式)
    • 5.1饿汉模式
    • 5.2懒汉模式
  • 六、总结


前言

今天我们讲解特殊类的设计,再我们的开发中难免会出现一些特殊情况,也是设计出一些特殊的类,他们和普通类不同担忧很相似,接下来博主会带大家设计出五种特殊的类,话不多说,我们开始进入正文的讲解。


一、请设计一个类,不能被拷贝

拷贝只会放生在两个场景中:拷贝构造函数以及赋值运算符重载,因此想要让一个类禁止拷贝,
只需让该类不能调用拷贝构造函数以及赋值运算符重载即可

C++98的使用方法:
class A
{
private:A(const A& a){}A& operator=(const A& a){}
};C++11的用法:
class A
{
public:A(const A& a)=delete;A& operator=(const A& a)=delete;
private:};

C++11扩展delete的用法,delete除了释放new申请的资源外,如果在默认成员函数后跟上
=delete,表示让编译器删除掉该默认成员函数。不拷贝再下面几个场景都需要使用到,防拷贝的用法

二、请设计一个类,只能在堆上创建对象

第一种方式:

class heaponly
{
public:private:~heaponly(){}};
int main()
{heaponly p0;//这样会报错heaponly* p2 = new heaponly;return 0;
}

原因是p2可以手动进行释放,但是需要再类中写一个接口来进行释放:
在这里插入图片描述

第二种方式:

class heaponly
{
public:static heaponly* creakobj(){return new heaponly;}
private://1.先将构造函数私有化heaponly(){}//3.防拷贝heaponly(const heaponly& h) = delete;heaponly& operator=(const heaponly& h) = delete;
};

将构造函数私有化,都创建不了对象在这里插入图片描述所以写一个接口,通过调用接口来实现创建对象。但是调用函数需要,这时候就是先有鸡还是先有蛋的问题,所以我们将接口设计成静态的。在这里插入图片描述解决了再堆上创建对象的问题,但是又有可能通过构造和赋值,再栈上创建对象,所以需要防拷贝在这里插入图片描述
这样就解决问题了。

三、 请设计一个类,只能在栈上创建对象

这个设计理念和上面哪个很相似
我们需要将构造函数私有化,这样才能保证都创建不了对象,然后提供一个接口。

class stackonly
{
public:static stackonly creatobj()//通过类名去调用函数。{stackonly st;return st;}
private:stackonly(){}//防拷贝//stackonly(const stackonly& h) = delete;//stackonly& operator=(const stackonly& h) = delete;//将new变成私有的void* operator new(size_t size){}
};
int main()
{stackonly s1=stackonly::creatobj();//1stackonly* s2 = new stackonly(s1);//2return 0;
}

再没有防拷贝和new私有化的前提下,我们的代码2是可以通过的,所以我们添加了防拷贝,或者new私有化,原因是new->operator new+构造,封其中一个就可以了。
但是我们栈区对象给栈区对象拷贝构造和赋值就不行了,所以我们不封防拷贝

四、请设计一个类,不能被继承

C++98的方式

// C++98中构造函数私有化,派生类中调不到基类的构造函数。则无法继承
class NonInherit
{
public:static NonInherit GetInstance(){return NonInherit();}
private:NonInherit(){}
};

c++11的方式:

final关键字,final修饰类,表示该类不能被继承class A  final
{    // ....};

五、请设计一个类,只能创建一个对象(单例模式)

这是我们这节重点介绍的,而且他很重要,他又两种模式–饿汉模式、懒汉模式

设计模式:
设计模式(Design Pattern)是一套被反复使用、多数人知晓的、经过分类的、代码设计经验的
总结。为什么会产生设计模式这样的东西呢?就像人类历史发展会产生兵法。最开始部落之间打
仗时都是人拼人的对砍。后来春秋战国时期,七国之间经常打仗,就发现打仗也是有套路的,后
来孙子就总结出了《孙子兵法》。孙子兵法也是类似。
使用设计模式的目的:为了代码可重用性、让代码更容易被他人理解、保证代码可靠性。 设计模
式使代码编写真正工程化;设计模式是软件工程的基石脉络,如同大厦的结构一样。

单例模式:
一个类只能创建一个对象,即单例模式,该模式可以保证系统中该类只有一个实例,并提供一个
访问它的全局访问点,该实例被所有程序模块共享。比如在某个服务器程序中,该服务器的配置
信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再
通过这个单例对象获取这些配置信息,这种方式简化了在复杂环境下的配置管理。

5.1饿汉模式


class Singleton
{
public://设计成静态的原因是通过类名去调用函数,因为进行了防拷贝,所以需要传引用static Singleton& GetInstance(){return _st;}//对单例对象的部分操作。void add(pair<string,string>& kv){_dict[kv.first] = kv.second;}void print(){for (auto& e : _dict){cout << e.first << ":" << e.second << endl;}cout << endl;}
private://1.构造函数私有化,这样都不能创建对象,所以要提供一个接口Singleton(){}map<string, string> _dict;static Singleton _st;//这个就是整个程序的单例对象,所以类共有的// Singleton _st这样不行,这时候_st属于类里面,会嵌套。//防拷贝,原因只能创建一个对象,再接口返回的时候要返回引用Singleton(const Singleton& s) = delete;Singleton& operator=(const Singleton& s) = delete;
};Singleton Singleton::_st;//静态成员变量类里面声明,类外名定义,这个定义还是可以访问到类里面的内容// 在程序入口之前就完成单例对象的初始化

上面代码已经有注释,我们来验证一下,我们来看看里面每次调用函数的接口返回的对象是不是同一个:
在这里插入图片描述

我们只能通过调用接口来创建对象,而每次调用都是同一个,那么这样就设计出了一个单例对象,这里巧妙的利用静态成员函数只有一份的特点,接下来验证一下操作:
在这里插入图片描述
至此我们的单例模式就设计出来了


饿汉模式的缺点
(1)单例对象的创建是再main函数之前的,万一单例对象很大,很多,那么启动时间就很长
(2)万一有两个单例对象互相有依赖关系,就不行了。
(3)不管你用不用他肯定都会创建的。

有了上面的缺点,我们有下面的一种模式,懒汉模式。
他再使用的时候才创建对象,他先创建一个静态指针,就算就main函数之前创建也就一个指针的大小。

namespace lazy
{class Singleton{public://设计成静态的原因是通过类名去调用函数,因为进行了防拷贝,所以需要传引用static Singleton& GetInstance(){if (_st == nullptr){_st=new Singleton;}return *_st;}//对单例对象的部分操作。void add(const pair<string, string>& kv){_dict[kv.first] = kv.second;}void print(){for (auto& e : _dict){cout << e.first << ":" << e.second << endl;}cout << endl;}private://1.构造函数私有化,这样都不能创建对象,所以要提供一个接口Singleton() {}map<string, string> _dict;static Singleton* _st;//这个就是整个程序的单例对象,所以类共有的// Singleton _st这样不行,这时候_st属于类里面,会嵌套。//防拷贝,原因只能创建一个对象,再接口返回的时候要返回引用Singleton(const Singleton& s) = delete;Singleton& operator=(const Singleton& s) = delete;};Singleton* Singleton::_st=nullptr;//静态成员变量类里面声明,类外名定义,这个定义还是可以访问到类里面的内容
}

在这里插入图片描述
再懒汉模式里面因为我们是手动new出来的,所以我们需要进行释放,正常情况,单例对象都是随着程序解释自动释放,但是不干保证有的时候再中间就需要释放去做持久化操作,将数据写进文件等等。拿这样我们怎么去设计呢??

第一种方法:
再类里面添加这个。

		static void DelInstance(){if (_st){delete _st;//这个delete会调用析构函数_st = nullptr;}}~Singleton()//自购函数只有程序结束后会自动调用,不能显示的调用,通过接口去调用他。{cout << "~Singleton()" << endl;// map数据写到文件中FILE* fin = fopen("map.txt", "w");for (auto& e : _dict){fputs(e.first.c_str(), fin);fputs(":", fin);fputs(e.second.c_str(), fin);fputs("\n", fin);}}

在这里插入图片描述
这种方法值适用于单例对象少,释放地方少,如果道理对象多,释放位置多,就麻烦了,这时候我们有第二种方法:

5.2懒汉模式

	
namespace lazy
{class Singleton{public://设计成静态的原因是通过类名去调用函数,因为进行了防拷贝,所以需要传引用static Singleton& GetInstance(){if (_st == nullptr){_st=new Singleton;}return *_st;}//对单例对象的部分操作。void add(const pair<string, string>& kv){_dict[kv.first] = kv.second;}void print(){for (auto& e : _dict){cout << e.first << ":" << e.second << endl;}cout << endl;}static void DelInstance(){if (_st){delete _st;//这个delete会调用析构函数_st = nullptr;}}~Singleton()//析构函数只有程序结束后会自动调用,不能显示的调用,通过接口去调用他。{cout << "~Singleton()" << endl;// map数据写到文件中FILE* fin = fopen("map.txt", "w");for (auto& e : _dict){fputs(e.first.c_str(), fin);fputs(":", fin);fputs(e.second.c_str(), fin);fputs("\n", fin);}}class GC{public:~GC(){lazy::Singleton::DelInstance();}};private://1.构造函数私有化,这样都不能创建对象,所以要提供一个接口Singleton() {}map<string, string> _dict;static Singleton* _st;//这个就是整个程序的单例对象,所以类共有的// Singleton _st这样不行,这时候_st属于类里面,会嵌套。//防拷贝,原因只能创建一个对象,再接口返回的时候要返回引用Singleton(const Singleton& s) = delete;Singleton& operator=(const Singleton& s) = delete;static GC _gc;};Singleton* Singleton::_st=nullptr;//静态成员变量类里面声明,类外名定义,这个定义还是可以访问到类里面的内容Singleton::GC Singleton::_gc;
}

在这里插入图片描述

我们发现不使用我写的两种方法,最后都无法调用到析构函数,第一种方法是可以再任意位置进行释放,第二种方式是防止程序结束后忘记释放,这样设计就可以直接自动释放了。

六、总结

至此我们的特殊类的设计就讲解完毕了,博主讲解的都是简单的特殊类,到实际开发的时候,类的设计就很复杂,大家先知道大致模式旧可以了,我们平时用到也少。但是要了解,别一点都没有听过。我们这篇的讲解旧到此结束了,我们下篇再见。

这篇关于【C++】-还在玩普通的类吗,这里面有好几种特殊的类的设计,快进来看看的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/248671

相关文章

Windows下C++使用SQLitede的操作过程

《Windows下C++使用SQLitede的操作过程》本文介绍了Windows下C++使用SQLite的安装配置、CppSQLite库封装优势、核心功能(如数据库连接、事务管理)、跨平台支持及性能优... 目录Windows下C++使用SQLite1、安装2、代码示例CppSQLite:C++轻松操作SQ

C++中RAII资源获取即初始化

《C++中RAII资源获取即初始化》RAII通过构造/析构自动管理资源生命周期,确保安全释放,本文就来介绍一下C++中的RAII技术及其应用,具有一定的参考价值,感兴趣的可以了解一下... 目录一、核心原理与机制二、标准库中的RAII实现三、自定义RAII类设计原则四、常见应用场景1. 内存管理2. 文件操

C++中零拷贝的多种实现方式

《C++中零拷贝的多种实现方式》本文主要介绍了C++中零拷贝的实现示例,旨在在减少数据在内存中的不必要复制,从而提高程序性能、降低内存使用并减少CPU消耗,零拷贝技术通过多种方式实现,下面就来了解一下... 目录一、C++中零拷贝技术的核心概念二、std::string_view 简介三、std::stri

C++高效内存池实现减少动态分配开销的解决方案

《C++高效内存池实现减少动态分配开销的解决方案》C++动态内存分配存在系统调用开销、碎片化和锁竞争等性能问题,内存池通过预分配、分块管理和缓存复用解决这些问题,下面就来了解一下... 目录一、C++内存分配的性能挑战二、内存池技术的核心原理三、主流内存池实现:TCMalloc与Jemalloc1. TCM

C++ 函数 strftime 和时间格式示例详解

《C++函数strftime和时间格式示例详解》strftime是C/C++标准库中用于格式化日期和时间的函数,定义在ctime头文件中,它将tm结构体中的时间信息转换为指定格式的字符串,是处理... 目录C++ 函数 strftipythonme 详解一、函数原型二、功能描述三、格式字符串说明四、返回值五

C++作用域和标识符查找规则详解

《C++作用域和标识符查找规则详解》在C++中,作用域(Scope)和标识符查找(IdentifierLookup)是理解代码行为的重要概念,本文将详细介绍这些规则,并通过实例来说明它们的工作原理,需... 目录作用域标识符查找规则1. 普通查找(Ordinary Lookup)2. 限定查找(Qualif

MyBatis设计SQL返回布尔值(Boolean)的常见方法

《MyBatis设计SQL返回布尔值(Boolean)的常见方法》这篇文章主要为大家详细介绍了MyBatis设计SQL返回布尔值(Boolean)的几种常见方法,文中的示例代码讲解详细,感兴趣的小伙伴... 目录方案一:使用COUNT查询存在性(推荐)方案二:条件表达式直接返回布尔方案三:存在性检查(EXI

java中BigDecimal里面的subtract函数介绍及实现方法

《java中BigDecimal里面的subtract函数介绍及实现方法》在Java中实现减法操作需要根据数据类型选择不同方法,主要分为数值型减法和字符串减法两种场景,本文给大家介绍java中BigD... 目录Java中BigDecimal里面的subtract函数的意思?一、数值型减法(高精度计算)1.

C/C++ chrono简单使用场景示例详解

《C/C++chrono简单使用场景示例详解》:本文主要介绍C/C++chrono简单使用场景示例详解,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友... 目录chrono使用场景举例1 输出格式化字符串chrono使用场景China编程举例1 输出格式化字符串示

C++/类与对象/默认成员函数@构造函数的用法

《C++/类与对象/默认成员函数@构造函数的用法》:本文主要介绍C++/类与对象/默认成员函数@构造函数的用法,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录名词概念默认成员函数构造函数概念函数特征显示构造函数隐式构造函数总结名词概念默认构造函数:不用传参就可以