C++标准库的容器分为序列容器和关联容器。编程
序列容器简单的有vector,list,deque,复杂的还有配接器stack,queue,priority_queue。数组
关联容器简单的有set,map,复杂的有multiset,multimap,这都是基于RB-tree的,基于hashtable的也有hash-set,hash-map,hash-multiset,hash-multimap。数据结构
工做中不少容器并不经常使用,经常使用的无非这几个vector,list,set,map,queue。下面就简单总结一下这几个简单容器的适用需求(游戏功能逻辑方面的需求)。函数
1.vector优化
使用频率超高。一般咱们使用数组是由于有明确的上限。而使用vector是由于有可能扩展。spa
有明确上限的,好比月签到系统。只须要一个31长度的数组,由于一个月最多31天。code
没有明确上限的,好比成就系统。成就系统里的杀怪成就,一开始需求多是10,100,1000,也就是杀怪10个,100个,1000个能够得到奖励。可是上线以后以为数值不合适,须要多一个2000的条目。这种就很是适合用vector。blog
更多的状况实际上是用数组和vector都行的。这种状况就须要编程者本身斟酌了。索引
看一个例子,等级礼包。如图:队列
咱们先枚举出奖励的的各类状态。
enum RoleLevelRewardState { RLR_INVALID = 0, //不可领取 RLR_CAN_GET, //可领取 RLR_HAS_GET, //已领取 };
咱们能够规定一个上限。这个上限是奖励的条目上限。若是策划需求改动了条目数量,好比加了一个100级的等级奖励,那咱们就须要改动MAX_ROLE_REWARD这个值。
固然通常状况下,最好是扩展,而不改动已经存在的。由于要兼顾老玩家,好比老玩家已经100级了,前面领取过50,70,90级的奖励。可是这时策划须要增长一个60级的奖励…这就比较乱。最好和策划商量兼容老玩家。
若是是增长一个120级的奖励,这天然就比较简单。
static const int MAX_ROLE_REWARD = 3;
定义咱们的数组:
int m_role_level_reward[MAX_ROLE_REWARD];
若是用vector,就是这样写:
std::vector<int> m_role_level_reward;
数组在构造函数里的初始化,或者重置清空一般是这样写:
memset(m_role_level_reward, 0, sizeof(m_role_level_reward));
vector初始化和清空就简单些:
m_role_level_reward.clear();
边界问题是咱们须要重视的,在引用数组或vector元素时,都用首先判断边界。
bool UpdateRoleRewardState(int index) { if (index < 0 || index >= MAX_ROLE_REWARD) return false; m_role_reward[index] = 1; //改变内容 return true; }
更多的不一样在于循环时:
for (int i = 0; i < MAX_ROLE_REWARD; ++i) // 数组
for (int i = 0; i < (int)m_role_level_reward.size() && i < MAX_ROLE_REWARD; ++i) // vector
(int)强转是由于.size()返回的是size_t类型,其实判断i < size已经足够,还要加上MAX_ROLE_REWARD是保证需求的数量限制和异常出现,较为稳妥。
固然vector用迭代器遍历更为标准。但下标遍历也是能够的。
2.list
有不少状况,需求可使用list,也可使用vector。其实只须要记得,若是需求须要随意在某个位置插入,随意删除某个位置的记录,改变总体顺序的就用list,其余均可以用vector。
list是个循环的双向链表,vector是连续空间。因此list能够随意在任何位置插入,随意删除某个位置的记录,而vector不行。vector的insert和erase代价都很大,会使得迭代器失效。
一个典型的例子,记录玩家杀死大boss的记录表。
首先它的数量确定不肯定的,因此数组不适合。并且要求只记录最近的50条,新的记录显示在前面。这就很是适合用list了。
由于咱们确定会有一个逻辑是记录数大于50的时候,删掉最后1条记录。再把新的记录加到前面。
static const MAX_KILL_RECORD_NUM = 50; if ((int)m_list.size() > MAX_KILL_RECORD_NUM) { m_list.pop_back(); //m_list.push_front(); //插入的数据结构略过 }
list能够任意的insert,erase,pop,push都是代价很小的。
咱们要遍历list,就不能像vector同样了。由于vector有[]操做,list没有。因此list须要用迭代器遍历。
3.map
map也是很经常使用的容器。与vector,list差异很大,它是一个关联容器。所谓关联容器就是有key和value对应。底层是红黑树(RB-Tree)实现的。
一般的使用场景是记录的数据有一个惟一的key做为索引的标志。好比结婚系统,夫妻的相关数据。
夫妻有关的数据结构好比是:
struct MarriageInfo { char man_name[64]; char woman_name[64]; int man_role_id; int woman_role_id; int love_value; //恩爱值 };
存在一个map里,key是夫妻俩的role_id组合。好比A的role_id是1000,B的role_id是1011,那么两人共同的夫妻数据key就是1000_1011,一个字符串类型。
std::map<string, MarriageInfo> m_marriage_data;
这个数据里有个惟一的key与其value对应。这种类型的数据一般用map合适。但其实你仔细想一想,用vector也能实现须要的功能。那样的话数据结构里加一项类型key的项就行。取决于编程者的风格。
struct MarriageInfo { string marriage_key; char man_name[64]; char woman_name[64]; int man_role_id; int woman_role_id; int love_value; //恩爱值 };
std::vector<MarriageInfo> m_marriage_data;
map在引用元素以前能够用key作find操做,来判断。
bool UpdateMarriageData(string key) { std::map<string, MarriageInfo>::iterator iter = m_marriage_data.find(key); if (iter == m_marriage_data.end()) return false; iter->second.love_value = 100; return true; }
插入一个map元素,一般能够make_pair一下,而后insert。也有一种简单的写法,就是直接[]引用。
bool InsertMarriageData(string key, MarriageInfo marriage_info) { std::map<string, MarriageInfo>::iterator iter = m_marriage_data.find(key); if (iter == m_marriage_data.end()) return false; m_marriage_data[key] = marriage_info; return true; }
4.set
set是实际上是一种简单的map,它的value隐藏了,只显示key出来。set适用比较简单的需求。
好比记录玩家得到的物品奖励,已经得到过的物品不能再得到,物品id做为key。
std::set<ItemId> m_reward_record;
那么set和map的用法相似,是简化的map。取决于需求的复杂度。
5.queue
队列queue。queue实际上是一种适配器。底层是deque实现,屏蔽了deque的部分功能,封装成一个队列。队列是一个后进先出的结构。
有一类需求适合,就是匹配需求。就拿吃鸡来讲。首先进入的是一个等待副本。先进来的玩家进入排队。满100我的,队列就出去100我的,进入另一个副本。
匹配实际是一个复杂的功能需求,取决于匹配的条件是什么。吃鸡的需求多是满100个,送走100个。这100我的有什么等级限制啊,经验限制啊,小队限制啊,都是复杂的。
另外匹配仍是一个轮询过程。轮询的优化策略也有讲究,再也不展开。
可是毫无疑问queue队列很符合这类的需求。