抱佛腳一時爽,一直抱佛腳一直爽!這篇文章總結(jié)常見的c++面試問題~因為是抱佛腳,所以結(jié)構(gòu)上沒有什么邏輯...
參考鏈接:Waking-Up CycNotes ??途W(wǎng) 博客園
面向?qū)ο蟮娜筇攸c
封裝、繼承、多態(tài)
c++中多態(tài)的實現(xiàn)
| 多態(tài)類型 | 時期 | 栗子 |
|---|---|---|
| 重載多態(tài) | 編譯期 | 函數(shù)重載、運算符重載 |
| 子類型多態(tài) | 運行期 | 虛函數(shù) |
| 參數(shù)多態(tài) | 編譯期 | 函數(shù)模板、類模板 |
| 強制多態(tài) | 編譯/運行期 | 基本類型轉(zhuǎn)換、自定義類型轉(zhuǎn)換 |
static
全局靜態(tài)變量
存在靜態(tài)存儲區(qū),整個程序運行期間都存在
默認初始值為0
作用域:從定義處到文件結(jié)尾;在聲明它的文件之外不可見(非靜態(tài)的全局變量是可以外鏈接的)
局部靜態(tài)變量
存在靜態(tài)存儲區(qū),整個程序運行期間都存在
默認初始值為0
作用域:定義它的函數(shù)/語句塊內(nèi)(離開作用域后并未被銷毀,而是駐留在內(nèi)存中,只是不能訪問,直到該函數(shù)/語句塊再次被調(diào)用,此時它的值不變)
靜態(tài)函數(shù)
作用域:從定義處到文件結(jié)尾;在聲明它的文件之外不可見,其他文件可以定義同名函數(shù)不會沖突(非靜態(tài)函數(shù)的定義和聲明默認是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ù)和全局變量:標識符的鏈接屬性由默認的external變?yōu)閕nternal
對局部變量:存儲區(qū)域由棧變?yōu)殪o態(tài)存儲區(qū);生存期變?yōu)檎麄€程序運行期間都存在
對類成員:所有對象共享
c++ vs c
c++面向?qū)ο?,c面向過程
c++有封裝、繼承、多態(tài)三種特性
類型轉(zhuǎn)換
顯示類型轉(zhuǎn)換
| 轉(zhuǎn)換 | 轉(zhuǎn)換完成時期 | 適用對象 | 作用 |
|---|---|---|---|
| const_cast | 編譯期 | 指針、引用 | 去掉const/volatile |
| static_cast | 編譯期 | 非const轉(zhuǎn)const、基礎類型轉(zhuǎn)換、void*轉(zhuǎn)指針、子類轉(zhuǎn)父類 可用于父類轉(zhuǎn)子類但不安全,父類轉(zhuǎn)自類應該用dynamic_cast 告訴編譯器不在乎精度損失 | |
| dynamic_cast | 運行期 | 指針、引用 | 含有虛函數(shù)的父類,在執(zhí)行到語句時動態(tài)轉(zhuǎn)為子類 |
| reinterpret_cast | 編譯期 | 不安全,盡量少用 |
隱式類型轉(zhuǎn)換
內(nèi)置類型:低精度變量可隱式轉(zhuǎn)換為高精度變量
對象:若某對象的構(gòu)造函數(shù)只有一個形參a,則其他函數(shù)在以該對象為實參進行傳遞時,可以直接傳a,編譯器將自動調(diào)用構(gòu)造函數(shù),把a隱式轉(zhuǎn)換為一個臨時對象
指針 vs 引用
指針是變量(有自己的存儲空間),引用是別名
指針初始化為nullptr或不初始化,引用必須初始化為已有對象的引用
指針可以改變指向的對象,引用不能改變引用的對象
sizeof指針是4,sizeof引用是被引用對象的大小
智能指針
概述
避免忘記釋放堆空間的問題
是對普通指針的封裝
會在析構(gòu)函數(shù)中釋放申請的內(nèi)存
auto_ptr
已被c++ 11棄用
采用獨占模式
p2 = p1會讓p2剝奪p1對指向?qū)ο蟮乃袡?quán),編譯不報錯,但運行時再訪問p1會報錯
unique_ptr
替換auto_ptr
獨占模式:同一時間只有一個智能指針可以指向該對象
p2 = p1會讓p2剝奪p1對指向?qū)ο蟮乃袡?quán),若p1是右值,編譯不報錯;否則報錯
shared_ptr
多個智能指針可以指向同一對象,該對象會在計數(shù)為0時釋放資源
一些函數(shù):swap(交換兩個指針所擁有的對象);reset(放棄對象的所有權(quán),計數(shù)--);get(返回普通指針)
不能用普通指針初始化shared_ptr,因為shared_ptr是一個類,必須通過make_shared函數(shù)或者shared_ptr的構(gòu)造函數(shù)
weak_ptr
作用:當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才能訪問指向的對象
野指針
指向一個已刪除的對象或未申請訪問受限內(nèi)存區(qū)域的指針
段錯誤
訪問非法內(nèi)存地址,如使用野指針、試圖修改字符串常量的內(nèi)容
虛函數(shù)
為什么析構(gòu)函數(shù)必須是虛函數(shù)
析構(gòu)函數(shù)是虛函數(shù)可以保證使用基類指針指向子類對象時,釋放基類指針時可以釋放掉子類的空間,防止內(nèi)存泄漏
為什么c++默認的析構(gòu)函數(shù)不是虛函數(shù)
虛函數(shù)需要額外的虛函數(shù)表和虛表指針,占用內(nèi)存,對于不會被繼承的類來說是一種浪費
為什么構(gòu)造函數(shù)不能是虛函數(shù)
在調(diào)用構(gòu)造函數(shù)時,虛表指針并沒有在對象的內(nèi)存空間中,必須要構(gòu)造函數(shù)調(diào)用完成后才會形成虛表指針
純虛函數(shù)
是一種特殊的虛函數(shù),在基類中不能對虛函數(shù)給出有意義的實現(xiàn),而把它聲明為純虛函數(shù),它的實現(xiàn)留給該基類的派生類去做
虛函數(shù)表與虛函數(shù)指針
有虛函數(shù)的對象的內(nèi)存最開始是虛函數(shù)指針,指向數(shù)據(jù)段中的虛函數(shù)表
虛函數(shù)表是一個函數(shù)指針數(shù)組,其中的函數(shù)指針指向要執(zhí)行的函數(shù)的入口地址
增加一個虛函數(shù), 只是簡單地向該類對應的虛函數(shù)表中增加一項而已, 并不會影響到類對象的大小以及布局情況
子類繼承父類時,會繼承它的虛函數(shù)表,但若它重寫了父類的虛函數(shù),會把繼承到的虛函數(shù)表中的函數(shù)替換為重寫的函數(shù)
同一個類的不同實例共用同一份虛函數(shù)表(父類和子類是兩個類,所以有兩張?zhí)摵瘮?shù)表)
虛函數(shù)表是編譯時期創(chuàng)建好的
運行時類型檢查 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ù)的時候,實參傳遞給形參需要調(diào)用拷貝構(gòu)造函數(shù),一直循環(huán),直到棧滿
i++ vs ++i
i++返回右值,++i返回左值
因為沒有生成臨時變量,所以++i更快
main函數(shù)
main函數(shù)執(zhí)行前執(zhí)行了什么
設置棧指針
初始化數(shù)據(jù)段內(nèi)容
初始化BSS段內(nèi)容:賦默認初始值或調(diào)用默認構(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ù)類型,在編譯階段進行替換,可以進行類型安全檢查;#define在預編譯階段替換,沒有數(shù)據(jù)類型檢查,只進行字符替換,不計算、不做表達式求解
const給出相應的內(nèi)存地址,在程序運行過程中只有一份拷貝;define給出立即數(shù),可能會有多個拷貝
extern
const變量默認是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
復制top-level const的對象時,其const屬性會被忽略
復制low-level const的對象時,其const屬性不會被忽略
auto類型推斷時,忽略top-level const,保留low-level const
constexpr
字面量和被常表達式初始化的常對象是常表達式
其值不能改變,在編譯時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返回的指針需要強轉(zhuǎn)(因為返回的是void*)
malloc原理
申請小內(nèi)存時
遍歷空閑塊鏈表,分配一塊合適的空閑塊
空閑塊鏈表:一個雙向鏈表把所有空閑塊連接起來
申請大內(nèi)存時
使用系統(tǒng)調(diào)用mmap分配
在堆和棧中間的文件映射區(qū)找一塊空閑的虛擬內(nèi)存,初始值為0
注意
分配的都是虛擬內(nèi)存,沒有分配物理內(nèi)存
在第一次訪問已分配的虛擬地址空間的時候,發(fā)生缺頁中斷,操作系統(tǒng)負責分配物理內(nèi)存,然后建立虛擬內(nèi)存和物理內(nèi)存之間的映射關(guān)系
STL
sort
底層算法:內(nèi)省排序
首先從快速排序開始,當遞歸深度超過一定深度(深度為排序元素數(shù)量的對數(shù)值)后轉(zhuǎn)為堆排序
allocator
用于封裝STL容器在內(nèi)存管理上的底層細節(jié)
在堆上分配空間
alloc::allocate()負責配置內(nèi)存,::construct()負責構(gòu)造對象,alloc::deallocate()負責釋放內(nèi)存,::destroy()負責析構(gòu)對象
-
采用兩級配置器:
分配大內(nèi)存時,使用第一級空間配置器,調(diào)用malloc的mmap
分配小內(nèi)存時,使用第二級空間配置器,采用內(nèi)存池技術(shù),通過空閑鏈表管理內(nèi)存,內(nèi)存池中有16個空閑鏈表,每個鏈表中的節(jié)點大小分別為8、16、24...128
迭代器失效
序列容器vector/deque:iter后的每個元素的迭代器都會失效,iter后的每個元素都會往前移動,erase返回下一個有效的迭代器
關(guān)聯(lián)容器map/set:iter失效,iter后的迭代器不會失效(因為用的是紅黑樹),earse不會返回下一個有效的迭代器
list:iter失效,iter后的迭代器不會失效,erase返回下一個有效的迭代器
迭代器 vs 指針
迭代器是類模板,是指針的封裝,重載了指針的操作符,提供了比指針更高級的行為(比如根據(jù)不同類型的數(shù)據(jù)結(jié)構(gòu)實現(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實例化為引用
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(因為堆是完全二叉樹)
默認用max_heap
編譯過程
預處理階段:對include的頭文件、宏定義語句進行分析和替換,去除注釋,生成預編譯文件
編譯階段:把預編譯文件經(jīng)過詞法分析、語法分析、語義分析轉(zhuǎn)換為匯編代碼,生成匯編文件 先編譯 member declarations,再編譯 member function bodies
匯編階段:把匯編文件轉(zhuǎn)換為目標機器指令,生成可重定位目標文件(含代碼段、數(shù)據(jù)段)
鏈接階段(靜態(tài)鏈接):將多個目標文件及所需要的庫連接成最終的可執(zhí)行目標文件 注意:動態(tài)鏈接在運行時才被載入;不同應用程序調(diào)用相同的庫時,內(nèi)存只需要有一份該共享庫的實例,避免空間浪費
"" vs <>
預處理階段查找頭文件的路徑不同
"":當前頭文件目錄、編譯器設置的頭文件路徑(可使用-I顯式指定)、系統(tǒng)變量C_INCLUDE_PATH指定的頭文件路徑
<>:編譯器設置的頭文件路徑(可使用-I顯式指定)、系統(tǒng)變量C_INCLUDE_PATH指定的頭文件路徑
c++中每個進程的虛擬內(nèi)存
從上(低地址)至下(高地址)依次是:
-
內(nèi)核空間(1G)
用戶代碼無法訪問這一塊,通過系統(tǒng)調(diào)用進入內(nèi)核態(tài)后才能訪問
所有進程共享一個內(nèi)核空間
由于內(nèi)核態(tài)空間與用戶態(tài)空間采用了不同的映射機制,雖然內(nèi)核態(tài)只有1GB的虛擬地址空間,但是它可以訪問所有的物理內(nèi)存地址
-
用戶空間(3G)
-
代碼段(正文段):由exec從程序文件中讀入
常量存儲區(qū)(只讀存儲區(qū)):存儲字符串常量
文本區(qū):存儲程序的機器代碼
-
數(shù)據(jù)段(已初始化數(shù)據(jù)區(qū)):由exec從程序文件中讀入
- 存儲已初始化的全局變量和靜態(tài)變量
-
BSS段:由exec初始化為0
存儲未初始化的全局變量和靜態(tài)變量
程序結(jié)束后資源由系統(tǒng)自動釋放
并不占用可執(zhí)行文件的大?。?strong>即:存儲在磁盤中的可執(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)鍵字:編譯器可以進行自動類型推斷,但不能用于函數(shù)傳參及數(shù)組類型的推斷
nullptr關(guān)鍵字
智能指針
可以使用初始化列表來對類進行初始化
右值引用:實現(xiàn)移動語義和完美轉(zhuǎn)發(fā)
atomic原子操作用于多線程資源互斥操作
新增了一些STL容器:unordered_map unordered_set array等
lambda表達式:定義一個匿名函數(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)
右值引用的作用
移動語義
用右值引用作為形參類型,實現(xiàn)移動構(gòu)造函數(shù),將形參(是一個臨時對象)的指針成員變量指向nullptr,將新構(gòu)造的對象的指針成員變量指向要指向的數(shù)據(jù)
完美轉(zhuǎn)發(fā)
指傳入轉(zhuǎn)發(fā)函數(shù)(函數(shù)模板中調(diào)用的函數(shù)模板)的是左(右)值對象,那目標函數(shù)(轉(zhuǎn)發(fā)函數(shù)通過類型推斷得到)就能獲得左(右)值對象
需要將轉(zhuǎn)發(fā)函數(shù)和目標函數(shù)的形參都設置為右值引用,或者在調(diào)用轉(zhuǎn)發(fā)函數(shù)時,用forward<T>(t)作為實參
forward<T>(t)的作用:當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ù)是默認inline的
當虛函數(shù)表現(xiàn)多態(tài)性的時候不能內(nèi)聯(lián)
與宏定義的區(qū)別:宏定義是預編譯時替換的,沒有類型檢查;內(nèi)聯(lián)函數(shù)是在編譯時替換的,有類型檢查
適用場景:一個函數(shù)不斷地被重復調(diào)用,且函數(shù)只有簡單幾行,且函數(shù)不包括for,while,switch語句
與static的區(qū)別:inline 因為編譯階段代碼展開導致函數(shù)本文件可見, static 符號屬性為local 本文件可見
-
優(yōu)點
在被調(diào)用處進行代碼展開,省去了參數(shù)壓棧、棧幀開辟與回收,結(jié)果返回等,從而提高程序運行速度
相比宏函數(shù)來說,在代碼展開時,會做安全檢查或自動類型轉(zhuǎn)換(同普通函數(shù))
在類中聲明同時定義的成員函數(shù),自動轉(zhuǎn)化為內(nèi)聯(lián)函數(shù),因此內(nèi)聯(lián)函數(shù)可以訪問類的成員變量,宏定義則不能
內(nèi)聯(lián)函數(shù)在運行時可調(diào)試,而宏定義不可以
-
缺點
代碼膨脹耗時間、耗空間
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ù)都是運行時才被替代)
為inline 函數(shù)中的局部變量分配內(nèi)存空間
將 inline 函數(shù)的的輸入?yún)?shù)和返回值映射到調(diào)用該函數(shù)的局部變量空間中
如果 inline 函數(shù)有多個返回點,將其轉(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)生過多重復的對象實體
遞歸調(diào)用層次過多
局部數(shù)組過大
指針或數(shù)組越界
內(nèi)存泄漏
程序未釋放已申請的內(nèi)存空間
為什么棧的效率比堆高
棧是OS提供的數(shù)據(jù)結(jié)構(gòu),計算機底層對棧提供了一系列支持:分配專門的寄存器存儲棧的地址,壓棧和入棧有專門的指令執(zhí)行
堆是c/c++函數(shù)庫提供的,機制復雜,需要一些分配內(nèi)存、合并內(nèi)存、釋放內(nèi)存的算法
各種情況的初始值
內(nèi)置類型和array
若在塊內(nèi)、非static、未給初始值:執(zhí)行默認初始化,其初始值未定義
否則:執(zhí)行值初始化,初始值為0
復合類型
引用:聲明時必須初始化
指針:執(zhí)行默認初始化,其初始值未定義
靜態(tài)變量
- 執(zhí)行值初始化,初始值為0
STL對象/類
- 無論是默認初始化還是值初始化,都調(diào)用其默認構(gòu)造函數(shù),初始值都一般為空對象(比如 string 初始值是 "")
類中未初始化變量
- 執(zhí)行默認初始化,其初始值未定義
初始化時初始值數(shù)量小于其維度的數(shù)組
- 剩下的元素會進行值初始化,初始值為0
使用形如T()的表達式顯示請求值初始化的
- 執(zhí)行值初始化
結(jié)構(gòu)體
- 未初始化的成員會有默認的初始值
decltype
使用函數(shù)時,編譯器并未調(diào)用f,但使用了f的返回類型
加括號-->變?yōu)橐?/p>
保留top-level const
對表達式使用decltype時,若表達式為左值,則結(jié)果為引用,若表達式為右值,則結(jié)果為指針
array做形參
因為不能復制array,所以無法通過傳值的方法,而是傳指針
在函數(shù)的形參列表里指定數(shù)組的成員個數(shù)是沒有意義的,比如const int [10]的聲明編譯器自動處理為const int *
不能定義元素是引用的array:引用沒有自身的地址,不占用內(nèi)存空間,因此,聲明引用數(shù)組沒有辦法分配內(nèi)存空間
臨時變量產(chǎn)生的時機
值傳遞時
強制/隱式類型轉(zhuǎn)換時
常引用傳遞且需要進行類型轉(zhuǎn)換時
++ --后置時
重載
must differ in the number or the type(s) of their parameters
有無top-level const不影響type difference
類中的const與nonconst成員函數(shù)可以重載,因為this指針類型不同
重載 vs 重寫 vs 隱藏
- 區(qū)別
- 當參數(shù)列表不同時,無論基類中的函數(shù)是否被virtual修飾,基類函數(shù)都是被隱藏,而不是被重寫;參數(shù)相同時,若基類函數(shù)沒有關(guān)鍵字virtual,此時基類函數(shù)被隱藏,若有則被重寫
聲明 vs 定義
聲明是給編譯器用的,定義是給連接器用的
代碼中可以調(diào)用只聲明未定義的函數(shù),是因為在調(diào)用函數(shù)處,編譯器會產(chǎn)生調(diào)用函數(shù)的代碼,但編譯器并不管函數(shù)的具體實現(xiàn)(即定義)在哪里,鏈接器才需要查找函數(shù)的實現(xiàn)代碼并與函數(shù)調(diào)用代碼對上
-
與普通函數(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ù)默認的缺省版本
=default:告知編譯器生成函數(shù)默認的缺省版本
空指針 vs 未初始化指針
空指針可以確保不指向任何對象或函數(shù),未初始化指針可能指向任何地方
-
訪問空指針指向的空間,會發(fā)生:
令指針=NULL會讓它存放地址0x0
Linux 中,每個進程空間的 0x0 虛擬地址開始的線性區(qū)都會被映射到一個用戶態(tài)沒有訪問權(quán)限的頁上
編譯器把空指針當做 0 對待,開心地讓你去訪問0x0
缺頁異常處理程序被調(diào)用,因為在 0x0 的頁沒有在物理內(nèi)存里面
缺頁異常處理程序發(fā)現(xiàn)你沒有訪問的權(quán)限
內(nèi)核發(fā)送 SIGSEGV 信號給進程,該信號默認是讓進程自殺
this指針
this 并不是一個常規(guī)變量,而是個右值,所以不能取得 this 的地址(不能 &this)
定義只能在堆(棧)上生成對象的類
只能在堆上
將析構(gòu)函數(shù)設為私有:編譯器在為類對象分配??臻g時,會先檢查類的析構(gòu)函數(shù)的訪問性。若析構(gòu)函數(shù)不可訪問,則不能在棧上創(chuàng)建對象
只能在棧上
將 new 和 delete 重載為私有
內(nèi)存對齊
對齊規(guī)則
第一個數(shù)據(jù)成員放在offset為0的地方,以后每個數(shù)據(jù)成員的對齊按照#pragma pack指定的數(shù)值和這個數(shù)據(jù)成員自身長度中,比較小的那個進行
在數(shù)據(jù)成員完成各自對齊之后,類(結(jié)構(gòu)或聯(lián)合)本身也要進行對齊,對齊將按照#pragma pack指定的數(shù)值和結(jié)構(gòu)(或聯(lián)合)最大數(shù)據(jù)成員長度中,比較小的那個進行
#pragma pack(n)作為一個預編譯指令,設置按多少個字節(jié)對齊;但編譯器只會按照1、2、4、8、16的方式分割內(nèi)存。若n為其他值,是無效的
栗子
go實現(xiàn)的并發(fā)模型
多線程并發(fā)
cpp中也有,以共享內(nèi)存的方式來通信
csp:以通信的方式來共享內(nèi)存
通過goroutine和channel實現(xiàn),channel是goroutine之間通信的管道
傳數(shù)據(jù)用channel<-data,取數(shù)據(jù)用<-data,它倆成對出現(xiàn);傳和取都是阻塞的,直到另外goroutine取/傳為止
main()本身就是一個goroutine