
const這個(gè)關(guān)鍵字初學(xué)C的人都覺得太簡(jiǎn)單了——不就表示“不能改”嘛??烧娴巾?xiàng)目里寫代碼const的坑一點(diǎn)不比模板少。我見過有人把const int*和int* const搞混導(dǎo)致編譯不過見過const_cast用順手之后把一個(gè)只讀字符串寫崩掉也見過面試時(shí)被問到頂層const和底層const直接卡殼的。這些我都經(jīng)歷過。這篇東西想把C里const的用法從頭到尾捋一遍從最簡(jiǎn)單的const變量到const指針、const引用、const成員函數(shù)、const_cast再到constexpr和面試真題全部過一遍。不管你是剛學(xué)C想弄明白還是工作幾年想系統(tǒng)補(bǔ)一下看完應(yīng)該都能收獲點(diǎn)東西。1. 先搞明白const到底是鎖住什么1.1 編譯期的“契約”不是運(yùn)行時(shí)的“鎖”很多人把const理解成“運(yùn)行時(shí)不允許修改”這個(gè)理解其實(shí)不太準(zhǔn)確。const真正起作用的地方是編譯期——編譯器在靜態(tài)檢查時(shí)發(fā)現(xiàn)你對(duì)一個(gè)const對(duì)象做了賦值或修改操作直接報(bào)錯(cuò)根本不會(huì)讓你生成可執(zhí)行文件。它本質(zhì)上是程序員和編譯器之間的一份契約我承諾這個(gè)對(duì)象不會(huì)被修改你幫我在編譯時(shí)盯著點(diǎn)。這也帶來兩個(gè)很實(shí)際的結(jié)果。第一const不會(huì)讓程序變慢因?yàn)樗粫?huì)生成任何額外的運(yùn)行時(shí)檢查代碼純粹是編譯期的約束。第二const對(duì)象在內(nèi)存里的位置不一定受保護(hù)它可能就是一個(gè)普通的棧變量。真正危險(xiǎn)的是那些被放進(jìn)只讀區(qū)域的const數(shù)據(jù)比如字符串字面量、某些const全局對(duì)象編譯器很可能會(huì)把它們放到只讀數(shù)據(jù)段。這時(shí)候如果你繞過const去寫那不是編譯錯(cuò)誤的問題而是未定義行為常見的表現(xiàn)就是段錯(cuò)誤或者在微軟VC的調(diào)試環(huán)境里彈出一句熟悉的提示write access to const memory has been detected, the output may be wrong!。我見過不少剛?cè)胄械耐乱豢吹竭@類運(yùn)行時(shí)錯(cuò)誤就懵了我明明有const_cast啊我明明沒有越界啊其實(shí)根本不是越界是“寫了不該寫的只讀內(nèi)存”。記住這句話const在運(yùn)行時(shí)提供的唯一“保護(hù)”就是讓那些真正放在只讀區(qū)域的臟數(shù)據(jù)在你臉上炸開給你一個(gè)慘痛的教訓(xùn)。1.2 頂層const與底層const所有指針困惑的根源面試?yán)飭朿onst十個(gè)里有八個(gè)會(huì)繞到頂層const和底層const上。這個(gè)概念不搞明白后面看別人代碼只能靠猜。先說定義頂層consttop-level const表示指針本身是const也就是指針變量不能指向別的對(duì)象。寫法是int* const p。這里的const修飾的是“p”這個(gè)指針變量。底層constlow-level const表示指針指向的對(duì)象是const也就是不能通過這個(gè)指針修改目標(biāo)對(duì)象。寫法是const int* p或int const* p。這里的const修飾的是“int”。判斷技巧很簡(jiǎn)單從變量名開始往右讀const在變量名右邊多半是頂層constconst在類型名那一邊多半是底層const。我自己的習(xí)慣是記住一個(gè)口訣const鎖的是離它最近的那個(gè)名詞。int* const p里const左邊是int*右邊是p它鎖的是p本身const int* p里const左邊是空白、右邊是int它鎖的是int。為什么要單獨(dú)拎出來講因?yàn)榭截惖臅r(shí)候頂層const和底層const的約束完全不同。頂層const拷貝不受影響我復(fù)制一個(gè)指針變量本身源對(duì)象是不是const無所謂但底層const的拷貝有嚴(yán)格限制——不能把一個(gè)const int*賦給一個(gè)int*否則你就能通過后者輕易修改一個(gè)本來聲明為const的對(duì)象等于單方面撕毀契約。反過來int*賦給const int*是允許的這是權(quán)限的收窄編譯器放心。2. 逐層拆解const在不同場(chǎng)景下的實(shí)戰(zhàn)用法2.1 const修飾變量常量怎么用才不“白用”最基礎(chǔ)的用法是const int maxLen 1024;。在C里如果初始化是編譯期就能確定的常量表達(dá)式這個(gè)const變量本身也是一個(gè)編譯期常量可以用來定義數(shù)組大小、作為模板參數(shù)、作為case標(biāo)簽等很多場(chǎng)景。但注意這不代表所有const變量都是編譯期常量比如const int n getUserInput();這就是個(gè)運(yùn)行時(shí)才確定的只讀變量不能當(dāng)數(shù)組長(zhǎng)度用。想?yún)^(qū)分這兩類得靠constexpr這個(gè)后面細(xì)講。一個(gè)容易忽略的點(diǎn)是const變量的鏈接性。C里普通非const變量默認(rèn)是外部鏈接而const變量默認(rèn)是內(nèi)部鏈接也就是每個(gè)翻譯單元都有一份自己的拷貝。如果你在頭文件里定義了一個(gè)const變量每個(gè)包含這個(gè)頭文件的.cpp都會(huì)各自擁有一份這在多數(shù)場(chǎng)景下沒問題甚至可以利用這一點(diǎn)省掉一些不必要的跨翻譯單元訪問。但如果你想在多個(gè)源文件間共享同一個(gè)const對(duì)象必須在定義時(shí)加extern并且在一個(gè).cpp里定義、在頭文件里用extern const聲明。這也是面試?yán)锉容^喜歡掛的一個(gè)小考點(diǎn)。還有一個(gè)從C語言轉(zhuǎn)過來的誤區(qū)別用const變量去“偽裝”宏定義。const是正兒八經(jīng)的變量有類型、有作用域、可以取地址。宏只是文本替換。能用const的地方優(yōu)先用const這不是風(fēng)格問題而是類型安全的問題。2.2 const與指針從右往左讀永遠(yuǎn)不慌先做一組速記對(duì)比這幾行代碼是面試題重災(zāi)區(qū)寫法指針是否可變指針指向的值是否可變const int* p可改可以指向其他int不可改int const* p可改不可改和上一行等價(jià)int* const p不可改可改const int* const p不可改不可改我在實(shí)際項(xiàng)目里最常遇到的是const char*和char* const的混用。比如某個(gè)函數(shù)簽名是void parse(const char* str);它表達(dá)的意思是傳入的字符串內(nèi)容我不會(huì)修改。但如果你寫成void parse(char* const str);語義就變成了“指針本身不能變但我可能在函數(shù)里改這個(gè)字符串的內(nèi)容”這兩個(gè)意思天差地別。特別注意字符串字面量。在C里字符串字面量比如hello的類型是const char[6]所以用const char* p hello;才是合法的。早期C為了兼容C允許char* p hello;這種寫法但一旦你通過p去修改字符串內(nèi)容就是寫入只讀內(nèi)存的未定義行為。我曾經(jīng)在一個(gè)舊項(xiàng)目的兼容層里見過一堆char*直接指向字符串字面量的代碼平時(shí)不寫不出事某個(gè)路徑下代碼偷懶復(fù)用了緩沖區(qū)去覆蓋“字符串”內(nèi)容結(jié)果整個(gè)內(nèi)存被寫壞排查了很久才發(fā)現(xiàn)是這種隱式轉(zhuǎn)換埋的雷?,F(xiàn)在C11之后這種寫法已經(jīng)被明確廢棄/禁止新代碼里再看到就可以直接改了。2.3 const引用傳參界的“性價(jià)比之王”引用本身在某種程度上有點(diǎn)像自動(dòng)解引用的指針而const引用則是傳參時(shí)的首選。有個(gè)非常經(jīng)典的規(guī)則如果函數(shù)不需要修改傳入的參數(shù)優(yōu)先寫成const T。為什么不是按值傳T因?yàn)榇髮?duì)象拷貝成本高。為什么不是非const引用T因?yàn)槿绻瘮?shù)內(nèi)部不修改參數(shù)把傳入對(duì)象的修改權(quán)限開放出去反而限制了調(diào)用方——人家一個(gè)const對(duì)象根本傳不進(jìn)來。用const T就能同時(shí)兼容const和非const實(shí)參還能避免拷貝這是C里少有的“既要又要”的解決方案。const T還有一個(gè)隱藏能力綁定臨時(shí)對(duì)象。比如const std::string s abc;是合法的因?yàn)榫幾g器會(huì)構(gòu)造一個(gè)臨時(shí)string并延長(zhǎng)它的生命周期到引用離開作用域?yàn)橹?。反過來std::string s abc;就是編譯錯(cuò)誤因?yàn)榉莄onst左值引用不能綁定右值。這個(gè)細(xì)節(jié)在寫函數(shù)重載和轉(zhuǎn)發(fā)時(shí)特別容易踩坑比如你把參數(shù)從std::string s改成std::string s想省一次拷貝結(jié)果發(fā)現(xiàn)所有傳字面量的調(diào)用全部編譯失敗原因就在這。2.4 const修飾函數(shù)返回值別給自己找麻煩返回值加const的情況比參數(shù)少得多但有必要說清楚。返回一個(gè)const值比如const std::string get();在C11以后基本是負(fù)優(yōu)化——它阻止了移動(dòng)語義還會(huì)讓一些調(diào)用方的代碼編譯不過。我有段時(shí)間喜歡給返回值加const覺得安全后來發(fā)現(xiàn)唯一的效果就是讓寫auto s obj.get();的人都來問我為什么代碼報(bào)錯(cuò)。返回值加const真正有意義的場(chǎng)景是返回引用或指針的時(shí)候比如const T operator[]、const T* get()這時(shí)候你是在聲明“返回的是內(nèi)部數(shù)據(jù)的只讀視圖”防止調(diào)用方通過返回值修改對(duì)象內(nèi)部狀態(tài)。3. const在類與對(duì)象中的進(jìn)階玩法3.1 const成員函數(shù)到底改了什么類里面void print() const;這種寫法很多初學(xué)者只背結(jié)論const成員函數(shù)里不能修改成員變量。但真要理解得看它的本質(zhì)。所謂const成員函數(shù)實(shí)際上是給this指針加了底層const約束也就是說this的類型從Foo*變成了const Foo*。所以不是“函數(shù)不能修改成員變量”而是“這個(gè)函數(shù)拿到的this指向一個(gè)const對(duì)象你不能通過它去修改對(duì)象”。這帶來一個(gè)連鎖約束const成員函數(shù)里只能調(diào)用其他const成員函數(shù)。道理很簡(jiǎn)單你的this是const Foo*你沒法把它轉(zhuǎn)成Foo*去調(diào)用一個(gè)非const成員函數(shù)。反過來非const成員函數(shù)可以隨便調(diào)用const成員函數(shù)因?yàn)闄?quán)限收窄是允許的。構(gòu)造函數(shù)和析構(gòu)函數(shù)不能是const這一點(diǎn)也經(jīng)常被問到。構(gòu)造函數(shù)期間對(duì)象還沒構(gòu)造完const語義還沒建立析構(gòu)函數(shù)更是要修改對(duì)象的內(nèi)部狀態(tài)來釋放資源把它標(biāo)成const毫無意義。3.2 mutable打破const限制的唯一正規(guī)通道如果在const成員函數(shù)里確實(shí)需要修改某個(gè)成員變量怎么辦標(biāo)準(zhǔn)答案是mutable。最常見的例子是安全計(jì)數(shù)器、統(tǒng)計(jì)字段、緩存這些“邏輯上不影響對(duì)象狀態(tài)”的數(shù)據(jù)class Service { public: int getData() const { callCount_; // mutable允許在const成員函數(shù)里改 return data_; } private: int data_{0}; mutable long callCount_{0}; };callCount_被const成員函數(shù)修改了但沒有改變對(duì)象的“邏輯狀態(tài)”所以這種用法是被認(rèn)可的。另一個(gè)極其經(jīng)典的場(chǎng)景是線程同步一個(gè)線程安全的類如果get()方法是const的但里面需要加鎖那互斥鎖就必須聲明為mutable否則根本沒法治。這在后面第5部分多線程場(chǎng)景里會(huì)再展開。我用過的最陰間的mutable用法是懶加載。有的老代碼為了在const接口里實(shí)現(xiàn)“首次訪問時(shí)初始化緩存”把緩存指針聲明為mutable在里面做new。這種代碼可以用但一定要保證線程安全否則并發(fā)環(huán)境下兩個(gè)線程同時(shí)觸發(fā)懶加載直接雙free我為此排查過一個(gè)線上崩潰教訓(xùn)很深刻。3.3 const重載同一個(gè)函數(shù)名兩種語義const成員函數(shù)可以和非const成員函數(shù)構(gòu)成重載編譯器根據(jù)調(diào)用對(duì)象是否為const來選擇。最典型的例子是容器的operator[]T operator[](size_t idx); const T operator[](size_t idx) const;對(duì)一個(gè)非const容器調(diào)用operator[]你拿到的可寫引用可以修改元素對(duì)一個(gè)const容器調(diào)用你只能拿到const引用不能修改。這很符合直覺const容器就不應(yīng)該允許你改里面的東西。如果你只提供非const版本const對(duì)象沒法調(diào)用如果你只提供const版本非const對(duì)象也能調(diào)用但拿到的引用就永遠(yuǎn)是const同樣很麻煩。在寫自定義類時(shí)養(yǎng)成習(xí)慣如果接口語義是只讀的就給const版本如果還要返回可寫引用就把兩個(gè)版本都寫全。不要偷懶只想寫一個(gè)。我踩過的一個(gè)坑是類內(nèi)部有個(gè)緩存寫了const T getCache() const但沒有非const版本結(jié)果外部需要在某個(gè)深層邏輯里修改緩存內(nèi)容被迫去const_cast繞了一圈最后全刪了重寫成兩個(gè)重載干凈利落。3.4 const與static結(jié)合時(shí)要注意什么static成員函數(shù)沒有this指針?biāo)圆荒苈暶鳛閏onst。這一點(diǎn)面試?yán)锱紶枙?huì)出判斷題原因就是const成員函數(shù)本質(zhì)是約束this的沒有this自然就談不上const。static成員變量有沒有const有通常用來定義類的編譯期常量比如static const int kMax 100;。注意如果這個(gè)static const變量被取地址或者ODR-used需要在外部的.cpp里再定義一次C17里可以用inline static const省掉這個(gè)麻煩。我在C14時(shí)代寫庫代碼被這個(gè)“undefined reference”折磨過一次之后全是inline static。4. 高頻考點(diǎn)const_cast、const與define、constexpr4.1 const_cast能用但大部分時(shí)候不該用const_cast是C里專門用來去掉const或volatile限定的強(qiáng)制轉(zhuǎn)換。為什么需要它一個(gè)典型場(chǎng)景是歷史遺留代碼里有很多C接口函數(shù)簽名是char*但你的數(shù)據(jù)實(shí)際上是const的而且背后是個(gè)可寫的緩沖區(qū)只是接口傳進(jìn)來時(shí)給標(biāo)成了const。這種你知根知底的情況下用const_cast是合理的。真正危險(xiǎn)的是“對(duì)象本身是const”的情況。比如const int a 100; const_castint(a) 200; // 未定義行為a本身聲明為const編譯器完全可以把它放到只讀存儲(chǔ)里你強(qiáng)轉(zhuǎn)完去寫輕則寫入被忽略重則直接崩潰。我記得有次調(diào)試一個(gè)崩潰堆棧最后定位到就是有人對(duì)一個(gè)全局const配置項(xiàng)用了const_cast去修改“配置”編譯器把那個(gè)對(duì)象放到了只讀段一寫就段錯(cuò)誤。這已經(jīng)不是用錯(cuò)工具的問題而是設(shè)計(jì)上根本沒有資格修改它。另一個(gè)絕對(duì)禁區(qū)是字符串字面量。前面說了字符串字面量的類型是const char[N]你就算用const_cast去掉const再寫同樣是改寫只讀內(nèi)存運(yùn)行時(shí)崩潰沒商量。給個(gè)判斷標(biāo)準(zhǔn)如果原始對(duì)象真的是const別用const_cast如果原始對(duì)象不是const只是你手里只有const引用或指針可以謹(jǐn)慎使用但要先問問自己為什么不一開始就用非const接口。項(xiàng)目中大部分const_cast都屬于設(shè)計(jì)異味能避免盡量避免。4.2 const和#define到底差在哪這個(gè)問題面試頻率極高。兩者表面上看都能定義“常量”實(shí)際差別非常大對(duì)比維度const#define處理階段編譯期預(yù)處理期文本替換類型檢查有完整類型編譯器會(huì)檢查無類型替換成字面值再來檢查作用域遵循C作用域規(guī)則從定義處到文件結(jié)束或undef調(diào)試是變量調(diào)試器可查看預(yù)處理后就沒了看不到內(nèi)存const變量有實(shí)體可取地址不分配實(shí)體純字面替換能力只能定義常量可以定義函數(shù)宏、拼接等我最想強(qiáng)調(diào)的一點(diǎn)是作用域。define只要一不小心沒有undef就會(huì)污染后面所有的代碼和頭文件。我之前在維護(hù)一個(gè)老項(xiàng)目時(shí)有個(gè)頭文件里define了一個(gè)名字很通用的宏MAX導(dǎo)致后面包含它的所有文件里但凡有個(gè)成員函數(shù)想叫g(shù)etMax()都會(huì)被預(yù)處理器活生生替換成get100()之類的災(zāi)難場(chǎng)面。排查這種問題極其痛苦因?yàn)槟憧吹降拇a和編譯器看到的代碼根本不一樣。而const變量老老實(shí)實(shí)遵守作用域完全不會(huì)有這種問題。C實(shí)踐里對(duì)“常量”的優(yōu)先級(jí)是能用constexpr用constexpr能用const用const實(shí)在需要宏再考慮define。函數(shù)宏在現(xiàn)代C里多數(shù)能被inline函數(shù)和模板替代別再用宏寫“函數(shù)”了。4.3 constexpr從“只讀”到“編譯期算完”constexpr是C11引入的關(guān)鍵字目的很明確讓某些值和函數(shù)可以在編譯期就求出來。它和const的最大區(qū)別是const表達(dá)的是“運(yùn)行時(shí)只讀”constexpr表達(dá)的是“編譯期常量”。一個(gè)const變量可能在運(yùn)行時(shí)才知道值但一個(gè)constexpr變量初始化必須能用常量表達(dá)式完成而且它本身就隱含const語義??磦€(gè)例子int a 5; const int b a; // 合法b運(yùn)行時(shí)才確定 constexpr int c a; // 錯(cuò)誤a不是常量表達(dá)式 constexpr int d 5 3; // 合法編譯期就算完了constexpr函數(shù)更有意思C14之后放寬了很多可以在里面寫循環(huán)、分支。它的規(guī)則是用常量實(shí)參調(diào)用時(shí)編譯期求值用運(yùn)行期實(shí)參調(diào)用時(shí)退化成普通函數(shù)。這種“兩棲”能力讓它特別適合做一些配置表、工具計(jì)算又不犧牲普通運(yùn)行時(shí)的靈活性。我在項(xiàng)目里最常用的組合是constexpr加上字符串解析、單位換算之類的純計(jì)算邏輯。比如一個(gè)把角度轉(zhuǎn)弧度的函數(shù)因?yàn)樗袇?shù)編譯期都知道直接被編譯器算成常量運(yùn)行時(shí)零開銷。面試問到constexpr一般會(huì)順帶問“constexpr和const能不能同時(shí)出現(xiàn)”答案是能但constexpr已經(jīng)隱含const變量上重復(fù)寫沒問題但有點(diǎn)冗余。5. 典型代碼場(chǎng)景與面試真題解析5.1 catch (const std::exception e) 為什么一定要const引用異常捕獲里最標(biāo)準(zhǔn)的寫法是catch (const std::exception e)這個(gè)const背后有好幾層意思。第一你應(yīng)該把異常對(duì)象當(dāng)成只讀信息它用來描述錯(cuò)誤不是用來改的。第二引用可以避免對(duì)象切片——如果你寫成catch (std::exception e)當(dāng)實(shí)際拋出的是std::runtime_error時(shí)派生部分會(huì)被切掉丟失重要的錯(cuò)誤信息而用引用捕獲多態(tài)就能正常發(fā)揮作用。第三加const能讓接口語義更清晰編譯器幫你保證你不會(huì)無意中去修改異常對(duì)象。很多人寫catch (std::exception e)也能工作但既然不打算改用const才是最佳實(shí)踐。同樣道理函數(shù)簽名里const std::exception這種寫法在異常相關(guān)的接口中也非常常見。我排查線上問題的時(shí)候經(jīng)常要靠e.what()的字符串定位錯(cuò)誤源如果曾經(jīng)因?yàn)榍衅瑏G失了what()的信息調(diào)試成本直接翻倍。5.2 回調(diào)函數(shù)和接口設(shè)計(jì)const void* 與const std::function回調(diào)函數(shù)里const最常見的體現(xiàn)是const void* data這種C風(fēng)格參數(shù)。為什么要加const因?yàn)榛卣{(diào)是別人把數(shù)據(jù)交給你處理如果你不是要修改數(shù)據(jù)就應(yīng)該聲明為const這樣調(diào)用方可以放心地把const數(shù)據(jù)傳進(jìn)去也避免你誤改。比如一個(gè)音頻采集回調(diào)、一個(gè)數(shù)據(jù)包解析回調(diào)絕大多數(shù)場(chǎng)景都是只讀標(biāo)記const是對(duì)調(diào)用方的承諾。在C現(xiàn)代代碼里我更喜歡用std::function比如void onData(const std::functionvoid(const char* data, size_t len) callback);這里const引用包住std::function避免拷貝內(nèi)部回調(diào)參數(shù)也是const指針表示只讀。設(shè)計(jì)接口的人如果不注意const就會(huì)出現(xiàn)調(diào)用方手里是const字符串卻傳不進(jìn)回調(diào)的尷尬局面。所以寫庫的人有一點(diǎn)很關(guān)鍵不修改的參數(shù)盡量全部加const寧可多寫幾行也別讓使用方到處const_cast。5.3 多線程里的const陷阱const不代表原子也不代表線程安全const成員函數(shù)在多線程環(huán)境中有一個(gè)極易踩的坑你以為const方法不修改對(duì)象所以并發(fā)調(diào)用是安全的其實(shí)不一定??催@個(gè)例子class Counter { public: int get() const { return count_; } void inc() { count_; } private: int count_{0}; };get()是const的但如果一個(gè)線程在inc()另一個(gè)線程在get()這里依然有數(shù)據(jù)競(jìng)爭(zhēng)。const只代表“不允許通過這個(gè)接口修改對(duì)象”不代表“對(duì)象不會(huì)被其他人修改”。反過來const成員函數(shù)內(nèi)部如果只讀取成員變量在別處不修改的情況下并發(fā)讀是安全的。真正要做到線程安全得靠同步原語。于是mutex和mutable的經(jīng)典組合就來了class ThreadSafeCounter { public: int get() const { std::lock_guardstd::mutex lock(mtx_); return count_; } private: mutable std::mutex mtx_; int count_{0}; };get()要加鎖必須能“修改”mtx_所以mtx_必須mutable。很多第一次看到mutable的人就是在這種線程安全的類里相遇的。一次我在審查代碼時(shí)看到有人直接在const成員函數(shù)里std::lock_guardstd::mutex lock(mtx_);編譯不過然后他把整個(gè)函數(shù)去掉const了導(dǎo)致所有const調(diào)用點(diǎn)全部編譯失敗。其實(shí)只需要給mtx_加mutable幾秒鐘的事。這個(gè)組合要當(dāng)成固定套路記下來。5.4 面試高頻const真題速查面試?yán)颿onst相關(guān)問題翻來覆去就那幾個(gè)整理一個(gè)速查表問題核心答案要點(diǎn)const int* p和int* const p區(qū)別前者不能改指向的值后者不能改指針本身頂層const和底層const頂層管指針本身底層管指向?qū)ο罂截悪?quán)限只收不放const T 能綁定臨時(shí)對(duì)象嗎能且會(huì)延長(zhǎng)臨時(shí)對(duì)象的生命周期const成員函數(shù)能修改成員變量嗎不能除非成員變量聲明為mutablestatic成員函數(shù)能是const嗎不能static函數(shù)沒有this指針const_cast什么時(shí)候安全原始對(duì)象本身非const只是接口傳成了const引用/指針時(shí)可用對(duì)象本身是const時(shí)絕對(duì)不能用const和define的區(qū)別預(yù)處理vs編譯期、有類型vs無類型、作用域、調(diào)試可見性constexpr和const的區(qū)別constexpr強(qiáng)調(diào)編譯期求值隱含const語義const容器能用operator[]嗎能用但返回的是const引用因?yàn)閏onst版本operator[]返回const T為什么char* p hello;不行C中字符串字面量類型是const char[N]需要const char*來接收這些題如果都能不看資料答出來const的基礎(chǔ)算是扎實(shí)了。但面試只是門檻真正的考驗(yàn)是項(xiàng)目里每一處接口簽名是否設(shè)計(jì)得合理。5.5 再補(bǔ)兩個(gè)實(shí)戰(zhàn)細(xì)節(jié)第一STL的const_iterator和iterator。const容器只能拿到const_iterator它指向的元素是只讀的。很多人會(huì)把const std::vectorint和std::vectorint::const_iterator搞混記住一點(diǎn)容器是const它給出來的迭代器就自動(dòng)是const版本邏輯非常一致。第二const成員函數(shù)里如果想把某個(gè)容器成員返回出去返回const auto或者const T才能保持只讀語義。有人圖省事直接返回一個(gè)拷貝結(jié)果每次調(diào)用都多一次拷貝開銷有人直接返回了非const引用結(jié)果外部通過這個(gè)引用把內(nèi)部數(shù)據(jù)改了等于給類的封裝開了個(gè)洞。6. 關(guān)于const我最想分享的經(jīng)驗(yàn)我在實(shí)際項(xiàng)目中養(yǎng)成了一個(gè)習(xí)慣寫任何接口前先問自己三個(gè)問題——這個(gè)參數(shù)我會(huì)在函數(shù)內(nèi)修改嗎這個(gè)成員函數(shù)允許被const對(duì)象調(diào)用嗎這個(gè)返回值能不能暴露內(nèi)部狀態(tài)每一問都會(huì)導(dǎo)向一個(gè)const相關(guān)的決定。這套思考流程救了我很多次也讓我給別人review代碼時(shí)能一眼看出接口設(shè)計(jì)是否合理。另外一個(gè)小技巧遇到復(fù)雜的指針const類型不要靠記憶硬扛直接按“從右往左讀變量聲明”來拆。const char* const* pp是個(gè)指針指向一個(gè)const char* const也就是p指向的元素是一個(gè)指向const字符的const指針。一層層剝絕對(duì)不會(huì)亂。最后再分享一個(gè)排查經(jīng)驗(yàn)如果程序莫名其妙報(bào)出寫只讀內(nèi)存的錯(cuò)誤先別急著崩潰看看代碼里有沒有出現(xiàn)const_cast特別是對(duì)字符串字面量和全局const對(duì)象的const_cast。把這行刪掉或者改用其他設(shè)計(jì)大概率事情就結(jié)束了。const這套機(jī)制說到底是為了幫我們寫出更可靠的代碼而不是為了讓我們?cè)诰幾g期和運(yùn)行時(shí)之間來回折騰。真正理解了它你會(huì)發(fā)現(xiàn)自己的C代碼從“能跑”到了“設(shè)計(jì)得清楚”那一步。