程式扎記: [ C++ Gossip ] 進階型態 : 指標 - 指標 與 記憶體位址

標籤

2014年8月6日 星期三

[ C++ Gossip ] 進階型態 : 指標 - 指標 與 記憶體位址

來源自 這裡 
Preface: 
在 變數 中曾經說過, 變數Variable提供一個有名稱的記憶體儲存空間,一個變數關係至一個資料型態、一個變數本身的值與一個變數的位址值. 變數資料型態決定了變數所分配到的記憶體大小;變數本身的值是指儲存於記憶體中的某個數值,而您可以透過變數名稱取得這個數值,這個數值又稱為 rvalue 或 read value;而變數的位址值則是指變數所分配到的記憶體之位置,變數本身又稱為 lvalue 或 location value

Pointer and Address: 
如果您想知道變數的記憶體位址為何,您可以使用 & 運算子, & 是「取址運算子」(Address-of operator),它可以取出變數的記憶體位址,例如: 
  1. #include   
  2. using namespace std;   
  3.   
  4. int main() {   
  5.     int var = 10;   
  6.   
  7.     cout << "變數var的值:" << var   
  8.          << endl;   
  9.     cout << "變數var的記憶體位址:" << &var   
  10.          << endl;   
  11.   
  12.     return 0;   
  13. }  
執行結果: 
 

這個程式宣告了一個 int 整數變數 var,var 指向的記憶體位址是 0xbfee21cc,這是記憶體位址的16進位表示法,從 0xbfee21cc 後的 4 個位元組都是 var 所配置到的記憶體空間,現在這個空間中儲存值為10. 直接存取變數即直接對所分配到的記憶體空間作存取,指標Pointer則提供了間接性,指標可指向特定的記憶體位址,而不直接操作變數或物件,宣告 一個指標,使用以下的語法: 
type *ptr;

其中 type 是指標的型態,每一個指標都有一個相對應的型態,用以指出所指向的資料或物件之型態有所不同,編譯器根據指標型態來確定特定記憶體位址上的資 料如何解釋,以及如何進行指標運算(Pointer arithmetic),以下是幾個指標宣告的範例: 
  1. int *iptr;  
  2. float *fptr;  
  3. char *cptr;  
您可以使用 & 運算子取出變數的位址值並指定給指標,例如: 
  1. #include   
  2. using namespace std;   
  3.   
  4. int main() {   
  5.     int var = 10;   
  6.     int *ptr = &var ;   
  7.   
  8.     cout << "變數var的位址:" << &var   
  9.          << endl;   
  10.     cout << "指標ptr指向的位址:" << ptr   
  11.          << endl;   
  12.   
  13.     return 0;   
  14. }  
執行結果: 
 

如以上的程式結果所示,您使用 & 來取出變數 var 所指向的記憶體位址,然後將這個位址指定給指標 ptr,因此 ptr 所儲存的值就與 &var 所取出的值相同. 

指標擁有兩種操作特性,一是操作指標所儲存的位址,一是操作指標所指向位址之資料,或是操作指向的位址上之物件,您可以使用提取 (Dereference)運算子 * 來提取指標所指向位址的資料,例如: 
  1. #include   
  2. using namespace std;   
  3.   
  4. int main() {   
  5.     int var = 10;   
  6.     int *ptr = &var;  
  7.   
  8.     cout << "指標ptr儲存的值:" << ptr  
  9.          << endl;   
  10.     cout << "取出ptr指向的記憶體位置之值:" << *ptr   
  11.          << endl;  
  12.   
  13.     return 0;   
  14. }  
執行結果: 
 

如果已經取得了記憶體位置,當將某個值指定給 *ptr 時,該記憶體位置的值也會跟著改變,相當於告訴程式,將值放到 ptr 所指向的記憶體位址,例如: 
  1. #include   
  2. using namespace std;   
  3.   
  4. int main() {   
  5.     int var = 10;   
  6.     int *ptr = &var ;   
  7.   
  8.     cout << "var = " << var   
  9.          << endl;   
  10.     cout << "*ptr = " << *ptr   
  11.          << endl;   
  12.   
  13.     *ptr = 20;   
  14.   
  15.     cout << "var = " << var   
  16.          << endl;  
  17.     cout << "*ptr = " << *ptr   
  18.          << endl;  
  19.   
  20.     return 0;   
  21. }  
執行結果: 
 

如以上所表示的,當指標 ptr 所儲存的值與變數 var 所指向的記憶體位置相同時,當您對 *ptr 進行指定的動作時,就會將值直接存入該記憶體位置,因此再 透過變數 var 所取出的值也就改變了. 

