C++ 有引用(&),C 語言沒有。函數參數始終是實參的副本,形參與實參佔據不同內存地址。這是理解一切參數傳遞問題的基石。
void foo(int x) {
x = 10; // 修改的是副本
}
int main() {
int a = 5;
foo(a); // a 仍為 5
}
形參 x 是實參 a 的值拷貝,兩者地址不同,修改不影響原變量。
void foo(int *p) {
*p = 10; // 通過地址修改原變量
}
int main() {
int a = 5;
foo(&a); // a 變為 10
}
雖然指針 p 也是副本,但它存儲的是 a 的地址。解引用 *p 直接操作原內存,達到「修改外部變量」的效果。
若需在函數內修改指針變量本身(如動態分配內存並返回),必須傳遞指針的地址:
void initTree(Tree **T) { // 二級指針
char ch = getchar();
if (ch == '#') { *T = NULL; }
else {
*T = (Tree*)malloc(sizeof(Tree)); // 給實參指針分配內存
(*T)->t = ch;
initTree(&(*T)->lchild); // 遞歸傳遞左孩子地址
initTree(&(*T)->rchild);
}
}
// 錯誤寫法
void initTree(Tree *T) { // 一級指針
if (ch == '#') { T = NULL; } // 修改的是副本!
else {
T = (Tree*)malloc(sizeof(Tree)); // 給副本分配內存
T->t = ch;
initTree(T->lchild); // 遞歸傳遞副本的孩子指針
initTree(T->rchild);
}
}
後果: 每層遞歸操作的都是指針副本,申請的內存無法連接成樹,函數返回後根指針仍為野指針/NULL,內存洩漏。
void initTree(Tree **T) {
// ...
*T = (Tree*)malloc(sizeof(Tree)); // 關鍵:解引用賦值,修改實參指向
// ...
initTree(&(*T)->lchild); // 傳遞左孩子指針的地址
}
Tree **T:接收 Tree* 的地址*T:解引用得到實參指針本身&(*T)->lchild:取左孩子指針的地址(二級指針)| 需求 | 形參類型 | 調用方式 | 函數內操作 |
|---|---|---|---|
| 只讀數值 | int x | foo(a) | 直接用 x |
| 修改數值 | int *p | foo(&a) | *p = val |
| 修改指針(如分配內存) | int **p | foo(&ptr) | *p = malloc(...) |
| 修改結構體指針成員 | Node **p | foo(&root->child) | *p = new_node |
C語言只有值傳遞。任何想在函數內修改的外部對象,必須傳遞其地址,函數以對應層級指針接收。