1.设计一个类只能在栈上创建
这个设计就很简单了,如果只能在栈上创建的话我们只需要将这个类的构造函数私有化即可。下面直接给出代码
//设计一个类只能在堆上创建
class StackOlny
{
public:
static StackOlny CreateObj()
{
return StackOlny();
}
private:
StackOlny()
{}
};
方法二:我们知道一个使用new来创建对象时,会先调用operator new,然后再调这个类的构造函数。如果我们把operator new 设置为私有此时我们自己写了operator new 那么就不会调用全局的operator new 了,下面我们来看看代码
//方法二:new 会调用operator new如果类写了自己的operator new 和operator delete 就不会去调用全局的了
class StackOnly
{
public:
//缺点无法禁掉在静态区定义的对象
StackOnly()
{}
private:
void* operator new (size_t size)//=delete
{}
void operator delete(void*ptr)//=delete
{}
};
2.编写一个类只能再堆上创建
会了上面那个,下面这个就很简单了,只需要将构造函数私有化即可
//构造函数私有
class HeapOlny
{
public:
static HeapOlny* CreateObj()//由于没有对象所以这个函数必须是静态的
{
return new HeapOlny();
}
HeapOlny(const HeapOlny& obj) = delete;//防拷贝
HeapOlny& operator=(const HeapOlny&) = delete;
private:
HeapOlny()
{}
};
什么是单例模式了?相信大家都听说过,很多老铁都写过。单例模式是指在内存当中只会创建一次对象的设计模式,有时候在一个程序当中多次使用一个对象并且作用相同时,此时如果创建多个对象是非常的浪费内存的,单例模式可以让对象在内存当中只被创建一份。
单例模式在面试当中也是考察的非常多的一个设计模式,在面试当中需要我们掌握的是场景的两种一种是懒汉模式,一种是饿汉模式。下面废话不多说,我们直接来看什么是懒汉模式什么是饿汉模式。
1.饿汉模式
什么是饿汉模式?饿汉模式是指在类加载时就已经创建好了该单例对象等待被程序使用。下面让我们来看看如何实现一个饿汉模式的类,并且看看他是如何保证只会创建一份的
class Singleton
{
public:
static Singleton* GetInstance()
{
return _inst;
}
private:
Singleton()
{}
Singleton(const Singleton&) = delete;
Singleton&operator=(const Singleton&) = delete;
static Singleton* _inst;
};
Singleton* Singleton::_inst = new Singleton();
我们发现这个函数的构造函数是被私有的,且类里面有一个静态的成员变量
静态的成员变量初始化是在程序加载前就已经初始化了,我们可以后面可以通过类名调用这个函数来获取单例,这样就保证了在内存当中只有一份。
下面我们在看一下饿汉模式的另外一种写法
class Singleton
{
public:
static Singleton* GetInstance()
{
return &_inst;
}
private:
Singleton()
{}
Singleton(const Singleton&) = delete;
Singleton&operator=(const Singleton&) = delete;
static Singleton _inst;
};
Singleton Singleton::_inst;
int main()
{
return 0;
}
这种饿汉模式的写法也是可以的。
2.懒汉模式
什么是懒汉模式了?懒汉模式是指在正在需要使用到对象的时候,才会去创建该单例对象
懒汉模式创建对象是在程序使用对象前,先判断该对象是否为空如果为空则创建对象,如果已经创建好了对象直接返回即可。下面我们来看看这个代码怎么写:
class Singleton
{
public:
static Singleton*GetInstance()
{
if (_inst == nullptr) {
_inst=new Singleton;
}
return _inst;
}
private:
Singleton()
{}
Singleton(const Singleton&) = delete;
Singleton&operator=(const Singleton&) = delete;
static Singleton* _inst;
};
Singleton* Singleton::_inst=nullptr;
int main()
{
return 0;
}
看起来很完美但是,还是有问题的。如果在多线程环境下线程一判断完这个_inst为空由于时间片的原因被切走了,此时线程二进来发现为空创建了对象,当线程一再次被切回来的时候,继续往下执行又创建了一份对象此时和我们预期的就不一样了。此时我们最容易的就是想到加锁,下面我们来修改一下代码看看加锁之后是否就可以了?下面我们来看看代码
#include
#include
using namespace std;
class Singleton
{
public:
static Singleton*GetInstance()
{
if (_inst == nullptr) {
_mtx.lock();
if (_inst == nullptr) {
_inst = new Singleton;
}
_mtx.unlock();
}
return _inst;
}
private:
Singleton()
{}
Singleton(const Singleton&) = delete;
Singleton&operator=(const Singleton&) = delete;
static Singleton* _inst;
static mutex _mtx;
};
Singleton* Singleton::_inst=nullptr;
int main()
{
return 0;
}
很多老铁可能说这部就完美了,通过加锁来实现这个线程安全。这样不就好了吗。但是这个任然是有问题的?我们知道new的底层会先调用operator new 然后再调用malloc,然后再调用类的构造函数,但是这里就有一个问题了。再有些编译器下这个步骤可能是这样的底层先调用了malloc开辟空间,然后就让_inst指向了这块空间此时另外一个线程来了发现不为空直接返回开始使用了但是这块空间还没有初始化。这样就可以能出现了问题,这个问题再c++里面解决起来非常的麻烦,需要利用很多c++11的新特性。下面给出一个简单的版本,个人认为完美的解决了这个问题
#include
#include
using namespace std;
class Singleton
{
public:
static Singleton&GetInstance()
{
static Singleton _inst;
return _inst;
}
private:
Singleton()
{}
Singleton(const Singleton&) = delete;
Singleton&operator=(const Singleton&) = delete;
};
int main()
{
return 0;
}
可能会有老铁会觉得这不是饿汉模式吗?静态的变量不是再程序加载前就初始化了吗?这个就要注意了这个是再函数里面并且他不是内置类型,如果是内置类型确实在之前就已经初始化好了,但是这是一个自定义类型,在第一次调用这个函数时初始化,这个要好好注意。
什么是观察者模式?观察者模式也被叫做订阅模式是一种行为型设计模式,也是在实际的开发中用得比较多的一种模式,当对象间存在一对多关系时,就可以使用观察者模式。定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象(观察者对象)都得到通知并被自动更新。这个十字路口就是一个典型的观察者模式,当红绿灯发生了变化,行人和车辆就好像收到通知了一样,马上就开始做响应的事情了
下面我们看一张图来看看是个什么样子的:
下面我们来举一个列子
#include
#include
#include
using namespace std;
namespace _nmsp1
{
class Fighter; //类前向声明
list<Fighter*> g_playerList;
//玩家父类(以往的战斗者类)
class Fighter
{
public:
Fighter(int tmpID, string tmpName) :m_iPlayerID(tmpID), m_sPlayerName(tmpName) //构造函数
{
m_iFamilyID = -1; //-1表示没有加入任何家族
}
virtual ~Fighter() {} //析构函数
public:
void SetFamilyID(int tmpID) //加入家族的时候要设置家族ID
{
m_iFamilyID = tmpID;
}
public:
void SayWords(string tmpContent) //玩家说了某句话
{
if (m_iFamilyID != -1)
{
//该玩家属于某个家族,应该把聊天内容信息传送给该家族的其他玩家
for (auto iter = g_playerList.begin(); iter != g_playerList.end(); ++iter)
{
if (m_iFamilyID == (*iter)->m_iFamilyID)
{
//同一个家族的其他玩家也应该收到聊天信息
NotifyWords((*iter), tmpContent);
}
}
}
}
private:
void NotifyWords(Fighter* otherPlayer, string tmpContent) //其他玩家收到了当前玩家的聊天信息
{
//显示信息
cout << "玩家:" << otherPlayer->m_sPlayerName << "收到了玩家:" << m_sPlayerName << " 发送的聊天信息:" << tmpContent << endl;
}
private:
int m_iPlayerID; //玩家ID,全局唯一
string m_sPlayerName; //玩家名字
int m_iFamilyID; //家族ID
};
//"战士"类玩家,父类为Fighter
class F_Warrior :public Fighter
{
public:
F_Warrior(int tmpID, string tmpName) :Fighter(tmpID, tmpName) {} //构造函数
};
//"法师"类玩家,父类为Fighter
class F_Mage :public Fighter
{
public:
F_Mage(int tmpID, string tmpName) :Fighter(tmpID, tmpName) {} //构造函数
};
}
int main(){
//创建游戏玩家
_nmsp1::Fighter* pplayerobj1 = new _nmsp1::F_Warrior(10, "张三"); //实际游戏中很多数据取自数据库。
pplayerobj1->SetFamilyID(100); //假设该玩家所在的家族的家族ID是100
_nmsp1::g_playerList.push_back(pplayerobj1); //加入到全局玩家列表中
_nmsp1::Fighter* pplayerobj2 = new _nmsp1::F_Warrior(20, "李四");
pplayerobj2->SetFamilyID(100);
_nmsp1::g_playerList.push_back(pplayerobj2);
_nmsp1::Fighter* pplayerobj3 = new _nmsp1::F_Mage(30, "王五");
pplayerobj3->SetFamilyID(100);
_nmsp1::g_playerList.push_back(pplayerobj3);
_nmsp1::Fighter* pplayerobj4 = new _nmsp1::F_Mage(50, "赵六");
pplayerobj4->SetFamilyID(200); //赵六和前面三人属于两个不同的家族
_nmsp1::g_playerList.push_back(pplayerobj4);
//当某个玩家聊天时,同族人都应该收到该信息
pplayerobj1->SayWords("全族人立即到沼泽地集结,准备进攻!");
//释放资源
delete pplayerobj1;
delete pplayerobj2;
delete pplayerobj3;
delete pplayerobj4;
return 0;
}
我们在玩游戏的时候,同一个族的人说了一句话,那么其它族人都应该收到这巨话。就好比你说的一句话你的队友都能够收到如果采用上面那种方式,一个玩家说了一巨话我们采用遍历的方式看这个玩家的族id是不是和说话玩家的族id相等从而决定这条消息是否要让这个玩家知道。这样设计就有一个缺点当玩家的数量太多的时候,这种设计的效率就会变得特别低。
下面我们来看看这个观察者模式是如何设计的
class Fighter; //类前向声明
class Notifier //通知器父类
{
public:
virtual void addToList(Fighter* player) = 0; //把要被通知的玩家加入到列表中
virtual void removeFromList(Fighter* player) = 0; //把不想被通知的玩家从列表中去除
virtual void notify(Fighter* talker, string tmpContent) = 0; //通知的一些细节信息
virtual ~Notifier() {}
};
//玩家父类
class Fighter
{
public:
Fighter(int tmpID, string tmpName) :m_iPlayerID(tmpID), m_sPlayerName(tmpName) //构造函数
{
m_iFamilyID = -1; //-1表示没有加入任何家族
}
virtual ~Fighter() {} //析构函数
public:
void SetFamilyID(int tmpID) //加入家族的时候要设置家族ID
{
m_iFamilyID = tmpID;
}
int GetFamilyID() //获取家族ID
{
return m_iFamilyID;
}
public:
void SayWords(string tmpContent,Notifier *notifier) //玩家说了某句话
{
notifier->notify(this, tmpContent);
}
//通知该玩家接收到其他玩家发送来的聊天信息,虚函数,子类可以覆盖以实现不同的功能
virtual void NotifyWords(Fighter* talker, string tmpContent)
{
//显示信息
cout << "玩家:" << m_sPlayerName << "收到了玩家:" << talker->m_sPlayerName << " 发送的聊天信息:" << tmpContent << endl;
}
private:
int m_iPlayerID; //玩家ID,全局唯一
string m_sPlayerName; //玩家名字
int m_iFamilyID; //家族ID
};
//"战士"类玩家,父类为Fighter
class F_Warrior :public Fighter
{
public:
F_Warrior(int tmpID, string tmpName) :Fighter(tmpID, tmpName) {} //构造函数
};
//"法师"类玩家,父类为Fighter
class F_Mage :public Fighter
{
public:
F_Mage(int tmpID, string tmpName) :Fighter(tmpID, tmpName) {} //构造函数
};
//聊天信息通知器
class TalkNotifier :public Notifier
{
public:
//将玩家增加到家族列表中来
virtual void addToList(Fighter* player)
{
int tmpfamilyid = player->GetFamilyID();
if (tmpfamilyid != -1) //加入了某个家族
{
auto iter = m_familyList.find(tmpfamilyid);
if (iter != m_familyList.end())
{
//该家族id在map中已经存在
iter->second.push_back(player); //直接把该玩家加入到该家族
}
else
{
//该家族id在map中不存在
list<Fighter*> tmpplayerlist;
m_familyList.insert(make_pair(tmpfamilyid, tmpplayerlist)); //以该家族id为key,增加条目到map中
m_familyList[tmpfamilyid].push_back(player); //向该家族中增加第一个玩家
}
}
}
//将玩家从家族列表中删除
virtual void removeFromList(Fighter* player)
{
int tmpfamilyid = player->GetFamilyID();
if (tmpfamilyid != -1) //加入了某个家族
{
auto iter = m_familyList.find(tmpfamilyid);
if (iter != m_familyList.end())
{
m_familyList[tmpfamilyid].remove(player);
}
}
}
//家族中某玩家说了句话,调用该函数来通知家族中所有人
virtual void notify(Fighter* talker, string tmpContent) //talker是讲话的玩家
{
int tmpfamilyid = talker->GetFamilyID();
if (tmpfamilyid != -1) //加入了某个家族
{
auto itermap = m_familyList.find(tmpfamilyid);
if (itermap != m_familyList.end())
{
//遍历该玩家所属家族的所有成员
for (auto iterlist = itermap->second.begin(); iterlist != itermap->second.end(); ++iterlist)
{
(*iterlist)->NotifyWords(talker, tmpContent);
}
}
}
}
private:
//map中的key表示家族id,value代表该家族中所有玩家列表
map<int, list<Fighter*> > m_familyList;
};
}
int main(){
_nmsp2::Fighter* pplayerobj1 = new _nmsp2::F_Warrior(10, "张三"); //实际游戏中很多数据取自数据库。
pplayerobj1->SetFamilyID(100); //假设该玩家所在的家族的家族ID是100
_nmsp2::Fighter* pplayerobj2 = new _nmsp2::F_Warrior(20, "李四");
pplayerobj2->SetFamilyID(100);
_nmsp2::Fighter* pplayerobj3 = new _nmsp2::F_Mage(30, "王五");
pplayerobj3->SetFamilyID(100);
_nmsp2::Fighter* pplayerobj4 = new _nmsp2::F_Mage(50, "赵六");
pplayerobj4->SetFamilyID(200); //赵六和前面三人属于两个不同的家族
//创建通知器
_nmsp2::Notifier* ptalknotify = new _nmsp2::TalkNotifier();
//玩家增加到家族列表中来,这样才能收到家族聊天信息
ptalknotify->addToList(pplayerobj1);
ptalknotify->addToList(pplayerobj2);
ptalknotify->addToList(pplayerobj3);
ptalknotify->addToList(pplayerobj4);
//某游戏玩家聊天,同族人都应该收到该信息
pplayerobj1->SayWords("全族人立即到沼泽地集结,准备进攻!", ptalknotify);
cout << "王五不想再收到家族其他成员的聊天信息了---" << endl;
ptalknotify->removeFromList(pplayerobj3); //将王五从家族列表中删除
pplayerobj2->SayWords("请大家听从族长调遣,前往沼泽地!", ptalknotify);
//释放资源
delete pplayerobj1;
delete pplayerobj2;
delete pplayerobj3;
delete pplayerobj4;
delete ptalknotify;
return 0;
}
其中在观察者设计模式当中,效率就很高了,使用一个map来保存族id而这个val是属于这个族的玩家。我们遍历的时候就只需要遍历这个族的id就可以了,这样效率得到了显著的提高。
总结:
观察者设计模式 定义(实现意图):定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会自动得到通知。
观察者模式的四种角色
1.Subject(主题):观察目标,这里指Notifier类。
2.ConcreteSubject(具体主题):这里指TalkNotifier类。
3.Observer(观察者):这里指Fighter类。
4.ConcreteObserver(具体观察者):这里指F_Warrior和F_Mage子类。
观察者模式的特点:
1.在观察者和观察目标之间建立了一个抽象的耦合
2.观察目标会向观察者列表中的所有观察者发送通知。
3.可以通过增加代码来增加新的观察者或者观察目标,符合开闭原则。
应用联想
1.救援家族成员镖车
2.将新闻推荐给符合其胃口的读者
3.通过改变自身绘制的图形来真实的反应公司的销售数据。
4.炮楼只会对30米内的玩家(列表内玩家)进行攻击。