目錄
1.part1 C++基礎(chǔ)
1 C++特點
2 說說C語言和C++的區(qū)別
3 說說 C++中 struct 和 class 的區(qū)別
4 include頭文件的順序以及雙引號""和尖括號<>的區(qū)別
5 說說C++結(jié)構(gòu)體和C結(jié)構(gòu)體的區(qū)別
6 導(dǎo)入C函數(shù)的關(guān)鍵字是什么,C++編譯時和C有什么不同?
7 C++從代碼到可執(zhí)行二進制文件的過程
8 說說 static關(guān)鍵字的作用
9 數(shù)組和指針的區(qū)別
10 說說什么是函數(shù)指針,如何定義函數(shù)指針,有什么使用場景
11 靜態(tài)變量什么時候初始化?
12 nullptr調(diào)用成員函數(shù)可以嗎?為什么?
13 說說什么是野指針,怎么產(chǎn)生的,如何避免
14 說說靜態(tài)局部變量,全局變量,局部變量的特點,以及使用場景
15 說說內(nèi)聯(lián)函數(shù)和宏函數(shù)的區(qū)別
17 說說new和malloc的區(qū)別,各自底層實現(xiàn)原理。
18 說說const和define的區(qū)別。
19 說說C++中函數(shù)指針和指針函數(shù)的區(qū)別。
20 說說const int *a, int const *a, const int a, int *const a, const int *const a分別是什么,有什么特點。
21 說說使用指針需要注意什么?
22 說說內(nèi)聯(lián)函數(shù)和函數(shù)的區(qū)別,內(nèi)聯(lián)函數(shù)的作用。
23 簡述C++有幾種傳值方式,之間的區(qū)別是什么?
24 簡述const(星號)和(星號)const的區(qū)別
2.C++內(nèi)存?
1 簡述堆和棧的區(qū)別
2 簡述C++的內(nèi)存管理
3. 內(nèi)存泄露及解決辦法:
3 malloc和局部變量分配在堆還是棧?
4 程序有哪些section,分別的作用?程序啟動的過程?怎么判斷數(shù)據(jù)分配在棧上還是堆上?
5 初始化為0的全局變量在bss還是data
6 簡述C++中內(nèi)存對齊的使用場景
3?面向?qū)ο?/p>
1 簡述一下什么是面向?qū)ο?/p>
2 簡述一下面向?qū)ο蟮娜筇卣?/p>
3簡述一下 C++ 的重載和重寫,以及它們的區(qū)別
4 說說 C++ 的重載和重寫是如何實現(xiàn)的
5 說說構(gòu)造函數(shù)有幾種,分別什么作用
6 只定義析構(gòu)函數(shù),會自動生成哪些構(gòu)造函數(shù)
7 說說一個類,默認會生成哪些函數(shù)
8 說說 C++ 類對象的初始化順序,有多重繼承情況下的順序
9 簡述下向上轉(zhuǎn)型和向下轉(zhuǎn)型
10 簡述下深拷貝和淺拷貝,如何實現(xiàn)深拷貝
11 簡述一下 C++ 中的多態(tài)
12 說說為什么要虛析構(gòu),為什么不能虛構(gòu)造
14 說說類繼承時,派生類對不同關(guān)鍵字修飾的基類方法的訪問權(quán)限
15 簡述一下移動構(gòu)造函數(shù),什么庫用到了這個函數(shù)?
16 構(gòu)造函數(shù)為什么不能被聲明為虛函數(shù)?
17 簡述一下什么是常函數(shù),有什么作用
18 說說什么是虛繼承,解決什么問題,如何實現(xiàn)?
19 簡述一下虛函數(shù)和純虛函數(shù),以及實現(xiàn)原理
21 說說C++中虛函數(shù)與純虛函數(shù)的區(qū)別
22 說說 C++ 中什么是菱形繼承問題,如何解決?
23 請問構(gòu)造函數(shù)中的能不能調(diào)用虛方法
24 請問拷貝構(gòu)造函數(shù)的參數(shù)是什么傳遞方式,為什么
25 如何理解抽象類?
26 什么是多態(tài)?除了虛函數(shù),還有什么方式能實現(xiàn)多態(tài)?
27 簡述一下虛析構(gòu)函數(shù),什么作用
28 說說什么是虛基類,可否被實例化?
29 簡述一下拷貝賦值和移動賦值?
30 仿函數(shù)了解嗎?有什么作用
31 C++ 中哪些函數(shù)不能被聲明為虛函數(shù)?
32?解釋下 C++ 中類模板和模板類的區(qū)別
32 虛函數(shù)表里存放的內(nèi)容是什么時候?qū)戇M去的?
1.part1 C++基礎(chǔ)
1 C++特點
1. C++ 在 C 語言基礎(chǔ)上引入了 面對對象 的機制,同時也 兼容 C 語言 。
2. C++ 有三大特性
(1)封裝。
(2)繼承。
(3)多態(tài);
3. C++ 語言編寫出的程序結(jié)構(gòu)清晰、易于擴充,程序 可讀性好 。
4. C++ 生成的代碼 質(zhì)量高 , 效率高 ,
5. C++ 更加安全,增加了 const 常量、引用、四類 cast 轉(zhuǎn)換( static_cast 、 dynamic_cast 、
const_cast 、 reinterpret_cast )、智能指針、 try—catch 等等;
6. C++ 可復(fù)用性 高, C++ 引入了 模板 的概念,標準模板庫 STL ( Standard Template Library )。
2 說說C語言和C++的區(qū)別
1. C 語言是 C++ 的子集, C++ 可以很好兼容 C 語言。但是 C++ 又有很多 新特性 ,如引用、智能指針、
auto 變量等。
2. C++ 是 面對對象 的編程語言; C 語言是 面對過程 的編程語言。
3. C 語言有一些不安全的語言特性,如指針使用的潛在危險、強制轉(zhuǎn)換的不確定性、內(nèi)存泄露等。而
C++ 對此增加了不少新特性來 改善安全性 ,如 const 常量、引用、 cast 轉(zhuǎn)換、智能指針、 try—catch
等等;
4. C++ 可復(fù)用性 高, C++ 引入了 模板 的概念,后面在此基礎(chǔ)上,實現(xiàn)了方便開發(fā)的標準模板庫 STL 。
C++ 的 STL 庫相對于 C 語言的函數(shù)庫 更靈活、更通用 。
3 說說 C++中 struct 和 class 的區(qū)別
1. struct 一般用于描述一個 數(shù)據(jù)結(jié)構(gòu)集合 ,而 class 是對一個 對象數(shù)據(jù)的封裝 ;
2. struct 中默認的訪問控制權(quán)限 是 public 的 ,而 class 中默認的訪問控制權(quán)限 是 private 的 。
3. 在繼承關(guān)系中, struct 默認是 公有繼承 ,而 class 是 私有繼承 ;
4. class 關(guān)鍵字可以用于定義模板參數(shù),就像 typename ,而 struct 不能用于定義模板參數(shù),
4 include頭文件的順序以及雙引號""和尖括號<>的區(qū)別
1. 區(qū)別:
(1)尖括號 <> 的頭文件是 系統(tǒng)文件 ,雙引號 "" 的頭文件是 自定義文件 。
(2)編譯器預(yù)處理階段查找頭文件的路徑不一樣。
2. 查找路徑:
(1)使用尖括號 <> 的頭文件的查找路徑:編譯器設(shè)置的頭文件路徑 --> 系統(tǒng)變量。
(2)使用雙引號 "" 的頭文件的查找路徑:當(dāng)前頭文件目錄 --> 編譯器設(shè)置的頭文件路徑 --> 系統(tǒng)變
量。
5 說說C++結(jié)構(gòu)體和C結(jié)構(gòu)體的區(qū)別
區(qū)別:
(1) C 的結(jié)構(gòu)體內(nèi) 不允許有函數(shù)存在 , C++ 允許有內(nèi)部成員函數(shù),且允許該函數(shù)是虛函數(shù) 。
(2) C 的結(jié)構(gòu)體對內(nèi)部成員變量的 訪問權(quán)限只能是 public ,而 C++ 允許 public,protected,private 三種 。
(3) C 語言的結(jié)構(gòu)體是 不可以繼承的 , C++ 的結(jié)構(gòu)體 可繼承 。
(4) C 中 使用結(jié)構(gòu)體需要加上 struct 關(guān)鍵字 ,而 C++ 中可以省略 struct 關(guān)鍵字直接使用。
6 導(dǎo)入C函數(shù)的關(guān)鍵字是什么,C++編譯時和C有什么不同?
1. 關(guān)鍵字: 在 C++ 中,導(dǎo)入 C 函數(shù)的關(guān)鍵字是 extern ,表達形式為 extern “C” , extern "C" 的主要作
用就是為了能夠正確實現(xiàn) C++ 代碼調(diào)用其他 C 語言代碼。加上 extern "C" 后,會指示編譯器這部分代
碼按 C 語言 的進行編譯,而不是 C++ 的。
2. 編譯區(qū)別: 由于 C++ 支持函數(shù)重載,因此編譯器編譯函數(shù)的過程中會將函數(shù)的 參數(shù)類型 也加到編譯
后的代碼中,而不僅僅是 函數(shù)名 ;而 C 語言并不支持函數(shù)重載,因此編譯 C 語言代碼的函數(shù)時不會帶
上函數(shù)的參數(shù)類型,一般只包括 函數(shù)名
7 C++從代碼到可執(zhí)行二進制文件的過程
C++ 和 C 語言類似,一個 C++ 程序從源碼到執(zhí)行文件,有四個過程, 預(yù)編譯、編譯、匯編、鏈接 。
預(yù)編譯: 這個過程主要的處理操作如下:
將所有的 #define 刪除,并且展開所有的宏定義處理所有的條件預(yù)編譯指令,如#if 、 #ifdef 處理#include 預(yù)編譯指令,將被包含的文件插入到該預(yù)編譯指令的位置。 過濾所有的注釋 添加行號和文件名標識。
編譯: 這個過程主要的處理操作如下:
詞法分析:將源代碼的字符序列分割成一系列的記號。 語法分析:對記號進行語法分析,產(chǎn)生語法樹。 語義分析:判斷表達式是否有意義。 代碼優(yōu)化。 目標代碼生成:生成匯編代碼。 目標代碼優(yōu)化。
匯編: 這個過程主要是將匯編代碼轉(zhuǎn)變成機器可以執(zhí)行的指令。
鏈接: 將不同的源文件產(chǎn)生的目標文件進行鏈接,從而形成一個可以執(zhí)行的程序。 鏈接分為 靜態(tài)鏈接和動態(tài)鏈接 。
靜態(tài)鏈接 ,是在鏈接的時候就已經(jīng)把要調(diào)用的函數(shù)或者過程鏈接到了生成的可執(zhí)行文件中,就算你在去 把靜態(tài)庫刪除也不會影響可執(zhí)行程序的執(zhí)行;生成的靜態(tài)鏈接庫,Windows 下以 .lib 為后綴, Linux 下 以.a 為后綴。
動態(tài)鏈接 ,是在鏈接的時候沒有把調(diào)用的函數(shù)代碼鏈接進去,而是在執(zhí)行的過程中,再去找要鏈接的函 數(shù),生成的可執(zhí)行文件中沒有函數(shù)代碼,只包含函數(shù)的重定位信息,所以當(dāng)你刪除動態(tài)庫時,可執(zhí)行程 序就不能運行。生成的動態(tài)鏈接庫,Windows下以 .dll 為后綴, Linux 下以 .so 為后綴。
8 說說 static關(guān)鍵字的作用
1. 定義全局靜態(tài)變量和局部靜態(tài)變量 :初始化的靜態(tài)變量會在數(shù)據(jù)段分配內(nèi)存,未初始化的靜態(tài)變量 會在BSS 段分配內(nèi)存。直到程序結(jié)束,靜態(tài)變量始終會維持前值。
2. 定義靜態(tài)函數(shù) :靜態(tài)函數(shù)只能在 本源文件 中使用;如 static void func() ;
3. 定義靜態(tài)變量 。 靜態(tài)變量只能在本源文件中使用 ;
4. 定義 類中的靜態(tài)成員變量 :使用靜態(tài)數(shù)據(jù)成員,它既可以被當(dāng)成全局變量那樣去存儲,但又被隱藏 在類的內(nèi)部。類中的static 靜態(tài)數(shù)據(jù)成員擁有一塊單獨的存儲區(qū),而不管創(chuàng)建了多少個該類的對
象。所有這些對象的靜態(tài)數(shù)據(jù)成員都 共享 這一塊靜態(tài)存儲空間。
5. 定義類中的靜態(tài)成員函數(shù) :如靜態(tài)成員函數(shù)也是類的一部分,而不是對象的一部分。所有這些對象 的靜態(tài)數(shù)據(jù)成員都共享 這一塊靜態(tài)存儲空間。
此外: 當(dāng)調(diào)用一個對象的非靜態(tài)成員函數(shù)時,系統(tǒng)會把該對象的起始地址賦給成員函數(shù)的this 指針。而 靜態(tài)成 員函數(shù)不屬于任何一個對象,因此 C++ 規(guī)定靜態(tài)成員函數(shù)沒有 this 指針 。既然它沒有指向某一對象,也 就無法對一個對象中的非靜態(tài)成員進行訪問。
9 數(shù)組和指針的區(qū)別
1. 概念:
(1)數(shù)組:數(shù)組是用于 儲存多個相同類型數(shù)據(jù)的集合 。 數(shù)組名是首元素的地址。
(2)指針:指針相當(dāng)于一個 變量 ,它存放的是其它變量在 內(nèi)存中的地址 。 指針名指向了內(nèi)存的首
地址。
2. 區(qū)別:
(1) 賦值 :同類型指針變量可以相互賦值;數(shù)組不行,只能一個一個元素的賦值或拷貝
(2) 存儲方式 :
數(shù)組:數(shù)組在內(nèi)存中是 連續(xù)存放的 ,數(shù)組的存儲空間,不是在靜態(tài)區(qū)就是在棧上。
指針:指針很靈活,它可以指向任意類型的數(shù)據(jù)。指針的類型說明了它所指向地址空間的內(nèi)存。由
于指針本身就是一個變量,再加上它所存放的也是變量,所以指針的存儲空間不能確定。
(3) 求 sizeof :
數(shù)組所占存儲空間的內(nèi)存大小: sizeof (數(shù)組名) /sizeof (數(shù)據(jù)類型) 在32 位平臺下,無論指針的類型是什么, sizeof (指針名)都是 4 ,在 64 位平臺下,無論指針的類型是什么,sizeof (指針名)都是 8 。
10 說說什么是函數(shù)指針,如何定義函數(shù)指針,有什么使用場景
1. 概念: 函數(shù)指針就是 指向函數(shù) 的指針變量。每一個函數(shù)都有一個入口地址,該入口地址就是函數(shù)指
針所指向的地址。
2. 定義 形式如下:
???????? int func(int a);
int (*f)(int a);
f = &func;
1. 函數(shù)指針的 應(yīng)用場景 : 回調(diào) ( callback )。我們調(diào)用別人提供的 API 函數(shù) (Application
Programming Interface, 應(yīng)用程序編程接口 ) ,稱為 Call ;如果別人的庫里面調(diào)用我們的函數(shù),就叫
Callback 。
11 靜態(tài)變量什么時候初始化?
對于 C 語言的全局和靜態(tài)變量, 初始化發(fā)生在任何代碼執(zhí)行之前 ,屬于編譯期初始化。
而 C++ 標準規(guī)定:全局或靜態(tài)對象當(dāng)且僅當(dāng)對象 首次用到時才進行構(gòu)造 。
12 nullptr調(diào)用成員函數(shù)可以嗎?為什么?
能。
原因:因為在 編譯時對象 就綁定了 函數(shù)地址 ,和指針空不空沒關(guān)系。
13 說說什么是野指針,怎么產(chǎn)生的,如何避免
1. 概念: 野指針就是指針指向的位置是不可知的(隨機的、不正確的、沒有明確限制的)
2. 產(chǎn)生原因 :釋放內(nèi)存后指針不及時置空(野指針),依然指向了該內(nèi)存,那么可能出現(xiàn)非法訪問的 錯誤。這些我們都要注意避免。
3. 避免辦法:
(1)初始化置 NULL
(2)申請內(nèi)存后判空
(3)指針釋放后置 NULL
(4)使用智能指針
14 說說靜態(tài)局部變量,全局變量,局部變量的特點,以及使用場景
1. 首先從作用域考慮 : C++ 里作用域可分為 6 種:全局,局部,類,語句,命名空間和文件作用域。
全局變量:全局作用域,可以通過 extern 作用于其他非定義的源文件。
靜態(tài)全局變量 :全局作用域 + 文件作用域,所以無法在其他文件中使用。
局部變量:局部作用域,比如函數(shù)的參數(shù),函數(shù)內(nèi)的局部變量等等。
靜態(tài)局部變量 :局部作用域,只被初始化一次,直到程序結(jié)束。
2. 從所在空間考慮 :除了局部變量在棧上外,其他都在靜態(tài)存儲區(qū)。因為靜態(tài)變量都在靜態(tài)存儲區(qū), 所以下次調(diào)用函數(shù)的時候還是能取到原來的值。
3. 生命周期 : 局部變量在棧上,出了作用域就回收內(nèi)存;而全局變量、靜態(tài)全局變量、靜態(tài)局部變量 都在靜態(tài)存儲區(qū),直到程序結(jié)束才會回收內(nèi)存。
15 說說內(nèi)聯(lián)函數(shù)和宏函數(shù)的區(qū)別
區(qū)別:
1. 宏定義不是函數(shù) ,但是使用起來像函數(shù)。預(yù)處理器用復(fù)制宏代碼的方式代替函數(shù)的調(diào)用,省去了函 數(shù)壓棧退棧過程,提高了效率;而內(nèi)聯(lián)函數(shù)本質(zhì)上是一個函數(shù) ,內(nèi)聯(lián)函數(shù)一般用于函數(shù)體的代碼比 較簡單的函數(shù),不能包含復(fù)雜的控制語句,while 、 switch ,并且內(nèi)聯(lián)函數(shù)本身不能直接調(diào)用自 身。
2. 宏函數(shù) 是在預(yù)編譯的時候把所有的宏名用宏體來替換,簡單的說就是字符串替換 ; 而內(nèi)聯(lián)函數(shù) 則是 在編譯的時候進行代碼插入,編譯器會在每處調(diào)用內(nèi)聯(lián)函數(shù)的地方直接把內(nèi)聯(lián)函數(shù)的內(nèi)容展開,這 樣可以省去函數(shù)的調(diào)用的開銷,提高效率
3. 宏定義 是 沒有類型檢查的 ,無論對還是錯都是直接替換; 而內(nèi)聯(lián)函數(shù) 在編譯的時候會進行類型的檢 查,內(nèi)聯(lián)函數(shù)滿足函數(shù)的性質(zhì),比如有返回值、參數(shù)列表等。
內(nèi)聯(lián)函數(shù)使用的條件:
內(nèi)聯(lián)是以代碼膨脹(復(fù)制)為代價,僅僅省去了函數(shù)調(diào)用的開銷,從而提高函數(shù)的執(zhí)行效率。如果
執(zhí)行函數(shù)體內(nèi)代碼的時間,相比于函數(shù)調(diào)用的開銷較大,那么效率 的收獲會很少。另一方面,每一 處內(nèi)聯(lián)函數(shù)的調(diào)用都要復(fù)制代碼,將使程序的總代碼量增大,消耗更多的內(nèi)存空間。以下情況不宜使用內(nèi)聯(lián):
(1)如果函數(shù)體內(nèi)的代碼比較長,使用內(nèi)聯(lián)將導(dǎo)致內(nèi)存消耗代價較高。
(2)如果函數(shù)體內(nèi)出現(xiàn)循環(huán),那么執(zhí)行函數(shù)體內(nèi)代碼的時間要比函數(shù)調(diào)用的開銷大。
內(nèi)聯(lián)不是什么時候都能展開的,一個好的編譯器將會根據(jù)函數(shù)的定義體,自動地取消不符合要求的
內(nèi)聯(lián)。
17 說說new和malloc的區(qū)別,各自底層實現(xiàn)原理。
1. new 是操作符,而 malloc 是函數(shù)。
2. new 在調(diào)用的時候 先分配內(nèi)存,在調(diào)用構(gòu)造函數(shù),釋放的時候調(diào)用析構(gòu)函數(shù) ;而 malloc 沒有構(gòu)造函
數(shù)和析構(gòu)函數(shù)。
3. malloc 需要給定申請內(nèi)存的大小, 返回的指針需要強轉(zhuǎn); new 會調(diào)用構(gòu)造函數(shù),不用指定內(nèi)存的大
小,返回指針不用強轉(zhuǎn)。
4. new 可以被重載 ; malloc 不行
5. new 分配內(nèi)存更直接和安全 。
6. new 發(fā)生錯誤拋出異常 , malloc 返回 null
malloc 底層實現(xiàn): 當(dāng)開辟的空間小于 128K 時,調(diào)用 brk ()函數(shù);當(dāng)開辟的空間大于 128K 時,調(diào)用 mmap()。 malloc 采用的是內(nèi)存池的管理方式,以減少內(nèi)存碎片。先申請大塊內(nèi)存作為堆區(qū),然后將 堆區(qū)分為多個內(nèi)存塊。當(dāng)用戶申請內(nèi)存時,直接從堆區(qū)分配一塊合適的空閑快。采用隱式鏈表將所有空閑塊,每一個空閑塊記錄了一個未分配的、連續(xù)的內(nèi)存地址。
new 底層實現(xiàn): 關(guān)鍵字 new 在調(diào)用構(gòu)造函數(shù)的時候?qū)嶋H上進行了如下的幾個步驟:
1. 創(chuàng)建一個新的對象
2. 將構(gòu)造函數(shù)的作用域賦值給這個新的對象(因此 this 指向了這個新的對象)
3. 執(zhí)行構(gòu)造函數(shù)中的代碼(為這個新對象添加屬性)
4. 返回新對象
18 說說const和define的區(qū)別。
const 用于定義 常量 ;而 define 用于 定義宏 ,而宏也可以用于定義常量。都用于常量定義時,它們的區(qū)別有:
1. const 生效于編譯的階段; define 生效于預(yù)處理階段。
2. const 定義的常量,在 C 語言中 是存儲在內(nèi)存中、需要額外的內(nèi)存空間的 ; define 定義的常量,運行時 是直接的操作數(shù),并不會存放在內(nèi)存中 。
3. const 定義的常量 是帶類型的 ; define 定義的常量 不帶類型 。因此 define 定義的常量不利于類型檢
查。
19 說說C++中函數(shù)指針和指針函數(shù)的區(qū)別。
參考回答
1. 定義不同
指針函數(shù)本質(zhì)是一個函數(shù),其返回值為指針。 函數(shù)指針本質(zhì)是一個指針,其指向一個函數(shù)。
2. 寫法不同
指針函數(shù):int *fun(int x,int y);
函數(shù)指針:int (*fun)(int x,int y);
3. 用法不同
指針函數(shù)可以在函數(shù)體內(nèi)通過 return
語句返回指向動態(tài)分配的內(nèi)存或全局/靜態(tài)變量的指針。這樣的函數(shù)可以方便地返回復(fù)雜的數(shù)據(jù)結(jié)構(gòu),如數(shù)組、字符串等。例如:
int* createArray(int size) {int* arr = (int*)malloc(size * sizeof(int));// 對 arr 進行初始化或其他操作return arr;
}
函數(shù)指針可以在程序運行時動態(tài)地指向不同的函數(shù)。這在使用回調(diào)函數(shù)(callback)時非常有用,可以根據(jù)需要在不同的上下文中調(diào)用不同的函數(shù)。例如:
nt (*operation)(int, int); // 函數(shù)指針的聲明
operation = &add; // 將函數(shù)指針指向 add 函數(shù)
int result = operation(3, 4); // 調(diào)用 add 函數(shù)進行計算
總之,指針函數(shù)和函數(shù)指針在定義、寫法和用法上存在一些區(qū)別。指針函數(shù)是一個返回指針的函數(shù),而函數(shù)指針是一個指向函數(shù)的指針,可以在運行時動態(tài)地指向不同的函數(shù)。這些概念在C和C++等語言中非常常見,可以提供更大的靈活性和可擴展性。
20 說說const int *a, int const *a, const int a, int *const a, const int *const a分別是什么,有什么特點。
1. const int a; //指的是a是一個常量,不允許修改。
2. const int *a; //a指針所指向的內(nèi)存里的值不變,即(*a)不變
3. int const *a; //同const int *a;
4. int *const a; //a指針所指向的內(nèi)存地址不變,即a不變
5. const int *const a; //都不變,即(*a)不變,a也不變
21 說說使用指針需要注意什么?
1. 定義指針時,先初始化為 NULL 。
2. 用 malloc 或 new 申請內(nèi)存之后,應(yīng)該 立即檢查 指針值是否為 NULL 。防止使用指針值為 NULL 的內(nèi)
存。
3. 不要忘記為數(shù)組和動態(tài)內(nèi)存 賦初值 。防止將未被初始化的內(nèi)存作為右值使用。
4. 避免數(shù)字或指針的下標 越界 ,特別要當(dāng)心發(fā)生 “ 多 1” 或者 “ 少 1” 操作
5. 動態(tài)內(nèi)存的申請與釋放必須配對,防止 內(nèi)存泄漏
6. 用 free 或 delete 釋放了內(nèi)存之后,立即將指針 設(shè)置為 NULL ,防止 “ 野指針 ”
22 說說內(nèi)聯(lián)函數(shù)和函數(shù)的區(qū)別,內(nèi)聯(lián)函數(shù)的作用。
1. 內(nèi)聯(lián)函數(shù)比普通函數(shù)多了關(guān)鍵字 inline
2. 內(nèi)聯(lián)函數(shù)避免了函數(shù)調(diào)用的 開銷 ;普通函數(shù)有調(diào)用的開銷
3. 普通函數(shù)在被調(diào)用的時候,需要 尋址(函數(shù)入口地址) ;內(nèi)聯(lián)函數(shù)不需要尋址。
4. 內(nèi)聯(lián)函數(shù)有一定的限制,內(nèi)聯(lián)函數(shù)體要求 代碼簡單 ,不能包含復(fù)雜的結(jié)構(gòu)控制語句;普通函數(shù)沒有這個要求。
內(nèi)聯(lián)函數(shù)的作用 :內(nèi)聯(lián)函數(shù)在調(diào)用時,是將調(diào)用表達式用內(nèi)聯(lián)函數(shù)體來替換。避免函數(shù)調(diào)用的開銷。
23 簡述C++有幾種傳值方式,之間的區(qū)別是什么?
參考回答
傳參方式有這三種: 值傳遞、引用傳遞、指針傳遞
1. 值傳遞:形參即使在函數(shù)體內(nèi)值發(fā)生變化,也不會影響實參的值;
2. 引用傳遞:形參在函數(shù)體內(nèi)值發(fā)生變化,會影響實參的值;
3. 指針傳遞:在指針指向沒有發(fā)生改變的前提下,形參在函數(shù)體內(nèi)值發(fā)生變化,會影響實參的值;
值傳遞用于對象時,整個對象會拷貝一個副本,這樣效率低;而引用傳遞用于對象時,不發(fā)生拷貝
行為,只是綁定對象,更高效;指針傳遞同理,但不如引用傳遞安全。
24 簡述const(星號)和(星號)const的區(qū)別
????????
//const* 是指針常量, *const 是常量指針
int const * a ; //a 指針所指向的內(nèi)存里的值不變,即( *a )不變,指針可變
int * const a ; //a 指針所指向的內(nèi)存地址不變,即 a 不變,其所指內(nèi)存值可變
2.C++內(nèi)存?
????????
1 簡述堆和棧的區(qū)別
區(qū)別:
1. 堆??臻g分配不同 。 棧由操作系統(tǒng)自動分配釋放 ,存放函數(shù)的參數(shù)值,局部變量的值等; 堆一般由 程序員分配釋放。
2. 堆棧緩存方式不同 。棧使用的是一級緩存, 它們通常都是被調(diào)用時處于存儲空間中,調(diào)用完畢立即釋放;堆則是存放在二級緩存中,速度要慢些。
3. 堆棧數(shù)據(jù)結(jié)構(gòu)不同 。堆類似數(shù)組結(jié)構(gòu);棧類似棧結(jié)構(gòu),先進后出。
2 簡述C++的內(nèi)存管理
1. 內(nèi)存分配方式 :
在 C++ 中,內(nèi)存分成 5 個區(qū),他們分別是 堆、棧、自由存儲區(qū)、全局 / 靜態(tài)存儲區(qū)和常量存儲區(qū) 。
棧 ,在執(zhí)行函數(shù)時,函數(shù)內(nèi)局部變量的存儲單元都可以在棧上創(chuàng)建,函數(shù)執(zhí)行結(jié)束時這些存儲單元
自動被釋放。
堆 ,就是那些由 new 分配的內(nèi)存塊,一般一個 new 就要對應(yīng)一個 delete 。
自由存儲區(qū) ,就是那些由 malloc 等分配的內(nèi)存塊,和堆是十分相似的,不過是用 free 來結(jié)束自己的
生命。
全局 / 靜態(tài)存儲區(qū) ,全局變量和靜態(tài)變量被分配到同一塊內(nèi)存中
常量存儲區(qū) ,這是一塊比較特殊的存儲區(qū),里面 存放的是常量,不允許修改。
2. 常見的內(nèi)存錯誤及其對策 :
(1)內(nèi)存分配未成功,卻使用了它。
(2)內(nèi)存分配雖然成功,但是尚未初始化就引用它。
(3)內(nèi)存分配成功并且已經(jīng)初始化,但操作越過了內(nèi)存的邊界。
(4)忘記了釋放內(nèi)存,造成內(nèi)存泄露。
(5)釋放了內(nèi)存卻繼續(xù)使用它。
對策:
(1)定義指針時,先初始化為 NULL 。
(2)用 malloc 或 new 申請內(nèi)存之后,應(yīng)該 立即檢查 指針值是否為 NULL 。防止使用指針值為 NULL
的內(nèi)存。
(3)不要忘記為數(shù)組和動態(tài)內(nèi)存 賦初值 。防止將未被初始化的內(nèi)存作為右值使用。
(4)避免數(shù)字或指針的下標 越界 ,特別要當(dāng)心發(fā)生 “ 多 1” 或者 “ 少 1” 操作
(5) 動態(tài)內(nèi)存的申請與釋放必須配對,防止 內(nèi)存泄漏
(6) 用 free 或 delete 釋放了內(nèi)存之后,立即將指針 設(shè)置為 NULL ,防止 “ 野指針 ”
(7)使用智能指針。
3. 內(nèi)存泄露及解決辦法:
什么是內(nèi)存泄露?
簡單地說就是 申請了一塊內(nèi)存空間,使用完畢后沒有釋放掉 。
(1) new 和 malloc 申請資源使用后,沒有用 delete 和 free 釋放;
(2)子類繼承父類時,父類析構(gòu)函數(shù)不是虛函數(shù)。
(3) Windows 句柄資源使用后沒有釋放。
怎么檢測?
第一: 良好的編碼習(xí)慣,使用了內(nèi)存分配的函數(shù),一旦使用完畢 , 要記得使用其相應(yīng)的函數(shù)釋放掉 。
第二: 將分配的內(nèi)存的指針以鏈表的形式自行管理 ,使用完畢之后從鏈表中刪除,程序結(jié)束時可檢 查改鏈表。
第三: 使用智能指針。
第四:一些常見的工具插件,如 ccmalloc 、 Dmalloc 、 Leaky 、 Valgrind 等等。
3 malloc和局部變量分配在堆還是棧?
malloc 是在 堆上分配內(nèi)存 ,需要程序員自己回收內(nèi)存;局部變量是在 棧中分配內(nèi)存 ,超過作用域就自動回收。
4 程序有哪些section,分別的作用?程序啟動的過程?怎么判斷數(shù)據(jù)分配在棧上還是堆上?
一個程序有哪些 section :
如上圖, 從低地址到高地址,一個程序由代碼段、數(shù)據(jù)段、 BSS 段組成。
1. 數(shù)據(jù)段: 存放程序中 已初始化的 全局變量和靜態(tài)變量的一塊內(nèi)存區(qū)域。
2. 代碼段: 存放程序執(zhí)行代碼的一塊內(nèi)存區(qū)域。只讀,代碼段的頭部還會包含一些只讀的常數(shù)變量。
3. BSS 段:存放程序中 未初始化的 全局變量和靜態(tài)變量的一塊內(nèi)存區(qū)域。
4. 可執(zhí)行程序在運行時又會多出兩個區(qū)域:堆區(qū)和棧區(qū)。
堆區(qū): 動態(tài)申請內(nèi)存用。堆從低地址向高地址增長。
棧區(qū): 存儲局部變量、函數(shù)參數(shù)值。棧從高地址向低地址增長。是一塊連續(xù)的空間。
5. 最后還有一個 文件映射區(qū) ,位于堆和棧之間。 堆 heap :由 new 分配的內(nèi)存塊,其釋放由程序員控制(一個 new 對應(yīng)一個 delete )
棧 stack :是那些編譯器在需要時分配,在不需要時自動清除的存儲區(qū)。存放局部變量、函數(shù)參數(shù)。
常量存儲區(qū) :存放常量,不允許修改。
程序啟動的過程:
1. 操作系統(tǒng) 首先創(chuàng)建相應(yīng)的進程并分配私有的進程空間 ,然后操作系統(tǒng)的 加載器負責(zé)把可執(zhí)行文件的
數(shù)據(jù)段和代碼段映射到進程的虛擬內(nèi)存空間中。
2. 加載器讀入可執(zhí)行程序的導(dǎo)入符號表 ,根據(jù)這些符號表可以查找出該可執(zhí)行程序的所有依賴的動態(tài)
鏈接庫。
3. 加載器針對該程序的每一個動態(tài)鏈接庫調(diào)用 LoadLibrary
(1)查找對應(yīng)的動態(tài)庫文件,加載器為該動態(tài)鏈接庫確定一個合適的基地址。
(2)加載器讀取該動態(tài)鏈接庫的導(dǎo)入符號表和導(dǎo)出符號表,比較應(yīng)用程序要求的導(dǎo)入符號是否匹
配該庫的導(dǎo)出符號。
(3)針對該庫的導(dǎo)入符號表,查找對應(yīng)的依賴的動態(tài)鏈接庫,如有跳轉(zhuǎn),則跳到 3
(4)調(diào)用該動態(tài)鏈接庫的初始化函數(shù)
4. 初始化應(yīng)用程序的全局變量,對于全局對象自動調(diào)用構(gòu)造函數(shù) 。
5. 進入應(yīng)用程序入口點函數(shù)開始執(zhí)行。
怎么判斷數(shù)據(jù)分配在棧上還是堆上:
首先局部變量分配在棧上;而通過 malloc 和 new 申請的空間是在堆上。
5 初始化為0的全局變量在bss還是data
BSS 段通常是指用來存放程序中 未初始化的或者初始化為 0 的 全局變量和靜態(tài)變量的一塊內(nèi)存區(qū)域。特點是可讀寫的, 在程序執(zhí)行之前 BSS 段會自動清 0 。
6 簡述C++中內(nèi)存對齊的使用場景
內(nèi)存對齊應(yīng)用于三種數(shù)據(jù)類型中: struct/class/union
struct/class/union 內(nèi)存對齊原則有四個:
1. 數(shù)據(jù)成員對齊規(guī)則 :結(jié)構(gòu) (struct) 或聯(lián)合 (union) 的數(shù)據(jù)成員, 第一個數(shù)據(jù)成員放在 offset 為 0 的地
方 ,以后每個數(shù)據(jù)成員存儲的 起始位置要從該成員大小或者成員的子成員大小的整數(shù)倍開始 。
2. 結(jié)構(gòu)體作為成員 : 如果一個結(jié)構(gòu)里有某些結(jié)構(gòu)體成員 , 則結(jié)構(gòu)體成員要從其 內(nèi)部 " 最寬基本類型成
員 " 的整數(shù)倍地址開始存儲。 (struct a 里存有 struct b,b 里有 char,int ,double 等元素 , 那 b 應(yīng)該從 8 的
整數(shù)倍開始存儲 ) 。
3. 收尾工作 : 結(jié)構(gòu)體的總大小,也就是 sizeof 的結(jié)果, 必須是其內(nèi)部最大成員的 " 最寬基本類型成員 " 的
整數(shù)倍。不足的要補齊。 ( 基本類型不包括 struct/class/uinon) 。
4. sizeof(union) ,以結(jié)構(gòu)里面 size 最大元素為 union 的 size ,因為在某一時刻, union 只有一個成員真
正存儲于該地址。
?什么是內(nèi)存對齊?
那么什么是字節(jié)對齊?在 C 語言中,結(jié)構(gòu)體是一種復(fù)合數(shù)據(jù)類型,其構(gòu)成元素既可以是基本數(shù)據(jù)類型 (如int 、 long 、 float 等)的變量,也可以是一些復(fù)合數(shù)據(jù)類型(如數(shù)組、結(jié)構(gòu)體、聯(lián)合體等)的數(shù)據(jù)單 元。在結(jié)構(gòu)體中,編譯器為結(jié)構(gòu)體的每個成員按其自然邊界( alignment )分配空間。 各個成員按照它 們被聲明的順序在內(nèi)存中順序存儲,第一個成員的地址和整個結(jié)構(gòu)體的地址相同。
為了使 CPU 能夠?qū)ψ兞窟M行快速的訪問,變量的起始地址應(yīng)該具有某些特性, 即所謂的 “ 對齊 ” ,比如 4 字 節(jié)的 int 型,其起始地址應(yīng)該位于 4 字節(jié)的邊界上,即起始地址能夠被 4 整除 ,也即 “ 對齊 ” 跟數(shù)據(jù)在內(nèi)存中 的位置有關(guān)。如果一個變量的內(nèi)存地址正好位于它長度的整數(shù)倍,他就被稱做自然對齊。
為什么要字節(jié)對齊?
為了快速準確的訪問,若沒有字節(jié)對齊則會出現(xiàn)多次訪問浪費時間。
舉例說明(定義一個 char 和 int 型數(shù)據(jù)不按照字節(jié)對齊存儲的情況需要多次訪問)
3?面向?qū)ο?/strong>
1 簡述一下什么是面向?qū)ο?
1. 面向?qū)ο笫且环N編程思想,把一切東西看成是一個個對象 ,把這些對象擁有的屬性變量和操作這些 屬性變量的函數(shù)打包成一個類來表示
2. 面向過程和面向?qū)ο蟮膮^(qū)別
面向過程:根據(jù)業(yè)務(wù)邏輯從上到下寫代碼
面向?qū)ο?#xff1a; 將數(shù)據(jù)與函數(shù)綁定到一起,進行封裝 ,加快開發(fā)程序,減少重復(fù)代碼的重寫過程。
2 簡述一下面向?qū)ο蟮娜筇卣?
? 面向?qū)ο蟮娜筇卣魇?/span> 封裝、繼承、多態(tài)
1. 封裝: 將數(shù)據(jù)和操作數(shù)據(jù)的方法進行有機結(jié)合,隱藏對象的屬性和實現(xiàn)細節(jié),僅對外公開接口來和對象進行 交互。 封裝 本質(zhì)上是一種管理 ,不想給別人看到的,我們使用 protected/private 把成員
封裝起來。開放一些共有的成員函數(shù)對成員合理的訪問。
2. 繼承: 可以使用現(xiàn)有類的所有功能 ,并在無需重新編寫原來的類的情況下對這些功能進行擴展。
三種繼承方式。
3. 多態(tài): 用父類型別的指針指向其子類的實例,然后通過父類的指針調(diào)用實際子類的成員函數(shù) 。實現(xiàn)多態(tài),有二種方式, 重寫,重載 。
?
3簡述一下 C++ 的重載和重寫,以及它們的區(qū)別
1. 重寫
是指 派生類中存在重新定義的函數(shù)。其函數(shù)名,參數(shù)列表,返回值類型,所有都必須同基類中被重
寫的函數(shù)一致。只有函數(shù)體不同(花括號內(nèi)) ,派生類對象調(diào)用時會調(diào)用派生類的重寫函數(shù),不會
調(diào)用被重寫函數(shù)。重寫的基類中被重寫的函數(shù)必須有 virtual 修飾。
#include<bits/stdc++.h>
using namespace std;
class A
{
public:
virtual void fun()
{
cout << "A";
}
};
class B :public A
{
public:
virtual void fun()
{
cout << "B";
}
};
int main(void)
{
A* a = new B();
a->fun();//輸出B,A類中的fun在B類中重寫
}
1. 重載
我們在平時寫代碼中會用到幾個函數(shù)但是他們的實現(xiàn)功能相同,但是有些細節(jié)卻不同。 函數(shù)重載是
指同一可訪問區(qū)內(nèi)被聲明的幾個具有不同參數(shù)列(參數(shù)的類型,個數(shù),順序不同)的同名函數(shù),根
據(jù)參數(shù)列表確定調(diào)用哪個函數(shù),重載不關(guān)心函數(shù)返回類型。
#include<bits/stdc++.h>
using namespace std;
class A
{
void fun() {};
void fun(int i) {};
void fun(int i, int j) {};
void fun1(int i,int j){};
};
4 說說 C++ 的重載和重寫是如何實現(xiàn)的
1. 重載: C++ 利用命名傾軋( name mangling )技術(shù),來改名函數(shù)名,區(qū)分參數(shù)不同的同名函數(shù)。命 名傾軋是在編譯階段完成的。 編譯時將參數(shù)類型加入以區(qū)分不同。
2. 重寫: 在 基類的函數(shù)前加上 virtual 關(guān)鍵字,在派生類中重寫該函數(shù),運行時將會根據(jù)對象的實際類 型來調(diào)用相應(yīng)的函數(shù) 。如果 對象類型是派生類,就調(diào)用派生類的函數(shù);如果對象類型是基類,就調(diào) 用基類的函數(shù)。
虛函數(shù)需要注意的地方:
用 virtual 關(guān)鍵字申明 的函數(shù)叫做虛函數(shù),虛函數(shù)肯定是類的成員函數(shù)。 存在虛函數(shù)的類都有一個一維的虛函數(shù)表叫做虛表 , 類的對象有一個指向虛表開始的虛指針。 虛表是和類對應(yīng)的,虛表指針是和對象對應(yīng)的。 多態(tài)性是一個接口多種實現(xiàn) ,是面向?qū)ο蟮暮诵?#xff0c;分為類的多態(tài)性和函數(shù)的多態(tài)性。 重寫用虛函數(shù)來實現(xiàn),結(jié)合動態(tài)綁定 。 純虛函數(shù) 是虛函數(shù)再加上 = 0 。 抽象類 是指包括至少一個 純虛函數(shù)的類 。
純虛函數(shù): virtual void fun()=0 。即抽象類 必須在子類實現(xiàn)這個函數(shù),即先有名稱,沒有內(nèi)容 ,在
派生類實現(xiàn)內(nèi)容。
5 說說構(gòu)造函數(shù)有幾種,分別什么作用
C++ 中的構(gòu)造函數(shù)可以分為 4 類:默認構(gòu)造函數(shù)、初始化構(gòu)造函數(shù)、拷貝構(gòu)造函數(shù)、移動構(gòu)造函數(shù)。 1. 默認構(gòu)造函數(shù)和初始化構(gòu)造函數(shù)。 在定義類的對象的時候,完成對象的初始化工作。
注意: 有了有參的構(gòu)造了,編譯器就不提供默認的構(gòu)造函數(shù)。
class Student
{
public:
//默認構(gòu)造函數(shù)
Student()
{
num=1001;
age=18;
}
//初始化構(gòu)造函數(shù)
Student(int n,int a):num(n),age(a){}
private:
int num;
int age;
};
int main()
{
//用默認構(gòu)造函數(shù)初始化對象S1
Student s1;
//用初始化構(gòu)造函數(shù)初始化對象S2
Student s2(1002,18);
return 0;
}
2. 拷貝構(gòu)造函數(shù)
#include "stdafx.h"
#include "iostream.h"
class Test
{
int i;
int *p;
public:
Test(int ai,int value)
{
i = ai;
p = new int(value);
}
~Test()
{
delete p;
}
Test(const Test& t)
{
this->i = t.i;
this->p = new int(*t.p);
}
};
//復(fù)制構(gòu)造函數(shù)用于復(fù)制本類的對象
int main(int argc, char* argv[])
{
Test t1(1,2);
Test t2(t1);//將對象t1復(fù)制給t2。注意復(fù)制和賦值的概念不同
return 0;
}
賦值構(gòu)造函數(shù)默認實現(xiàn)的是值拷貝(淺拷貝) 。
3. 移動構(gòu)造函數(shù)。用于將其他類型的變量,隱式轉(zhuǎn)換為本類對象。 下面的轉(zhuǎn)換構(gòu)造函數(shù),將 int 類型的 r轉(zhuǎn)換為 Student 類型的對象,對象的 age 為 r , num 為 1004.
Student(int r)
{
int num=1004;
int age= r;
}
6 只定義析構(gòu)函數(shù),會自動生成哪些構(gòu)造函數(shù)
只定義了析構(gòu)函數(shù),編譯器將自動為我們生成 拷貝構(gòu)造函數(shù)和默認構(gòu)造函數(shù) 。
默認構(gòu)造函數(shù)和初始化構(gòu)造函數(shù)。 在定義類的對象的時候,完成對象的初始化工作。
class Student
{
public:
//默認構(gòu)造函數(shù)
Student()
{
num=1001;
age=18;
}
//初始化構(gòu)造函數(shù)
Student(int n,int a):num(n),age(a){}
private:
int num;
int age;
};
int main()
{
//用默認構(gòu)造函數(shù)初始化對象S1
Student s1;
//用初始化構(gòu)造函數(shù)初始化對象S2
Student s2(1002,18);
return 0;
}
?有了有參的構(gòu)造了,編譯器就不提供默認的構(gòu)造函數(shù)??截悩?gòu)造函數(shù)
#include "stdafx.h"
#include "iostream.h"
class Test
{
int i;
int *p;
public:
Test(int ai,int value)
{
i = ai;
p = new int(value);
}
~Test()
{
delete p;
}
Test(const Test& t)
{
this->i = t.i;
this->p = new int(*t.p);
}
};
//復(fù)制構(gòu)造函數(shù)用于復(fù)制本類的對象
int main(int argc, char* argv[])
{
Test t1(1,2);
Test t2(t1);//將對象t1復(fù)制給t2。注意復(fù)制和賦值的概念不同。
return 0;
}
賦值構(gòu)造函數(shù)默認實現(xiàn)的是值拷貝(淺拷貝)。
答案解析
示例如下:
class HasPtr
{
public:
HasPtr(const string& s = string()) :ps(new string(s)), i(0) {}
~HasPtr() { delete ps; }
private:
string * ps;
int i;
};
如果類外面有這樣一個函數(shù):
???????? HasPtr f(HasPtr hp)
{
HasPtr ret = hp;
///... 其他操作
return ret;
}
當(dāng)函數(shù)執(zhí)行完了之后,將會調(diào)用 hp 和 ret 的析構(gòu)函數(shù),將 hp 和 ret 的成員 ps 給 delete 掉,但是由于 ret 和 hp指向了同一個對象,因此該對象的 ps 成員被 delete 了兩次,這樣產(chǎn)生一個未定義的錯誤,所以說,如 果一個類定義了析構(gòu)函數(shù),那么它要定義自己的拷貝構(gòu)造函數(shù)和默認構(gòu)造函數(shù)。
7 說說一個類,默認會生成哪些函數(shù)
定義一個空類
class Empty
{
};
默認會生成以下幾個函數(shù):無參的構(gòu)造函數(shù)在定義類的對象的時候,完成對象的初始化工作。
Empty()
{
}
拷貝構(gòu)造函數(shù)
拷貝構(gòu)造函數(shù)用于復(fù)制本類的對象
Empty(const Empty& copy)
{
}
賦值運算符
Empty& operator = (const Empty& copy)
{
}
析構(gòu)函數(shù)(非虛)
~Empty()
{
}
8 說說 C++ 類對象的初始化順序,有多重繼承情況下的順序
1. 創(chuàng)建派生類的對象,基類的構(gòu)造函數(shù)優(yōu)先被調(diào)用 (也優(yōu)先于派生類里的成員類);
2. 如果類里面有成員類,成員類的構(gòu)造函數(shù)優(yōu)先被調(diào)用 ; ( 也優(yōu)先于該類本身的構(gòu)造函數(shù))
3. 基類構(gòu)造函數(shù)如果有多個基類,則構(gòu)造函數(shù)的調(diào)用順序是某類在類派生表中出現(xiàn)的順序 而不是它們 在成員初始化表中的順序;
4. 成員類對象構(gòu)造函數(shù)如果有多個成員類對象,則構(gòu)造函數(shù)的調(diào)用順序是對象在類中被聲明的順序 而 不是它們出現(xiàn)在成員初始化表中的順序;
5. 派生類構(gòu)造函數(shù),作為一般規(guī)則派生類構(gòu)造函數(shù)應(yīng)該不能直接向一個基類數(shù)據(jù)成員賦值而是把值傳遞給適當(dāng)?shù)幕悩?gòu)造函數(shù)。
6. 綜上可以得出,初始化順序:
父類構(gòu)造函數(shù) –> 成員類對象構(gòu)造函數(shù) –> 自身構(gòu)造函數(shù)
其中成員變量的初始化與聲明順序有關(guān),構(gòu)造函數(shù)的調(diào)用順序是類派生列表中的順序。
析構(gòu)順序和構(gòu)造順序相反 。
9 簡述下向上轉(zhuǎn)型和向下轉(zhuǎn)型
1. 子類轉(zhuǎn)換為父類:向上轉(zhuǎn)型,使用 dynamic_cast (expression) ,這種轉(zhuǎn)換相對來說 比較安全 不會有數(shù)據(jù)的丟失;
2. 父類轉(zhuǎn)換為子類:向下轉(zhuǎn)型 ,可以使用強制轉(zhuǎn)換,這種轉(zhuǎn)換時 不安全的,會導(dǎo)致數(shù)據(jù)的丟失 ,原因是 父類的指針或者引用的內(nèi)存中可能不包含子類的成員的內(nèi)存 。
10 簡述下深拷貝和淺拷貝,如何實現(xiàn)深拷貝
1. 淺拷貝: 又稱值拷貝, 將源對象的值拷貝到目標對象中去 ,本質(zhì)上來說 源對象和目標對象共用一份實體 , 只是所引用的變量名不同,地址其實還是相同的 。舉個簡單的例子,你的小名叫西西,大名 叫冬冬,當(dāng)別人叫你西西或者冬冬的時候你都會答應(yīng),這兩個名字雖然不相同,但是都指的是你。
2. 深拷貝 ,拷貝的時候先 開辟出和源對象大小一樣的空間,然后將源對象里的內(nèi)容拷貝到目標對象中去 ,這樣兩個 指針就指向了不同的內(nèi)存位置。并且里面的內(nèi)容是一樣的 , 深拷貝情況下,不會出現(xiàn)重復(fù)釋放同一塊內(nèi)存的錯誤。
???????? 11 簡述一下 C++ 中的多態(tài)
由于 派生類重寫基類方法,然后用基類引用指向派生類對象,調(diào)用方法時候會進行動態(tài)綁定,這就是多態(tài) 。 多態(tài)分為 靜態(tài)多態(tài)和動態(tài)多態(tài) :
1. 靜態(tài)多態(tài) : 編譯器在編譯期間完成的,編譯器會根據(jù)實參類型來推斷該調(diào)用哪個函數(shù) ,如果有對應(yīng)的函數(shù),就調(diào)用,沒有則在編譯時報錯。比如一個簡單的加法函數(shù):
include<iostream>
using namespace std;
int Add(int a,int b)//1
{
return a+b;
}
char Add(char a,char b)//2
{
return a+b;
}
int main()
{
cout<<Add(666,888)<<endl;//1
cout<<Add('1','2');//2
return 0;
}
顯然,第一條語句會調(diào)用函數(shù) 1 ,而第二條語句會調(diào)用函數(shù) 2 ,這絕不是因為函數(shù)的聲明順序,不信
你可以將順序調(diào)過來試試。
2. 動態(tài)多態(tài): 其實要實現(xiàn)動態(tài)多態(tài),需要幾個條件 —— 即動態(tài)綁定條件:
1. 虛函數(shù)。 基類中必須有虛函數(shù),在派生類中必須重寫虛函數(shù)。
2. 通過 基類類型的指針或引用來調(diào)用虛函數(shù) 。
12 說說為什么要虛析構(gòu),為什么不能虛構(gòu)造
1. 虛析構(gòu): 將可能會被繼承的父類的析構(gòu)函數(shù)設(shè)置為虛函數(shù),可以保證當(dāng)我們 new 一個子類,然后使用基類指針指向該子類對象,釋放基類指針時可以釋放掉子類的空間,防止內(nèi)存泄漏 。如果基類的 析構(gòu)函數(shù)不是虛函數(shù), 在特定情況下會 導(dǎo)致派生類無法被析構(gòu) 。
1. 用派生類類型指針綁定派生類實例,析構(gòu)的時候,不管基類析構(gòu)函數(shù)是不是虛函數(shù),都會正常
析構(gòu)
2. 用基類類型指針綁定派生類實例,析構(gòu)的時候,如果基類析構(gòu)函數(shù)不是虛函數(shù),則只會析構(gòu)基
類,不會析構(gòu)派生類對象,從而造成內(nèi)存泄漏。為什么會出現(xiàn)這種現(xiàn)象呢,個人認為析構(gòu)的時
候如果沒有虛函數(shù)的動態(tài)綁定功能,就只根據(jù)指針的類型來進行的,而不是根據(jù)指針綁定的對
象來進行,所以只是調(diào)用了基類的析構(gòu)函數(shù);如果基類的析構(gòu)函數(shù)是虛函數(shù),則析構(gòu)的時候就
要根據(jù)指針綁定的對象來調(diào)用對應(yīng)的析構(gòu)函數(shù)了。
C++ 默認的析構(gòu)函數(shù)不是虛函數(shù)是因為虛函數(shù)需要額外的虛函數(shù)表和虛表指針,占用額外的內(nèi)存。
2. 不能虛構(gòu)造:
1. 從存儲空間角度: 虛函數(shù)對應(yīng)一個 vtale, 這個表的地址是存儲在對象的內(nèi)存空間的 。如果將構(gòu)
造函數(shù)設(shè)置為虛函數(shù),就需要到 vtable 中調(diào)用, 可是對象還沒有實例化,沒有內(nèi)存空間分配,如何調(diào)用 。(悖論)
2. 從實現(xiàn)上看, vbtl 在構(gòu)造函數(shù)調(diào)用后才建立,因而構(gòu)造函數(shù)不可能成為虛函數(shù)。
13 說說模板類是在什么時候?qū)崿F(xiàn)的
1. 模板實例化:模板的實例化分為顯示實例化和隱式實例化,前者是研發(fā)人員明確的告訴模板應(yīng)該使用什么樣的類型去生成具體的類或函數(shù),后者是在編譯的過程中由編譯器來決定使用什么類型來實例化一個模板不管是顯示實例化或隱式實例化,最終生成的類或函數(shù)完全是按照模板的定義來實現(xiàn)的
2. 模板具體化:當(dāng)模板使用某種類型類型實例化后生成的類或函數(shù)不能滿足需要時,可以考慮對模板進行具體化。具體化時可以修改原模板的定義,當(dāng)使用該類型時,按照具體化后的定義實現(xiàn),具體化相當(dāng)于對某種類型進行特殊處理。
3. 代碼示例
#include <iostream>
using namespace std;
// #1 模板定義
template<class T>
struct TemplateStruct
{
TemplateStruct()
{
cout << sizeof(T) << endl;
}
};
// #2 模板顯示實例化
template struct TemplateStruct<int>;
// #3 模板具體化
template<> struct TemplateStruct<double>
{
TemplateStruct() {
cout << "--8--" << endl;
}
};
int main()
{
TemplateStruct<int> intStruct;
TemplateStruct<double> doubleStruct;
// #4 模板隱式實例化
TemplateStruct<char> llStruct;
}

