一、C++有幾種傳值方式之間的區別
一、值傳遞(Pass by Value)
- 機制:創建參數的副本,函數內操作不影響原始數據
- 語法:
void func(int x)
- 特點:
- 數據安全:原始數據不受影響
- 性能開銷:需要復制大對象(如結構體、類)
- 示例:
void increment(int x) { x++; } // 修改副本,不影響原始值
int a = 10;
increment(a); // a仍為10引用高效移動資源
?
二、指針傳遞(Pass by Pointer)
- 機制:傳遞變量的地址,函數通過指針間接操作原始數據
- 語法:
void func(int* ptr)
- 特點:
- 可修改原始數據:通過
*ptr
修改 - 空指針風險:需檢查
ptr != nullptr
- 示例:
- 可修改原始數據:通過
void increment(int* ptr) { if (ptr) (*ptr)++; // 安全檢查
}
int a = 10;
increment(&a); // a變為11
?
三、引用傳遞(Pass by Reference)
- 機制:傳遞變量的別名,函數直接操作原始數據
- 語法:
void func(int& ref)
- 特點:
- 可修改原始數據:直接操作引用
- 安全性:引用必須初始化,無空引用
- 示例:
void increment(int& ref) { ref++; } // 直接操作原始值
int a = 10;
increment(a); // a變為11
?
四、核心區別對比
特性 | 值傳遞 | 指針傳遞 | 引用傳遞 |
---|---|---|---|
操作對象 | 副本 | 原始數據(通過地址) | 原始數據(通過別名) |
是否修改原值 | 否 | 是 | 是 |
語法復雜度 | 簡單(直接傳值) | 較復雜(需解引用) | 簡單(類似值傳遞) |
空值風險 | 無 | 有空指針風險 | 無(必須初始化) |
典型用途 | 簡單數據、只讀操作 | 需顯式傳遞地址、可空 | 對象參數、避免拷貝 |
五、示例對比?
// 值傳遞
void passByValue(int val) { val = 20; } // 不影響原始值// 指針傳遞
void passByPointer(int* ptr) { if (ptr) *ptr = 20; // 需檢查空指針
}// 引用傳遞
void passByReference(int& ref) { ref = 20; } // 直接修改int main() {int x = 10;passByValue(x); // x仍為10passByPointer(&x); // x變為20passByReference(x); // x變為20return 0;
}
?
六、C++11 新增:右值引用(Move Semantics)
- 機制:專門處理臨時對象(右值)的引用,避免深拷貝
- 語法:
void func(Type&& rvalue)
- 典型用途:移動構造函數、移動賦值運算符
- 示例:
std::vector<int> createVector() {return std::vector<int>{1,2,3};
}std::vector<int> vec = createVector(); // 通過右值引用高效移動資源
?二.數組指針與指針數組的區別
一、核心區別
特性 | 數組指針(Pointer to Array) | 指針數組(Array of Pointers) |
---|---|---|
本質 | 指針:指向一個數組 | 數組:存儲多個指針 |
語法 | int (*ptr)[5]; (括號強制 ptr 為指針) | int* arr[5]; (arr 先與 [] 結合為數組) |
指向對象 | 整個數組 | 數組的元素(每個元素是一個指針) |
指針運算 | ptr + 1 ?跳過整個數組(如 5 個 int) | arr + 1 ?指向下一個元素(下一個指針) |
典型用途 | 傳遞多維數組、精確控制內存布局 | 管理多個動態分配的對象、字符串數組 |
二、語法對比
1.?數組指針(指向數組的指針)?
int arr[5] = {1,2,3,4,5};
int (*ptr)[5] = &arr; // 指向包含5個int的數組// 訪問元素
(*ptr)[0] = 10; // 修改arr[0]為10
2.?指針數組(包含指針的數組)?
?
int a = 1, b = 2, c = 3;
int* arr[3] = {&a, &b, &c}; // 數組的每個元素是int*// 訪問元素
*arr[0] = 10; // 修改a為10
?三、內存布局差異
1.?數組指針?
ptr ──> [1, 2, 3, 4, 5] // 指向整個數組
?
ptr
?存儲整個數組的起始地址sizeof(ptr)
?為指針大小(通常 4/8 字節)
2.?指針數組?
arr ──> [&a, &b, &c] // 數組元素為指針│ │ │▼ ▼ ▼a b c
?
arr
?是一個數組,包含多個指針sizeof(arr)
?為?3 * sizeof(int*)
四、典型應用場景
1.?數組指針的應用
// 傳遞多維數組
void printMatrix(int (*matrix)[4], int rows) {for (int i = 0; i < rows; i++) {for (int j = 0; j < 4; j++) {printf("%d ", matrix[i][j]);}printf("\n");}
}int main() {int matrix[3][4] = {...};printMatrix(matrix, 3); // matrix退化為int (*)[4]
}
?2.?指針數組的應用
// 字符串數組
const char* fruits[3] = {"Apple","Banana","Cherry"
};// 動態內存管理
int* ptrs[5];
for (int i = 0; i < 5; i++) {ptrs[i] = new int(i);
}
?
五、常見混淆點
1.?括號位置決定類型
?
int (*ptr)[5]; // 數組指針:ptr是指向包含5個int的數組的指針
int* ptr[5]; // 指針數組:ptr是包含5個int*的數組
2.?數組名 vs 數組指針
int arr[5];
int* ptr1 = arr; // 指向首元素的指針(隱式轉換)
int (*ptr2)[5] = &arr; // 指向整個數組的指針printf("%p\n", arr); // 數組首元素地址
printf("%p\n", &arr); // 整個數組的地址(數值相同,但類型不同)
printf("%p\n", arr + 1); // 跳過1個元素
printf("%p\n", &arr + 1); // 跳過整個數組(5個元素)
?三.指針函數與函數指針的區別
一、核心區別
特性 | 指針函數(Function Returning Pointer) | 函數指針(Pointer to Function) |
---|---|---|
本質 | 函數:返回值為指針類型 | 指針:指向一個函數 |
語法 | int* func(int a); (返回 int*) | int (*ptr)(int a); (ptr 為指針) |
用途 | 返回動態分配的內存或全局變量地址 | 作為參數傳遞函數、實現回調機制 |
調用方式 | int* result = func(10); | int val = (*ptr)(10); ?或?ptr(10); |
?
二、語法對比
1.?指針函數(返回指針的函數)
int* createArray(int size) {int* arr = new int[size];for (int i = 0; i < size; i++) {arr[i] = i;}return arr; // 返回動態分配的數組指針
}// 調用
int* ptr = createArray(5);
2.?函數指針(指向函數的指針)
int add(int a, int b) { return a + b; }// 定義函數指針并初始化
int (*op)(int, int) = add;// 調用方式1
int result = (*op)(3, 4); // 顯式解引用// 調用方式2(C++允許隱式解引用)
int result2 = op(3, 4); // 等價于上一行
?
三、典型應用場景
1.?指針函數的應用
// 返回靜態變量的地址
const char* getMessage() {static const char* msg = "Hello";return msg;
}
?2.?函數指針的應用
// 回調函數示例
void process(int a, int b, int (*func)(int, int)) {int result = func(a, b);printf("Result: %d\n", result);
}int main() {int (*add)(int, int) = [](int a, int b) { return a + b; };process(3, 4, add); // 輸出7
}
?
四、常見混淆點
1.?括號位置決定類型
int* func(int a); // 指針函數:返回int*
int (*ptr)(int a); // 函數指針:ptr指向返回int的函數
?2.?函數指針作為參數
// 排序函數接受比較函數指針
void sort(int* arr, int size, bool (*compare)(int, int)) {// 排序邏輯...
}bool ascending(int a, int b) { return a < b; }// 調用
sort(array, 10, ascending);
?四.malloc和calloc的區別
?
一、核心區別
特性 | malloc | calloc |
---|---|---|
初始化 | 不初始化分配的內存(內容隨機) | 將內存初始化為 0 |
參數 | 單個參數:所需內存字節數 | 兩個參數:元素數量和元素大小 |
原型 | void* malloc(size_t size); | void* calloc(size_t num, size_t size); |
性能 | 略快(無需初始化) | 略慢(需清零內存) |
二、示例對比
1.?malloc 的使用
?
int* ptr = (int*)malloc(5 * sizeof(int)); // 分配5個int的內存
if (ptr != NULL) {// 內存內容未初始化,可能包含隨機值for (int i = 0; i < 5; i++) {printf("%d ", ptr[i]); // 輸出隨機值}
}
2.?calloc 的使用
int* ptr = (int*)calloc(5, sizeof(int)); // 分配5個int的內存并初始化為0
if (ptr != NULL) {// 內存內容已初始化為0for (int i = 0; i < 5; i++) {printf("%d ", ptr[i]); // 輸出: 0 0 0 0 0}
}
?三、內存布局差異
?
// malloc分配的內存(未初始化)
ptr ──> [隨機值][隨機值][隨機值][隨機值][隨機值]// calloc分配的內存(初始化為0)
ptr ──> [0][0][0][0][0]
四、安全與性能考量
-
安全性:
calloc
適合需要初始化的場景(如存儲結構體、數組)malloc
需手動初始化(如使用memset
):
int* ptr = malloc(5 * sizeof(int));
memset(ptr, 0, 5 * sizeof(int)); // 手動清零
?
-
性能:
calloc
因初始化操作會稍慢- 大數據塊初始化可能影響性能
五、典型應用場景
場景 | 推薦函數 | 原因 |
---|---|---|
存儲需要初始化的數據 | calloc | 自動清零,避免未定義行為 |
存儲無需初始化的數據 | malloc | 略高效 |
分配二進制緩沖區 | malloc | 后續會寫入數據,無需提前初始化 |
分配結構體數組 | calloc | 確保結構體成員初始化為有效值 |
?五.內存泄漏,如何檢測和避免?
一、什么是內存泄漏?
- 定義:程序動態分配的內存(如
malloc
/new
)未被正確釋放(如free
/delete
),導致這部分內存永久無法被回收 - 危害:
- 隨著程序運行,可用內存逐漸減少
- 最終導致系統性能下降、程序崩潰或系統崩潰
二、內存泄漏的常見原因
-
忘記釋放內存:
?
void func() {int* ptr = new int[100]; // 分配內存// 忘記調用delete[] ptr;
}
異常導致路徑未釋放:
void func() {int* ptr = new int[100];if (condition) {throw std::exception(); // 異常退出,未釋放內存}delete[] ptr;
}
?指針覆蓋:
int* ptr = new int;
ptr = new int; // 原內存丟失,無法釋放
?循環分配內存:
while (true) {int* ptr = new int[1000]; // 持續分配,無釋放
}
?類中未定義析構函數:
class Resource {
public:Resource() { data = new int[100]; }// 未定義析構函數釋放data
private:int* data;
};
?
三、檢測內存泄漏的方法
1.?靜態代碼分析工具
- 工具:Cppcheck、Clang-Tidy、PC-Lint
- 特點:
- 不運行程序,直接分析代碼
- 檢測常見模式(如分配后未釋放)
- 示例命令:
cppcheck --enable=all --inconclusive your_file.cpp
2.?動態內存分析工具
-
Valgrind(Linux):
-
valgrind --leak-check=full ./your_program
?
四、避免內存泄漏的最佳實踐
1.?RAII(資源獲取即初始化)原則
- 使用智能指針(C++):
cpp
運行
#include <memory>void func() {std::unique_ptr<int[]> ptr(new int[100]); // 自動釋放// 無需手動delete }
2.?容器替代原始數組
cpp
運行
#include <vector>void func() {std::vector<int> data(100); // 自動管理內存
}
3.?異常安全
- 使用
try-catch
確保資源釋放:cpp
運行
void func() {int* ptr = new int[100];try {// 可能拋出異常的代碼} catch (...) {delete[] ptr;throw;}delete[] ptr; }
4.?遵循配對原則
malloc
?→?free
new
?→?delete
new[]
?→?delete[]
5.?避免指針淺拷貝
- 使用深拷貝或禁用拷貝構造函數
- 使用智能指針的移動語義
6.?代碼審查
- 重點檢查:
- 長時間運行的程序(如服務器)
- 循環中的內存分配
- 復雜函數中的多條返回路徑
五、高級技術
1.?內存池(Memory Pool)
- 預先分配大塊內存,按需分配小塊,減少系統調用
- 避免頻繁分配 / 釋放導致的碎片
2.?智能指針的使用場景
類型 | 用途 |
---|---|
std::unique_ptr | 獨占所有權 |
std::shared_ptr | 共享所有權(引用計數) |
std::weak_ptr | 弱引用,避免循環引用 |