> 技术文档 > 【c++】特殊类的设计(不能拷贝的类,只能在堆/栈上创建对象的类,不能被继承的类,单例模式——饿汉模式、懒汉模式)

【c++】特殊类的设计(不能拷贝的类,只能在堆/栈上创建对象的类,不能被继承的类,单例模式——饿汉模式、懒汉模式)


小编个人主页详情<—请点击
小编个人gitee代码仓库<—请点击
c++系列专栏<—请点击
倘若命中无此运,孤身亦可登昆仑,送给屏幕面前的读者朋友们和小编自己!
在这里插入图片描述


目录

    • 前言
    • 一、设计一个不能被拷贝的类
      • c++98
      • c++11
    • 二、设计一个只能在堆上创建对象的类
      • 方法一
      • 方法二
    • 三、设计一个类只能在栈上创建对象
    • 四、设计一个不能被继承的类
      • c++98
      • c++11
    • 五、单例模式
      • 饿汉模式
        • 概念讲解
        • 模拟实现
          • 测试
      • 懒汉模式
        • 概念讲解
        • 模拟实现
          • 测试一
        • 优化
          • 测试二
        • 优化
          • 测试三
    • 六、源代码
      • test.cpp
    • 总结

前言

【c++】智能指针(auto_ptr, unique_ptr, shared_ptr, weak_ptr)——书接上文 详情请点击<——
本文由小编为大家介绍—【c++】特殊类的设计(不能拷贝的类,只能在堆/栈上创建对象的类,不能被继承的类,单例模式——饿汉模式、懒汉模式)


一、设计一个不能被拷贝的类

拷贝会发生在两个场景:拷贝构造函数和赋值运算符重载,因此如果我们想要设计一个不能被拷贝的类,只需要让该类不能调用拷贝构造函数和赋值运算符重载即可

c++98

  1. 只需要将该类的拷贝构造函数和赋值运算符重载函数只声明不定义,并且访问权限设置为私有private即可
  2. 只声明不定义是因为,我们声明了拷贝构造函数和赋值运算符重载之后编译器就不会再生成默认的拷贝构造函数和赋值运算符重载了,对于不定义即使定义了也没有什么用处,因为我们要设计的类就是为了不能拷贝,也不会去调用拷贝,并且不定义还可以避免类内部的成员函数去调用拷贝
  3. 将访问权限设置为私有private可以防止在类外进行调用拷贝构造函数和赋值运算符重载,同时将访问权限设置为私有还可以避免用户在类外编写拷贝构造函数和赋值运算符重载的实现
class CopyBan{private:CopyBan(const CopyBan& cb);CopyBan& operator=(const CopyBan& cb);};

c++11

  1. c++11中新增了玩法,可以在默认成员函数后跟上delete,表示该默认成员函数被删除,这样编译器就不会去生成默认的成员函数了,同时类外也无法调用已删除的默认成员函数
  2. 那么我们就可以在类的拷贝构造函数和赋值运算符重载函数后面跟上delete即可
class CopyBan{public:CopyBan(const CopyBan& cb) = delete;CopyBan& operator=(const CopyBan& cb) = delete;};

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

方法一

  1. 由于在栈上的对象具有自动调用析构函数进行析构释放的特性,所以我们将构造函数私有即可
  2. 同时提供一个Destroy函数用于delete堆上的对象进行delete进行析构
class HeapOnly{public:void Destroy(){delete this;}private:~HeapOnly(){cout << \"~HeapOnly()\" << endl;}};int main(){HeapOnly* hp1 = new HeapOnly;hp1->Destroy();return 0;}

方法二

  1. 将构造函数私有,这样栈上的对象就没有办法进行构造,所以也自然不能创建对象,同时提供一个静态成员函数CreatObj(静态成员函数不需要对象进行调用,由于构造函数被进行,此时我们没有对象可以调用成员函数,所以将这个CreatObj设置为静态成员函数,属于这个类,使用类::的方式进行调用就可以了)用于获取在堆上new出的对象,这样就可以创建堆上的对象了
  2. 当我们有了堆的对象之后,还应该将拷贝构造也使用delete禁掉,防止在栈上调用拷贝构造去拷贝已创建的堆对象,一般的,禁用拷贝构造也应该禁用赋值运算符重载,所以将赋值运算符重载也使用delete禁掉
class HeapOnly{public:static HeapOnly* CreatObj(){return new HeapOnly;}private:HeapOnly(){cout << \"HeapOnly()\" << endl;}HeapOnly(const HeapOnly& hp) = delete;HeapOnly& operator=(const HeapOnly& hp) = delete;};int main(){HeapOnly* hp3 = HeapOnly::CreatObj();delete hp3;return 0;}

运行结果如下
【c++】特殊类的设计(不能拷贝的类,只能在堆/栈上创建对象的类,不能被继承的类,单例模式——饿汉模式、懒汉模式)

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

