C++ 重點知識梳理 (一) --------- 重點關鍵字及其用法
本文總結一下C++面試時常遇到的問題。C++面試中,主要涉及的考點有
- 關鍵字極其用法,常考的關鍵字有const, sizeof, typedef, inline, static, extern, new, delete等等
- 語法問題
- 型別轉換
- 指標以及指標和引用的區別
- 面向物件的相關問題,如虛擬函式機制等
- 泛型程式設計的相關問題,如模板和函式的區別等
- 記憶體管理,如位元組對齊(記憶體對齊)、動態記憶體管理、記憶體洩漏等
- 編譯和連結
- 實現函式和類
零、序章
0.1 C++與C的對比
- C++有三種程式設計方式:過程性,面向物件,泛型程式設計。
- C++函式符號由 函式名+引數型別 組成,C只有函式名。所以,C沒有函式過載的概念。
- C++ 在 C的基礎上增加了封裝、繼承、多型的概念
- C++增加了泛型程式設計
- C++增加了異常處理,C沒有異常處理
- C++增加了bool型
- C++允許無名的函式形參(如果這個形參沒有被用到的話)
- C允許main函式呼叫自己
- C++支援預設引數,C不支援
- C語言中,區域性變數必須在函式開頭定義,不允許類似for(int a = 0; ;;)這種定義方法。
- C++增加了引用
- C允許變長陣列,C++不允許
- C中函式原型可選,C++中在呼叫之前必須宣告函式原型
- C++增加了STL標準模板庫來支援資料結構和演算法
一、重要的關鍵字極其用法
1.1 const
主要用法
C++ 的const關鍵字的作用有很多,幾乎無處不在,面試中往往會問“說一說const有哪些用法”。下面是一些常見的const用法的總結:
除此以外,const的用法還有:
- const引用可以引用右值,如const int& a = 1;
注:
- const 成員方法本質上是使得this指標是指向const物件的指標,所以在const方法內,
- const 成員函式可以被非const和const物件呼叫,而const物件只能呼叫const 成員函式。原因得從C++底層找,C++方法呼叫時,會傳一個隱形的this引數(本質上是物件的地址,形參名為this)進去,所有成員方法的第一個引數是this隱形指標。const成員函式的this指標是指向const物件的const指標,當非const物件呼叫const方法時,實參this指標的型別是非const物件的const指標,賦給const物件的const指標沒有問題;但是如果const物件呼叫非const方法,此時實參this指標是指向const物件的const指標,無法賦給非const物件的const指標,所以無法呼叫。注意this實參是放在ecx暫存器中,而不是壓入棧中,這是this的特殊之處。在類的非成員函式中如果要用到類的成員變數,就可以通過訪問ecx暫存器來得到指向物件的this指標,然後再通過this指標加上成員變數的偏移量來找到相應的成員變數。文章來源
- const 指標、指向const的指標和指向const的const指標,涉及到const的特性“const左效、最左右效”
- const 全域性變數有內部連結性,即不同的檔案可以定義不同的同名const全域性變數,使用extern定義可以消除內部連結性,稱為類似全域性變數,如extern const int a = 10.另一個檔案使用extern const int a; 來引用。而且編譯器會在編譯時,將const變數替換為它的值,類似define那樣。
const 常量和define 的區別
- const常量有資料型別,而巨集定義沒有資料型別。編譯器可以對前者進行型別安全檢查,而對後者只進行字元替換,沒有型別安全檢查,並且在字元替換中可能會產生意想不到的錯誤(邊際效應)。
- 有些整合化的除錯工具可以對const常量進行除錯,但是不能對巨集定義進行除錯。
- 在C++程式中只使用const常量而不使用巨集常量,即const常量完全取代巨集常量。
- 記憶體空間的分配上。define進行巨集定義的時候,不會分配記憶體空間,編譯時會在main函式裡進行替換,只是單純的替換,不會進行任何檢查,比如型別,語句結構等,即巨集定義常量只是純粹的置放關係,如#define null 0;編譯器在遇到null時總是用0代替null它沒有資料型別.而const定義的常量具有資料型別,定義資料型別的常量便於編譯器進行資料檢查,使程式可能出現錯誤進行排查,所以const與define之間的區別在於const定義常量排除了程式之間的不安全性.
- const常量存在於程式的資料段,#define常量存在於程式的程式碼段
- const常量存在“常量摺疊”,在編譯器進行語法分析的時候,將常量表達式計算求值,並用求得的值來替換表示式,放入常量表,可以算作一種編譯優化。因為編譯器在優化的過程中,會把碰見的const全部以內容替換掉,類似巨集。
1.2 sizeof
- sizeof關鍵字不會計算表示式的值,而只會根據型別推斷大小。
- sizeof() 的括號可以省略, 如 sizeof a ;
- 類A的大小是 所有非靜態成員變數大小之和+虛擬函式指標大小
1.3 static
static的用法有:
(1)宣告靜態全域性變數,如static int a; 靜態全域性變數的特點:
- 該變數在全域性資料區分配記憶體;
- 未經初始化的靜態全域性變數會被程式自動初始化為0(自動變數的值是隨機的,除非它被顯式初始化);
- 靜態全域性變數在宣告它的整個檔案都是可見的,而在檔案之外是不可見的;
(2)宣告靜態區域性變數,即在函式內部宣告的,靜態區域性變數的特點:
- 該變數在全域性資料區分配記憶體;
- 靜態區域性變數在程式執行到該物件的宣告處時被首次初始化,即以後的函式呼叫不再進行初始化;
- 靜態區域性變數一般在宣告處初始化,如果沒有顯式初始化,會被程式自動初始化為0;
- 它始終駐留在全域性資料區,直到程式執行結束。但其作用域為區域性作用域,當定義它的函式或語句塊結束時,其作用域隨之結束;
(3)宣告靜態函式,限定函式的區域性訪問性,僅在檔案內部可見
(4)類的靜態資料成員,與全域性變數相比,靜態資料成員的好處有:
- 靜態資料成員沒有進入程式的全域性名字空間,因此不存在與程式中其它全域性名字衝突的可能性;
- 可以實現資訊隱藏。靜態資料成員可以是private成員,而全域性變數不能;
(5)類的靜態方法
1.4 typedef
typedef 用來定義新的型別,類似的還有#define 和 using (C++11) (應該儘可能用using ,比如 using AAA = int64_t; )
與巨集定義的對比
- #define 在預處理階段進行簡單替換,不做型別檢查; typedef在編譯階段處理,在作用域內給型別一個別名。
- typedef 是一個語句,結尾有分號;#define是一個巨集指令,結尾沒有分號
- typedef int* pInt; 和 #define pInt int* 不等價,前者定義 pInt a, b;會定義兩個指標,後者是一個指標,一個int。
不能宣告為inline的函式
- 包含了遞迴、迴圈等結構的函式一般不會被內聯。
- 虛擬函式一般不會內聯,但是如果編譯器能在編譯時確定具體的呼叫函式,那麼仍然會就地展開該函式。
- 如果通過函式指標呼叫行內函數,那麼該函式將不會內聯而是通過call進行呼叫。
- 構造和解構函式一般會生成大量程式碼,因此一般也不適合內聯。
- 如果行內函數呼叫了其他函式也不會被內聯。
1.5 inline
inline用來向編譯器請求宣告為行內函數,編譯器有權拒絕。
與巨集函式的對比
- 行內函數在執行時可除錯,而巨集定義不可以;
- 編譯器會對行內函數的引數型別做安全檢查或自動型別轉換(同普通函式),而巨集定義則不會;
- 行內函數可以訪問類的成員變數,巨集定義則不能;
- 在類中宣告同時定義的成員函式,自動轉化為行內函數
- 巨集只是預定義的函式,在編譯階段不進行型別安全性檢查,在編譯的時候將對應函式用巨集命令替換。對程式效能無影響
1.6 static const \ const \ static
1. static const static const 資料成員可以在類內初始化 也可以在類外,不能在建構函式中初始化,也不能在建構函式的初始化列表中初始化 2. static static資料成員只能在類外,即類的實現檔案中初始化,也不能在建構函式中初始化,不能在建構函式的初始化列表中初始化; 3. const const資料成員只能在建構函式的初始化列表中初始化;
1.7 explicit
explicit禁止了隱式轉換型別,用來修飾建構函式。原則上應該在所有的建構函式前加explicit關鍵字,當你有心利用隱式轉換的時候再去解除explicit,這樣可以大大減少錯誤的發生。如果一個建構函式 Foo(int) ;則下面的語句是合法的:
Foo f;
f = 12; // 發生了隱式轉換,先呼叫Foo(int)用12構建了一個臨時物件,然後呼叫賦值運算子複製到 f 中
如果給建構函式加了explicit,即 explicit Foo(int);就只能進行顯示轉換,無法進行隱式轉換了:
f = 12; // 非法,隱式轉換
f = Foo(12); // 合法,顯示轉換
f = (Foo)12;//合法,顯示轉換,C風格
1.8 extern
extern可以置於變數或者函式前,以標示變數或者函式的定義在別的檔案中,提示編譯器遇到此變數和函式時在其他模組中尋找其定義。此外extern也可用來進行連結指定。