在開始前先看一段程式碼:
#include <iostream>
using namespace std;
int main()
{
int a = 87, b;
b = a;
a = 69;
cout << "a = " << a << ", b = " << b << '\n';
return 0;
}
69 69
, 但實際上是 69 87
, 原因是我們宣告 a, b
兩個整數變數,他們分別儲存在記憶體的不同位置,而我們強調過:b = a;
將 b
的值設為 a
當時的值也就是 \(87\), 之後第九行將 a
的值設為 \(69\), 此時對 b
的值毫無影響。那麼假如我們真的有綁定變數的需求,要如何實做呢??指標#
指標向來被視為 C/C++ 中令人聞風喪膽的魔王 (? 其實我覺得沒那麼恐怖。簡單而言,一個變數的指標,就是其在記憶體的地址。看看下面的例子:
#include <iostream>
using namespace std;
int main()
{
int n = 87, *ptr = &n;
cout << "n = " << n << ", &n = " << &n << '\n'
<< "ptr = " << ptr << ", *ptr = " << *ptr << '\n';
return 0;
}
注意到第七行 int *ptr = &n
, 我們在變數名稱前加上 *
宣告一個整數的指標變數,透過在 n
前方加上 &
取得 n
的記憶體位置並使 ptr
指向 n
. 接著第八行我們輸出 n
值與其記憶體位置,最後輸出 ptr
指向的位置及利用 *
取得其指向的內容。
這次我們修改 n
的值,會發現連帶地 ptr
所指向的值當然也隨之變化。所以指標第一個麻煩的地方,就是,*
既拿來做為宣告指標,又用作解析指標的內容。
const T\*, T\* const, const T\* const#
前面我們有介紹過常數變數,不知道還有迷有印象??沒有也沒關係喇 這邊就隨便帶一下,const T\* ptr0;
是常數變數的指標,它自身指向某個常數變數,但它不是常數,所以它可以被修改,指向別的常數變數;T\* const ptr1;
是變數的常數指標,它自身是常數,因此它初始化指向一個變數後,就不能再指向其他變數,但那個變數當然還是可以被修改;const T\* const ptr2;
是常數變數的常數指標,它自身是常數,因此它初始化指向一個常數後,就不能再指向其他常數,而且那個常數當然不能被修改。
到這邊暈惹嗎??舉個例子好惹: 這邊可能需要慢慢體會。
指標的指標的… & MISC#
指標除惹指向整數、浮點數、字元等等基本型別,當然還可以指向另一個指標,怎麼樣,好玩吧??
注意到各種整數、浮點數、字元、指標的指標等等其實殊途同歸,本質上是一樣的,都是關於記憶體位置的變數,你可以用介紹過的 sizeof()
確認看看。非常危險地,它們彼此之間是可以互相轉換的,更概括的說,所有指標都可以被視為 void *
, 需要強調型別的原因是因為不同型別記憶體儲存的內容當然不一樣,錯誤的解析會引發 Undefined Behaviors.
另外,當我們宣告一個指標,在確定它指向誰之前,不應該去嘗試存取它的值,這同樣是非常危險的。C 傳統上以 NULL
這個巨集表示空指標,通常會被展開為 0
, 但還是需要小心使用;C++11 新推出的 nullptr
是個好東西,請多愛用它。
最後,指標還有許多妙用,是陣列、字串及動態配置記憶體的基礎,也是 C++ STL iterator 的精神,等到我們遇見函式在來介紹函式指標吧。
成大教授好文推介。
參照#
指標這種直接碰觸到記憶體的操作,是比較底層、低階的,雖然非常方便同時也十分不安全,因此現代高階語言多半使用參照這個技巧,或譯為參考。
#include <iostream>
using namespace std;
int main()
{
int a = 87, &b = a;
a = 69;
cout << "a = " << a << ", b = " << b << '\n';
b = 426;
cout << "a = " << a << ", b = " << b << '\n';
return 0;
}
在 int &b = a;
這行,我們用 &b
表示 b
是個參照,參考到 a
的值,因此當 a
修改時,b
也會隨之更動;同樣地,當 b
修改時,a
也會隨之更動。
實務上,參照的實作通常就是指標,雖然比較安全,但也少惹許多彈性,包括一定要立即初始化且無法改參考其他的變數。
另外 C++11 新增惹 r-value reference, 不過我們應該很久以後才會討論它。
Comments