  1. 类似的我们采用只能在堆上创建对象的方式二,将构造函数私有,使用静态成员函数CreatObj以拷贝构造函数的形式返回匿名对象的拷贝
  2. 注意这里采用堆上创建对象的方式一,即不能将析构函数私有,栈对象会自动调用析构函数,如果将析构函数私有那么栈对象就无法调用析构函数了,那么就会直接编译报错
  3. 但是这种方式其实还是不能够完全禁掉堆使用new进行创建对象,因为new除了使用构造函数创建,new还可以使用拷贝构造函数的方式进行在堆上创建对象
  4. 同时这个拷贝构造我们不能禁用或者私有,因为静态成员函数CreatObj还需要以拷贝构造函数的形式进行返回,所以不能禁用拷贝构造,同时在类外上还会调用拷贝进行拷贝静态成员函数CreatObj的返回值所以不能私有拷贝构造
class StackOnly{public:static StackOnly CreatObj(){return StackOnly();}private:StackOnly(){cout << \"StackOnly()\" << endl;}};int main(){StackOnly st = StackOnly::CreatObj();StackOnly* st1 = new StackOnly(st);return 0;}

运行结果如下
【c++】特殊类的设计(不能拷贝的类,只能在堆/栈上创建对象的类,不能被继承的类,单例模式——饿汉模式、懒汉模式)

  1. 那么我们知道new实际上是在内部调用operator new实现的,operator new是一个全局函数,同时函数的调用规则是局部优先,所以我们在类内部使用delete禁调用operator new即可,在类内部的operator new被编译器做了特殊处理并不是重载的new,而是变成了可以被new进行调用的operator new,但是由于类内部的operator new被小编使用delete删除了,new进行调用operator new是局部优先,有局部的operator new就不会去调用全局的operator new了,所以当new调用了局部被小编删除operator new发现是被删除的之后就会不能进行调用了,所以会编译报错,所以此时就不可以使用new在堆上申请了
class StackOnly{public:static StackOnly CreatObj(){return StackOnly();}void* operator new(size_t size) = delete;private:StackOnly(){cout << \"StackOnly()\" << endl;}};int main(){StackOnly st = StackOnly::CreatObj();StackOnly* st1 = new StackOnly(st);return 0;}

运行结果如下,在类内部删除operator new之后,此时无法在堆上创建对象,只能在栈上创建对象
【c++】特殊类的设计(不能拷贝的类,只能在堆/栈上创建对象的类,不能被继承的类,单例模式——饿汉模式、懒汉模式)

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

c++98

  1. 我们知道派生类的对象要进行实例化之前,需要调用派生类的构造函数,而调用派生类的构造函数之前要调用基类的构造函数,所以c++98的方式是将基类的构造函数私有化,此时派生类的对象进行实例化就无法调用基类的构造函数,派生类的对象也就无法实例化了,那么派生类也就无法继承基类了
//c++98class NonInherit{public:static NonInherit Creat(){return NonInherit();}private:NonInherit(){}};class Inherit : public NonInherit{};int main(){Inherit ih;return 0;}

运行结果如下,无法继承
【c++】特殊类的设计(不能拷贝的类,只能在堆/栈上创建对象的类,不能被继承的类,单例模式——饿汉模式、懒汉模式)

c++11

