一、動態內存管理:new/delete與底層原理
核心問題1:new/delete vs malloc/free
區別對比:
特性 | new/delete | malloc/free |
---|---|---|
類型安全 | 自動推導類型,無需轉型 | 返回void*,需強制轉型 |
生命周期 | 自動調用構造/析構函數 | 需手動初始化/清理 |
錯誤處理 | 拋bad_alloc異常 | 返回NULL,檢查errno |
數組支持 | new[]/delete[]自動管理元素 | 需手動處理數組元素 |
底層實現 | 封裝operator new/delete(調malloc) | 直接調用系統函數 |
面試點睛:自定義類型必須用new/delete,因C語言的malloc無法管理對象生命周期,如String
類的構造函數會分配內存,析構函數釋放內存,而malloc僅分配原始空間,易導致資源泄漏。
核心問題2:定位new的使用場景
定義:在已分配的內存上顯式調用構造函數,語法為new(地址) 類型(參數)
。
場景:
- 內存池:預分配內存(如
char* buf = new char[sizeof(String)]
),用定位new初始化new(buf) String("hello")
。 - 緩沖區解析:網絡接收的二進制數據解析為對象,復用已有內存。
- STL底層:如vector擴容時,對舊空間元素調用析構,新空間用定位new構造。
注意:需手動調用析構函數(s->~String()
)再釋放內存,避免資源泄漏。
二、內存分布:棧、堆、數據段的本質區別
五大內存區域
- 棧區:自動分配釋放,存局部變量、函數參數,生長方向高地址→低地址,效率高但空間有限(幾MB)。
- 堆區:手動分配(new/malloc),存動態對象,生長方向低地址→高地址,大小靈活。
- 數據段:靜態存儲,分初始化(全局變量
int a=1
)和未初始化(BSS段,int b;
默認0)。 - 代碼段:存可執行代碼和只讀常量(如
"hello"
),只讀屬性,共享性(多進程共享)。 - 內存映射段:動態鏈接庫、共享內存,用于高效I/O和進程間通信。
典型變量位置
int global = 1; // 數據段(初始化)
static int stat = 2; // 數據段(靜態全局)
void func() { int localVar = 3; // 棧區 static int statLoc = 4; // 數據段(靜態局部) int* heapPtr = new int(5); // heapPtr在棧區,指向堆區
}
面試高頻問:new
和malloc
分配的內存位于堆區,但new會調用構造函數,而malloc僅返回原始指針。
三、面向對象:封裝本質與C++改進
核心問題1:封裝的實現與作用
定義:通過類將數據(private成員)和方法(public接口)結合,控制訪問權限。
示例:
class Stack {
public: void Push(int x); // 公開接口
private: int* _array; // 私有數據,外部不可直接訪問
};
作用:
- 隱藏細節:用戶無需知道棧如何擴容,只需調用Push。
- 數據保護:禁止外部直接修改
_top
,確保棧邏輯正確(如C語言中誤用st.array[st.top]
會導致錯誤)。
核心問題2:構造/析構函數解決C語言痛點
C語言缺陷:手動調用StackInit
/StackDestroy
,易遺漏導致資源泄漏。
C++改進:
Stack st; // 自動調用構造函數初始化
st.Push(10);
// 離開作用域自動調用析構函數釋放內存
本質:自動管理對象生命周期,避免人為錯誤,如文件句柄、網絡連接等資源必在析構函數中釋放。
四、STL:組件協作與容器選擇
核心組件
- 容器:分順序(vector/list/deque)、關聯(set/map,紅黑樹實現)、適配器(stack/queue,封裝底層容器)。
- 算法:通過迭代器操作容器,如
sort(vec.begin(), vec.end())
,與容器解耦。 - 迭代器:銜接容器與算法,分隨機訪問(vector)、雙向(list)等,提供統一接口(
++
/*
)。
容器選擇策略
- 隨機訪問優先:vector(下標O(1),連續存儲,適合緩存友好)。
- 頻繁頭尾操作:deque(雙端高效,如push_front/pop_back)。
- 有序唯一集合:set(自動排序,去重);鍵值對快速查找:unordered_map(哈希表,平均O(1)查找)。
面試陷阱:stack默認基于deque實現,而非vector,因deque頭尾操作均為O(1),而vector尾插O(1),頭插O(n)。
五、模板:泛型編程與實例化原理
核心問題1:函數模板vs類模板
特性 | 函數模板 | 類模板 |
---|---|---|
實例化 | 隱式(編譯器推導) | 顯式(必須寫<T> ) |
類型參數 | 可省略 | 不可省略 |
生成產物 | 具體函數 | 具體類 |
作用范圍 | 單個函數通用化 | 整個類通用化 |
示例:
template<typename T> void Swap(T& x, T& y); // 函數模板
Swap(1, 2); // 隱式實例化,T=int template<typename T> class Stack; // 類模板
Stack<int> st; // 顯式實例化,必須指定T=int
核心問題2:模板為何導致代碼膨脹?
原因:每個不同類型的實例化(如vector<int>
和vector<double>
)生成獨立代碼,無運行時類型擦除(對比Java泛型)。
優化:避免過度嵌套模板(如vector<vector<vector<int>>>
),利用編譯器鏈接時優化(LTO)合并重復代碼。
六、sizeof vs strlen:必考點對比
核心區別表
區別點 | sizeof(運算符) | strlen(庫函數) |
---|---|---|
本質 | 編譯時計算內存大小 | 運行時遍歷\0 算長度 |
包含\0 | 是(如char arr[5]占5字節) | 否("abc"返回3) |
適用類型 | 所有類型 | 僅C風格字符串(\0 結尾) |
對指針處理 | 算指針本身大小(4/8字節) | 算指向字符串的長度 |
示例:
char str[] = "abc"; // sizeof(str)=4(含`\0`),strlen(str)=3
char* ptr = str; // sizeof(ptr)=8(64位指針),strlen(ptr)=3
易錯點:對未以\0
結尾的字符數組用strlen,會越界訪問,導致未定義行為。
七、string類:QT開發必知必會
核心問題1:小字符串優化(SBO)
機制:短字符串(如≤15字節,GCC實現)直接存在棧上,避免堆分配,提升性能。
string s = "hello"; // "hello"(5字節)存于string對象內部,無堆分配
s += "world"; // 超過SBO閾值,轉為堆分配,capacity動態擴展(通常1.5倍)
核心問題2:與QT交互的編碼處理
string轉QString(UTF-8):
QString qstr = QString::fromUtf8(str.c_str(), str.size());
QString轉string:
string str = qstr.toUtf8().data(); // 確保編碼一致,避免中文亂碼
多線程安全:
- 只讀:線程安全,無需加鎖;
- 寫入:需用
QMutex
保護,如多個線程同時push_back
日志數據時。
面試準備建議
- 對比記憶:用表格梳理new/malloc、棧/堆、函數模板/類模板的區別,清晰直觀。
- 原理深挖:理解new的底層步驟(operator new調malloc+構造函數),STL迭代器如何解耦容器與算法。
- 項目結合:如QT上位機開發中,用string處理UTF-8日志,通過reserve預分配提升性能,避免頻繁擴容。
- 邊界測試:準備越界訪問(string::at()拋異常 vs operator[]斷言)、內存泄漏(未配對delete[])等問題的解決方案。
通過系統梳理這些核心知識點,結合實際項目場景,可有效應對C++面試中的高頻問題,展現扎實的基礎與工程實踐能力。