游戏功能编程需求中STL容器的运用

发表于:2018-3-23 11:15

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:yao_2_yao    来源:博客园

  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是保证需求的数量限制和异常出现,较为稳妥。
  当然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。也有一种简单的写法,就是直接[]引用。
boolInsertMarriageData(stringkey,MarriageInfomarriage_info)
{
std::map<string,MarriageInfo>::iteratoriter=m_marriage_data.find(key);
if(iter==m_marriage_data.end())returnfalse;
m_marriage_data[key]=marriage_info;
returntrue;
}
  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队列很符合这类的需求。

上文内容不用于商业目的,如涉及知识产权问题,请权利人联系博为峰小编(021-64471599-8017),我们将立即处理。
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号