
【C 面試真題】聊聊 C 的 final 和 overridefinal和override是 C11 引入的兩個(gè)**“小而美的關(guān)鍵字**——代碼里就一個(gè)單詞卻能擋住一大類跟虛函數(shù)相關(guān)的隱蔽 bug。它們不是讓代碼能跑”而是讓編譯器替你檢查你寫的多態(tài)對(duì)不對(duì)。本文用問(wèn)答的方式把這兩個(gè)關(guān)鍵字一次講透。一、先說(shuō)結(jié)論兩個(gè)都是護(hù)欄? final 和 override 分別是干嘛的? 一句話區(qū)分override—— 明確告訴編譯器我這是在重寫父類的虛函數(shù)寫錯(cuò)了函數(shù)名/參數(shù)/const 不匹配編譯器直接報(bào)錯(cuò)final—— 明確告訴編譯器這個(gè)虛函數(shù)/類到此為止不允許再被重寫/繼承。兩者都是編譯期的檢查標(biāo)記運(yùn)行期零開銷純粹用來(lái)把運(yùn)行期才會(huì)暴露的 bug 提前到編譯期。關(guān)鍵字防止什么override你以為重寫了其實(shí)沒有final別人再重寫/繼承你不想要的二、為什么需要 override最經(jīng)典的坑? 不加 override 不也能重寫虛函數(shù)嗎為什么要加? 不加語(yǔ)法上也能重寫但有個(gè)致命陷阱——你以為重寫了其實(shí)只是隱藏了一個(gè)新函數(shù)編譯器一聲不吭。看這個(gè)經(jīng)典翻車現(xiàn)場(chǎng)structBase{virtualvoidf(int){}};structDer:Base{voidf(int)const{}// ? 你以為重寫了// 其實(shí)沒有參數(shù)列表不同// 這是另一個(gè) f隱藏了基類的};Der::f加了const簽名和基類不一樣所以根本不是重寫——但編譯器不報(bào)錯(cuò)你以為多態(tài)生效了運(yùn)行期才發(fā)現(xiàn)f沒被正確調(diào)用。這種 bug 極難排查。加override后structDer:Base{voidf(int)constoverride;// ? 編譯直接報(bào)錯(cuò)// 沒有可重寫的基類虛函數(shù)};核心價(jià)值override 讓編譯器主動(dòng)檢查這次重寫對(duì)不對(duì)——簽名必須和基類虛函數(shù)完全一致。一旦不一致編譯期就攔下而不是留到運(yùn)行期踩坑。這種 bug 在真實(shí)項(xiàng)目里有多常見想象一個(gè)有幾十層繼承、上百個(gè)虛函數(shù)的大型框架?;惖哪硞€(gè)虛函數(shù)簽名被改了一下比如加了個(gè)默認(rèn)參數(shù)、或 const 調(diào)整所有派生類如果靠人工同步幾乎必然漏掉一兩個(gè)。漏掉的那個(gè)函數(shù)就從重寫變成了隱藏多態(tài)靜悄悄地失效——程序能編譯、能跑但行為錯(cuò)了。override 就是消滅這類 bug 的銀彈。常見的不匹配來(lái)源參數(shù)類型/個(gè)數(shù)不同const 修飾不一致成員函數(shù)的 const 也算簽名返回類型不兼容函數(shù)名拼錯(cuò)比如Base::RendervsDer::render。三、override 的正確寫法? override 加在哪怎么用? 加在派生類重寫的虛函數(shù)聲明后寫在const之后、 0/{}之前structBase{virtualvoidf(int)0;virtualvoidg()const{}};structDer:Base{voidf(int)override;// ? 重寫voidg()constoverride{}// ? 重寫};最佳實(shí)踐派生類里重寫虛函數(shù)一律加 override。這是現(xiàn)代 C 的共識(shí)——幾乎沒有理由不加。它零開銷卻能把手滑沒重寫成的 bug 擋在編譯期。四、final到此為止不許再動(dòng)? final 是干嘛的?final有兩種用法——修飾函數(shù)和修飾類意思都是到此為止。用法一修飾虛函數(shù)——禁止子類再重寫structBase{virtualvoidf();};structMid:Base{voidf()final;// Mid 之后f 不能再重寫};structDer:Mid{voidf()override;// ? 編譯報(bào)錯(cuò)f 已被 final};用法二修飾類——禁止被繼承structLockfinal{};// 誰(shuí)也不能繼承 LockstructX:Lock{};// ? 編譯報(bào)錯(cuò)Lock 是 final 類加分點(diǎn)final 還能給編譯器優(yōu)化開綠燈。當(dāng)一個(gè)虛函數(shù)被標(biāo)記為 final或一個(gè)類被標(biāo)記為 final 時(shí)編譯器確定它不會(huì)再被重寫于是可以去虛化devirtualization——把虛函數(shù)調(diào)用直接變成普通調(diào)用省掉查虛表的開銷。這是 final 的隱藏收益。這個(gè)收益有多大取決于調(diào)用頻率。在每幀執(zhí)行成千上萬(wàn)次的循環(huán)里虛函數(shù)調(diào)用的間接跳轉(zhuǎn)會(huì)破壞指令流水、阻礙內(nèi)聯(lián)可能比直接調(diào)用慢不少。給這類熱函數(shù)加 final讓編譯器去虛化有時(shí)能帶來(lái)可觀的性能提升。但別為了優(yōu)化亂加 final——它也限制了擴(kuò)展性只有在確定確實(shí)不該再被重寫時(shí)才加。五、final 和 override 能一起用嗎? 一個(gè)函數(shù)能同時(shí)加 final 和 override 嗎? 能而且推薦這么寫。順序是 override 在前、final 在后structDer:Base{voidf()overridefinal;// 既是重寫又封死后續(xù)};語(yǔ)義疊加“我重寫了基類的 f而且在我這層之后就別再重寫了”。這表達(dá)得很完整——既讓編譯器檢查重寫正確性override又關(guān)上后續(xù)重寫的大門final。??注意單獨(dú)寫final不帶override也能編譯但不推薦——因?yàn)?final 不做是否真的重寫了基類的檢查。帶 override 更安全意圖也更清晰。六、為什么重寫這么容易出錯(cuò)? C 的虛函數(shù)重寫為什么會(huì)有一堆坑? 因?yàn)?C 的重寫規(guī)則極其嚴(yán)格——要求派生類函數(shù)和基類虛函數(shù)簽名完全一致參數(shù)列表、const 修飾、兼容的返回類型任何一處不匹配就不構(gòu)成重寫而是變成一個(gè)全新的函數(shù)隱藏。structBase{virtualvoidf(int);};structDer:Base{voidf(long);// ? 不是重寫// int vs long簽名不同// 這是隱藏不是多態(tài)};這種簽名微妙不同導(dǎo)致重寫失效的情況在以下場(chǎng)景高發(fā)基類加了const、派生類忘了或反過(guò)來(lái)參數(shù)類型微調(diào)int vs long、T*vsT函數(shù)名大小寫/拼寫不一致基類虛函數(shù)簽名后來(lái)被改了派生類沒同步更新。override 就是來(lái)兜底的——只要簽名不一致立刻編譯報(bào)錯(cuò)。七、核心規(guī)則速查表維度overridefinal作用檢查重寫正確性禁止再重寫/繼承加在派生類函數(shù)函數(shù)或類能否同用可override final可運(yùn)行期開銷無(wú)無(wú)是否幫優(yōu)化否是去虛化八、面試高頻追問(wèn)? Q1override 是不是多余的不加不也能重寫嗎? 語(yǔ)法上不多余但工程上強(qiáng)烈建議加。不加時(shí)簽名不匹配會(huì)變成隱藏而非重寫編譯器不報(bào)錯(cuò)bug 留到運(yùn)行期。override 把這個(gè)檢查提前到編譯期。? Q2final 修飾類和修飾函數(shù)有什么區(qū)別? 修飾類——該類不能被繼承修飾虛函數(shù)——該函數(shù)在當(dāng)前層之后不能再被重寫。兩者都是封死后續(xù)擴(kuò)展但作用域不同。? Q3final 能帶來(lái)性能提升嗎? 能。final 讓編譯器確定不會(huì)再有更下層的重寫從而可以去虛化——把虛函數(shù)調(diào)用改成直接調(diào)用省掉虛表查找。在性能敏感的熱路徑里有實(shí)際收益。? Q4派生類重寫虛函數(shù)時(shí)不寫 virtual 行不行? 行。一旦基類函數(shù)是 virtual派生類重寫后自動(dòng)是 virtualvirtual 性質(zhì)會(huì)沿繼承鏈傳遞。但加 override 比加 virtual 更好——override 做檢查virtual 不做。? Q5override 和 final 都是 C11 加的之前怎么辦? C11 之前只能靠人工保證簽名一致bug 很常見。這也是為什么 override 被認(rèn)為是 C11 最實(shí)用的特性之一——它消滅了一整類以為重寫了其實(shí)沒有的 bug。九、總結(jié)速查表場(chǎng)景推薦寫法重寫虛函數(shù)加 override封死后續(xù)重寫override final禁止類被繼承類名后加 final性能敏感的虛函數(shù)考慮 final去虛化一句話回顧override是我確實(shí)在重寫的聲明讓編譯器替你檢查簽名final是到此為止的封條擋住再重寫/繼承。兩者都是編譯期零開銷的護(hù)欄——派生類重寫一律加 override幾乎從不出錯(cuò)。如果您覺得本篇內(nèi)容對(duì)你有幫助歡迎點(diǎn)贊 、收藏 ?、轉(zhuǎn)發(fā) 。關(guān)鍵字篇到此完結(jié)下期我們正式進(jìn)入面向?qū)ο笃牧臉?gòu)造與析構(gòu)敬請(qǐng)關(guān)注