在〈變數〉談過,變數提供一個有名稱的記憶體儲存空間,變數可包含的資訊包含變數資料型態、變數記憶體位址與變數儲存值。
如果想知道變數的位址為何,可以使用 &
取址運算子(Address-of operator),例如:
#include <iostream>
using namespace std;
int main() {
int n = 10;
cout << "n 的值:" << n << endl
<< "n 位址:" << &n << endl;
return 0;
}
執行結果:
n 的值:10
n 位址:0x61febc
這個程式中,宣告了一個 int
整數變數 n
,n
的位址是 0x61febc
,使用 16 進位表示法,若 int
長度為 4 個位元組,從 0x61febc
後 4 個位元組都是配置給 n
的空間,現在這個空間中儲存值為 10。
直接存取變數會對分配到的空間作存取,指標(Pointer)是一種變數,指標可儲存特定的記憶體位址,要宣告指標,使用以下的語法:
type *ptr;
ptr
可儲存位址,而 type
為該位址儲存值的型態,實際宣告的方式如下:
int *n;
float *s;
char *c;
雖然宣告指標時,C++ 習慣將 *
前置在變數名稱前,不過 n
的型態是 int*
,s
的型態是 float*
,而 c
的型態是 char*
,指標的型態決定了位址上的資料如何解釋,以及如何進行指標運算(Pointer arithmetic)。
可以使用 &
運算子取得變數位址並指定給指標,例如:
#include <iostream>
using namespace std;
int main() {
int n = 10;
int *p = &n ;
cout << "n 變數的位址:" << &n << endl
<< "p 儲存的位址:" << p << endl;
return 0;
}
執行結果:
n 變數的位址:0x61feb8
p 儲存的位址:0x61feb8
以上的程式使用 &
來取得變數 n
儲存的位址,然後指定給指標 p
,因此 p
儲存的值就與 &n
取出的值相同。
可以使用提取 (Dereference)運算子 *
來提取指標儲存位址處的物件。例如:
#include <iostream>
using namespace std;
int main() {
int n = 10;
int *p = &n;
cout << "指標 p 儲存的位址:" << p << endl
<< "提取 p 儲存位址處的物件:" << *p << endl;
return 0;
}
*p
提取了 p
儲存的位址處之物件,這個物件就是 n
變數,因此執行結果如下:
指標 p 儲存的位址:0x61feb8
提取 p 儲存位址處的物件:10
*p
提取了變數 n
,將值指定給 *p
時,就是指定給變數 n
,例如:
#include <iostream>
using namespace std;
int main() {
int n = 10;
int *p = &n ;
cout << "n = " << n << endl
<< "*p = " << *p << endl;
*p = 20;
cout << "n = " << n << endl
<< "*p = " << *p << endl;
return 0;
}
執行結果:
n = 10
*p = 10
n = 20
*p = 20
如果宣告指標但不指定初值,指標儲存的位址是未知的,存取未知位址的記憶體內容是危險的,例如:
int *p;
*p = 10;
這會造成不可預知的結果,最好為指標設定初值,如果指標一開始不儲存任何位址,可設定初值為 0,或者是使用 nullptr
,例如:
int *p = nullptr;
在指標宣告時,可以靠在變數旁邊,也可以靠在型態關鍵字旁邊,或者是置中,例如:
int *p1;
int* p2;
int * p3;
這三個宣告方式都是可允許的,C++ 開發者傾向用第一個,因為可以避免以下的錯誤:
int* p1, p2;
這樣的宣告方式,初學者可能以為 p2
也是指標,但事實上並不是,以下的宣告 p1
與 p2
才都是指標:
int *p1, *p2;
有時只希望儲存位址而不關心型態,可以使用 void*
來宣告指標,例如:
void* p;
由於 void*
型態的指標沒有任何型態資訊,只用來持有位址,不可以使用 *
運算子對 void*
型態指標提取值,編譯器也不會允許將 void*
指標直接指定給具有型態資訊的指標,必須使用 reinterpret_cast
明確告知編譯器,這個動作是你允許的,例如:
#include <iostream>
using namespace std;
int main() {
int n = 10;
void *p = &n ;
int *iptr = reinterpret_cast<int*>(p);
cout << *iptr << endl; // 顯示 10
return 0;
}
reinterpret_cast
用於指標,它告訴編譯器,你就是要以指定型態重新解釋 p
位址處的資料。
被 const
宣告的變數指定值後,就不能再改變變數值,也無法對該變數取址:
const int n = 10;
int *p = &n; // error, invalid conversion from `const int*' to `int*'
用 const
宣告的變數,必須使用對應的 const
型態指標才可以:
const int n = 10;
const int *p = &n;
同樣地,也就不能如下試圖改變位址處的資料:
*p = 20; // error: assignment of read-only location '* p'
被 const
宣告的變數指定值後,就不能再改變變數值,也無法對該變數取址,編譯會不通過,不過必要時,可以用 const_cast
叫編譯器住嘴:
const int n = 10;
int *p = const_cast<int*>(&n);
在〈算術運算、型態轉換〉最後也提到過 const_cast
,這只是叫編譯器住嘴罷了,後續程式碼也是別對 pi 位址處的資料做變動,以避免執行時期不可預期的結果。
要留意的是,const int *p
宣告的 p
並不是常數,可以儲存不同的位址。例如:
#include <iostream>
using namespace std;
int main() {
const int n = 10;
const int m = 20;
const int *p = &n;
cout << p << endl;
p = &m;
cout << p << endl;
return 0;
}
執行結果:
0x61feb8
0x61feb4
如果想令指標儲存的值無法變動,必須建立指標常數,先來看看來源變數沒有 const
的情況:
int n = 10;
int m = 20;
int* const p = &n;
p = &m; // error: assignment of read-only variable 'p'
如果 n
、m
被 const
修飾,那麼就必須如下建立指標常數:
const int n = 10;
const int m = 20;
const int* const p = &n;
p = &m; // error: assignment of read-only variable 'p'