2013年11月9日 星期六

const 的使用 (2) - 指標篇

上一篇: const 的使用 (1) - 基礎篇

指標在概念上牽涉到兩個東西,一個是儲存位址的指標本身,另一個是指標所指向的變數。為了分辨他們,這邊稍微複習一下指標的語法:

int a, b;     // a 和 b 分別都是一個 int
int *p1;      // p1 是一個指標,指向一個 int
int *p2 = &a; // p2 是一個指標,指向一個 int,而該 int 就是 a
p2 = &b;      // [賦值運算] p2 指向的 int 從原本的 a 變成了 b
*p2 = 3;      // [賦值運算] *p2 就是 b 被賦值為 3

上面的程式碼有兩個賦值運算,第 4 行是將指標本身賦值為 b 的位址而第 5 行是將指標指向的 int 變數 b 賦值為 3。換句話說,在談論指標的 const 性質時,可分為兩個部分:

  • 指標本身是否為 const?
  • 指標指向的變數是否為 const?

當指標本身是 const 時,指標本身不能再被賦值,指標指向哪個對象是不能改變的,而當指向的變數是 const 時,則所指向的變數不能再被賦值。


指標本身是否為 const ?

如果要指定指標本身的 const 性質,在宣告指標變數時,要在 * 『後面』加上 const 修飾字:

int * const p3 = &a;   
// [編譯成功] p3 是一個 const 的指標,指向一個 int,而該 int 就是 a
之前基礎篇我們對 const 變數的限制也套用在這個指標上,也就是說『初始化時要給予值且初始化後不能重新賦值』:
int * const p4;
// [編譯失敗] 因為 p4 是個 const 的變數,初始化時一定要給予值
p3 = &b; 
// [編譯失敗] 因為 p3 本身是 const 的,不能做賦值運算

指標指向的變數是否為 const?

當指標指向的變數是 const 時,指標本身不一定是 const 的:

const int c = 0, d = 1;
const int *p5;
// [編譯成功] p5 本身不是 const 的,不需要在初始化時給予值
// 請與第 8 行做比較
p5 = &c;  
// [編譯成功] p5 本身不是 const 的,所以可以被賦值
// 請與第 10 行做比較

當指標指向的變數是 const 時,從指標取出所指的變數具有唯讀性:

*p5 = 5;  
// [編譯失敗] p5 指向一個 const 的 int,所以 *p5 是 const 的

當指標指向的變數是 const 時,指標本身也可以是 const 的,但是一樣要在 * 『後面』加上 const 修飾:

const int * const p6 = &c;
// [編譯成功] p6 是個 const 的指標,指向一個 const 的 int,
// 而該 int 就是 c
const int * const p7;
// [編譯失敗] 因為 p7 是 const 的指標,初始化時要給予值
// 請與第 13 行做比較
p6 = &d; 
// [編譯失敗] p6 本身是 const 的,不能做賦值
// 請與第 16 行做比較

對於 const 在指標型態上的使用是要先了解每個 const 修飾的是指標本身還是指標指向的變數。


轉型的難題

指標使用 const 裡面最困惑的時候通常是關於轉型,考慮下面這段程式碼:

int e = 10;
const int *p8 = &e; // (const int *) = (int *)
// [編譯成功] p8 是一個指標,指向一個 const 的 int ,而該 int 就是 e
const int f = 10;
int *p9 = &f;       // (int *) = (const int *)
// [編譯失敗] p9 是一個指標,指向一個 int,但是該 int 不能是 f

這裡的問題在於為什麼 p8 的定義可以而 p9 的定義不行?

我們先想想為什麼 p8 的定義可以? p8 指向的變數需要是一個 const 的 int,但這裡讓他去指向一個非 const 的 int (也就是 e)。理論上因為型態不同,這個操作應該要失敗,但是因為這個 const 表示的是 p8 所指向的變數是唯讀的,就算指向的是一個可以重新賦值的 e ,也並不會讓 p8 無法提供需要的操作,所以 C/C++ 允許這樣的隱性指標轉型。

相對地,為什麼 p9 的定義不可以? p9 指向的變數需要是一個一般的 int,但這裡試著讓他去指向一個 const 的 int (也就是 f)。我們可以像 p8 一樣做隱性指標轉型嗎? 答案是不行。因為 p9 的型態表示這個指標所指向的變數是可以被重新賦值,但是 f 卻是一個唯讀的 const int,是不能重新賦值的,這會讓 p9 無法提供需要的操作。例如可以用 *p9 = 0 來間接修改唯讀的 f,並不合理,所以 C/C++ 不允許這樣的轉型。


[小結]一個指向非 const 變數的指標 (例如 int *) 可以隱性轉型成一個指向 const 變數的指標 (例如 const int *),但是反過來不行!


下一篇: const 的使用 (3) - 基礎測驗篇

沒有留言: