盤(pán):指針內(nèi)存STL與多線程考點(diǎn)精講)
2017年秋天我在圖書(shū)館刷到愛(ài)奇藝的校招筆試通知點(diǎn)進(jìn)去做完那套C開(kāi)發(fā)工程師筆試卷整整花了一晚上復(fù)盤(pán)。說(shuō)起來(lái)這套卷子不算偏但也正因?yàn)椴黄貏e能暴露基礎(chǔ)功底的薄弱點(diǎn)。指針、內(nèi)存、STL、算法、多線程基本把C方向校招筆試的高頻考點(diǎn)都覆蓋了一遍題目難度呈現(xiàn)明顯的梯度前面還能靠記憶應(yīng)付越往后越考驗(yàn)真功夫。我當(dāng)時(shí)做完最大感受是這套卷子篩的不是誰(shuí)刷題多而是誰(shuí)對(duì)C這門(mén)語(yǔ)言有真正體系化的理解。今天我把這類卷子的出題邏輯、高頻考點(diǎn)和答題策略拆開(kāi)講一遍給準(zhǔn)備秋招的同學(xué)做個(gè)參考。1. 這套卷子的出題邏輯它到底想篩什么樣的人1.1 筆試形式與C崗位的考察范圍先說(shuō)形式。這類大廠C方向的秋招筆試卷通常會(huì)分為三個(gè)部分選擇題、簡(jiǎn)答題和編程題。選擇題大概20到30道覆蓋語(yǔ)言基礎(chǔ)、操作系統(tǒng)、網(wǎng)絡(luò)、數(shù)據(jù)結(jié)構(gòu)簡(jiǎn)答題一般2到3道考設(shè)計(jì)思路或問(wèn)題分析編程題有2到3道要求在線編碼跑通測(cè)試用例。整套卷子時(shí)間給得不多有些公司甚至是90分鐘到120分鐘完成所有題目這就要求答題節(jié)奏非常緊湊。愛(ài)奇藝2017年這套卷子的整體風(fēng)格是基礎(chǔ)題量大、編程題有區(qū)分度。選擇題里的C語(yǔ)法和內(nèi)存題數(shù)量不少而且選項(xiàng)之間非常接近如果平時(shí)只是眼熟而沒(méi)有真正理解很容易掉坑。編程題里既有可以直接套模板的經(jīng)典題也有需要現(xiàn)場(chǎng)推理的變種題靠死記硬背過(guò)不了最后那幾道。從崗位屬性來(lái)說(shuō)C開(kāi)發(fā)工程師在互聯(lián)網(wǎng)公司主要做的是底層基礎(chǔ)設(shè)施、音視頻處理、網(wǎng)絡(luò)服務(wù)和高性能組件。愛(ài)奇藝這類視頻平臺(tái)對(duì)C的需求尤其集中在播放器內(nèi)核、CDN調(diào)度、流媒體傳輸、轉(zhuǎn)碼服務(wù)這一條鏈路上。筆試考的東西其實(shí)就是在為這些業(yè)務(wù)方向篩選候選人。1.2 從愛(ài)奇藝的業(yè)務(wù)屬性反推考點(diǎn)偏好視頻平臺(tái)的核心技術(shù)棧里C主要出現(xiàn)在三個(gè)方面一是流媒體服務(wù)端需要處理高并發(fā)連接和海量數(shù)據(jù)轉(zhuǎn)發(fā)二是音視頻處理涉及編解碼、轉(zhuǎn)碼、畫(huà)質(zhì)增強(qiáng)對(duì)內(nèi)存管理和性能極度敏感三是播放器內(nèi)核和客戶端組件要求代碼在資源受限的終端設(shè)備上穩(wěn)定運(yùn)行。這三個(gè)方向?qū)?yīng)的C技能要求其實(shí)很明確內(nèi)存管理和對(duì)象生命周期要扎實(shí)這塊直接決定線上服務(wù)穩(wěn)定性多線程和并發(fā)控制要熟練因?yàn)榱髅襟w服務(wù)本質(zhì)就是并發(fā)系統(tǒng)STL和算法要熟海量數(shù)據(jù)的排序、查找、去重是日常工作網(wǎng)絡(luò)編程和系統(tǒng)調(diào)用要懂這涉及傳輸協(xié)議和IO模型。所以這套卷子里出現(xiàn)的知識(shí)點(diǎn)并不是出題人隨手抓的而是業(yè)務(wù)倒推考點(diǎn)的結(jié)果。你在復(fù)習(xí)的時(shí)候如果能站在這個(gè)角度理解題目就不容易覺(jué)得某些偏怪問(wèn)題沒(méi)有意義。比如虛函數(shù)考察的是你對(duì)對(duì)象內(nèi)存布局的理解放到實(shí)際場(chǎng)景里就是基類指針能不能安全地操作派生類對(duì)象這在組件化架構(gòu)中天天遇到。1.3 這套卷子的時(shí)間節(jié)奏與丟分重災(zāi)區(qū)我當(dāng)時(shí)給自己模擬了真實(shí)考試的時(shí)間分配發(fā)現(xiàn)最耗時(shí)間的不是編程題而是前面的選擇題和簡(jiǎn)答題。選擇題里很多是下列哪個(gè)說(shuō)法是錯(cuò)誤的這類否定式提問(wèn)每個(gè)選項(xiàng)都很像是對(duì)的需要逐個(gè)排查。一道這樣的題就可能花掉3到5分鐘20道基礎(chǔ)題做完40分鐘就沒(méi)了。結(jié)合周?chē)瑢W(xué)反饋這套卷子丟分比較多的位置通常有三處內(nèi)存和指針相關(guān)的題目尤其涉及二維指針、指針數(shù)組、數(shù)組指針的辨析拷貝構(gòu)造與移動(dòng)語(yǔ)義的調(diào)用時(shí)機(jī)判斷題目改一個(gè)參數(shù)傳遞方式答案就完全不同編程題里的邊界條件處理測(cè)試用例隱藏了空指針、越界和溢出導(dǎo)致看似正確的代碼只有部分用例通過(guò)。搞清楚這些丟分點(diǎn)后你會(huì)發(fā)現(xiàn)復(fù)習(xí)重心其實(shí)很明確?;A(chǔ)題靠體系化復(fù)習(xí)編程題靠高頻題型訓(xùn)練兩者缺一不可。下面我把每類考點(diǎn)的答題思路拆開(kāi)細(xì)講。2. 語(yǔ)言基礎(chǔ)題拆解指針、內(nèi)存和對(duì)象生命周期2.1 指針與引用的組合陷阱C筆試題里指針和引用是一個(gè)怎么考都不過(guò)時(shí)的主題。這套試卷里涉及指針的部分主要不是讓你寫(xiě)指針運(yùn)算而是給你一段代碼讓判斷輸出結(jié)果或指出錯(cuò)誤。比如下面這類問(wèn)題int a[5] {1, 2, 3, 4, 5}; int *p a; cout *(p) endl; cout *p endl; cout *p 1 endl;很多人會(huì)在這種題上翻車(chē)原因是把p和p的副作用時(shí)機(jī)搞混了。*(p)先把p指向的值取出來(lái)然后p后移一位所以輸出1*p先把p前移一位再取值此時(shí)p指向a[2]輸出3*p 1由于運(yùn)算符優(yōu)先級(jí)等價(jià)于(*p) 1輸出4。這類題考的不是你記沒(méi)記住優(yōu)先級(jí)表而是你對(duì)表達(dá)式求值過(guò)程中指針狀態(tài)如何變化有沒(méi)有清晰認(rèn)知。指針數(shù)組和數(shù)組指針也是一個(gè)經(jīng)典區(qū)分點(diǎn)。int *p[3]表示一個(gè)數(shù)組數(shù)組元素是三個(gè)int*指針int (*p)[3]表示一個(gè)指針指向含有三個(gè)int元素的數(shù)組。在二維數(shù)組傳參時(shí)才分得清這兩種寫(xiě)法的差異。不少同學(xué)在簡(jiǎn)化代碼時(shí)把int (*p)[3]錯(cuò)寫(xiě)成int *p[3]編譯雖然能過(guò)但語(yǔ)義完全錯(cuò)了。2.2 構(gòu)造、析構(gòu)、拷貝與移動(dòng)的調(diào)用時(shí)機(jī)C筆試?yán)锟疾鞂?duì)象生命周期的方式很多最常見(jiàn)的是輸出題讓你數(shù)一個(gè)類被構(gòu)造了幾次、析構(gòu)了幾次、拷貝了幾次。比如傳值、傳引用、返回臨時(shí)對(duì)象的情況class Test { public: Test() { cout ctor endl; } Test(const Test t) { cout copy endl; } Test(Test t) { cout move endl; } ~Test() { cout dtor endl; } }; Test func() { Test t; return t; } int main() { Test a func(); }在C11之前這段代碼可能會(huì)觸發(fā)兩次構(gòu)造和多次拷貝在C11之后由于返回值優(yōu)化和移動(dòng)語(yǔ)義的存在實(shí)際輸出可能簡(jiǎn)化。這里的關(guān)鍵點(diǎn)是編譯器優(yōu)化RVO/NRVO)并不是強(qiáng)制行為而移動(dòng)構(gòu)造的優(yōu)先級(jí)高于拷貝構(gòu)造當(dāng)你有右值引用時(shí)return t會(huì)優(yōu)先走移動(dòng)而不是拷貝。當(dāng)時(shí)這套卷子在移動(dòng)語(yǔ)義上挖了一個(gè)很細(xì)的坑類里如果手動(dòng)實(shí)現(xiàn)了析構(gòu)函數(shù)或者拷貝構(gòu)造函數(shù)編譯器就不會(huì)自動(dòng)生成移動(dòng)構(gòu)造函數(shù)。這意味著即使你的類滿足移動(dòng)條件也只能退回到拷貝性能差異在上千萬(wàn)元素容器里非常明顯。這類題表面考語(yǔ)法實(shí)際考的是你知不知道C11規(guī)則中的隱式函數(shù)生成條件。2.3 虛函數(shù)與多態(tài)的實(shí)現(xiàn)機(jī)制虛函數(shù)是C筆試的必考內(nèi)容考察方向一般有兩種一種是讓你描述虛表vtable的實(shí)現(xiàn)機(jī)制另一種是給一段多態(tài)代碼讓判斷輸出。描述虛表時(shí)關(guān)鍵是講清楚三點(diǎn)每個(gè)包含虛函數(shù)的類都有一張?zhí)摵瘮?shù)表表中存放虛函數(shù)地址每個(gè)對(duì)象內(nèi)部有一個(gè)虛表指針vptr指向所屬類的虛表構(gòu)造對(duì)象時(shí)vptr會(huì)被設(shè)置為指向當(dāng)前正在構(gòu)造的類的虛表所以在構(gòu)造函數(shù)里調(diào)用虛函數(shù)不會(huì)觸發(fā)多態(tài)。關(guān)于最后一點(diǎn)經(jīng)常有人踩坑。因?yàn)楦割悩?gòu)造過(guò)程中子類部分還沒(méi)有初始化如果這時(shí)候調(diào)用虛函數(shù)走的是子類實(shí)現(xiàn)子類成員可能還沒(méi)構(gòu)造程序就會(huì)出問(wèn)題。C選擇了在構(gòu)造期間把vptr指向當(dāng)前類從機(jī)制上避免這個(gè)風(fēng)險(xiǎn)。筆試題里讓你判斷new Derived()之后構(gòu)造函數(shù)中虛函數(shù)調(diào)用的輸出答案就是父類版本。這套卷子還考過(guò)一個(gè)很容易錯(cuò)的知識(shí)點(diǎn)析構(gòu)函數(shù)為什么要聲明為虛函數(shù)。如果基類析構(gòu)函數(shù)不是虛的通過(guò)基類指針刪除派生類對(duì)象時(shí)只會(huì)調(diào)用基類析構(gòu)函數(shù)派生類資源無(wú)法釋放造成內(nèi)存泄漏。代碼層面對(duì)應(yīng)的場(chǎng)景是所有組件類、接口類、抽象基類這類體系設(shè)計(jì)中的基類析構(gòu)函數(shù)幾乎都應(yīng)該是虛的。3. 算法與編程題實(shí)戰(zhàn)字符串、排序和數(shù)學(xué)計(jì)算的拿分策略3.1 字符串類題目從字符數(shù)組的轉(zhuǎn)換說(shuō)起C筆試題里字符串處理是編程題常客而且經(jīng)常結(jié)合字符數(shù)組和std::string互相轉(zhuǎn)換來(lái)考。很多同學(xué)對(duì)string用得很熟一旦碰到C風(fēng)格字符串就懵。比如要求按指定分隔符把字符串拆成數(shù)組有人會(huì)用strtok但這個(gè)函數(shù)會(huì)修改原字符串并且不是線程安全的在多線程題目場(chǎng)景下容易被扣分。更好的做法是使用std::string的find和substr組合實(shí)現(xiàn)分詞std::vectorstd::string split(const std::string s, char delim) { std::vectorstd::string result; std::string::size_type start 0; auto pos s.find(delim); while (pos ! std::string::npos) { result.push_back(s.substr(start, pos - start)); start pos 1; pos s.find(delim, start); } result.push_back(s.substr(start)); return result; }寫(xiě)這類代碼時(shí)注意find的第二個(gè)參數(shù)表示從哪個(gè)位置開(kāi)始搜索避免漏掉最后一個(gè)分隔符后面的內(nèi)容。字符串轉(zhuǎn)數(shù)字也是高頻題std::stoi和std::to_string雖然方便但筆試題更愛(ài)考察手寫(xiě)轉(zhuǎn)換邏輯因?yàn)槟愕米约禾幚矸?hào)位、溢出和非法字符。3.2 排序算法冒泡和選擇之外的考察角度排序算法在筆試?yán)锏目挤ǚ譃閮煞N一種是直接讓你實(shí)現(xiàn)某個(gè)排序算法另一種是考察算法特性和復(fù)雜度。選做題里經(jīng)常出現(xiàn)冒泡排序和選擇排序因?yàn)榇a短、易于驗(yàn)證。但這里有個(gè)容易被忽略的知識(shí)點(diǎn)冒泡排序是穩(wěn)定排序選擇排序是不穩(wěn)定排序。為什么冒泡排序只在相鄰元素之間做交換相等元素的相對(duì)順序不會(huì)改變選擇排序在每一輪把一個(gè)元素放到最終位置如果當(dāng)前輪發(fā)現(xiàn)一個(gè)更小值會(huì)和前面的元素交換這個(gè)交換可能跨過(guò)相等的元素導(dǎo)致相對(duì)順序改變??此浦皇前斯晌膶?shí)際在排序?qū)ο蟮膶傩杂行蛐砸笊虾荜P(guān)鍵比如按分?jǐn)?shù)降序、分?jǐn)?shù)相同按學(xué)號(hào)升序的場(chǎng)景??焖倥判蛟诠P試中出現(xiàn)頻率也很高但多數(shù)不是讓你寫(xiě)基本版而是考察如何優(yōu)化。比如三數(shù)取中、小區(qū)間插入排序、尾遞歸優(yōu)化。我見(jiàn)過(guò)一道題是要求手寫(xiě)快速排序的partition函數(shù)并保證把所有等于pivot的元素集中在中間這就是三路快排的思路。寫(xiě)三路快排的要點(diǎn)是維護(hù)lt、gt兩個(gè)邊界把小于pivot、等于pivot、大于pivot分成三個(gè)區(qū)域避免重復(fù)元素的性能退化。3.3 數(shù)學(xué)類題目快速冪與最小公倍數(shù)的邊界處理數(shù)學(xué)類題目看著不起眼但往往是編程題里的送分題前提是你能快速寫(xiě)出無(wú)Bug的版本??焖賰缡歉哳l考點(diǎn)中的高頻描述很簡(jiǎn)單計(jì)算a的b次方模mod。如果直接循環(huán)乘b次b到1e9量級(jí)就超時(shí)了所以要用二分思想long long quickPow(long long a, long long b, long long mod) { long long result 1; a % mod; while (b 0) { if (b 1) result result * a % mod; a a * a % mod; b 1; } return result; }寫(xiě)這個(gè)代碼有三個(gè)容易出錯(cuò)的地方第一步要a % mod否則a可能溢出乘法時(shí)要用long long接收中間結(jié)果因?yàn)閮蓚€(gè)1e9量級(jí)的數(shù)相乘會(huì)超過(guò)int范圍循環(huán)條件是while (b 0)而不是while (b)雖然效果一樣但顯式比較更清晰不容易讓閱卷人誤解。最小公倍數(shù)的題目也不少見(jiàn)核心公式是lcm(a, b) a / gcd(a, b) * b。注意這里要先除后乘否則a * b可能溢出。如果是多個(gè)整數(shù)求最小公倍數(shù)就兩兩迭代計(jì)算。筆試?yán)锍=o幾個(gè)較大的數(shù)比如6、8、12、15很多人能算對(duì)但如果換成包含質(zhì)數(shù)的長(zhǎng)列表并且在代碼里要求處理就需要確保gcd函數(shù)在遞歸和迭代兩種寫(xiě)法下都能正確工作尤其注意gcd中a或b為零的邊界情況。3.4 進(jìn)階結(jié)構(gòu)單調(diào)棧與鏈表操作的常見(jiàn)考法這套卷子或者同類互聯(lián)網(wǎng)公司的筆試卷編程題里偶爾會(huì)拔高到單調(diào)棧這種進(jìn)階數(shù)據(jù)結(jié)構(gòu)。單調(diào)棧的典型應(yīng)用是尋找每個(gè)元素下一個(gè)更大或更小的元素位置比如每日溫度類問(wèn)題。核心理解是元素入棧時(shí)保持棧內(nèi)單調(diào)性出棧時(shí)就可以確定某些答案。寫(xiě)單調(diào)棧的代碼初學(xué)者最常犯的錯(cuò)誤是混淆棧中存值和存下標(biāo)的區(qū)別。大多數(shù)情況下應(yīng)該存儲(chǔ)下標(biāo)因?yàn)樽罱K要求的往往是位置距離或者需要根據(jù)下標(biāo)去原數(shù)組取值。如果存值當(dāng)數(shù)組里有重復(fù)元素時(shí)索引信息會(huì)丟失。鏈表操作中反轉(zhuǎn)鏈表、合并有序鏈表、判斷鏈表是否有環(huán)是三個(gè)標(biāo)配題。反轉(zhuǎn)鏈表迭代寫(xiě)法要維護(hù)三個(gè)指針順序上容易錯(cuò)ListNode* reverseList(ListNode* head) { ListNode* prev nullptr; ListNode* curr head; while (curr ! nullptr) { ListNode* next curr-next; curr-next prev; prev curr; curr next; } return prev; }這道題的關(guān)鍵是在改變curr-next之前先把next保存下來(lái)否則鏈表斷掉。很多面試官會(huì)追問(wèn)如果鏈表有環(huán)會(huì)怎樣、如果要求遞歸實(shí)現(xiàn)呢所以現(xiàn)場(chǎng)編碼時(shí)要把邊界情況一并考慮清楚。4. 多線程、設(shè)計(jì)模式與C新特性容易拖后腿的半基礎(chǔ)題4.1 多線程同步從互斥鎖到ABA問(wèn)題多線程是互聯(lián)網(wǎng)公司C筆試?yán)锢@不開(kāi)的題因?yàn)樗苯訉?duì)應(yīng)真實(shí)服務(wù)端的高并發(fā)場(chǎng)景。基礎(chǔ)考察方向包括互斥鎖、條件變量、讀寫(xiě)鎖、原子操作以及死鎖的形成條件。一個(gè)經(jīng)典考題是多個(gè)線程對(duì)同一個(gè)變量進(jìn)行自增操作如何保證結(jié)果正確。最直接的回答是加互斥鎖但要注意自增操作count不是原子的它包含讀、加、寫(xiě)三步。用C11的std::atomicint可以解決但如果你在代碼里用了兩次原子操作做比較并交換就會(huì)引入ABA問(wèn)題。ABA問(wèn)題的場(chǎng)景是線程A讀取到值X線程B把X改成Y又改回X線程A的CAS操作無(wú)法察覺(jué)中間的變動(dòng)。這在無(wú)鎖數(shù)據(jù)結(jié)構(gòu)中特別危險(xiǎn)比如無(wú)鎖棧中可能因?yàn)锳BA問(wèn)題導(dǎo)致重復(fù)釋放同一塊內(nèi)存。解決辦法一般是引入版本號(hào)或者使用帶有標(biāo)簽的指針每次修改帶上遞增的標(biāo)簽CAS時(shí)比較值的同時(shí)比較標(biāo)簽。筆試中能說(shuō)出這層說(shuō)明你真的理解并發(fā)場(chǎng)景下的內(nèi)存安全問(wèn)題。另外還有個(gè)細(xì)節(jié)容易被忽略std::mutex類型的變量不能拷貝所以包含互斥鎖的類不能直接放進(jìn)std::vector或使用默認(rèn)拷貝構(gòu)造。如果類里需要互斥鎖又必須實(shí)現(xiàn)拷貝只能自定義拷貝邏輯這一般在筆試簡(jiǎn)答題里出現(xiàn)。4.2 設(shè)計(jì)模式的經(jīng)典問(wèn)法與答題思路C方向筆試涉及設(shè)計(jì)模式時(shí)最??嫉氖菃卫J狡浯问怯^察者模式、工廠模式。單例模式問(wèn)你如何實(shí)現(xiàn)線程安全的單例這道題有一個(gè)標(biāo)準(zhǔn)的進(jìn)化路線懶漢式加鎖在獲取實(shí)例的成員函數(shù)里加鎖但性能差雙重檢查鎖先判斷指針是否為空為空才加鎖加鎖后再判斷一次注意防止指令重排序要加內(nèi)存屏障C11之后的靜態(tài)局部變量初始化編譯器保證線程安全。class Singleton { public: static Singleton getInstance() { static Singleton instance; return instance; } Singleton(const Singleton) delete; Singleton operator(const Singleton) delete; private: Singleton() {} };這個(gè)寫(xiě)法的核心在static局部變量C11標(biāo)準(zhǔn)規(guī)定它的初始化是線程安全的所以最簡(jiǎn)潔高效的實(shí)現(xiàn)方式就是靠它。把構(gòu)造函數(shù)設(shè)為私有并把拷貝構(gòu)造刪除防止外部創(chuàng)建實(shí)例。觀察者模式的考察方式一般是用一句話說(shuō)明模式中Subject和Observer的關(guān)系或者讓你寫(xiě)一個(gè)簡(jiǎn)化版本。答題時(shí)不需要硬背UML圖只要說(shuō)清主題對(duì)象維護(hù)一個(gè)觀察者列表狀態(tài)變化時(shí)遍歷列表通知觀察者更新即可寫(xiě)代碼時(shí)用std::vectorstd::functionvoid()來(lái)存放回調(diào)比定義抽象觀察者基類更符合現(xiàn)代C的做法。4.3 C11之后的新特性constexpr、智能指針與回調(diào)C11是C發(fā)展史的分水嶺筆試中涉及新特性的題目逐年增加。一個(gè)常見(jiàn)的送分題是constexpr是哪個(gè)C版本引入的。答案是C11引入C14放寬了函數(shù)內(nèi)可以包含的邏輯C17又允許在if和switch中聲明變量C20進(jìn)一步支持constexpr虛函數(shù)和constexpr動(dòng)態(tài)分配。這個(gè)問(wèn)題不難但很多人會(huì)答錯(cuò)成C14或C17。智能指針也是重點(diǎn)。unique_ptr是獨(dú)占所有權(quán)語(yǔ)義不可拷貝只能移動(dòng)shared_ptr是共享所有權(quán)語(yǔ)義通過(guò)引用計(jì)數(shù)管理生命周期weak_ptr用于打破循環(huán)引用它不會(huì)增加引用計(jì)數(shù)。筆試?yán)锝?jīng)常給一段代碼問(wèn)你shared_ptr管理的對(duì)象什么時(shí)候析構(gòu)。關(guān)鍵是要能發(fā)現(xiàn)循環(huán)引用兩個(gè)對(duì)象互相持有對(duì)方的shared_ptr引用計(jì)數(shù)永遠(yuǎn)不為零析構(gòu)函數(shù)永遠(yuǎn)不會(huì)被調(diào)用。解決辦法就是把其中一個(gè)方向的指針改為weak_ptr?;卣{(diào)函數(shù)的題目這幾年越來(lái)越多主要問(wèn)法有回調(diào)函數(shù)是什么、在C里怎么實(shí)現(xiàn)。從最傳統(tǒng)C函數(shù)指針到std::functionstd::bind再到C11的lambda表達(dá)式三層遞進(jìn)。答題時(shí)寫(xiě)出lambda版本是最討巧的因?yàn)榇a簡(jiǎn)潔且捕獲列表能控制捕獲方式std::functionint(int, int) add [](int a, int b) { return a b; };如果深究還可以講一下回調(diào)在異步IO、定時(shí)器、事件循環(huán)中的底層作用。理解到這個(gè)層面筆試的簡(jiǎn)答題分?jǐn)?shù)基本穩(wěn)了。4.4 現(xiàn)場(chǎng)答題的代碼風(fēng)格與踩坑預(yù)防編程題除了算法正確性代碼風(fēng)格也會(huì)影響整體評(píng)價(jià)。我當(dāng)時(shí)總結(jié)出幾條實(shí)用原則變量命名要有含義i、j、k可以用在循環(huán)里但最好不要出現(xiàn)tmp1、tmp2這種寫(xiě)完代碼要自查邊界條件空數(shù)組、單元素?cái)?shù)組、全相同元素?cái)?shù)組、指針為空的情況涉及數(shù)組下標(biāo)的地方留意是不是會(huì)越界尤其是while循環(huán)里同時(shí)訪問(wèn)i和i1的情況能用常量引用做參數(shù)的就用常量引用避免無(wú)意義拷貝這既是性能優(yōu)化也是代碼習(xí)慣的體現(xiàn)。還有一個(gè)小技巧筆試環(huán)境沒(méi)有本地IDE的自動(dòng)糾錯(cuò)代碼寫(xiě)完之后自己是沒(méi)法編譯運(yùn)行的所以要靠人工編譯自查。我會(huì)在腦中模擬一次執(zhí)行過(guò)程用一個(gè)小例子從頭走一遍比如排序算法用一個(gè)5元素?cái)?shù)組模擬基本能發(fā)現(xiàn)絕大多數(shù)低級(jí)錯(cuò)誤。5. 復(fù)盤(pán)與延伸這套卷子留給今天的備考建議5.1 筆試后的復(fù)盤(pán)方法不管是這套愛(ài)奇藝的卷子還是其他公司的筆試卷考完以后最重要的事是復(fù)盤(pán)不是看分?jǐn)?shù)。復(fù)盤(pán)第一步是把每道題按會(huì)做但錯(cuò)了蒙對(duì)的完全不會(huì)三個(gè)標(biāo)簽分類。第二步是回來(lái)查每一個(gè)錯(cuò)題背后的知識(shí)點(diǎn)找到知識(shí)盲區(qū)后補(bǔ)一輪系統(tǒng)學(xué)習(xí)而不是只看這道題的解析。我當(dāng)時(shí)會(huì)把所有錯(cuò)題對(duì)應(yīng)的知識(shí)點(diǎn)整理成一份清單比如指針數(shù)組和數(shù)組指針辨析移動(dòng)構(gòu)造的生成條件單調(diào)棧應(yīng)用場(chǎng)景。每收集一個(gè)新知識(shí)點(diǎn)就往清單里加到秋招結(jié)束時(shí)這份清單已經(jīng)覆蓋了上百個(gè)考點(diǎn)。筆試前重看一遍這份清單比刷一大堆新題更有針對(duì)性。5.2 從筆試到技術(shù)面試的知識(shí)銜接筆試通過(guò)后緊接著是技術(shù)面試很多在筆試?yán)锟嫉闹R(shí)點(diǎn)會(huì)在面試中以追問(wèn)形式出現(xiàn)。比如筆試考了單例模式面試就會(huì)追問(wèn)靜態(tài)局部變量初始化為什么線程安全或者如果我需要提前釋放單例對(duì)象怎么辦。因此筆試復(fù)盤(pán)的內(nèi)容其實(shí)也是技術(shù)面試的復(fù)習(xí)素材你需要從知道怎么回事升級(jí)到能講清楚原理。編程題也一樣筆試?yán)飳?xiě)了快速冪面試時(shí)可能會(huì)讓你口頭分析復(fù)雜度并說(shuō)明為什么取模要分布在整個(gè)計(jì)算過(guò)程中。準(zhǔn)備面試的時(shí)候把筆試中遇到的每個(gè)知識(shí)點(diǎn)都往深處想一想基本就能覆蓋大部分問(wèn)題。5.3 幾條具體的刷題和復(fù)習(xí)建議結(jié)合我自己的經(jīng)驗(yàn)給準(zhǔn)備秋招C方向的同學(xué)幾條實(shí)操建議語(yǔ)言基礎(chǔ)要系統(tǒng)過(guò)一遍推薦找一本講C原理的書(shū)完整讀下來(lái)做筆記、寫(xiě)示例代碼不要只看博客碎片算法題按專題刷數(shù)組、字符串、鏈表、樹(shù)、動(dòng)態(tài)規(guī)劃、數(shù)學(xué)計(jì)算各找20到30道代碼手寫(xiě)到熟練為止多線程、設(shè)計(jì)模式這類半基礎(chǔ)題一定要?jiǎng)邮謱?xiě)demo光看概念記不住比如自己實(shí)現(xiàn)一個(gè)線程安全的單例再用兩個(gè)線程并發(fā)調(diào)用測(cè)試每次筆試完都要把錯(cuò)題整理到個(gè)人題庫(kù)里標(biāo)記好錯(cuò)誤原因和正確思路。如果時(shí)間有限優(yōu)先保證基礎(chǔ)和常見(jiàn)算法題的正確率這兩項(xiàng)撐起了筆試的大部分分?jǐn)?shù)。我之前見(jiàn)過(guò)不少同學(xué)在偏題怪題上花了很多時(shí)間結(jié)果基礎(chǔ)選擇題錯(cuò)一片編程題也沒(méi)寫(xiě)完非常可惜。把握住自己能穩(wěn)穩(wěn)拿分的部分再逐步提升難度這才是校招筆試最務(wù)實(shí)的策略。