  1. c++11的方式则更为简单,使用关键字final即可,在我们不想要被继承的类使用final进行修饰之后,该类就无法被继承了
//c++11class NonInherit final{};class Inherit : public NonInherit{};

运行结果如下,无法继承
【c++】特殊类的设计(不能拷贝的类,只能在堆/栈上创建对象的类,不能被继承的类,单例模式——饿汉模式、懒汉模式)

五、单例模式

单例模式:一个类只能创建一个对象,即单例模式。
该模式可以保证该类在系统中只有一个实例,并且提供一个该实例的全局访问点,该实例被所有程序模块化共享。
例如:对于系统的环境变量的配置文件就只需要一份,那么我们可以使用单例模式将这份环境变量的配置文件实例化一份。使用单例对象统一读写
单例模式的实现方式分为饿汉模式和懒汉模式,下面小编将一 一进行讲解

饿汉模式

概念讲解

饿汉模式:一开始(main函数)之前就创建单例对象

  1. 如果单例对象要初始化的内容很多,那么就会影响进程的启动速度
  2. 如果两个单例类,互相有依赖关系,例如:有两个单例类A,B。要求A先创建,B再创建,B的初始化依赖于A。倘若单例类A,B不在同一个文件,那么我们无法保证编译器会去先执行哪一个文件,那么就无法保证A始终是先被创建的,那么B的初始化工作可能就无法运行,这就很坑
模拟实现
  1. 对于由于饿汉模式和懒汉模式都是单例,在命名上会有冲突,所以小编分别将它们两个放到两个命名空间中
  2. 那么接下来小编设计一个单例类,这个类的成员变量有一个map对象_dict,那么我们可以通过这个单例类实例化出的唯一对象,去对成员变量_dict进行诸如添加,打印的操作
  3. 类似于设计出只能在栈上创建对象的类的设计方式,这里由于单例类只能实例化出一个对象,所以需要防拷贝,那么我们使用delete修饰拷贝构造和赋值运算符重载,同时构造函数私有化
  4. 接下来我们需要思考,如何才能让这个单例类仅仅实例化出一份,如果将定义对象放在main函数中那必然是不行的,因为饿汉模式的要求是在main函数之前就创建唯一对象,全局对象或者main函数外的static对象要在main函数之前实例化,那么使用单例类实例化的对象就应该是全局对象,那么我们思考一下,如果使用单例类实例化的对象是全局对象,能否实例化?
  5. 显而易见,由于饿汉模式的构造函数是私有的,所以在类外是无法创建对象的,所以我们要想办法让创建的对象可以放在单例类的内部用于调用构造函数,既然全局对象不行,我们就尝试一下main函数外的static对象
  6. 我们在单例类内部创建一个static的单例类Singleton对象_sinst,由于是static的,所以这个单例类Singleton对象_sinst是存储在静态区的,所以这里不会套娃式的不断在内部创建,而是只会实例化一次并且存储在静态区,此时在Singleton单例类内部就有了一个单例类对象_sinst,在类域内不受访问限定符的限制,所以可以访问构造函数,这个单例类对象可以调用构造函数进行构造,类的静态成员变量需要在类外初始化,那么我们就在单例类外初始化定义这个static的单例类对象_sinst即可,此时这个单例类对象_sinst属于Singleton这个单例类,实际上_sinst的生命周期是具有全局属性的,只不过受Singleton类域限制
  7. 接下来我们提供一个静态成员函数接口GetInstace用于获取这个单例类对象_sinst,那么这个静态成员函数就可以使用类::的方式进行访问调用获取单例对象进行使用了
  8. 接下来我们加入Add和Print接口用于对成员变量_dict进行进行添加和打印操作
#define _CRT_SECURE_NO_WARNINGS 1#include #include using namespace std;namespace hungry{class Singleton{public:static Singleton& GetInstace(){return _sinst;}void Add(const pair<string, string>& kv){_dict[kv.first] = kv.second;}void Print(){for (auto& kv : _dict){cout << kv.first << \' \' << kv.second << endl;}cout << endl;}private:Singleton(){}Singleton(const Singleton& _sinst) = delete;Singleton& operator=(const Singleton& _sinst) = delete;map<string, string> _dict;static Singleton _sinst;};Singleton Singleton::_sinst;}
测试
  1. 那么我们在类外就可以通过类域::的方式调用获取单例类的静态成员函数获取到唯一的单例类对象进行操作了
int main(){hungry::Singleton::GetInstace().Add({ \"插入\", \"insert\" });hungry::Singleton::GetInstace().Add({ \"删除\", \"delete\" });hungry::Singleton::GetInstace().Add({ \"查找\", \"xxx\" });hungry::Singleton::GetInstace().Print();hungry::Singleton::GetInstace().Add({ \"查找\", \"find\" });hungry::Singleton::GetInstace().Print();return 0;}

运行结果如下,无误
【c++】特殊类的设计(不能拷贝的类,只能在堆/栈上创建对象的类,不能被继承的类,单例模式——饿汉模式、懒汉模式)

懒汉模式

概念讲解

如果单例对象构造十分耗时或者占用很多资源,例如加载插件,初始化网络,读取文件等,如果在程序一开始就进行初始化,那么程序的启动就会十分的缓慢,那么此时我们就可以使用懒汉模式(延迟加载)
懒汉模式:第一次使用实例对象时,才创建对象,进程启动无负荷,进程启动速度快,多个单例实例启动顺序自由控制,可以说比较完美的解决了饿汉模式的缺点,懒汉模式的缺点是设计比较复杂

模拟实现
  1. 懒汉模式的基础实现框架和饿汉模式的近似,都是需要将构造函数私有化,删除拷贝,提供一个静态成员函数获取单例对象,同样的在单例类内部管理一个map对象,添加Add,Print函数进行管理
  2. 不同的,懒汉模式的静态成员变量,即单例对象其实是一个指针,因为只有这样我们才可以将这个单例对象的指针初始化为nullptr,这样也就不会调用构造函数了,等到真正使用单例对象的时候,判断单例对象的指针是否为空,如果单例对象的指针为空,那么就说明此时单例对象还未调用构造函数创建,此时我们new一个单例对象给给懒汉模式的单例对象的指针即可,这样就调用了单例类的构造函数进行的初始化单例对象,也正是最开始在类外给单例对象的指针初始化为nullptr才可以做到进程启动无负荷,因为最开始仅仅是给一个空指针没有什么消耗,当第一次真正需要使用单例对象的时候才进行new调用构造函数实例化
namespace lazy{class Singleton{public:static Singleton& GetInstace(){if (_sinst == nullptr){_sinst = new Singleton();}return *_sinst;}void Add(const pair<string, string>& kv){_dict[kv.first] = kv.second;}void Print(){for (auto& kv : _dict){cout << kv.first << \' \' << kv.second << endl;}cout << endl;}private:Singleton(){}Singleton(const Singleton& _sinst) = delete;Singleton& operator=(const Singleton& _sinst) = delete;map<string, string> _dict;static Singleton* _sinst;};Singleton* Singleton::_sinst = nullptr;}
测试一
  1. 那么我们在类外就可以通过类域::的方式调用获取单例类的静态成员函数获取到唯一的单例类对象进行操作了,当第一次调用懒汉模式的单例类获取单例对象的时候,单例对象才会new调用构造函数进行实例化
int main(){lazy::Singleton::GetInstace().Add({ \"插入\", \"insert\" });lazy::Singleton::GetInstace().Add({ \"删除\", \"delete\" });lazy::Singleton::GetInstace().Add({ \"查找\", \"xxx\" });lazy::Singleton::GetInstace().Print();lazy::Singleton::GetInstace().Add({ \"查找\", \"find\" });lazy::Singleton::GetInstace().Print();return 0;}

运行结果如下,无误
【c++】特殊类的设计(不能拷贝的类,只能在堆/栈上创建对象的类,不能被继承的类,单例模式——饿汉模式、懒汉模式)

优化
  1. 其实单例对象一般不需要编写析构函数进行析构,因为对于单例对象是属于进程的,当进程正常结束的时候,会连带着单例对象的空间一起进行释放
  2. 有两个特殊场景需要进行释放:1. 显示释放,如当前单例对象的原数据我不想要了,我显示释放一下在插入新的数据 2. 程序结束时,需要做一些特殊动作,例如:数据持久化
  3. 那么接下来小编就来带领大家学习一下如何进行懒汉模式的单例对象的析构
  4. 同样的需要将析构函数私有化,提供一个静态成员函数DelInstace进行调用释放,这个静态成员函数DelInstace可以让我们显示手动释放单例对象
  5. 那么在析构函数中小编就做一些数据持久化处理,将当前map对象_dict的内容写入到文件中去做数据持久化处理
public:static void DelInstace(){if (_sinst){delete _sinst;_sinst = nullptr;}}private:~Singleton(){cout << \"~Singleton()\" << endl;FILE* fin = fopen(\"text.txt\", \"w\");for (auto& kv : _dict){fputs(kv.first.c_str(), fin);fputs(\" \", fin);fputs(kv.second.c_str(), fin);fputs(\"\\n\", fin);}}
测试二
  1. 那么接下来小编就可以使用类::显示调用静态成员函数DelInstace去释放单例对象,并且做一些数据持久化内容了
int main(){lazy::Singleton::GetInstace().Add({ \"插入\", \"insert\" });lazy::Singleton::GetInstace().Add({ \"删除\", \"delete\" });lazy::Singleton::GetInstace().Add({ \"查找\", \"xxx\" });lazy::Singleton::GetInstace().Print();lazy::Singleton::GetInstace().Add({ \"查找\", \"find\" });lazy::Singleton::GetInstace().Print();lazy::Singleton::DelInstace();return 0;}

运行结果如下
【c++】特殊类的设计(不能拷贝的类,只能在堆/栈上创建对象的类,不能被继承的类,单例模式——饿汉模式、懒汉模式)
数据也已经写入文件成功了
【c++】特殊类的设计(不能拷贝的类,只能在堆/栈上创建对象的类,不能被继承的类,单例模式——饿汉模式、懒汉模式)

优化
  1. 我们思考一下,当前还只有一个单例对象,可是如果有50个,100个甚至更多呢?都需要我们手动的去调用静态成员函数DelInstace去释放单例对象吗?这未免也太难受了
  2. 如何进行优化呢?我们可以借助智能指针的思想去搞,但是注意这里没必要,由于析构函数被我们私有化了,所以智能指针无法在类外调用析构函数进行析构
  3. 这里我们可以借助智能指针的思想去搞,定义一个内部类GC,利用析构函数的特定调用DelInstace进行管理单例对象的释放,当GC实例化的对象生命周期结束,那么就会自动调用析构函数进行析构,那么此时单例对象就得到了析构释放
  4. 同时使用这个内部类GC在单例类声明一个私有的静态成员变量_gc,在类外进行初始化,这样每一个单例类的单例对象就会搭配一个进行管理单例对象析构的_gc对象
class Singleton{public:class GC{public:~GC(){DelInstace();}};static Singleton::GC _gc;};Singleton::GC Singleton::_gc;
测试三
  1. 此时小编不显示调用静态成员函数DelInstace去释放单例对象,看看_gc对象能否完成单例对象的析构
int main(){lazy::Singleton::GetInstace().Add({ \"插入\", \"insert\" });lazy::Singleton::GetInstace().Add({ \"删除\", \"delete\" });lazy::Singleton::GetInstace().Add({ \"查找\", \"xxx\" });lazy::Singleton::GetInstace().Print();lazy::Singleton::GetInstace().Add({ \"查找\", \"find\" });lazy::Singleton::GetInstace().Print();return 0;}

运行结果如下,完成了单例对象的析构
【c++】特殊类的设计(不能拷贝的类,只能在堆/栈上创建对象的类,不能被继承的类,单例模式——饿汉模式、懒汉模式)
并且由于调用了单例对象的析构函数,所以做了数据持久化处理,观察时间,相对于上一次写入时间更新了,所以_gc对象自动调用析构函数,析构单例对象,进行数据持久化处理成功
【c++】特殊类的设计(不能拷贝的类,只能在堆/栈上创建对象的类,不能被继承的类,单例模式——饿汉模式、懒汉模式)

六、源代码

test.cpp

#define _CRT_SECURE_NO_WARNINGS 1#include #include using namespace std;//class CopyBan//{////private://CopyBan(const CopyBan& cb);//CopyBan& operator=(const CopyBan& cb);//};//class CopyBan//{//public://CopyBan(const CopyBan& cb) = delete;//CopyBan& operator=(const CopyBan& cb) = delete;//};//class HeapOnly//{//public://void Destroy()//{//delete this;//}////private://~HeapOnly()//{//cout << \"~HeapOnly()\" << endl;//}//};////int main()//{//HeapOnly* hp1 = new HeapOnly;//hp1->Destroy();////return 0;//}//class HeapOnly//{//public:////static HeapOnly* CreatObj()//{//return new HeapOnly;//}////private://HeapOnly()//{//cout << \"HeapOnly()\" << endl;//}////HeapOnly(const HeapOnly& hp) = delete;//HeapOnly& operator=(const HeapOnly& hp) = delete;//};////int main()//{//HeapOnly* hp3 = HeapOnly::CreatObj();//delete hp3;////return 0;//}//class StackOnly//{//public://static StackOnly CreatObj()//{//return StackOnly();//}////void* operator new(size_t size) = delete;////private://StackOnly()//{//cout << \"StackOnly()\" << endl;//}////};////int main()//{//StackOnly st = StackOnly::CreatObj();////StackOnly* st1 = new StackOnly(st);////return 0;//}//c++98//class NonInherit//{//public://static NonInherit Creat()//{//return NonInherit();//}////private://NonInherit()//{}//};////class Inherit : public NonInherit//{////};//////int main()//{//Inherit ih;////return 0;//}//c++11//class NonInherit final//{////};//////class Inherit : public NonInherit//{////};//namespace hungry//{//class Singleton//{//public://static Singleton& GetInstace()//{//return _sinst;//}////void Add(const pair& kv)//{//_dict[kv.first] = kv.second;//}////void Print()//{//for (auto& kv : _dict)//{//cout << kv.first << \' \' << kv.second << endl;//}//cout << endl;//}////private://Singleton()//{}////Singleton(const Singleton& _sinst) = delete;//Singleton& operator=(const Singleton& _sinst) = delete;////map _dict;//static Singleton _sinst;//};////Singleton Singleton::_sinst;//}////int main()//{//hungry::Singleton::GetInstace().Add({ \"插入\", \"insert\" });//hungry::Singleton::GetInstace().Add({ \"删除\", \"delete\" });//hungry::Singleton::GetInstace().Add({ \"查找\", \"xxx\" });////hungry::Singleton::GetInstace().Print();////hungry::Singleton::GetInstace().Add({ \"查找\", \"find\" });//hungry::Singleton::GetInstace().Print();////return 0;//}//namespace lazy//{//class Singleton//{//public://static Singleton& GetInstace()//{//if (_sinst == nullptr)//{//_sinst = new Singleton();//}////return *_sinst;//}////void Add(const pair& kv)//{//_dict[kv.first] = kv.second;//}////void Print()//{//for (auto& kv : _dict)//{//cout << kv.first << \' \' << kv.second << endl;//}//cout << endl;//}////private://Singleton()//{}////Singleton(const Singleton& _sinst) = delete;//Singleton& operator=(const Singleton& _sinst) = delete;////map _dict;//static Singleton* _sinst;//};////Singleton* Singleton::_sinst = nullptr;//}namespace lazy{class Singleton{public:static Singleton& GetInstace(){if (_sinst == nullptr){_sinst = new Singleton();}return *_sinst;}static void DelInstace(){if (_sinst){delete _sinst;_sinst = nullptr;}}void Add(const pair<string, string>& kv){_dict[kv.first] = kv.second;}void Print(){for (auto& kv : _dict){cout << kv.first << \' \' << kv.second << endl;}cout << endl;}class GC{public:~GC(){DelInstace();}};private:Singleton(){}~Singleton(){cout << \"~Singleton()\" << endl;FILE* fin = fopen(\"text.txt\", \"w\");for (auto& kv : _dict){fputs(kv.first.c_str(), fin);fputs(\" \", fin);fputs(kv.second.c_str(), fin);fputs(\"\\n\", fin);}}Singleton(const Singleton& _sinst) = delete;Singleton& operator=(const Singleton& _sinst) = delete;map<string, string> _dict;static Singleton* _sinst;static Singleton::GC _gc;};Singleton* Singleton::_sinst = nullptr;Singleton::GC Singleton::_gc;}int main(){lazy::Singleton::GetInstace().Add({ \"插入\", \"insert\" });lazy::Singleton::GetInstace().Add({ \"删除\", \"delete\" });lazy::Singleton::GetInstace().Add({ \"查找\", \"xxx\" });lazy::Singleton::GetInstace().Print();lazy::Singleton::GetInstace().Add({ \"查找\", \"find\" });lazy::Singleton::GetInstace().Print();return 0;}

总结

以上就是今天的博客内容啦,希望对读者朋友们有帮助
水滴石穿,坚持就是胜利,读者朋友们可以点个关注
点赞收藏加关注,找到小编不迷路!