如果宣告指標但不指定初值,則指標指向的位址是未知的,存取未知位址的記憶體內容是危險的,例如: 
  1. int *ptr;   
  2. *ptr = 10;  
這個程式片段並未初始指標就指定值給 *ptr,所以會造成不可預知的結果(通常是記憶體區段錯誤),最好為指標設定初值,如果指標一開始不指向任何的物 件,則可設定初值為0,表示不指向任何物件,例如: 
  1. int *iptr = 0;  
在這邊必須注意一個指標宣告常犯的錯誤,在指標宣告時,可以靠在名稱旁邊,也可以靠在關鍵字旁邊,例如: 
  1. int *ptr1;   
  2. int* ptr2;  
這兩個宣告方式都是可允許的,一般比較傾向用第一個,因為可以避免以下的錯誤: 
  1. int* ptr1, ptr2;  
這樣的宣告方式,初學者可能以為ptr2也是指標,但事實上並不是,以下的宣告ptr1與ptr2才都是指標: 
  1. int *ptr1, *ptr2;  
有時候,您只希望儲存記憶體的位址,然後將之與另一個記憶體位址作比較,這時並不需要關心型態的問題,您可以使用void*來宣告指標,例如: 
由於void型態的指標沒有任何的型態資訊,所以只用來持有位址資訊,您不可以使用 * 運算子對 void 型態指標提取值,而必須使用 reinterpret_cast 作轉型動作至對應的型態,例 如: 
  1. #include   
  2. using namespace std;   
  3.   
  4. int main() {   
  5.     int var = 10;   
  6.     void *vptr = &var ;   
  7.   
  8.     // 下面這句不可行,void型態指標不可取值   
  9.     //cout << *vptr << endl;  
  10.   
  11.     // 轉型為int型態指標並指定給iptr   
  12.     int *iptr = reinterpret_cast<int*>(vptr);  
  13.     cout << *iptr << endl;   
  14.   
  15.     return 0;   
  16. }  
執行結果: 
 

您也可以使用舊風格的轉型語法,如下所示: 
  1. int var = 10;   
  2. void *vptr = &var ;   
  3. // 轉型為int型態指標並指定給iptr   
  4. int *iptr = (int*)(vptr);  
順便來看一下 const 宣告的變數,被 const 宣告的變數一但被指定值,就不能再改變變數的值,您也無法對該變數如下取值: 
  1. const int var = 10;  
  2. var = 20// error, assignment of read-only variable `var'   
  3. int *ptr = &var; // error,  invalid conversion from `const int*' to `int*'   
用const宣告的變數,必須使用對應的const型態指標才可以: 
  1. const int var = 10;  
  2. const int *vptr = &var;  

同樣的vptr所指向的記憶體中的值一但指定,就不能再改變記憶體中的值,您不能如下試圖改變所指向記憶體中的資料: 
  1. *vptr = 20// error, assignment of read-only location   
另外還有指標常數,也就是您一旦指定給指標值,就不能指定新的記憶體位址值給它,例如: 
  1. int x = 10;  
  2. int y = 20;  
  3. intconst vptr = &x;  
  4. vptr = &x;  // error,  assignment of read-only variable `vptr'  
在某些情況下,您會想要改變唯讀區域的值,這時您可以使用 const_cast 改變指標的型態,例如: 
  1. #include   
  2. using namespace std;   
  3.   
  4. void foo(const int*);   
  5.   
  6. int main() {   
  7.     int var = 10;   
  8.   
  9.     cout << "var = " << var << endl;   
  10.   
  11.     foo(&var);   
  12.   
  13.     cout << "var = " << var << endl;   
  14.   
  15.     return 0;   
  16. }  
  17.   
  18. void foo(const int* p) {  
  19.     int* v = const_cast<int*> (p);   
  20.     *v = 20;   
  21. }  
執行結果: 
 

由於函式(Function)定義中傳入的指標使用const加上唯讀性,所以您不能對傳入的指標進行以下的動作: 
  1. *p = 20// error, assignment of read-only location  
但在某種原因下,您真的必須改變指標指向的位址處之資料,則您可以使用 const_cast 改變指標的唯讀性: 
  1. int* v = const_cast<int*> (p);   
  2. *v = 20;   
您也可以使用舊風格的轉型語法,例如: 
  1. void foo(const int* p) {  
  2.     int* v = (int*) (p);   
  3.     *v = 20;   
  4. }  
Supplement: 
[C++] 標準類型轉換:static_cast, dynamic_cast, reinterpre...t, and const_cast. static_cast

沒有留言:

張貼留言

網誌存檔

關於我自己

我的相片
Where there is a will, there is a way!