提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
目录
前言
世上有两种耀眼的光芒,一种是正在升起的太阳,一种是正在努力学习编程的你!一个爱学编程的人。各位看官,我衷心的希望这篇博客能对你们有所帮助,同时也希望各位看官能对我的文章给与点评,希望我们能够携手共同促进进步,在编程的道路上越走越远!
提示:以下是本篇文章正文内容,下面案例可供参考
一、请设计一个类,不能被拷贝
拷贝只会发生在两个场景中:拷贝构造函数以及赋值运算符重载,因此想要让一个类禁止拷贝, 只需让该类不能调用拷贝构造函数以及赋值运算符重载即可。
C++98
将拷贝构造函数与赋值运算符重载只声明不定义,并且将其访问权限设置为私有即可。
class CopyBan
{
// ...
private:
CopyBan(const CopyBan&);
CopyBan& operator=(const CopyBan&);
//...
};
原因:
- 设置成私有:如果只声明没有设置成private,用户自己如果在类外定义了,就可以不能禁止拷贝了
- 只声明不定义:不定义是因为该函数根本不会调用,定义了其实也没有什么意义,不写反而还简单,而且如果定义了就不会防止成员函数内部拷贝了。
C++11
C++11扩展delete的用法,delete除了释放new申请的资源外,如果在默认成员函数后跟上 =delete,表示让编译器删除掉该默认成员函数。
class CopyBan
{
// ...
CopyBan(const CopyBan&) = delete;
CopyBan& operator=(const CopyBan&) = delete;
//...
};
二、请设计一个类,只能在堆上创建对象
实现方式:
- 将类的构造函数私有,拷贝构造声明成私有。防止别人调用拷贝在栈上生成对象。
- 提供一个静态的成员函数,在该静态成员函数中完成堆对象的创建。
方法一:
// 对象只能在堆上
class HeapOnly
{
public:
template<class... Args>
static HeapOnly* CreateObj(Args&&... args)
{
// 访问限定符只限定类外面的,而不限定类里面的,可以在类里面创建一个该类型的对象
return new HeapOnly(args...);
}
HeapOnly(const HeapOnly&) = delete;
HeapOnly& operator=(const HeapOnly&) = delete;
private:
// 构造私有化
HeapOnly()
{}
// 带参的构造函数
HeapOnly(int x, int y)
:_x(x)
,_y(y)
{}
int _x;
int _y;
vector<int> _a;
};
int main()
{
//HeapOnly ho1;
//HeapOnly* ho2 = new HeapOnly;
// 我们想要调用类成员函数,我们就得有类的对象,但是类的对象又是由该函数生成的,这就是先有鸡还是先有蛋的问题;
// 所以我们可以将生成类的对象的函数定义为静态的
HeapOnly* ho3 = HeapOnly::CreateObj();
HeapOnly* ho4 = HeapOnly::CreateObj(1,1);
//HeapOnly copy(*ho3);
return 0;
}
方法二:
// 只能在堆上(方法二)
class HeapOnly
{
public:
// 构造私有化
HeapOnly()
{}
HeapOnly(int x, int y)
:_x(x)
,_y(y)
{}
void Destroy()
{
delete this;// 调用析构函数
// delete分为两步:先调用析构,再free指向的空间
}
private:
// 封掉析构函数,但是还要用析构,所以不能彻底封死,放到私有部分
~HeapOnly()
{
cout << "~HeapOnly()" << endl;
}
int _x;
int _y;
vector<int> _a;
};
int main()
{
//HeapOnly ho1;// 栈区对象出了作用域,会自动调用析构函数
// 方法一:手动释放空间
HeapOnly* ptr = new HeapOnly;// 不会自动调用析构,因为是堆区上面的
//delete ptr;
ptr->Destroy();
// 方法二:lambda表达式
// 释放的删除器是delete,但是底层的delete调不动,会报错,所以只能默认的写一个删除器,调用Destroy()函数
shared_ptr<HeapOnly> ptr(new HeapOnly, [](HeapOnly* ptr) {ptr->Destroy(); });
return 0;
}
三、请设计一个类,只能在栈上创建对象
第一种方式:
将构造函数私有化,阻止了在堆上创建对象,然后设计静态方法创建对象返回即可。
第二种方式:
new是由operator new和构造函数(拷贝构造也是构造)两部分组成,封一下operator new,operator new是默认调用的是全局的operator new,我们可以重载一个专属的operator new
// 对象只能在Stack
class StackOnly
{
public:
template<class... Args>
static StackOnly CreateObj(Args&&... args)
{
return StackOnly(args...);// 返回一个栈上的匿名对象
}
//StackOnly(const StackOnly&) = delete;
StackOnly& operator=(const StackOnly&) = delete;
// 重载一个类专属的operator new
// 意思是new想在堆区上开辟一个StackOnly类型的对象,就不能调用全局的operator new,只能调用该类的operator new
// 但是该类的operator new()封住了,所以new也就不能在堆区上开辟空间了
void* operator new(size_t n) = delete;
void operator delete(void* p) = delete; // 禁止调用delet
private:
// 构造私有化,阻止了在堆上创建对象
StackOnly()
{}
StackOnly(int x, int y)
:_x(x)
,_y(y)
{}
int _x;
int _y;
vector<int> _a;
};
int main()
{
// 优化了之后,可能是直接构造,但是优化是在语法逻辑之后,得先拷贝,然后再优化
StackOnly so1 = StackOnly::CreateObj();
StackOnly so2 = StackOnly::CreateObj(1,1);
// 因为上面两行,拷贝构造函数是不能封死的
// new的时候,调不了构造函数,但可以调用拷贝构造函数(没封)
// new是在堆区上开辟一个对象空间,我们只想在栈区上开辟空间,但是拷贝构造又不能封死,所以要让new不能使用
//StackOnly* so3 = new StackOnly(so1);
// new是由operator new和构造函数(拷贝构造也是构造)两部分组成
// 封一下operator new,operator new是默认调用的是全局的operator new,我们可以重载一个专属的operator new
return 0;
}
四、请设计一个类,不能被继承
C++98方式
// C++98中构造函数私有化,派生类中调不到基类的构造函数。则无法继承
class NonInherit
{
public:
static NonInherit GetInstance()
{
return NonInherit();
}
private:
NonInherit()
{}
};
C++11方法
final关键字,final修饰类,表示该类不能被继承。
class A final
{
// ....
};
五、请设计一个类,只能创建一个对象(单例模式)
设计模式:
设计模式(Design Pattern)是一套被反复使用、多数人知晓的、经过分类的、代码设计经验的总结。为什么会产生设计模式这样的东西呢?就像人类历史发展会产生兵法。最开始部落之间打仗时都是人拼人的对砍。后来春秋战国时期,七国之间经常打仗,就发现打仗也是有套路的,后来孙子就总结出了《孙子兵法》。孙子兵法也是类似。
使用设计模式的目的:为了代码可重用性、让代码更容易被他人理解、保证代码可靠性。 设计模 式使代码编写真正工程化;设计模式是软件工程的基石脉络,如同大厦的结构一样。
单例模式:
一个类只能创建一个对象,即单例模式,该模式可以保证系统中该类只有一个实例,并提供一个 访问它的全局访问点,该实例被所有程序模块共享。比如在某个服务器程序中,该服务器的配置 信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再 通过这个单例对象获取这些配置信息,这种方式简化了在复杂环境下的配置管理。
单例模式有两种实现模式:
饿汉模式
就是说不管你将来用不用,程序启动时就创建一个唯一的实例对象。
namespace hunger
{
// 饿汉:一开始(main之前)就创建出全局的对象
// 问题:
// 1、如果单例对象数据较多,构造初始化成本较高,那么会影响程序启动的速度。迟迟进不了main函数
// 2、多个单例类有初始化启动依赖关系,饿汉无法控制。
// 假设:A和B两个单例,假设要求A先初始化,B再初始化,饿汉无法保证。
// 都是全局的,谁先谁后不清楚,而且还定义在不同的文件
class Singleton
{
public:
static Singleton* GetInstance()
{
return &_sint;// 返回该类型的对象
}
void Print()
{
cout << _x << endl;
cout << _y << endl;
for (auto& e : _vstr)
{
cout << e << " ";
}
cout << endl;
}
// 添加数据
void AddStr(const string& s)
{
_vstr.push_back(s);
}
Singleton(Singleton const&) = delete;
Singleton& operator=(Singleton const&) = delete;
private:
// 构造函数私有化
Singleton(int x = 0, int y = 0, const vector<string>& vstr = { "yyyyy","xxxx" })
:_x(x)
, _y(y)
, _vstr(vstr)
{}
// 想让一些数据,当前程序只有一份,那就可以把这些数据放到这个类里面
// 再把这个类设计成单例,这个数据就只有一份了
int _x;
int _y;
vector<string> _vstr;
// 静态成员对象,不存在类的其中一个对象中,存在静态区,相当于全局的,是属于这个类中的所有对象的,
// 定义在类中,受类域限制,但是能访问私有的构造函数
static Singleton _sint;
};
// 静态成员变量在类里面声明,在类外面定义;也可以调用私有的构造函数
// 第一个Singleton是类型;第二个Singleton是类域
Singleton Singleton::_sint(1, 1, { "陕西","四川" });
}
// Singleton _sint;
// 在类外面不能创建该类的对象,因为该类的构造函数私有话了,所以只能把它放到类里面,写成静态的
如果这个单例对象在多线程高并发环境下频繁使用,性能要求较高,那么显然使用饿汉模式来避 免资源竞争,提高响应速度更好。
懒汉模式
如果单例对象构造十分耗时或者占用很多资源,比如加载插件啊, 初始化网络连接啊,读取文件啊等等,而有可能该对象程序运行时不会用到,那么也要在程序一开始就进行初始化, 就会导致程序启动时非常的缓慢。 所以这种情况使用懒汉模式(延迟加载)更好。
懒汉方式一:使用指针
// 完美的解决了饿汉的问题(懒汉模式)
namespace lazy
{
// 懒汉方式一:两个问题:new出来的对象要考虑释放问题、线程安全问题
class Singleton
{
public:
static Singleton* GetInstance()
{
// 第一次调用时,创建单例对象
// 线程安全问题,需要加锁
if (_psint == nullptr)
{
_psint = new Singleton;
}
return _psint;
}
static void DelInstance()
{
if (_psint)
{
delete _psint;
_psint = nullptr;
}
}
void Print()
{
cout << _x << endl;
cout << _y << endl;
for (auto& e : _vstr)
{
cout << e << " ";
}
cout << endl;
}
void AddStr(const string& s)
{
_vstr.push_back(s);
}
Singleton(Singleton const&) = delete;
Singleton& operator=(Singleton const&) = delete;
private:
Singleton(int x = 0, int y = 0, const vector<string>& vstr = { "yyyyy","xxxx" })
:_x(x)
, _y(y)
, _vstr(vstr)
{}
// 什么情况下需要显示的释放?
~Singleton()
{
// 把数据写到文件
cout << "~Singleton()" << endl;
}
// 想让一些数据,当前程序只有一份,那就可以把这些数据放到这个类里面
// 再把这个类设计成单例,这个数据就只有一份了
int _x;
int _y;
vector<string> _vstr;
// 静态成员对象,不存在对象中,存在静态区,相当于全局的,定义在类中,受类域限制
// 懒汉模式 ---> 第一种方式:使用指针
static Singleton* _psint;
// 万一外面忘记调用DelInstance()函数来显示释放单例对象,怎么办?
// 内部类
class GC
{
public:
~GC()
{
Singleton::DelInstance();
}
};
static GC gc;// 定义一个全局的GC类的对象,出了作用域会自动调用析构函数
};
Singleton* Singleton::_psint = nullptr;
Singleton::GC Singleton::gc;
懒汉方式二:局部的静态对象
- 饿汉定义的是全局的静态变量,将全局的静态变量放入类域中;全局的静态变量在main()函数之前就要创建;
- 懒汉定义的是局部的静态变量,第一次调用函数时构造初始化;第二次调用函数时,不会重复构造初始化;
- 局部的静态变量的声明周期也是全局的
// 懒汉方式二:
class Singleton
{
public:
static Singleton* GetInstance()
{
// 局部的静态对象,第一次调用函数时构造初始化;第二次调用函数时,不会重复构造初始化
// C++11及之后这样写才可以
// C++11之前无法保证这里的构造初始化是线程安全
static Singleton _sinst;
return &_sinst;
}
void Print()
{
cout << _x << endl;
cout << _y << endl;
for (auto& e : _vstr)
{
cout << e << " ";
}
cout << endl;
}
void AddStr(const string& s)
{
_vstr.push_back(s);
}
Singleton(Singleton const&) = delete;
Singleton& operator=(Singleton const&) = delete;
private:
Singleton(int x = 0, int y = 0, const vector<string>& vstr = { "yyyyy","xxxx" })
:_x(x)
, _y(y)
, _vstr(vstr)
{
cout << "Singleton()" << endl;
}
~Singleton()
{
// 把数据写到文件
cout << "~Singleton()" << endl;
}
// 想让一些数据,当前程序只有一份,那就可以把这些数据放到这个类里面
// 再把这个类设计成单例,这个数据就只有一份了
int _x;
int _y;
vector<string> _vstr;
};
}
int main()
{
cout << "xxxxxxxxx" << endl;
cout << hunger::Singleton::GetInstance() << endl;
cout << hunger::Singleton::GetInstance() << endl;
cout << hunger::Singleton::GetInstance() << endl;
lazy::Singleton::GetInstance()->Print();
lazy::Singleton::GetInstance()->Print();
lazy::Singleton::GetInstance()->Print();
lazy::Singleton::GetInstance()->AddStr("甘肃");
lazy::Singleton::GetInstance()->Print();
//lazy::Singleton::DelInstance();
cout << "xxxxxxxxx" << endl;
return 0;
}
总结
好了,本篇博客到这里就结束了,如果有更好的观点,请及时留言,我会认真观看并学习。
不积硅步,无以至千里;不积小流,无以成江海。