抱佛腳一時爽锌仅,一直抱佛腳一直爽撒会!這篇文章總結(jié)常見的c++面試問題~因?yàn)槭潜Х鹉_,所以結(jié)構(gòu)上沒有什么邏輯...
參考鏈接:Waking-Up CycNotes 欧构客網(wǎng) 博客園
面向?qū)ο蟮娜筇攸c(diǎn)
封裝废士、繼承、多態(tài)
c++中多態(tài)的實(shí)現(xiàn)
多態(tài)類型 | 時期 | 栗子 |
---|---|---|
重載多態(tài) | 編譯期 | 函數(shù)重載硫麻、運(yùn)算符重載 |
子類型多態(tài) | 運(yùn)行期 | 虛函數(shù) |
參數(shù)多態(tài) | 編譯期 | 函數(shù)模板爸邢、類模板 |
強(qiáng)制多態(tài) | 編譯/運(yùn)行期 | 基本類型轉(zhuǎn)換、自定義類型轉(zhuǎn)換 |
static
全局靜態(tài)變量
存在靜態(tài)存儲區(qū)拿愧,整個程序運(yùn)行期間都存在
默認(rèn)初始值為0
作用域:從定義處到文件結(jié)尾杠河;在聲明它的文件之外不可見(非靜態(tài)的全局變量是可以外鏈接的)
局部靜態(tài)變量
存在靜態(tài)存儲區(qū),整個程序運(yùn)行期間都存在
默認(rèn)初始值為0
作用域:定義它的函數(shù)/語句塊內(nèi)(離開作用域后并未被銷毀浇辜,而是駐留在內(nèi)存中券敌,只是不能訪問,直到該函數(shù)/語句塊再次被調(diào)用柳洋,此時它的值不變)
靜態(tài)函數(shù)
作用域:從定義處到文件結(jié)尾待诅;在聲明它的文件之外不可見,其他文件可以定義同名函數(shù)不會沖突(非靜態(tài)函數(shù)的定義和聲明默認(rèn)是extern的)
使用注意:不要在頭文件中聲明static的全局函數(shù)熊镣;不要在cpp文件中聲明非static的全局函數(shù)
靜態(tài)函數(shù)不能是虛函數(shù)
類的靜態(tài)成員
初始化:必須在類聲明的外部初始化卑雁;初始化時不能加static
內(nèi)存分配:在類外初始化時分配內(nèi)存
訪問:可以通過對象或類來訪問
類的靜態(tài)成員函數(shù)
訪問:只能訪問類的靜態(tài)成員和靜態(tài)成員函數(shù)
調(diào)用:可以通過對象或類來調(diào)用
this指針:沒有this指針
定義:必須在類外定義;定義時不能加static
static的作用小結(jié)
對函數(shù)和全局變量:標(biāo)識符的鏈接屬性由默認(rèn)的external變?yōu)閕nternal
對局部變量:存儲區(qū)域由棧變?yōu)殪o態(tài)存儲區(qū)绪囱;生存期變?yōu)檎麄€程序運(yùn)行期間都存在
對類成員:所有對象共享
c++ vs c
c++面向?qū)ο蟛舛祝琧面向過程
c++有封裝、繼承鬼吵、多態(tài)三種特性
類型轉(zhuǎn)換
顯示類型轉(zhuǎn)換
轉(zhuǎn)換 | 轉(zhuǎn)換完成時期 | 適用對象 | 作用 |
---|---|---|---|
const_cast | 編譯期 | 指針扣甲、引用 | 去掉const/volatile |
static_cast | 編譯期 | 非const轉(zhuǎn)const、基礎(chǔ)類型轉(zhuǎn)換而柑、void*轉(zhuǎn)指針文捶、子類轉(zhuǎn)父類 可用于父類轉(zhuǎn)子類但不安全,父類轉(zhuǎn)自類應(yīng)該用dynamic_cast 告訴編譯器不在乎精度損失 | |
dynamic_cast | 運(yùn)行期 | 指針媒咳、引用 | 含有虛函數(shù)的父類粹排,在執(zhí)行到語句時動態(tài)轉(zhuǎn)為子類 |
reinterpret_cast | 編譯期 | 不安全,盡量少用 |
隱式類型轉(zhuǎn)換
內(nèi)置類型:低精度變量可隱式轉(zhuǎn)換為高精度變量
對象:若某對象的構(gòu)造函數(shù)只有一個形參a涩澡,則其他函數(shù)在以該對象為實(shí)參進(jìn)行傳遞時顽耳,可以直接傳a,編譯器將自動調(diào)用構(gòu)造函數(shù),把a(bǔ)隱式轉(zhuǎn)換為一個臨時對象
指針 vs 引用
指針是變量(有自己的存儲空間)射富,引用是別名
指針初始化為nullptr或不初始化膝迎,引用必須初始化為已有對象的引用
指針可以改變指向的對象,引用不能改變引用的對象
sizeof指針是4胰耗,sizeof引用是被引用對象的大小
智能指針
概述
避免忘記釋放堆空間的問題
是對普通指針的封裝
會在析構(gòu)函數(shù)中釋放申請的內(nèi)存
auto_ptr
已被c++ 11棄用
采用獨(dú)占模式
p2 = p1會讓p2剝奪p1對指向?qū)ο蟮乃袡?quán)限次,編譯不報錯,但運(yùn)行時再訪問p1會報錯
unique_ptr
替換auto_ptr
獨(dú)占模式:同一時間只有一個智能指針可以指向該對象
p2 = p1會讓p2剝奪p1對指向?qū)ο蟮乃袡?quán)柴灯,若p1是右值卖漫,編譯不報錯;否則報錯
shared_ptr
多個智能指針可以指向同一對象赠群,該對象會在計(jì)數(shù)為0時釋放資源
一些函數(shù):swap(交換兩個指針?biāo)鶕碛械膶ο螅┭蚴迹籸eset(放棄對象的所有權(quán),計(jì)數(shù)--)查描;get(返回普通指針)
不能用普通指針初始化shared_ptr突委,因?yàn)閟hared_ptr是一個類,必須通過make_shared函數(shù)或者shared_ptr的構(gòu)造函數(shù)
weak_ptr
作用:當(dāng)shared_ptr相互引用構(gòu)成死鎖時(比如兩個對象相互使用一個shared_ptr成員變量指向?qū)Ψ剑┒梢园哑渲幸粋€改為weak_ptr
只可以從一個shared_ptr或另一個weak_ptr來構(gòu)造
不會引起use_count的變化
shared_ptr可以直接賦值給weak_ptr匀油;weak_ptr要調(diào)用lock函數(shù)才能轉(zhuǎn)化為shared_ptr
weak_ptr必須轉(zhuǎn)化為shared_ptr才能訪問指向的對象
野指針
指向一個已刪除的對象或未申請?jiān)L問受限內(nèi)存區(qū)域的指針
段錯誤
訪問非法內(nèi)存地址,如使用野指針勾笆、試圖修改字符串常量的內(nèi)容
虛函數(shù)
為什么析構(gòu)函數(shù)必須是虛函數(shù)
析構(gòu)函數(shù)是虛函數(shù)可以保證使用基類指針指向子類對象時钧唐,釋放基類指針時可以釋放掉子類的空間,防止內(nèi)存泄漏
為什么c++默認(rèn)的析構(gòu)函數(shù)不是虛函數(shù)
虛函數(shù)需要額外的虛函數(shù)表和虛表指針匠襟,占用內(nèi)存,對于不會被繼承的類來說是一種浪費(fèi)
為什么構(gòu)造函數(shù)不能是虛函數(shù)
在調(diào)用構(gòu)造函數(shù)時该园,虛表指針并沒有在對象的內(nèi)存空間中酸舍,必須要構(gòu)造函數(shù)調(diào)用完成后才會形成虛表指針
純虛函數(shù)
是一種特殊的虛函數(shù),在基類中不能對虛函數(shù)給出有意義的實(shí)現(xiàn)里初,而把它聲明為純虛函數(shù)啃勉,它的實(shí)現(xiàn)留給該基類的派生類去做
虛函數(shù)表與虛函數(shù)指針
有虛函數(shù)的對象的內(nèi)存最開始是虛函數(shù)指針,指向數(shù)據(jù)段中的虛函數(shù)表
虛函數(shù)表是一個函數(shù)指針數(shù)組双妨,其中的函數(shù)指針指向要執(zhí)行的函數(shù)的入口地址
增加一個虛函數(shù), 只是簡單地向該類對應(yīng)的虛函數(shù)表中增加一項(xiàng)而已, 并不會影響到類對象的大小以及布局情況
子類繼承父類時淮阐,會繼承它的虛函數(shù)表,但若它重寫了父類的虛函數(shù)刁品,會把繼承到的虛函數(shù)表中的函數(shù)替換為重寫的函數(shù)
同一個類的不同實(shí)例共用同一份虛函數(shù)表(父類和子類是兩個類泣特,所以有兩張?zhí)摵瘮?shù)表)
虛函數(shù)表是編譯時期創(chuàng)建好的
運(yùn)行時類型檢查 RTTI
虛函數(shù)表的-1位置存放了指向type_info的指針
對于存在虛函數(shù)的類型,typeid和dynamic_cast都會查詢type_info
析構(gòu)函數(shù)
只能有一個析構(gòu)函數(shù)挑随,不能重載
不能有參數(shù)和返回值
即使自定義了析構(gòu)函數(shù)状您,編譯器也會合成析構(gòu)函數(shù),執(zhí)行時,先調(diào)用自定義析構(gòu)函數(shù)再調(diào)用合成的析構(gòu)函數(shù)
拷貝構(gòu)造函數(shù)
其形參不能是值傳遞膏孟,否則眯分,調(diào)用拷貝構(gòu)造函數(shù)的時候,實(shí)參傳遞給形參需要調(diào)用拷貝構(gòu)造函數(shù)柒桑,一直循環(huán)弊决,直到棧滿
i++ vs ++i
i++返回右值,++i返回左值
因?yàn)闆]有生成臨時變量魁淳,所以++i更快
main函數(shù)
main函數(shù)執(zhí)行前執(zhí)行了什么
設(shè)置棧指針
初始化數(shù)據(jù)段內(nèi)容
初始化BSS段內(nèi)容:賦默認(rèn)初始值或調(diào)用默認(rèn)構(gòu)造函數(shù)
將main函數(shù)的參數(shù)(argc飘诗、argv)傳給main函數(shù)
如果使用gcc,可以用attribute((constructor)) void func() 編寫想在main執(zhí)行前執(zhí)行的代碼
main函數(shù)執(zhí)行后執(zhí)行了什么
全局對象的析構(gòu)函數(shù)
被注冊到onexit的函數(shù) // 注冊方法:onexit( fn1 );
如果使用gcc先改,可以用attribute((destructor))void after()
main的參數(shù)
- argv中的第一個元素要么指向程序名字疚察,要么指向空字符串
const
初始化
常量在定義時必須初始化
存放位置
局部常量:棧
全局常量:數(shù)據(jù)段
字面值常量:代碼段的常量存儲區(qū)
常成員函數(shù)
表示調(diào)用該函數(shù)不會對對象做出任何更改
const/非const對象均可調(diào)用
只有成員函數(shù)才可以是常函數(shù)
常變量、指向常變量的指針\引用只能調(diào)用常成員函數(shù)
常成員函數(shù)中的 this 是 const T *const 型仇奶,即 this 指向常量
const vs #define
const有數(shù)據(jù)類型貌嫡,在編譯階段進(jìn)行替換,可以進(jìn)行類型安全檢查该溯;#define在預(yù)編譯階段替換岛抄,沒有數(shù)據(jù)類型檢查,只進(jìn)行字符替換狈茉,不計(jì)算夫椭、不做表達(dá)式求解
const給出相應(yīng)的內(nèi)存地址,在程序運(yùn)行過程中只有一份拷貝氯庆;define給出立即數(shù)蹭秋,可能會有多個拷貝
extern
const變量默認(rèn)是local to the file的,所以如果要多個文件共享一個const變量堤撵,需要在定義它時也加上extern
const & *
const T x仁讨、T const x x是常變量,top-level const
const T& x实昨、T const& x 引用常量的引用洞豁,low-level const
const T* x、T const* x 指向常量的指針荒给,low-level const
T* const x 常指針丈挟,top-level const
const T* const x、T const * const x 既不能修改其指向?qū)ο笠膊荒苄薷钠渥陨碇赶蛭恢玫闹羔?/p>
const T& x志电、T const& x 對指向常量的指針的引用
T* const& x 對T*的常引用
T const * const & x 對const T*的常引用
top/low-level const
復(fù)制top-level const的對象時曙咽,其const屬性會被忽略
復(fù)制low-level const的對象時,其const屬性不會被忽略
auto類型推斷時溪北,忽略top-level const桐绒,保留low-level const
constexpr
字面量和被常表達(dá)式初始化的常對象是常表達(dá)式
其值不能改變夺脾,在編譯時evaluate
constexpr指針/引用只能指向/引用有固定地址的變量:即存在數(shù)據(jù)段/BSS段的
constexpr函數(shù)是隱式inline的:the compiler will replace a call to a constexpr function with its resulting value
構(gòu)造函數(shù)不能是常函數(shù)
定義一個常對象時,the object does not assume its “constness” until after the constructor completes the object’s initialization茉继,因此咧叭,constructors can write to const objects during their construction
new/delete vs malloc/free
前者是c++關(guān)鍵字,后者是c關(guān)鍵字
使用malloc時必須指明申請的空間大小烁竭,new不需要
前者會調(diào)用構(gòu)造函數(shù)和析構(gòu)函數(shù)菲茬,后者不會
new是操作符,可以重載派撕;malloc是庫函數(shù)
new失敗拋出異常婉弹,malloc失敗返回NULL
malloc返回的指針需要強(qiáng)轉(zhuǎn)(因?yàn)榉祷氐氖莢oid*)
malloc原理
申請小內(nèi)存時
遍歷空閑塊鏈表,分配一塊合適的空閑塊
空閑塊鏈表:一個雙向鏈表把所有空閑塊連接起來
申請大內(nèi)存時
使用系統(tǒng)調(diào)用mmap分配
在堆和棧中間的文件映射區(qū)找一塊空閑的虛擬內(nèi)存终吼,初始值為0
注意
分配的都是虛擬內(nèi)存镀赌,沒有分配物理內(nèi)存
在第一次訪問已分配的虛擬地址空間的時候,發(fā)生缺頁中斷际跪,操作系統(tǒng)負(fù)責(zé)分配物理內(nèi)存商佛,然后建立虛擬內(nèi)存和物理內(nèi)存之間的映射關(guān)系
STL
sort
底層算法:內(nèi)省排序
首先從快速排序開始,當(dāng)遞歸深度超過一定深度(深度為排序元素數(shù)量的對數(shù)值)后轉(zhuǎn)為堆排序
allocator
用于封裝STL容器在內(nèi)存管理上的底層細(xì)節(jié)
在堆上分配空間
alloc::allocate()負(fù)責(zé)配置內(nèi)存姆打,::construct()負(fù)責(zé)構(gòu)造對象良姆,alloc::deallocate()負(fù)責(zé)釋放內(nèi)存,::destroy()負(fù)責(zé)析構(gòu)對象
-
采用兩級配置器:
分配大內(nèi)存時幔戏,使用第一級空間配置器玛追,調(diào)用malloc的mmap
分配小內(nèi)存時,使用第二級空間配置器闲延,采用內(nèi)存池技術(shù)痊剖,通過空閑鏈表管理內(nèi)存,內(nèi)存池中有16個空閑鏈表垒玲,每個鏈表中的節(jié)點(diǎn)大小分別為8邢笙、16、24...128
迭代器失效
序列容器vector/deque:iter后的每個元素的迭代器都會失效侍匙,iter后的每個元素都會往前移動,erase返回下一個有效的迭代器
關(guān)聯(lián)容器map/set:iter失效叮雳,iter后的迭代器不會失效(因?yàn)橛玫氖羌t黑樹)想暗,earse不會返回下一個有效的迭代器
list:iter失效,iter后的迭代器不會失效帘不,erase返回下一個有效的迭代器
迭代器 vs 指針
迭代器是類模板说莫,是指針的封裝,重載了指針的操作符寞焙,提供了比指針更高級的行為(比如根據(jù)不同類型的數(shù)據(jù)結(jié)構(gòu)實(shí)現(xiàn)不同的++和--)
resize vs reserve
resize改變的是size储狭,若原size小于len互婿,則新增0作為元素
reserve改變的是capacity,若原cap小于len辽狈,則重新分配一塊可以存len個對象的空間慈参,把原vector拷貝過來,銷毀之前的內(nèi)存
map
底層是紅黑樹
const map中不允許用[]
set
底層是紅黑樹
迭代器不允許修改元素
vector
底層是動態(tài)數(shù)組
任何改變?nèi)萜鞔笮〉牟僮鞫伎赡苁沟魇?/p>
引用不是對象刮萌,所以無法把vector實(shí)例化為引用
vector<noDefault> v2(10); // error: must supply an element initializer
list
底層是雙向鏈表
prev next
unordered_map
底層是哈希表(數(shù)組)+鏈表
通過拉鏈法解決哈希沖突驮配,用頭插法新增元素
鏈表大小超過8會轉(zhuǎn)為紅黑樹
unordered_set
- 與unordered_map類似,集合元素是key
deque
底層:以分段連續(xù)空間組合而成着茸,隨時可以增加一段新空間連接起來 (list和vector綜合)
stack壮锻、queue
priority_queue
底層是vector(因?yàn)槎咽峭耆鏄洌?/p>
默認(rèn)用max_heap
編譯過程
預(yù)處理階段:對include的頭文件、宏定義語句進(jìn)行分析和替換涮阔,去除注釋猜绣,生成預(yù)編譯文件
編譯階段:把預(yù)編譯文件經(jīng)過詞法分析、語法分析敬特、語義分析轉(zhuǎn)換為匯編代碼掰邢,生成匯編文件 先編譯 member declarations,再編譯 member function bodies
匯編階段:把匯編文件轉(zhuǎn)換為目標(biāo)機(jī)器指令擅羞,生成可重定位目標(biāo)文件(含代碼段尸变、數(shù)據(jù)段)
鏈接階段(靜態(tài)鏈接):將多個目標(biāo)文件及所需要的庫連接成最終的可執(zhí)行目標(biāo)文件 注意:動態(tài)鏈接在運(yùn)行時才被載入;不同應(yīng)用程序調(diào)用相同的庫時减俏,內(nèi)存只需要有一份該共享庫的實(shí)例召烂,避免空間浪費(fèi)
"" vs <>
預(yù)處理階段查找頭文件的路徑不同
"":當(dāng)前頭文件目錄、編譯器設(shè)置的頭文件路徑(可使用-I顯式指定)娃承、系統(tǒng)變量C_INCLUDE_PATH指定的頭文件路徑
<>:編譯器設(shè)置的頭文件路徑(可使用-I顯式指定)奏夫、系統(tǒng)變量C_INCLUDE_PATH指定的頭文件路徑
c++中每個進(jìn)程的虛擬內(nèi)存
從上(低地址)至下(高地址)依次是:
-
內(nèi)核空間(1G)
用戶代碼無法訪問這一塊,通過系統(tǒng)調(diào)用進(jìn)入內(nèi)核態(tài)后才能訪問
所有進(jìn)程共享一個內(nèi)核空間
由于內(nèi)核態(tài)空間與用戶態(tài)空間采用了不同的映射機(jī)制历筝,雖然內(nèi)核態(tài)只有1GB的虛擬地址空間酗昼,但是它可以訪問所有的物理內(nèi)存地址
-
用戶空間(3G)
-
代碼段(正文段):由exec從程序文件中讀入
常量存儲區(qū)(只讀存儲區(qū)):存儲字符串常量
文本區(qū):存儲程序的機(jī)器代碼
-
數(shù)據(jù)段(已初始化數(shù)據(jù)區(qū)):由exec從程序文件中讀入
- 存儲已初始化的全局變量和靜態(tài)變量
-
BSS段:由exec初始化為0
存儲未初始化的全局變量和靜態(tài)變量
程序結(jié)束后資源由系統(tǒng)自動釋放
并不占用可執(zhí)行文件的大小(即:存儲在磁盤中的可執(zhí)行文件只包括代碼段和數(shù)據(jù)段)梳猪,它是由鏈接器獲取內(nèi)存的
-
堆區(qū)
大小不確定
由程序員申請麻削、釋放
申請堆空間時庫函數(shù)按照一定的算法搜索可用的足夠大的空間,所以效率比棧低很多
-
文件映射區(qū)
- 存儲動態(tài)鏈接庫等文件映射春弥、申請大內(nèi)存時mmap函數(shù)分配的對象
-
棧區(qū)
大小確定
能從棧中獲取的空間較小
存儲函數(shù)的返回地址呛哟、參數(shù)、局部變量匿沛、返回值
由編譯器自動釋放
命令行參數(shù)和環(huán)境變量
-
內(nèi)存泄漏的原因
用了malloc/new/reallco扫责,沒用free/delete,造成堆內(nèi)存泄漏
用了系統(tǒng)分配的資源handle/socket等逃呼,沒釋放鳖孤,造成系統(tǒng)資源泄漏
沒有將基類的析構(gòu)函數(shù)定義為虛函數(shù)者娱,子類的資源沒有正確釋放
c++ 11新特性
auto關(guān)鍵字:編譯器可以進(jìn)行自動類型推斷,但不能用于函數(shù)傳參及數(shù)組類型的推斷
nullptr關(guān)鍵字
智能指針
可以使用初始化列表來對類進(jìn)行初始化
右值引用:實(shí)現(xiàn)移動語義和完美轉(zhuǎn)發(fā)
atomic原子操作用于多線程資源互斥操作
新增了一些STL容器:unordered_map unordered_set array等
lambda表達(dá)式:定義一個匿名函數(shù)苏揣,可以捕獲上下文中的變量
可變參數(shù)模板
nullptr vs NULL
NULL是宏定義黄鳍,值為0,含義是void*腿准,不能轉(zhuǎn)換為任意類型的指針
nullptr是字面值常量际起,可以轉(zhuǎn)換為任意類型的指針
可變參數(shù)模板
可表示任意數(shù)目、任意類型的參數(shù)
Template<class ... T> void func(int n, T ... args)
右值引用的作用
移動語義
用右值引用作為形參類型吐葱,實(shí)現(xiàn)移動構(gòu)造函數(shù)街望,將形參(是一個臨時對象)的指針成員變量指向nullptr,將新構(gòu)造的對象的指針成員變量指向要指向的數(shù)據(jù)
完美轉(zhuǎn)發(fā)
指傳入轉(zhuǎn)發(fā)函數(shù)(函數(shù)模板中調(diào)用的函數(shù)模板)的是左(右)值對象弟跑,那目標(biāo)函數(shù)(轉(zhuǎn)發(fā)函數(shù)通過類型推斷得到)就能獲得左(右)值對象
需要將轉(zhuǎn)發(fā)函數(shù)和目標(biāo)函數(shù)的形參都設(shè)置為右值引用灾前,或者在調(diào)用轉(zhuǎn)發(fā)函數(shù)時,用forward<T>(t)作為實(shí)參
forward<T>(t)的作用:當(dāng)T是左(右)值引用時孟辑,t被轉(zhuǎn)為T類型的左(右)值
內(nèi)聯(lián)函數(shù)
關(guān)鍵字inline必須與函數(shù)定義放在一起才能使函數(shù)成為內(nèi)聯(lián)函數(shù)哎甲,僅僅將inline放在函數(shù)聲明前面不起任何作用
inline函數(shù)可以多次定義,但每次定義必須是一樣的
定義在類中的函數(shù)是默認(rèn)inline的
當(dāng)虛函數(shù)表現(xiàn)多態(tài)性的時候不能內(nèi)聯(lián)
與宏定義的區(qū)別:宏定義是預(yù)編譯時替換的饲嗽,沒有類型檢查炭玫;內(nèi)聯(lián)函數(shù)是在編譯時替換的,有類型檢查
適用場景:一個函數(shù)不斷地被重復(fù)調(diào)用貌虾,且函數(shù)只有簡單幾行吞加,且函數(shù)不包括for,while,switch語句
與static的區(qū)別:inline 因?yàn)榫幾g階段代碼展開導(dǎo)致函數(shù)本文件可見, static 符號屬性為local 本文件可見
-
優(yōu)點(diǎn)
在被調(diào)用處進(jìn)行代碼展開,省去了參數(shù)壓棧尽狠、棧幀開辟與回收衔憨,結(jié)果返回等,從而提高程序運(yùn)行速度
相比宏函數(shù)來說袄膏,在代碼展開時践图,會做安全檢查或自動類型轉(zhuǎn)換(同普通函數(shù))
在類中聲明同時定義的成員函數(shù),自動轉(zhuǎn)化為內(nèi)聯(lián)函數(shù)沉馆,因此內(nèi)聯(lián)函數(shù)可以訪問類的成員變量码党,宏定義則不能
內(nèi)聯(lián)函數(shù)在運(yùn)行時可調(diào)試,而宏定義不可以
-
缺點(diǎn)
代碼膨脹耗時間斥黑、耗空間
inline 函數(shù)無法隨著函數(shù)庫升級而升級闽瓢。inline函數(shù)的改變需要重新編譯,不像 non-inline 可以直接鏈接
是否內(nèi)聯(lián)心赶,程序員不可控。內(nèi)聯(lián)函數(shù)只是對編譯器的建議缺猛,是否對函數(shù)內(nèi)聯(lián)缨叫,決定權(quán)在于編譯器
-
編譯器對inline的處理步驟
把內(nèi)聯(lián)函數(shù)的代碼副本放置在每一個調(diào)用它的地方(其他函數(shù)都是運(yùn)行時才被替代)
為inline 函數(shù)中的局部變量分配內(nèi)存空間
將 inline 函數(shù)的的輸入?yún)?shù)和返回值映射到調(diào)用該函數(shù)的局部變量空間中
如果 inline 函數(shù)有多個返回點(diǎn)椭符,將其轉(zhuǎn)變?yōu)?inline 函數(shù)代碼塊末尾的分支(使用 GOTO)
內(nèi)存溢出 vs 內(nèi)存泄漏
內(nèi)存溢出
程序申請內(nèi)存時,沒有足夠的內(nèi)存空間內(nèi)供其使用
-
栗子
一次從db中取出過多數(shù)據(jù)
有死循環(huán)產(chǎn)生過多重復(fù)的對象實(shí)體
遞歸調(diào)用層次過多
局部數(shù)組過大
指針或數(shù)組越界
內(nèi)存泄漏
程序未釋放已申請的內(nèi)存空間
為什么棧的效率比堆高
棧是OS提供的數(shù)據(jù)結(jié)構(gòu)耻姥,計(jì)算機(jī)底層對棧提供了一系列支持:分配專門的寄存器存儲棧的地址销钝,壓棧和入棧有專門的指令執(zhí)行
堆是c/c++函數(shù)庫提供的,機(jī)制復(fù)雜琐簇,需要一些分配內(nèi)存蒸健、合并內(nèi)存、釋放內(nèi)存的算法
各種情況的初始值
內(nèi)置類型和array
若在塊內(nèi)婉商、非static似忧、未給初始值:執(zhí)行默認(rèn)初始化,其初始值未定義
否則:執(zhí)行值初始化丈秩,初始值為0
復(fù)合類型
引用:聲明時必須初始化
指針:執(zhí)行默認(rèn)初始化盯捌,其初始值未定義
靜態(tài)變量
- 執(zhí)行值初始化,初始值為0
STL對象/類
- 無論是默認(rèn)初始化還是值初始化蘑秽,都調(diào)用其默認(rèn)構(gòu)造函數(shù)饺著,初始值都一般為空對象(比如 string 初始值是 "")
類中未初始化變量
- 執(zhí)行默認(rèn)初始化,其初始值未定義
初始化時初始值數(shù)量小于其維度的數(shù)組
- 剩下的元素會進(jìn)行值初始化肠牲,初始值為0
使用形如T()的表達(dá)式顯示請求值初始化的
- 執(zhí)行值初始化
結(jié)構(gòu)體
- 未初始化的成員會有默認(rèn)的初始值
decltype
使用函數(shù)時幼衰,編譯器并未調(diào)用f,但使用了f的返回類型
加括號-->變?yōu)橐?/p>
保留top-level const
對表達(dá)式使用decltype時缀雳,若表達(dá)式為左值渡嚣,則結(jié)果為引用,若表達(dá)式為右值俏险,則結(jié)果為指針
array做形參
因?yàn)椴荒軓?fù)制array严拒,所以無法通過傳值的方法,而是傳指針
在函數(shù)的形參列表里指定數(shù)組的成員個數(shù)是沒有意義的竖独,比如const int [10]的聲明編譯器自動處理為const int *
不能定義元素是引用的array:引用沒有自身的地址裤唠,不占用內(nèi)存空間,因此莹痢,聲明引用數(shù)組沒有辦法分配內(nèi)存空間
臨時變量產(chǎn)生的時機(jī)
值傳遞時
強(qiáng)制/隱式類型轉(zhuǎn)換時
常引用傳遞且需要進(jìn)行類型轉(zhuǎn)換時
++ --后置時
重載
must differ in the number or the type(s) of their parameters
有無top-level const不影響type difference
類中的const與nonconst成員函數(shù)可以重載种蘸,因?yàn)閠his指針類型不同
重載 vs 重寫 vs 隱藏
- 區(qū)別
- 當(dāng)參數(shù)列表不同時,無論基類中的函數(shù)是否被virtual修飾竞膳,基類函數(shù)都是被隱藏航瞭,而不是被重寫;參數(shù)相同時坦辟,若基類函數(shù)沒有關(guān)鍵字virtual刊侯,此時基類函數(shù)被隱藏,若有則被重寫
聲明 vs 定義
聲明是給編譯器用的锉走,定義是給連接器用的
代碼中可以調(diào)用只聲明未定義的函數(shù)滨彻,是因?yàn)樵谡{(diào)用函數(shù)處藕届,編譯器會產(chǎn)生調(diào)用函數(shù)的代碼,但編譯器并不管函數(shù)的具體實(shí)現(xiàn)(即定義)在哪里亭饵,鏈接器才需要查找函數(shù)的實(shí)現(xiàn)代碼并與函數(shù)調(diào)用代碼對上
-
與普通函數(shù)不同休偶,編譯器會生成實(shí)例化后的模板的代碼,所以the compiler needs to have the code that defines a function template or class template member function
所以headers for templates typically include definitions as well as declarations
when the compiler sees the definition of a template, it does not generate code(但會檢查模板的語法等)辜羊;編譯器 generates code only when we instantiate a specific instance of the template
-
編譯器給對象分配內(nèi)存的前提是知道其大小踏兜,所以只聲明而未定義的類類型只能用于:
define pointers or references to such types
declare (but not define) functions that use it as a parameter or return type
友元
一個類的友元函數(shù)/類可以access its nonpublic members
繼承中的訪問控制
public繼承:父類中的public、protected和private屬性在子類中不發(fā)生改變
protected繼承:父類中的public屬性在子類中變?yōu)閜rotected八秃,父類中的protected和private屬性在子類中不變
private繼承:父類中的三種訪問屬性在子類中都會變成private
=delete vs =default
=delete:告知編譯器不生成函數(shù)默認(rèn)的缺省版本
=default:告知編譯器生成函數(shù)默認(rèn)的缺省版本
空指針 vs 未初始化指針
空指針可以確保不指向任何對象或函數(shù)碱妆,未初始化指針可能指向任何地方
-
訪問空指針指向的空間,會發(fā)生:
令指針=NULL會讓它存放地址0x0
Linux 中喜德,每個進(jìn)程空間的 0x0 虛擬地址開始的線性區(qū)都會被映射到一個用戶態(tài)沒有訪問權(quán)限的頁上
編譯器把空指針當(dāng)做 0 對待山橄,開心地讓你去訪問0x0
缺頁異常處理程序被調(diào)用,因?yàn)樵?0x0 的頁沒有在物理內(nèi)存里面
缺頁異常處理程序發(fā)現(xiàn)你沒有訪問的權(quán)限
內(nèi)核發(fā)送 SIGSEGV 信號給進(jìn)程舍悯,該信號默認(rèn)是讓進(jìn)程自殺
this指針
this 并不是一個常規(guī)變量航棱,而是個右值,所以不能取得 this 的地址(不能 &this)
定義只能在堆(棧)上生成對象的類
只能在堆上
將析構(gòu)函數(shù)設(shè)為私有:編譯器在為類對象分配椕瘸模空間時饮醇,會先檢查類的析構(gòu)函數(shù)的訪問性。若析構(gòu)函數(shù)不可訪問秕豫,則不能在棧上創(chuàng)建對象
只能在棧上
將 new 和 delete 重載為私有
內(nèi)存對齊
對齊規(guī)則
第一個數(shù)據(jù)成員放在offset為0的地方朴艰,以后每個數(shù)據(jù)成員的對齊按照#pragma pack指定的數(shù)值和這個數(shù)據(jù)成員自身長度中,比較小的那個進(jìn)行
在數(shù)據(jù)成員完成各自對齊之后混移,類(結(jié)構(gòu)或聯(lián)合)本身也要進(jìn)行對齊祠墅,對齊將按照#pragma pack指定的數(shù)值和結(jié)構(gòu)(或聯(lián)合)最大數(shù)據(jù)成員長度中,比較小的那個進(jìn)行
#pragma pack(n)作為一個預(yù)編譯指令歌径,設(shè)置按多少個字節(jié)對齊毁嗦;但編譯器只會按照1、2回铛、4狗准、8、16的方式分割內(nèi)存茵肃。若n為其他值腔长,是無效的
栗子
go實(shí)現(xiàn)的并發(fā)模型
多線程并發(fā)
cpp中也有,以共享內(nèi)存的方式來通信
csp:以通信的方式來共享內(nèi)存
通過goroutine和channel實(shí)現(xiàn)验残,channel是goroutine之間通信的管道
傳數(shù)據(jù)用channel<-data捞附,取數(shù)據(jù)用<-data,它倆成對出現(xiàn);傳和取都是阻塞的鸟召,直到另外goroutine取/傳為止
main()本身就是一個goroutine