1.设计一个不能被拷贝的类
C++98中:
将拷贝构造函数和赋值运算符重载只声明不定义,将访问权限设置私有
原因:
- 设置成私有:如果只声明没有设置成private,用户自己如果在类外定义了,就可以不能禁止拷贝了
- 只声明不定义:不定义是因为该函数根本不会调用,定义了其实也没有什么意义,不写反而还简单,而且如果定义了就不会防止成员函数内部拷贝了。
C++11中:
扩展了delete的用法,delete除了释放new申请的资源外,如果在默认成员函数后跟上=delete,表示让编译器删除掉该默认成员函数。
复习内存分配
栈:
用途:
1.存储局部变量(包括函数参数和局部变量)。
2.存储函数调用的上下文信息(如返回地址、寄存器保存值等)。
特点:
1.生命周期由作用域决定。当函数调用结束时,局部变量的内存自动释放。
2.内存分配和释放速度快,因为栈的操作是连续的。
3.栈的大小通常有限制(例如,Windows默认栈大小为1MB)。
4.存在栈溢出(Stack Overflow):如果栈空间不足,可能会导致程序崩溃。
堆:
用途:
1.存储动态分配的对象。
2.适合存储生命周期较长或大小不确定的对象。
特点:
1.内存分配和释放由程序员手动管理(C++中使用new和delete,C中使用malloc和free)。
2.堆的大小通常由系统可用内存决定,理论上可以分配较大的内存块。
3.内存分配和释放速度相对较慢,因为需要查找可用内存块并进行管理。
4.如果忘记释放内存,会导致内存泄漏(Memory Leak)。
静态区(数据段):
用途:
1.存储全局变量和静态变量。
2.存储静态分配的对象。
特点:
1.生命周期贯穿整个程序的运行时间。
2.内存分配在程序启动时完成,释放时在程序结束时完成。(注意:main函数是程序的入口点。程序的生命周期由操作系统管理,常量区的内容初始化和释放也是由操作系统管理的)
3.静态变量的值在函数调用之间保持不变。
常量区(代码段):
用途:
1.存储常量数据。
2.包括字符串常量、数字常量等。
特点:
1.内存中的值不能被修改(只读)。
2.常量区的内容在程序启动时初始化,程序结束时释放。
3.常量区的内容通常存储在只读内存中,以防止意外修改。
2.设计一个只能在堆上创建对象的类
实现方式:
将类的构造函数私有,拷贝构造声明成私有。防止别人调用拷贝在栈上生成对象。
提供一个静态的成员函数,在该静态成员函数中完成堆对象的创建
有两种思路来实现
- 1.析构函数私有化
class HeapOnly
{
public:void Destroy(){delete this;}
private:~HeapOnly(){}
};//测试
int main()
{//栈上创建对象HeapOnly hp1;//静态区上创建的对象static HeapOnly hp2;//堆上创建对象HeapOnly* hp3 = new HeapOnly;hp3->Destroy();return 0;
}
1.在栈上创建的对象会在函数作用域结束后自动调用析构函数来释放资源
2.在静态区上创建的对象会在程序结束时自动调用析构函数
由于在编译阶段检查析构函数私有化它们无法调用,出现报错程序无法运行起来,所以无法创建它们。
3.私有域只对类外面的对象起限制作用,可以定义一个成员函数来调用析构,这样在堆上创建的变量就能够正常释放注意:
- 程序运行起来,才会执行相关的代码,涉及到变量的创建等
2.析构被禁用,虽然说不影响构造,但无法析构,导致程序报错,是无法编译通过 也就是无法访问析构程序无法运行,所以没法创建在栈上和静态区上的变量
3.如果显示写析构函数,C++运行环境会依赖,若默认就是公有的
- 2.构造函数私有化
class HeapOnly
{
public:static HeapOnly* creatobj(){return new HeapOnly;}private:HeapOnly(){//...}HeapOnly(const HeapOnly& hp) = delete;HeapOnly& operator=(const HeapOnly& hp) = delete;
};//测试
int main()
{//HeapOnly hp1;不允许,给禁止了//Heaponly* hp3 = new HeapOnly;//HeapOnly copy(hp3);HeapOnly* hp3 = HeapOnly::creatobj();delete hp3;return 0;
}
将构造函数设为私有,利用C++11中delete关键字来禁止拷贝构造和赋值重载的生成,使得无法在类外面直接创建或通过拷贝创建对象,也就是无法在栈上或静态区上创建对象
1.定义一个成员函数使得能new一个在堆上创建的对象,加上static修饰,可以使该函数属于类,它的调用不在依赖于实例化的对象,而是直接通过类名+访问限定符就可以直接访问。避免了创建对象需要对象来调用构造函数的先有鸡还是先有蛋的问题
3.设计一个只能在栈上创建对象的类
class StackOnly
{
public:static StackOnly creatobj(){return StackOnly();/*StackOnly st;return st;*/}
private:StackOnly(){//...禁用}//~StackOnly() = delete;void* operator new(size_t size) = delete;
};int main()
{//StackOnly st1;//static StackOnly st2;静态区,禁用//StackOnly st3 = new StackOnly;堆,禁用StackOnly st1 = StackOnly::creatobj();//StackOnly* hp4 = new StackOnly(hp3); new+构造return 0;
}
1.与2.相同禁用构造,使之无法调用在静态区创建对象
2.通过定义一个静态方法可以不依赖对象的实例直接构造对象返回
3.禁用operator new,禁止在堆上创建对象:
new是一个运算符,用于动态分配内存并调用构造函数来初始化对象。operator new是一个全局函数,负责实际的内存分配。new 关键字在内部调用 operator new 来获取内存。operator new 可以被重载,所以要禁用operator new而不是new
4.设计一个不能被继承的类
1.C++98中构造函数私有化,派生类中调不到基类的构造函数。则无法继承
2.C++11可以使用final关键字,final修饰类,表示该类不能被继承,在类名后面修饰
5.单例模式
定义:一个类中只能创建一个对象
设计模式:
是一套被反复使用、多数人知晓的、经过分类的、代码设计经验的
总结。通俗来讲就是套路
目的:为了代码可重用性、让代码更容易被他人理解、保证代码可靠性。 设计模式使代码编写真正工程化;设计模式是软件工程的基石脉络,如同大厦的结构一样。
单例模式:
一个类只能创建一个对象,即单例模式,该模式可以保证系统中该类只有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块享。
单例模式的两种实现模式:
1.饿汉模式:
一开始(main函数之前)就创建单例对象
优点:
简单,不需要手动管理,交给程序来管理其生命周期
缺点:
1.如果单例对象初始化内容很多,影响启动速度
2.如果两个单例类,互相有依赖关系。不好控制先后创建的关系
使用场景:
如果这个单例对象在多线程高并发环境下频繁使用,性能要求较高,那么显然使用饿汉模式来避免资源竞争,提高响应速度更好。
namespace hungry
{class Singleton{public://提供获取单例对象接口的函数static Singleton& GetInstance(){return _sinst;}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:Singleton(){//...}//防拷贝Singleton(const Singleton& s) = delete;Singleton& operator=(const Singleton& s) = delete;map<string, string> _dict;static Singleton _sinst;};// 在程序入口之前就完成单例对象的初始化Singleton Singleton::_sinst;
}
2.懒汉模式
优点:
第一次使用实例对象时,创建对象。进程启动无负载。多个单例实例启动顺序自由控制。
缺点:
相比饿汉模式相对复杂
使用场景:
如果单例对象构造十分耗时或者占用很多资源,比如加载插件啊, 初始化网络连接啊,读取文件啊等等,而有可能该对象程序运行时不会用到,那么也要在程序一开始就进行初始化,就会导致程序启动时非常的缓慢。 所以这种情况使用懒汉模式(延迟加载)更好。
namespace lazy
{class Singleton{public://提供获取单例对象的接口函数static Singleton& GetInstance(){if (_psinst == nullptr){//第一次调用GetInstance的时候创建单例对象_psinst = new Singleton;}return *_psinst;}// 一般单例不用释放。//特殊场景:1、中途需要显示释放 //2、程序结束时,需要做一些特殊动作(如持久化)static void DelInstance(){if (_psinst){delete _psinst;_psinst = nullptr;}}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;}// 实现一个内嵌垃圾回收类 class GC{public:~GC(){lazy::Singleton::DelInstance();}};private://构造私有化Singleton(){//...}~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);}}//防拷贝Singleton(const Singleton& s) = delete;Singleton& operator=(const Singleton& s) = delete;map<string, string> _dict;static Singleton* _psinst;// 定义一个静态成员变量,程序结束时,//系统会自动调用它的析构函数从而释放单例对象static GC _gc;};Singleton* Singleton::_psinst = nullptr;Singleton::GC Singleton::_gc;
}//测试模式
int main()
{cout << &lazy::Singleton::GetInstance() << endl;cout << &hungry::Singleton::GetInstance() << endl;lazy::Singleton::GetInstance().Add({ "mint","薄荷" });lazy::Singleton::GetInstance().Add({ "1024","34" });lazy::Singleton::GetInstance().Print();return 0;
}