?
14 說說類繼承時,派生類對不同關(guān)鍵字修飾的基類方法的訪問權(quán)限
類中的成員可以分為三種類型,分別為 public 成員、 protected 成員、 public 成員。類中可以直接訪問自己類的public 、 protected 、 private 成員,但 類對象只能訪問自己類的 public 成員 。
1. public 繼承: 派生類可以訪問基類的 public 、 protected 成員,不可以訪問基類的 private 成員 ;
派生 類對象可以訪問基類的 public 成員 , 不可以訪問 基類的 protected 、 private 成員。
2. protected 繼承: 派生類可以訪問基類的 public 、 protected 成員 ,不可以訪問基類的 private 成員;
派生類對象不可以訪問 基類的 public 、 protected 、 private 成員。
3. private 繼承:派生類可以訪問基類的 public 、 protected 成員,不可以訪問基類的 private 成員;
派生類對象不可以訪問 基類的 public 、 protected 、 private 成員。
15 簡述一下移動構(gòu)造函數(shù),什么庫用到了這個函數(shù)?
移動也使用一個對象的值設(shè)置另一個對象的值。 移動實現(xiàn)的是對象值真實的轉(zhuǎn)移 (源對象到目的對
象): 源對象將丟失其內(nèi)容,其內(nèi)容將被目的對象占有 。移動操作的發(fā)生的時候,是當(dāng)移動值的對象是未命名的對象的時候。這里未命名的對象就是那些臨時變量,甚至都不會有名稱。典型的未命名對象就是函數(shù)的返回值或者類型轉(zhuǎn)換的對象。
16 構(gòu)造函數(shù)為什么不能被聲明為虛函數(shù)?
1. 從存儲空間角度:虛函數(shù)對應(yīng)一個 vtale, 這個表的地址是存儲在對象的內(nèi)存空間的。如果將構(gòu)造函數(shù)設(shè)置為虛函數(shù),就需要到vtable 中調(diào)用,可是對象還沒有實例化,沒有內(nèi)存空間分配,如何調(diào)
用。(悖論)
2. 從使用角度:虛函數(shù)主要用于在信息不全的情況下,能使重載的函數(shù)得到對應(yīng)的調(diào)用。構(gòu)造函數(shù)本身就是要初始化實例,那使用虛函數(shù)也沒有實際意義呀。所以構(gòu)造函數(shù)沒有必要是虛函數(shù)。虛函數(shù)的作用在于通過父類的指針或者引用來調(diào)用它的時候能夠變成調(diào)用子類的那個成員函數(shù)。而構(gòu)造函數(shù)是在創(chuàng)建對象時自動調(diào)用的,不可能通過父類的指針或者引用去調(diào)用,因此也就規(guī)定構(gòu)造函數(shù)不能是虛函數(shù)。
3. 從實現(xiàn)上看, vbtl 在構(gòu)造函數(shù)調(diào)用后才建立,因而構(gòu)造函數(shù)不可能成為虛函數(shù)。從實際含義上看,在調(diào)用構(gòu)造函數(shù)時還不能確定對象的真實類型(因為子類會調(diào)父類的構(gòu)造函數(shù));而且構(gòu)造函數(shù)的作用是提供初始化,在對象生命期只執(zhí)行一次,不是對象的動態(tài)行為,也沒有太大的必要成為虛函數(shù)。
17 簡述一下什么是常函數(shù),有什么作用
類的成員函數(shù)后面加 const ,表明這個函數(shù)不會對這個類對象的數(shù)據(jù)成員(準確地說是非靜態(tài)數(shù)據(jù)成員)作任何改變。 在設(shè)計類的時候,一個 原則就是對于不改變數(shù)據(jù)成員的成員函數(shù)都要在后面加 const , 而對于改變數(shù)據(jù)成員的成員函數(shù)不能加 const 。所以 const 關(guān)鍵字對成員函數(shù)的行為作了更明確的限定:有 const 修飾的成員函數(shù)(指 const 放在函數(shù)參數(shù)表的后面,而不是在函數(shù)前面或者參數(shù)表內(nèi)),只能讀取數(shù)據(jù)成員,不能改變數(shù)據(jù)成員;沒有 const 修飾的成員函數(shù),對數(shù)據(jù)成員則是可讀可寫的。除此之外,在類的成員函數(shù)后面加 const 還有什么好處呢?那就是常量(即 const )對象可以調(diào)用 const成員函數(shù),而不能調(diào)用非const 修飾的函數(shù)。正如非 const 類型的數(shù)據(jù)可以給 const 類型的變量賦值一樣,反之則不成立。
#include<iostream>
using namespace std;
class CStu
{
public:
int a;
CStu()
{
a = 12;
}
void Show() const
{
//a = 13; //常函數(shù)不能修改數(shù)據(jù)成員
cout <<a << "I am show()" << endl;
}
};
int main()
{
CStu st;
st.Show();
system("pause");
return 0;
}
18 說說什么是虛繼承,解決什么問題,如何實現(xiàn)?
虛繼承是 解決 C++ 多重繼承問題的一種手段,從不同途徑繼承來的同一基類,會在子類中存在多份拷貝 。這將存在兩個問題:其一, 浪費存儲空間 ;第二, 存在二義性問題 ,通常可以將派生類對象的地址賦值給基類對象,實現(xiàn)的具體方式是,將基類指針指向繼承類(繼承類有基類的拷貝)中的基類對象的地址,但是多重繼承可能存在一個基類的多份拷貝,這就出現(xiàn)了二義性。虛繼承可以解決多種繼承前面 提到的兩個問題。
#include<iostream>
using namespace std;
class A{
public:
int _a;
};
class B :virtual public A
{
public:
int _b;
};
class C :virtual public A
{
public:
int _c;
};
class D :public B, public C
{
public:
int _d;
};
//菱形繼承和菱形虛繼承的對象模型
int main()
{
D d;
d.B::_a = 1;
d.C::_a = 2;
d._b = 3;
d._c = 4;
d._d = 5;
cout << sizeof(D) << endl;
return 0;
}
分別從菱形繼承和虛繼承來分析:菱形繼承中A 在 B,C,D, 中各有一份,虛繼承中, A 共享。上面的虛繼承表實際上是一個指針數(shù)組。B 、 C 實際上是虛基表指針,指向虛基表。虛基表:存放相對偏移量,用來找虛基類。
19 簡述一下虛函數(shù)和純虛函數(shù),以及實現(xiàn)原理
1. C++ 中的虛函數(shù)的作用主要是實現(xiàn)了多態(tài)的機制。關(guān)于多態(tài),簡而言之就是用父類型的指針指向其子類的實例,然后通過父類的指針調(diào)用實際子類的成員函數(shù)。這種技術(shù)可以讓父類的指針有“ 多種形態(tài)” ,這是一種泛型技術(shù)。如果調(diào)用非虛函數(shù),則無論實際對象是什么類型,都執(zhí)行基類類型所定義的函數(shù)。非虛函數(shù)總是在編譯時根據(jù)調(diào)用該函數(shù)的對象,引用或指針的類型而確定。如果調(diào)用 虛函數(shù),則直到運行時才能確定調(diào)用哪個函數(shù),運行的虛函數(shù)是引用所綁定或指針所指向的對象所。 屬類型定義的版本。虛函數(shù)必須是基類的非靜態(tài)成員函數(shù)。虛函數(shù)的作用是實現(xiàn)動態(tài)聯(lián)編,也就是在程序的運行階段動態(tài)地選擇合適的成員函數(shù),在定義了虛函數(shù)后,可以在基類的派生類中對虛函數(shù)重新定義,在派生類中重新定義的函數(shù)應(yīng)與虛函數(shù)具有相同的形參個數(shù)和形參類型。以實現(xiàn)統(tǒng)一的接口,不同定義過程。如果在派生類中沒有對虛函數(shù)重新定義,則它繼承其基類的虛函數(shù)。
class Person{
public:
//虛函數(shù)
virtual void GetName(){
cout<<"PersonName:xiaosi"<<endl;
};
};
class Student:public Person{
public:
void GetName(){
cout<<"StudentName:xiaosi"<<endl;
};
};
int main(){
//指針
Person *person = new Student();
//基類調(diào)用子類的函數(shù)
person->GetName();//StudentName:xiaosi
}
虛函數(shù)( Virtual Function )是通過一張?zhí)摵瘮?shù)表( Virtual Table )來實現(xiàn)的。簡稱為 V-Table 。在
這個表中,主是要一個類的虛函數(shù)的地址表,這張表解決了繼承、覆蓋的問題,保證其容真實反應(yīng)
實際的函數(shù)。這樣,在有虛函數(shù)的類的實例中這個表被分配在了這個實例的內(nèi)存中,所以,當(dāng)我們
用父類的指針來操作一個子類的時候,這張?zhí)摵瘮?shù)表就顯得由為重要了,它就像一個地圖一樣,指
明了實際所應(yīng)該調(diào)用的函數(shù)。
2. 純虛函數(shù)是在基類中聲明的虛函數(shù),它在基類中沒有定義,但要求任何派生類都要定義自己的實現(xiàn)方法。在基類中實現(xiàn)純虛函數(shù)的方法是在函數(shù)原型后加“=0” virtualvoid GetName() =0 。在很多情況下,基類本身生成對象是不合情理的。例如,動物作為一個基類可以派生出老虎、孔雀等子類,但動物本身生成對象明顯不合常理。為了解決上述問題,將函數(shù)定義為純虛函數(shù),則編譯器要求在派生類中必須予以重寫以實現(xiàn)多態(tài)性。同時含有純虛擬函數(shù)的類稱為抽象類,它不能生成對象。這樣就很好地解決了上述兩個問題。將函數(shù)定義為純虛函數(shù)能夠說明,該函數(shù)為后代類型提供了可以覆蓋的接口,但是這個類中的函數(shù)絕不會調(diào)用。聲明了純虛函數(shù)的類是一個抽象類。所以,用戶不能創(chuàng)建類的實例,只能創(chuàng)建它的派生類的實例。必須在繼承類中重新聲明函數(shù)(不要后面的=0 )否則該派生類也不能實例化,而且它們在抽象類中往往沒有定義。定義純虛函數(shù)的目的在于,使派生類僅僅只是繼承函數(shù)的接口。純虛函數(shù)的意義,讓所有的類對象(主要是派生類對象)都可以執(zhí)行純虛函數(shù)的動作,但類無法為純虛函數(shù)提供一個合理的缺省實現(xiàn)。所以類純虛函數(shù)的聲明就是在告訴子類的設(shè)計者,“ 你必須提供一個純虛函數(shù)的實現(xiàn),但我不知道你會怎樣實現(xiàn)它 ” 。
//抽象類
class Person{
public:
//純虛函數(shù)
virtual void GetName()=0;
};
class Student:public Person{
public:
Student(){
};
void GetName(){
cout<<"StudentName:xiaosi"<<endl;
};
};
int main(){
Student student;
}
20 說說純虛函數(shù)能實例化嗎,為什么?派生類要實現(xiàn)嗎,為什么?
參考回答
1. 純 虛函數(shù)不可以實例化,但是可以用其派生類實例化 ,示例如下:
class Base
{
public:
virtual void func() = 0;
};
#include<iostream>
using namespace std;
class Base
{
public:
virtual void func() = 0;
};
class Derived :public Base
{
public:
void func() override
{
cout << "哈哈" << endl;
}
};
int main()
{
Base *b = new Derived();
b->func();
return 0;
}
2. 虛函數(shù)的原理采用 vtable 。類中含有純虛函數(shù)時,其 vtable 不完全,有個空位。
即 “ 純虛函數(shù)在類的 vftable 表中對應(yīng)的表項被賦值為 0 。也就是指向一個不存在的函數(shù)。由于編譯器
絕對不允許有調(diào)用一個不存在的函數(shù)的可能,所以該類不能生成對象。在它的派生類中,除非重寫
此函數(shù),否則也不能生成對象。 ”
所以純虛函數(shù)不能實例化。
3. 純虛函數(shù)是在基類中聲明的虛函數(shù),它要求任何派生類都要定義自己的實現(xiàn)方法,以實現(xiàn)多態(tài)性 。
4. 定義純虛函數(shù)是為了實現(xiàn)一個接口,用來規(guī)范派生類的行為,也即規(guī)范繼承這個類的程序員必須實 現(xiàn)這個函數(shù)。派生類僅僅只是繼承函數(shù)的接口。純虛函數(shù)的意義在于,讓所有的類對象(主要是派生類對象)都可以執(zhí)行純虛函數(shù)的動作,但基類無法為純虛函數(shù)提供一個合理的缺省實現(xiàn)。所以類純虛函數(shù)的聲明就是在告訴子類的設(shè)計者,“ 你必須提供一個純虛函數(shù)的實現(xiàn),但我不知道你會怎樣實現(xiàn)它” 。
21 說說C++中虛函數(shù)與純虛函數(shù)的區(qū)別
參考回答
1. 虛函數(shù)和純虛函數(shù)可以定義在同一個類中,含有純虛函數(shù)的類被稱為抽象類,而只含有虛函數(shù)的類不能被稱為抽象類。
2. 虛函數(shù)可以被直接使用,也可以被子類重載以后,以多態(tài)的形式調(diào)用,而純虛函數(shù)必須在子類中實現(xiàn)該函數(shù)才可以使用,因為純虛函數(shù)在基類有聲明而沒有定義。
3. 虛函數(shù)和純虛函數(shù)都可以在子類中被重載,以多態(tài)的形式被調(diào)用。
4. 虛函數(shù)和純虛函數(shù)通常存在于抽象基類之中,被繼承的子類重載,目的是提供一個統(tǒng)一的接口。
5. 虛函數(shù)的定義形式: virtual{} ; 純虛函數(shù)的定義形式: virtual { } = 0 ; 在虛函數(shù)和純虛函數(shù)
的定義中不能有 static 標識符,原因很簡單,被 static 修飾的函數(shù)在編譯時要求前期綁定 , 然而虛函數(shù)
卻是動態(tài)綁定,而且被兩者修飾的函數(shù)生命周期也不一樣。
答案解析
1. 我們舉個虛函數(shù)的例子:
class A
{
public:
virtual void foo()
{
cout<<"A::foo() is called"<<endl;
}
};
class B:public A
{
public:
void foo()
{
cout<<"B::foo() is called"<<endl;
}
};
int main(void)
{
A *a = new B();
a->foo(); // 在這里,a雖然是指向A的指針,但是被調(diào)用的函數(shù)(foo)卻是B的!
return 0;
}
這個例子是虛函數(shù)的一個典型應(yīng)用,通過這個例子,也許你就對虛函數(shù)有了一些概念。它虛就虛在
所謂 “ 推遲聯(lián)編 ” 或者 “ 動態(tài)聯(lián)編 ” 上,一個類函數(shù)的調(diào)用并不是在編譯時刻被確定的,而是在運行時
刻被確定的。由于編寫代碼的時候并不能確定被調(diào)用的是基類的函數(shù)還是哪個派生類的函數(shù),所以
被成為 “ 虛 ” 函數(shù)。虛函數(shù)只能借助于指針或者引用來達到多態(tài)的效果。
2. 純虛函數(shù)是在基類中聲明的虛函數(shù),它在基類中沒有定義,但要求任何派生類都要定義自己的實現(xiàn)方法。在基類中實現(xiàn)純虛函數(shù)的方法是在函數(shù)原型后加“=0”
virtual void funtion1()=0
為了方便使用多態(tài)特性,我們常常需要在基類中定義虛擬函數(shù)。
在很多情況下,基類本身生成對象是不合情理的。例如,動物作為一個基類可以派生出老虎、孔雀
等子類,但動物本身生成對象明顯不合常理。
為了解決上述問題,引入了純虛函數(shù)的概念,將函數(shù)定義為純虛函數(shù)(方法: virtual ReturnType
Function()= 0; ),則編譯器要求在派生類中必須予以重寫以實現(xiàn)多態(tài)性。同時含有純虛擬函數(shù)的類稱為抽象類,它不能生成對象。這樣就很好地解決了上述兩個問題。聲明了純虛函數(shù)的類是一個抽象類。所以,用戶不能創(chuàng)建類的實例,只能創(chuàng)建它的派生類的實例。純虛函數(shù)最顯著的特征是:它們必須在繼承類中重新聲明函數(shù)(不要后面的=0 ,否則該派生類也不能實例化),而且它們在抽象類中往往沒有定義。定義純虛函數(shù)的目的在于,使派生類僅僅只是繼承函數(shù)的接口。純虛函數(shù)的意義,讓所有的類對象(主要是派生類對象)都可以執(zhí)行純虛函數(shù)的動作,但類無法為純虛函數(shù)提供一個合理的缺省實現(xiàn)。所以類純虛函數(shù)的聲明就是在告訴子類的設(shè)計者,“ 你必須提供一個純虛函數(shù)的實現(xiàn),但我不知道你會怎樣實現(xiàn)它” 。
22 說說 C++ 中什么是菱形繼承問題,如何解決?
導(dǎo)致問題: 菱形繼承會導(dǎo)致數(shù)據(jù)重復(fù)和產(chǎn)生歧義;
解決辦法: 使用虛繼承,可確保每份數(shù)據(jù)自繼承一次;
23 請問構(gòu)造函數(shù)中的能不能調(diào)用虛方法
參考回答
1. 不要在構(gòu)造函數(shù)中調(diào)用虛方法,從語法上講,調(diào)用完全沒有問題,但是從效果上看,往往不能達到需要的目的。
派生類對象構(gòu)造期間進入基類的構(gòu)造函數(shù)時,對象類型變成了基類類型,而不是派生類類型。
同樣,進入基類析構(gòu)函數(shù)時,對象也是基類類型。所以,虛函數(shù)始終僅僅調(diào)用基類的虛函數(shù)(如果是基類調(diào)用虛函數(shù)),不能達到多態(tài)的效果,所以放在構(gòu)造函數(shù)中是沒有意義的,而且往往不能達到本來想要的效果。
24 請問拷貝構(gòu)造函數(shù)的參數(shù)是什么傳遞方式,為什么
參考回答
1. 拷貝構(gòu)造函數(shù)的參數(shù)必須使用引用傳遞
2. 如果拷貝構(gòu)造函數(shù)中的參數(shù)不是一個引用,即形如 CClass(const CClass c_class) ,那么就相當(dāng)于采用了傳值的方式(pass-by-value) ,而傳值的方式會調(diào)用該類的拷貝構(gòu)造函數(shù),從而造成無窮遞歸地調(diào)用拷貝構(gòu)造函數(shù)。因此拷貝構(gòu)造函數(shù)的參數(shù)必須是一個引用。需要澄清的是,傳指針其實也是傳值,如果上面的拷貝構(gòu)造函數(shù)寫成CClass(const CClass* c_class),也是不行的。事實上,只有傳引用不是傳值外,其他所有的傳遞方式都是傳值。
25 如何理解抽象類?
參考回答
1. 抽象類的定義如下:
純虛函數(shù)是在基類中聲明的虛函數(shù),它在基類中沒有定義,但要求任何派生類都要定義自己的實現(xiàn)
方法。 在基類中實現(xiàn)純虛函數(shù)的方法是在函數(shù)原型后加 “=0” ,有虛函數(shù)的類就叫做抽象類。
2. 抽象類有如下幾個特點:
1 ) 抽象類只能用作其他類的基類,不能建立抽象類對象 。
2 )抽象類不能用作參數(shù)類型、函數(shù)返回類型或顯式轉(zhuǎn)換的類型。 3 )可以定義指向抽象類的指針和引用,此指針可以指向它的派生類,進而實現(xiàn)多態(tài)性。
26 什么是多態(tài)?除了虛函數(shù),還有什么方式能實現(xiàn)多態(tài)?
參考回答
1. 多態(tài)是面向?qū)ο蟮闹匾匦灾?#xff0c;它是一種行為的封裝,就是不同對象對同一行為會有不同的狀
態(tài)。 ( 舉例 : 學(xué)生和成人都去買票時 , 學(xué)生會打折 , 成人不會 )
2. 多態(tài)是以封裝和繼承為基礎(chǔ)的。在 C++ 中多態(tài)分為靜態(tài)多態(tài)(早綁定)和動態(tài)多態(tài)(晚綁定)兩
種,其中動態(tài)多態(tài)是通過虛函數(shù)實現(xiàn),靜態(tài)多態(tài)通過函數(shù)重載實現(xiàn),代碼如下:
class A
{
public:
void do(int a);
void do(int a, int b);
};
27 簡述一下虛析構(gòu)函數(shù),什么作用
參考回答
1. 虛析構(gòu)函數(shù),是將基類的析構(gòu)函數(shù)聲明為 virtual ,舉例如下:
class TimeKeeper
{
public:
TimeKeeper() {}
virtual ~TimeKeeper() {}
};
2. 虛析構(gòu)函數(shù)的 主要作用是防止內(nèi)存泄露 。
定義一個基類的指針 p ,在 delete p 時,如果基類的析構(gòu)函數(shù)是虛函數(shù),這時只會看 p 所賦值的對
象,如果 p 賦值的對象是派生類的對象,就會調(diào)用派生類的析構(gòu)函數(shù)(毫無疑問,在這之前也會先
調(diào)用基類的構(gòu)造函數(shù),在調(diào)用派生類的構(gòu)造函數(shù),然后調(diào)用派生類的析構(gòu)函數(shù),基類的析構(gòu)函數(shù),
所謂先構(gòu)造的后釋放);如果 p 賦值的對象是基類的對象,就會調(diào)用基類的析構(gòu)函數(shù),這樣就不會
造成內(nèi)存泄露。 如果基類的析構(gòu)函數(shù)不是虛函數(shù),在delete p 時,調(diào)用析構(gòu)函數(shù)時,只會看指針的數(shù)據(jù)類型,而不 會去看賦值的對象,這樣就會造成內(nèi)存泄露。
答案解析
我們創(chuàng)建一個 TimeKeeper 基類和一些及其它的派生類作為不同的計時方法。
class TimeKeeper
{
public:
TimeKeeper() {}
~TimeKeeper() {} //非virtual的
};
//都繼承與TimeKeeper
class AtomicClock :public TimeKeeper{};
class WaterClock :public TimeKeeper {};
class WristWatch :public TimeKeeper {};
如果客戶想要在程序中使用時間,不想操作時間如何計算等細節(jié),這時候我們可以設(shè)計 factory (工
廠)函數(shù),讓函數(shù)返回指針指向一個計時對象。該函數(shù)返回一個基類指針,這個基類指針是指向于
派生類對象的。
TimeKeeper* getTimeKeeper()
{
//返回一個指針,指向一個TimeKeeper派生類的動態(tài)分配對象
}
因為函數(shù)返回的對象存在于堆中,因此為了 在不使用時我們需要使用釋放該對象( delete )
TimeKeeper* ptk = getTimeKeeper();
delete ptk;
此處基類的析構(gòu)函數(shù)是非 virtual 的,因此 通過一個基類指針刪除派生類對象是錯誤的
解決辦法: 將基類的析構(gòu)函數(shù)改為 virtual 就正確了。
class TimeKeeper
{
public:
TimeKeeper() {}
virtual ~TimeKeeper() {}
};
聲明為 virtual 之后,通過 基類指針刪除派生類對象就會釋放整個對象(基類 + 派生類)
28 說說什么是虛基類,可否被實例化?
1. 在被繼承的類前面加上virtual關(guān)鍵字,這時被繼承的類稱為虛基類,代碼如下:
class A
class B1:public virtual A;
class B2:public virtual A;
class D:public B1,public B2;
?2. 虛繼承的類可以被實例化,舉例如下:
class Animal {/* ... */ };
class Tiger : virtual public Animal { /* ... */ };
class Lion : virtual public Animal { /* ... */ }
int main( )
{
Liger lg ;
/*既然我們已經(jīng)在Tiger和Lion類的定義中聲明了"virtual"關(guān)鍵字,于是下面的代碼編譯OK */
int weight = lg.getWeight();
}
29 簡述一下拷貝賦值和移動賦值?
參考回答
1. 拷貝賦值是通過拷貝構(gòu)造函數(shù)來賦值,在創(chuàng)建對象時,使用同一類中之前創(chuàng)建的對象來初始化新創(chuàng)建的對象。
2. 移動賦值是通過移動構(gòu)造函數(shù)來賦值,二者的主要區(qū)別在于
1 )拷貝構(gòu)造函數(shù)的形參是一個左值引用,而移動構(gòu)造函數(shù)的形參是一個右值引用;
2 )拷貝構(gòu)造函數(shù)完成的是整個對象或變量的拷貝,而移動構(gòu)造函數(shù)是生成一個指針指向源對象或
變量的地址,接管源對象的內(nèi)存,相對于大量數(shù)據(jù)的拷貝節(jié)省時間和內(nèi)存空間。
30 仿函數(shù)了解嗎?有什么作用
參考回答
1. 仿函數(shù)( functor )又稱為函數(shù)對象( function object )是一個能行使函數(shù)功能的類。仿函數(shù)的語
法幾乎和我們普通的函數(shù)調(diào)用一樣,不過作為仿函數(shù)的類,都必須重載 operator() 運算符,舉個例
子:
class Func{
public:
void operator() (const string& str) const {
cout<<str<<endl;
}
};
Func myFunc;
myFunc("helloworld!");
>>>helloworld!
1. 仿函數(shù)既能想普通函數(shù)一樣傳入給定數(shù)量的參數(shù),還能存儲或者處理更多我們需要的有用信息。我們可以舉個例子:
假設(shè)有一個 vector<string> ,你的任務(wù)是統(tǒng)計長度小于 5 的 string 的個數(shù),如果使用 count_if 函
數(shù)的話,你的代碼可能長成這樣:
bool LengthIsLessThanFive(const string& str) {
return str.length()<5;
}
int res=count_if(vec.begin(), vec.end(), LengthIsLessThanFive);
其中 count_if 函數(shù)的第三個參數(shù)是一個函數(shù)指針,返回一個 bool 類型的值。一般的,如果需要將
特定的閾值長度也傳入的話,我們可能將函數(shù)寫成這樣:
bool LenthIsLessThan(const string& str, int len) {
return str.length()<len;
}
這個函數(shù)看起來比前面一個版本更具有一般性,但是他不能滿足 count_if 函數(shù)的參數(shù)要求:
count_if 要求的是 unary function (僅帶有一個參數(shù))作為它的最后一個參數(shù)。如果我們使用仿
函數(shù),是不是就豁然開朗了呢:
class ShorterThan {
public:
explicit ShorterThan(int maxLength) : length(maxLength) {}
bool operator() (const string& str) const {
return str.length() < length;
}
private:
const int length;
};
31 C++ 中哪些函數(shù)不能被聲明為虛函數(shù)?
參考回答
常見的不不能聲明為虛函數(shù)的有: 普通函數(shù)(非成員函數(shù)),靜態(tài)成員函數(shù),內(nèi)聯(lián)成員函數(shù),構(gòu)造函
數(shù),友元函數(shù)。
1. 為什么 C++ 不支持普通函數(shù)為虛函數(shù)?
普通函數(shù)(非成員函數(shù))只能被 overload ,不能被 override ,聲明為虛函數(shù)無意義 ,因此編譯器會
在編譯時綁定函數(shù)。
2. 為什么 C++ 不支持構(gòu)造函數(shù)為虛函數(shù)?
這個原因很簡單,主要是從語義上考慮,所以不支持。因為構(gòu)造函數(shù)本來就是為了明確初始化對象
成員才產(chǎn)生的,然而 virtual function 主要是為了再不完全了解細節(jié)的情況下也能正確處理對象。另
外, virtual 函數(shù)是在不同類型的對象產(chǎn)生不同的動作,現(xiàn)在對象還沒有產(chǎn)生,如何使用 virtual 函數(shù)
來完成你想完成的動作 。(這不就是典型的悖論)
構(gòu)造函數(shù)用來創(chuàng)建一個新的對象 , 而虛函數(shù)的運行是建立在對象的基礎(chǔ)上 , 在構(gòu)造函數(shù)執(zhí)行時 , 對象尚
未形成 , 所以不能將構(gòu)造函數(shù)定義為虛函數(shù)
3. 為什么 C++ 不支持內(nèi)聯(lián)成員函數(shù)為虛函數(shù)?
其實很簡單,那 內(nèi)聯(lián)函數(shù)就是為了在代碼中直接展開,減少函數(shù)調(diào)用花費的代價,虛函數(shù)是為了在
繼承后對象能夠準確的執(zhí)行自己的動作 ,這是不可能統(tǒng)一的。(再說了, inline 函數(shù)在編譯時被展
開 ,虛函數(shù)在運行時才能動態(tài)的綁定函數(shù))
內(nèi)聯(lián)函數(shù)是在編譯時期展開 , 而虛函數(shù)的特性是運行時才動態(tài)聯(lián)編 , 所以兩者矛盾 , 不能定義內(nèi)聯(lián)函數(shù)
為虛函數(shù)
4. 為什么 C++ 不支持靜態(tài)成員函數(shù)為虛函數(shù)?
這也很簡單,靜態(tài)成員函數(shù)對于每個類來說只有一份代碼,所有的對象都共享這一份代碼,他也沒
有要動態(tài)綁定的必要性。
靜態(tài)成員函數(shù)屬于一個類而非某一對象 , 沒有 this 指針 , 它無法進行對象的判別
5. 為什么 C++ 不支持友元函數(shù)為虛函數(shù)?
因為 C++ 不支持友元函數(shù)的繼承,對于沒有繼承特性的函數(shù)沒有虛函數(shù)的說法 。
32?解釋下 C++ 中類模板和模板類的區(qū)別
參考回答
1. 類模板是模板的定義,不是一個實實在在的類,定義中用到通用類型參數(shù)
2. 模板類是實實在在的類定義,是類模板的實例化。類定義中參數(shù)被實際類型所代替。
答案解析
1. 類模板的類型參數(shù)可以有一個或多個,每個類型前面都必須加 class ,如 template <class T1,class
T2>class someclass{…}; 在定義對象時分別代入實際的類型名,如 someclass<int,double> obj;
2. 和使用類一樣,使用類模板時要注意其作用域,只能在其有效作用域內(nèi)用它定義對象。
3. 模板可以有層次,一個類模板可以作為基類,派生出派生模板類.
32 虛函數(shù)表里存放的內(nèi)容是什么時候?qū)戇M去的?
參考回答
1. 虛函數(shù)表是一個存儲虛函數(shù)地址的數(shù)組 , 以 NULL 結(jié)尾。 虛表( vftable )在編譯階段生成,對象內(nèi)存空間開辟以后,寫入對象中的 vfptr ,然后調(diào)用構(gòu)造函數(shù)。即:虛表在構(gòu)造函數(shù)之前寫入
2. 除了在構(gòu)造函數(shù)之前寫入之外,我們還需要考慮到 虛表的二次寫入機制,通過此機制讓每個對象的虛表指針都能準確的指向到自己類的虛表,為實現(xiàn)動多態(tài)提供支持。