值 (value)、指標 (pointer/address)、參考 (reference)

AdSense

前言

C 裡面其實只有值 (value)指標 (pointer),C++ 才增加了參考 (reference)
C# 沒有指標 (pointer),有值 (value)參考 (reference)

指標 (pointer/address)

就像是 int 只能儲存數字不能儲存字串一樣。指標,是一種變數的型別,只能用來儲存記憶體位址,也可以說是變數資料的地址。

指標 (pointer) 就是某變數的記憶體位址。而指標變數 (pointer variable),則是用來存放指標的變數。

a, b, p1, p2都是一般的變數,儲存在記憶體 (memory) 中。其中,p1 所儲存的值是 a 的記憶體 (memory) 位址,而 p2 則儲存 b 的記憶體位址,像這樣的狀況,我們就稱 p1 是一個指向 a 的指標p2 是一個指向 b 的指標

在 C/C++ 中,我們用下面的式子來表示這個關係:

1
2
3
int a = 2, b = 5;
int* p1 = &a; //也可以寫作int *p1 = &a
int* p2 = &b; //也可以寫作int *p2 = &b

符號*,代表的意義是指標。
int* p1 要由後往前閱讀來瞭解它的意義:p1 is a pointer points to an integer。p1是一個指標,指向整數。或是,p1 是一個儲存「整數記憶體位址」的變數。
符號&,稱為 address of (取址)。&a = address of a。
因此,int* p1 = &a; 這整行,我們可以看成:p1 is a pointer points to integer variable a,即:p1 是一個指標,指向整數變數 a。或是,p1 是一個儲存「整數變數 a 的記憶體位址」的變數。

但是我們有 p1 (a的地址) 可以做什麼呢?這時可以用「符號*」表示「取出內容」,這裡的「符號*」和宣告指標變數的 int* p1 的意義不一樣。

當我們在 C 中 printf(*p1); 時,代表取出「p1」的內容,也就是取出「a 的地址」的內容。可以把 *p1 當作 a 來使用,例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//C
int a = 2;
int* p1 = &a;
printf("變數 a 的值:%d\n", a); // 2
printf("變數 a 的地址:%p\n", &a); // 0x7ffeefbff57c
printf("指標變數 p1 的值:%p\n", p1); // 0x7ffeefbff57c
printf("指標變數 p1 的地址:%p\n", &p1); // 0x7ffeefbff570

// 取出 p1 的內容 => 變數 a 的值
printf("*p1 的值:%d\n", *p1); // 2

*p1 = 100;
printf("*p1 的值:%d\n", *p1); // 100
printf("變數 a 的值:%d\n", a); // 100

宣告時「符號*」表示「宣告指標變數」。使用時「符號*」表示「取出內容」。

參考資料:
C語言: 超好懂的指標,初學者請進~
C/C++之指標 (pointer),參考 (reference) 觀念整理與常見問題 (轉貼)

參考 (reference)

參考,可以想像成是一個變數或物件的別名 (alias)。如同美國國父就是華盛頓一樣。取別名時一定要初始化,指明它是誰的別名。且初始化後不能再轉變成其他變數的別名。

在 C++ 中,我們用下面的式子來表示這個關係:

1
2
3
4
5
6
7
8
9
10
11
12
//C++
int a = 2, b = 5;
int &r1 = a; // r1 是 a 的別名
//r1 = b; // 會報錯,不能轉變成其他變數的別名

cout << "變數 a 的值:" << a << "\n"; // 2
cout << "變數 a 的地址:" << &a << "\n"; // 0x7ffeefbff57c
cout << "r1 的值:" << r1 << "\n"; // 2
cout << "r1 的地址:" << &r1 << "\n"; // 0x7ffeefbff57c

//會報錯,因為 r1 是整數型別,無法用 * 取出內容
//cout << "*r1 的值:" << *r1 << "\n";

C++ compiler 不會額外為 ref variables 分配記憶體空間。

配合指標時,「符號&」前面有 =,表示「取址」,如 int* p = &a;
「符號&」前面有資料型態,表示「參考、別名」,如 int &r1 = a;

參考資料:
C++中引用(reference)的用法详解

指標和參考

1
2
3
4
5
6
7
8
9
10
int a = 2, b = 5;
int* p = &a; // p 是指標,指向整數變數 a (pointer to int variable)
int &r = b; // r 是 b 的別名 (reference to int variable)

//int pointer = p; // 不能這樣寫,因為 pointer 是儲存 4byte 的整數,但是 p 是儲存十六進制的記憶體位址
int* pointer = p; // 要改成這樣,這時 pointer 和 p 儲存一樣的東西,pointer 有自己的記憶體空間

// 所以當 p2 要儲存指標 p 的記憶體位址時,要用 ** 來宣告,告知 pointer to pointer
int** p2 = &p; // p2 是指標,指向指標變數 p (pointer to int pointer)
int* &r2 = p; // r2 是 p 的別名,r2 沒有自己的記憶體空間 (reference to int pointer)

Value types call/pass by value (C/C++/C#)

1
2
3
4
5
6
7
8
int x = 5, y = 10;
swap(x, y);

void swap(int a, int b){
int tmp = a;
a = b;
b = tmp;
}

call by value:將 x, y 的值 copy 一份給 a, b。

swap() 執行完畢後 x, y 不會互換,a, b 會互換。

Value types call/pass by pointer/address (C/C++)

1
2
3
4
5
6
7
8
int x = 5, y = 10;
swap(&x, &y); // &x = address of x,將 x 位址傳入

void swap(int* a, int* b){ // 位址只能由指標型別接收,宣告時要宣告指標型別
int tmp = *a; // 使用時「符號*」表示「取出內容」
*a = *b;
*b = tmp;
}

call by pointer/address:將 x, y 的位址 copy 一份給 a, b (a, b 是指標變數)。因為實際上是 copy,骨子裡是 call by value。

swap() 執行完畢後 x, y 會互換,a, b 不會互換。因為在 swap() 中操作 *a, *b 相當於操作 x, y

Value types call/pass by reference (C++/C#)

1
2
3
4
5
6
7
8
9
//C++
int x = 5, y = 10;
swap(x, y);

void swap(int &a, int &b){
int tmp = a;
a = b;
b = tmp;
}
1
2
3
4
5
6
7
8
9
//C#
int x = 5, y = 10;
swap(ref x, ref y);

void swap(ref int a, ref int b){
int tmp = a;
a = b;
b = tmp;
}

call by reference:為 x, y 取別名 a, b (取別名時一定要初始化,指明它是誰的別名)。C++ compiler 不會為 ref variables 分配內存空間。

swap() 執行完畢後 x, y 會互換

參考資料:
Parameter passing in C#

總結

call by value、call by pointer/address 是 copy 複製一份變數的值或記憶體位址,傳入函式中操作。

call by reference 是直接取別名,直接操作該變數。