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是因为有可能扩展。
有明确上限的,比如月签到系统。只需要一个31长度的数组,因为一个月最多31天。
没有明确上限的,比如成就系统。成就系统里的杀怪成就,一开始需求可能是10,100,1000,也就是杀怪10个,100个,1000个可以获得奖励。但是上线之后觉得数值不合适,需要多一个2000的条目。这种就非常适合用vector。
更多的情况其实是用数组和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是保证需求的数量限制和异常出现,较为稳妥。
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_front(); //m_list.push_back(); //插入的数据结构略过 }