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隊列很符合這類的需求。