聊聊C++陷阱與套路
本文轉(zhuǎn)載自微信公眾號(hào)「碼磚雜役」,作者人民副首席碼仔 。轉(zhuǎn)載本文請(qǐng)聯(lián)系碼磚雜役公眾號(hào)。
# 一、導(dǎo)語(yǔ)
C++是一門被廣泛使用的系統(tǒng)級(jí)編程語(yǔ)言,更是高性能后端標(biāo)準(zhǔn)開發(fā)語(yǔ)言;C++雖功能強(qiáng)大,靈活巧妙,但卻屬于易學(xué)難精的專家型語(yǔ)言,不僅新手難以駕馭,就是老司機(jī)也容易掉進(jìn)各種陷阱。本文結(jié)合號(hào)主的工作經(jīng)驗(yàn)和學(xué)習(xí)心得,對(duì)C++語(yǔ)言的一些高級(jí)特性,做了簡(jiǎn)單介紹;對(duì)一些常見的誤解,做了解釋澄清;對(duì)比較容易犯錯(cuò)的地方,做了歸納總結(jié);希望借此能增進(jìn)大家對(duì)C++語(yǔ)言了解,減少編程出錯(cuò),提升工作效率。
# 二、陷阱
## 1.我的程序里用了全局變量,為何進(jìn)程退出會(huì)莫名其妙的core掉?
Rule:C++在不同模塊(源文件)里定義的全局變量,不保證構(gòu)造順序;但保證在同一模塊(源文件)里定義的全局變量,按定義的先后順序構(gòu)造,按定義的相反次序析構(gòu)。
我們程序在a.cpp里定義了依次全局變量X和Y;
按照規(guī)則:X先構(gòu)造,Y后構(gòu)造;進(jìn)程停止執(zhí)行的時(shí)候,Y先析構(gòu),X后析構(gòu);但如果X的析構(gòu)依賴于Y,那么core的事情就有可能發(fā)生。
**結(jié)論**:如果全局變量有依賴關(guān)系,那么就把它們放在同一個(gè)源文件定義,且按正確的順序定義,確保依賴關(guān)系正確,而不是定義在不同源文件;對(duì)于系統(tǒng)中的單件,單件依賴也要注意這個(gè)問題。
## 2.坑中坑:std::sort()
相信工作5年以上至少50%的C/C++程序員都被它坑過,我已經(jīng)聽到過了無數(shù)個(gè)悲傷的故事,《圣斗士星矢》,《仙劍》,還有別人家的項(xiàng)目《天天愛消除》,都有人掉坑,程序運(yùn)行幾天莫名奇妙的Crash掉,一臉懵逼。
sort算法對(duì)比較函數(shù)有很強(qiáng)的約束,不能亂來,如果要用,要自己提供比較函數(shù)或者函數(shù)對(duì)象,一定搞清楚什么叫“嚴(yán)格弱排序”,一定要滿足以下3個(gè)特性:
1. 非自反性
2. 非對(duì)稱性
3. 傳遞性
盡量對(duì)索引或者指針sort,而不是針對(duì)對(duì)象本身,因?yàn)槿绻麑?duì)象比較大,交換(復(fù)制)對(duì)象比交換指針或索引更耗費(fèi)。
## 3.注意操作符短路
考慮游戲玩家回血回藍(lán)(魔法)刷新給客戶端的邏輯。玩家每3秒回一點(diǎn)血,玩家每5秒回一點(diǎn)藍(lán),回藍(lán)回血共用一個(gè)協(xié)議通知客戶端,也就是說只要有回血或者回藍(lán)就要把新的血量和魔法值通知客戶端。
玩家的心跳函數(shù)heartbeat()在主邏輯線程被循環(huán)調(diào)用
- ```c++
- void GamePlayer::Heartbeat()
- {
- if (GenHP() || GenMP())
- {
- NotifyClientHPMP();
- }
- }
- ```
如果GenHP回血了,就返回true,否則false;不一定每次調(diào)用GenHP都會(huì)回血,取決于是否達(dá)到3秒間隔。
如果GenMP回藍(lán)了,就返回true,否則false;不一定每次調(diào)用GenMP都會(huì)回血,取決于是否達(dá)到5秒間隔。
實(shí)際運(yùn)行發(fā)現(xiàn)回血回藍(lán)邏輯不對(duì),Word麻,原來是操作符短路了,如果GenHP()返回true了,那GenMP()就不會(huì)被調(diào)用,就有可能失去回藍(lán)的機(jī)會(huì)。你需要修改程序如下:
- ```c++
- void GamePlayer::Heartbeat()
- {
- bool hp = GenHP();
- bool mp = GenMP();
- if (hp || mp)
- {
- NotifyClientHPMP();
- }
- }
- ```
邏輯與(&&)跟邏輯或(||)有同樣的問題, if (a && b) 如果a的表達(dá)式求值為false,b表達(dá)式也不會(huì)被計(jì)算。
有時(shí)候,我們會(huì)寫出 if (ptr != nullptr && ptr->Do())這樣的代碼,這正是利用了操作符短路的語(yǔ)法特征。
## 4.別讓循環(huán)停不下來
- ```c++
- for (unsigned int i = 5; i >=0; --i)
- {
- //...
- }
- ```
程序跑到這,WTF?根本停不下來啊?問題很簡(jiǎn)單,unsigned永遠(yuǎn)>=0,是不是心中一萬只馬奔騰?
解決這個(gè)問題很簡(jiǎn)單,但是有時(shí)候這一類的錯(cuò)誤卻沒這么明顯,你需要罩子放亮點(diǎn)。
## 5.當(dāng)心越界
memcpy,memset有很強(qiáng)的限制,僅能用于POD結(jié)構(gòu),不能作用于stl容器或者帶有虛函數(shù)的類。
帶虛函數(shù)的類對(duì)象會(huì)有一個(gè)虛函數(shù)表的指針,memcpy將破壞該指針指向。
對(duì)非POD執(zhí)行memset/memcpy,免費(fèi)送你四個(gè)字:**自求多福**
## 6.內(nèi)存重疊
內(nèi)存拷貝的時(shí)候,如果src和dst有重疊,需要用memmov替代memcpy。
## 7.理解user stack空間很有限
不能在棧上定義過大的臨時(shí)對(duì)象。一般而言,用戶棧只有幾兆(典型大小是4M,8M),所以棧上創(chuàng)建的對(duì)象不能太大。
## 8.用sprintf格式化字符串的時(shí)候,類型和符號(hào)要嚴(yán)格匹配
因?yàn)閟printf的函數(shù)實(shí)現(xiàn)里是按格式化串從棧上取參數(shù),任何不一致,都有可能引起不可預(yù)知的錯(cuò)誤;/usr/include/inttypes.h里定義了跨平臺(tái)的格式化符號(hào),比如PRId64用于格式化int64_t
## 9.用c標(biāo)準(zhǔn)庫(kù)的安全版本(帶n標(biāo)識(shí))替換非安全版本
比如用strncpy替代strcpy,用snprintf替代sprintf,用strncat代替strcat,用strncmp代替strcmp,memcpy(dst, src, n)要確保[dst,dst+n]和[src, src+n]都有有效的虛擬內(nèi)存地址空間。多線程環(huán)境下,要用系統(tǒng)調(diào)用或者庫(kù)函數(shù)的安全版本代替非安全版本(_r版本),謹(jǐn)記strtok,gmtime等標(biāo)準(zhǔn)c函數(shù)都不是線程安全的。
## STL容器的遍歷刪除要小心迭代器失效
vector,list,map,set等各有不同的寫法:
- vector,list,map,set等各有不同的寫法:
- ```c++
- int main(int argc, char *argv[])
- {
- //vector遍歷刪除
- std::vector<int> v(8);
- std::generate(v.begin(), v.end(), std::rand);
- std::cout << "after vector generate...\n";
- std::copy(v.begin(), v.end(), std::ostream_iterator<int>(std::cout, "\n"));
- for (auto x = v.begin(); x != v.end(); )
- {
- if (*x % 2)
- x = v.erase(x);
- else
- ++x;
- }
- std::cout << "after vector erase...\n";
- std::copy(v.begin(), v.end(), std::ostream_iterator<int>(std::cout, "\n"));
- //map遍歷刪除
- std::map<int, int> m = {{1,2}, {8,4}, {5,6}, {6,7}};
- for (auto x = m.begin(); x != m.end(); )
- {
- if (x->first % 2)
- m.erase(x++);
- else
- ++x;
- }
- return 0;
- }
- ```
有時(shí)候遍歷刪除的邏輯不是這么明顯,可能循環(huán)里調(diào)了另一個(gè)函數(shù),而該函數(shù)在某種特定的情況下才會(huì)刪除當(dāng)前元素,這樣的話,就是很長(zhǎng)一段時(shí)間,程序都運(yùn)行得好好的,而當(dāng)你正跟別人談笑風(fēng)生的時(shí)候,忽然crash,這就尷尬了。
圣斗士星矢項(xiàng)目曾經(jīng)遭遇過這個(gè)問題,基本規(guī)律是一個(gè)禮拜game server crash一次,折磨團(tuán)隊(duì)將近一個(gè)月。
比較low的處理方式可以把待刪元素放到另一個(gè)容器WaitEraseContainer里保存下來,再走一趟單獨(dú)的循環(huán),刪除待刪元素。
當(dāng)然,我們推薦在遍歷的同時(shí)刪除,因?yàn)檫@樣效率更高,也顯得行家里手。
# 三、性能
## 空間置換時(shí)間
通過空間換取時(shí)間是提高性能的慣用法,bitmap,int map[]這些慣用法要了然于胸。
## 減少拷貝 & COW
了解Copy On Write。
只要可能就應(yīng)該減少拷貝,比如通過共享,比如通過引用指針的形式傳遞參數(shù)和返回值。
## 延遲計(jì)算和預(yù)計(jì)算
比如游戲服務(wù)器端玩家的戰(zhàn)力,由屬性a,b決定,也就是說屬性a,b任何一個(gè)變化,都需要重算戰(zhàn)力;但如果ModifyPropertyA(),ModifyPropertyB()之后,都重算戰(zhàn)力卻并非真正必要,因?yàn)樾薷膶傩訟之后有可能馬上修改B,兩次重算戰(zhàn)力,顯然第一次重算的結(jié)果會(huì)很快被第二次的重算覆蓋。
而且很多情況下,我們可能需要在心跳里,把最新的戰(zhàn)力值推送給客戶端,這樣的話,ModifyPropertyA(),ModifyPropertyB()里,我們其實(shí)只需要把戰(zhàn)力置臟,延遲計(jì)算,這樣就能避免不必要的計(jì)算。
在GetFightValue()里判斷FightValueDirtyFlag,如果臟,則重算,清臟標(biāo)記;如果不臟,直接返回之前計(jì)算的結(jié)果。
預(yù)計(jì)算的思想類似。
## 分散計(jì)算
分散計(jì)算是把任務(wù)分散,打碎,避免一次大計(jì)算量,卡住程序。
## 哈希
減少字符串比較,構(gòu)建hash,可能會(huì)多費(fèi)一點(diǎn)存儲(chǔ)空間,但收益可觀,值得一試。
## 日志節(jié)制
日志的開銷不容忽視,要分級(jí),不要過于奔放,可以把日志作為debug手段,但要release干凈。
## 編譯器為什么不給局部變量和成員變量做默認(rèn)初始化
因?yàn)樾?,C++被設(shè)計(jì)為系統(tǒng)級(jí)的編程語(yǔ)言,效率是優(yōu)先考慮的方向,c++秉持的一個(gè)設(shè)計(jì)哲學(xué)是“不為不必要的操作付出任何額外的代價(jià)”。所以它有別于java,不給成員變量和局部變量做默認(rèn)初始化,如果需要賦初值,那就由程序員自己去保證。
**結(jié)論**:從安全的角度出發(fā),不應(yīng)使用未初始化的變量,定義變量的時(shí)候賦初值是一個(gè)好的習(xí)慣,很多錯(cuò)誤皆因未正確初始化而起,C++11支持成員變量定義的時(shí)候直接初始化,成員變量盡量在成員初始化列表里初始化,且要按定義的順序初始化。
## 理解函數(shù)調(diào)用的性能開銷,性能敏感函數(shù)考慮inline
棧幀建立和銷毀,參數(shù)傳遞,控制轉(zhuǎn)移這些對(duì)性能有損。
X86_64體系結(jié)構(gòu)因?yàn)橥ㄓ眉拇嫫鲾?shù)目增加到16個(gè),所以64位系統(tǒng)下參數(shù)數(shù)目不多的函數(shù)調(diào)用,將會(huì)由寄存器傳遞代替壓棧方式傳遞參數(shù),但棧幀建立、撤銷和控制轉(zhuǎn)移依然會(huì)對(duì)性能有所影響。
## 遞歸的優(yōu)點(diǎn)、缺點(diǎn)
優(yōu)點(diǎn):方便編寫,容易理解。
缺點(diǎn):運(yùn)行速度變慢,所以需要預(yù)估好遞歸深度。
建議:優(yōu)先考慮非遞歸實(shí)現(xiàn)版本。遞歸函數(shù)要有退出條件且不能遞歸過深,不然有爆棧危險(xiǎn)。
# 四、數(shù)據(jù)結(jié)構(gòu)和容器
## 了解std::vector的方方面面和底層實(shí)現(xiàn)
1. vector是動(dòng)態(tài)擴(kuò)容的,2的次方往上翻,為了確保數(shù)據(jù)保存在連續(xù)空間,每次擴(kuò)充,會(huì)將原member悉數(shù)拷貝到新的內(nèi)存塊;不要保存vector內(nèi)對(duì)象的指針,擴(kuò)容會(huì)導(dǎo)致其失效 ;可以通過保存其下標(biāo)index替代。
2. 運(yùn)行過程中需要?jiǎng)討B(tài)增刪的vector,不宜存放大的對(duì)象本身 ,因?yàn)閿U(kuò)容會(huì)導(dǎo)致所有成員拷貝構(gòu)造,消耗較大,可以通過保存對(duì)象指針替代。
3. resize()是重置大小;reserve()是預(yù)留空間,并未改變size(),可避免多次擴(kuò)容;clear()并不會(huì)導(dǎo)致空間收縮 ,如果需要釋放空間,可以跟空的vector交換,std::vector
4. 理解at()和operator[]的區(qū)別 :at()會(huì)做下標(biāo)越界檢查,operator[]提供數(shù)組索引級(jí)的訪問,在release版本下不會(huì)檢查下標(biāo),VC會(huì)在Debug版本會(huì)檢查;c++標(biāo)準(zhǔn)規(guī)定:operator[]不提供下標(biāo)安全性檢查。
5. C++標(biāo)準(zhǔn)規(guī)定了std::vector的底層用數(shù)組實(shí)現(xiàn),認(rèn)清這一點(diǎn)并利用這一點(diǎn)。
## 常用數(shù)據(jù)結(jié)構(gòu)
**數(shù)組**:內(nèi)存連續(xù),隨機(jī)訪問,性能高,局部性好,不支持動(dòng)態(tài)伸縮,最常用,通常也是最正確的選擇。
**鏈表**:動(dòng)態(tài)伸縮,插入/脫離極快(特別是帶前后驅(qū)指針),節(jié)點(diǎn)內(nèi)存通常不連續(xù)(當(dāng)然可以通過從固定內(nèi)存池分配規(guī)避),不支持隨機(jī)訪問。只在需要快速增刪、動(dòng)態(tài)伸縮的有限場(chǎng)景下才被使用,比如游戲里面地圖劃分格子,每個(gè)格子維護(hù)一個(gè)玩家鏈表(格子進(jìn)入玩家視野的時(shí)候需要遍歷該列表),玩家會(huì)在格子之間頻繁移動(dòng)。
**查找**:3種:bst,hashtable,基于有序數(shù)組的bsearch。二叉搜索樹(RBTree),這個(gè)從begin到end有序,最壞查找速度logN,壞處內(nèi)存不連續(xù),節(jié)點(diǎn)有額外空間浪費(fèi);hashtable,好的hash函數(shù)不好選,搜索最壞退化成鏈表,難以估計(jì)捅數(shù)量,開大了浪費(fèi)內(nèi)存,開小了增加沖突幾率,擴(kuò)容會(huì)卡一下,無序;基于有序數(shù)組的bsearch,局部性好,insert/delete慢。
# 五、最佳實(shí)踐
## 對(duì)于在啟動(dòng)時(shí)加載好,運(yùn)行中不變化的查詢結(jié)構(gòu),可以考慮用sorted array替代map,hash表等
因?yàn)橛行驍?shù)組支持二分查找,效率跟map差不多。對(duì)于只需要在程序啟動(dòng)的時(shí)候構(gòu)建(排序)一次的查詢結(jié)構(gòu),有序數(shù)組相比map和hash可能有更好的內(nèi)存命中性(局部命中性)。
運(yùn)行過程中,穩(wěn)定的查詢結(jié)構(gòu)(比如配置表,需要根據(jù)id查找配置表項(xiàng),運(yùn)行過程中不增刪),有序數(shù)組是個(gè)不錯(cuò)的選擇;如果不穩(wěn)定,則有序數(shù)組的插入刪除效率比map,hashtable差,所以選用有序數(shù)組需要注意適用場(chǎng)合。
## std::map or std::unorder_map?
想清楚他們的利弊,map是用紅黑樹做的,unorder_map底層是hash表做的,hash表相對(duì)于紅黑樹有更高的查找性能。hash表的效率取決于hash算法和沖突解決方法(一般是拉鏈法,hash桶),以及數(shù)據(jù)分布,如果負(fù)載因子高,就會(huì)降低命中率,為了提高命中率,就需要擴(kuò)容,重新hash,而重新hash是很慢的,相當(dāng)于卡一下。
而紅黑樹有更好的平均復(fù)雜度,所以如果數(shù)據(jù)量不是特別大,map是勝任的。
## 積極的使用const
理解const不僅僅是一種語(yǔ)法層面的保護(hù)機(jī)制,也會(huì)影響程序的編譯和運(yùn)行。
const常量會(huì)被編碼到機(jī)器指令。
## 理解四種轉(zhuǎn)型的含義和區(qū)別
避免用錯(cuò),盡量少用向下轉(zhuǎn)型(可以通過設(shè)計(jì)加以改進(jìn))
static_cast, dynamic_cast,const_cast,reinterpret_cast,傻傻分不清?
C++磚家說:一句話,盡量少用轉(zhuǎn)型,強(qiáng)制類型轉(zhuǎn)換是C Style,如果你的C++代碼需要類型強(qiáng)轉(zhuǎn),你需要去考慮是否設(shè)計(jì)有問題。
## 理解字節(jié)對(duì)齊
字節(jié)對(duì)齊能讓存儲(chǔ)器訪問速度更快。
字節(jié)對(duì)齊跟cpu架構(gòu)相關(guān),有些cpu訪問特定類型的數(shù)據(jù)必須在一定地址對(duì)齊的儲(chǔ)存器位置,否則會(huì)觸發(fā)異常。
字節(jié)對(duì)齊的另一個(gè)影響是調(diào)整結(jié)構(gòu)體成員變量的定義順序,有可能減少結(jié)構(gòu)體大小,這在某些情況下,能節(jié)省內(nèi)存。
## 牢記3 rules和5 rules,當(dāng)然C++11又多了&&的copy ctor和op=版本
只在需要接管的時(shí)候才自定義operator=和copy constructor,如果編譯器提供的默認(rèn)版本工作的很好,不要去自找麻煩,自定義的版本勿忘拷貝每一個(gè)成分,如果要接管就要處理好。
## 組合優(yōu)先于繼承,繼承是一種最強(qiáng)的類間關(guān)系
典型的適配器模式有類適配器和對(duì)象適配器,一般而言,建議用對(duì)象適配的方式,而非用基于繼承的類適配方式,比如STL中的queue/stack的實(shí)現(xiàn)就是基于對(duì)象適配。
## 減少依賴,注意隔離
+ 最大限度的減少文件間的依賴關(guān)系,用前向聲明拆解相互依賴。
+ 了解pimpl技術(shù)。
+ 頭文件要自給自足,不要圖省事all.h,不要包含不必要的頭文件,也不要把該包含的頭文件推給user去包含,一句話,頭文件包含要不多不少剛剛好。
## 嚴(yán)格配對(duì)(RAII)
打開的句柄要關(guān)閉,加鎖/解鎖,new/delete,new[]/delete[],malloc/free要配對(duì),可以使用RAII技術(shù)防止資源泄露,編寫符合規(guī)范的代碼
Valgrind對(duì)程序的內(nèi)存使用方式有期望,需要干凈的釋放,所以規(guī)范編程才能寫出valgrind干凈的代碼,不然再好的工具碰到不按規(guī)劃寫的代碼也是武功盡廢啊。
## 理解多繼承潛在的問題,慎用多繼承
多繼承會(huì)存在菱形繼承的問題,多個(gè)基類有相同成員變量會(huì)有問題,需要謹(jǐn)慎對(duì)待。
## 有多態(tài)用法抽象基類的析構(gòu)函數(shù)要加virtual關(guān)鍵字
主要是為了基類的析構(gòu)函數(shù)能得到正確的調(diào)用。
virtual dtor跟普通虛函數(shù)一樣,基類指針指向子類對(duì)象的時(shí)候,delete ptr,根據(jù)虛函數(shù)特征,如果析構(gòu)函數(shù)是普通函數(shù),那么就調(diào)用ptr顯式(基類)類型的析構(gòu)函數(shù);如果析構(gòu)函數(shù)是virtual,則會(huì)調(diào)用子類的析構(gòu)函數(shù),然后再調(diào)用基類析構(gòu)函數(shù)。
## 避免在構(gòu)造函數(shù)和析構(gòu)函數(shù)里調(diào)用虛函數(shù)
構(gòu)造函數(shù)里,對(duì)象并沒有完全構(gòu)建好,此時(shí)調(diào)用虛函數(shù)不一定能正確綁定,析構(gòu)亦如此。
從輸入流獲取數(shù)據(jù),要做好數(shù)據(jù)不夠的處理,要加try catch;沒有被吞咽的exception,會(huì)被傳播
從網(wǎng)絡(luò)數(shù)據(jù)流讀取數(shù)據(jù),從數(shù)據(jù)庫(kù)恢復(fù)數(shù)據(jù)都需要注意這個(gè)問題。
## 協(xié)議盡量不要傳float,如果傳float要了解NaN的概念,要做好檢查,避免惡意傳播
可以考慮用整數(shù)替代浮點(diǎn),比如萬分之五(5%%),就保存5。
## 定義宏要遵循常規(guī)
要對(duì)每個(gè)變量加括弧,有時(shí)候需要加do {} while(0)或者{},以便能將一條宏當(dāng)成一個(gè)語(yǔ)句。要理解宏在預(yù)處理階段被替換,不用的時(shí)候要#undef,要防止污染別人的代碼,要避免宏定義中依賴特定外部變量名。
## 智能指針
理解基于引用計(jì)數(shù)法的智能指針實(shí)現(xiàn)方式,了解所有權(quán)轉(zhuǎn)移的概念,理解shared_ptr和unique_ptr的區(qū)別和適用場(chǎng)景。
不要誤用指針,指針帶來彈性,但如果沒有這個(gè)需要,就不要用(比如命名全局變量能搞定,非要整一個(gè)全局變量的指針,然后在init里new,在terminate里delete,自找麻煩)。
## 考慮用std::shared_ptr管理動(dòng)態(tài)分配的對(duì)象。
指針能帶來彈性,但不要誤用,它的彈性指一方面它能在運(yùn)行時(shí)改變指向,可以用來做多態(tài),另一方面對(duì)于不能固定大小的數(shù)組可以動(dòng)態(tài)伸縮,但很多時(shí)候,我們對(duì)固定大小的array,也在init里new/malloc出來,其實(shí)沒必要,而且會(huì)多占用sizeof(void*)字節(jié),而且增加一層間接訪問。
## size_t到底是個(gè)什么?我該用有符號(hào)還是無符號(hào)整數(shù)?
size_t類型是被設(shè)計(jì)來保存系統(tǒng)存儲(chǔ)器上能保存的對(duì)象的最大個(gè)數(shù)。
32位系統(tǒng),一個(gè)對(duì)象最小的單位是一個(gè)字節(jié),那2的32次方內(nèi)存,最多能保存的對(duì)象數(shù)目就是4G/1字節(jié),正好一個(gè)unsigned int能保存下來(typedef unsigned int size_t)。
同樣,64位系統(tǒng),unsigned long是8字節(jié),所以size_t就是unsigned long的類型別名。
對(duì)于像索引,位置這樣的變量,是用有符號(hào)還是無符號(hào)呢?像money這樣的屬性呢?
一句話:要講道理,用最自然,最順理成章的類型。比如索引不可能為負(fù)用size_t,賬戶可能欠錢,則money用int。比如:
- ```
- template <class T> class vector
- {
- T& operator(size_t index) {}
- };
- ```
標(biāo)準(zhǔn)庫(kù)給出了最好的示范,因?yàn)槿绻怯蟹?hào)的話,你需要這樣判斷
- if (index < 0 || index >= max_num) throw out_of_bound();
而如果是無符號(hào)整數(shù),你只需要判斷 if (index >= max_num),你認(rèn)可嗎?
## 整型一般用int,long就很好,用short,char需要很仔細(xì),要防止溢出
大多數(shù)情況下,用int,long就很好,long一般等于機(jī)器字長(zhǎng),long能直接放到寄存器,硬件處理起來速度也更快。
很多時(shí)候,我們希望用short,char(8位整型)達(dá)到減少結(jié)構(gòu)體大小的目的。但是由于字節(jié)對(duì)齊,可能并不能真正減少,而且1,2個(gè)字節(jié)的整型位數(shù)太少,一不小心就溢出了,需要特別注意。
所以,除非在db、網(wǎng)絡(luò)這些對(duì)存儲(chǔ)大小非常敏感的場(chǎng)合,我們才需要考慮是否以short,char替代int,long。
# 六、擴(kuò)展
## 了解c++高階特性
模板和泛型編程,union,bitfield,指向成員的指針,placement new,顯式析構(gòu),異常機(jī)制,nested class,local class,namespace,多繼承、虛繼承,volatile,extern "C"等
有些高級(jí)特性只有在特定情況下才會(huì)被用到,但技多不壓身,平時(shí)還是需要積累和了解,這樣在需求出現(xiàn)時(shí),才能從自己的知識(shí)庫(kù)里拿出工具來對(duì)付它。
## 了解C++新標(biāo)準(zhǔn)
關(guān)注新技術(shù),c++11/14/17、lambda,右值引用,move語(yǔ)義,多線程庫(kù)等
c++98/03標(biāo)準(zhǔn)到c++11標(biāo)準(zhǔn)的推出歷經(jīng)13年,13年來程序設(shè)計(jì)語(yǔ)言的思想得到了很大的發(fā)展,c++11新標(biāo)準(zhǔn)吸收了很多其他語(yǔ)言的新特性,雖然c++11新標(biāo)準(zhǔn)主要是靠引入新的庫(kù)來支持新特征,核心語(yǔ)言的變化較少,但新標(biāo)準(zhǔn)還是引入了move語(yǔ)義等核心語(yǔ)法層面的修改,每個(gè)CPPer都應(yīng)該了解新標(biāo)準(zhǔn)。
## OOD設(shè)計(jì)原則并不是胡扯
- + 設(shè)計(jì)模式六大原則(1):?jiǎn)我宦氊?zé)原則
- + 設(shè)計(jì)模式六大原則(2):里氏替換原則
- + 設(shè)計(jì)模式六大原則(3):依賴倒置原則
- + 設(shè)計(jì)模式六大原則(4):接口隔離原則
- + 設(shè)計(jì)模式六大原則(5):迪米特法則
- + 設(shè)計(jì)模式六大原則(6):開閉原則
## 熟悉常用設(shè)計(jì)模式,活學(xué)活用,不生搬硬套
神化設(shè)計(jì)模式和反設(shè)計(jì)模式,都不是科學(xué)的態(tài)度,設(shè)計(jì)模式是軟件設(shè)計(jì)的經(jīng)驗(yàn)總結(jié),有一定的價(jià)值;GOF書上對(duì)每一個(gè)設(shè)計(jì)模式,都用專門的段落講它的應(yīng)用場(chǎng)景和適用性,限制和缺陷,在正確評(píng)估得失的情況下,是鼓勵(lì)使用的,但顯然,你首先需要準(zhǔn)確get到她。