指標(pointer)是對資料物件或函數的一種參照。指標有多種用途,例如定義“傳址呼叫”函數,它還可以實現動態資料結構,例如連結串列和樹。接下來我們來介紹如何初始化一個指標。
指標初始化
具有動態儲存週期的指標變數,開始是沒有定義值的,除非它們在宣告的同時進行了顯式地初始化。
在語句塊內定義的所有變數,只要沒有被宣告為 static,就具有動態儲存週期。所有指標在定義時如果沒有初始化,則空指標預設作為它們的初始化值。
可以用下列方式來初始化一個指標:
(1) 一個空指標常數。
(2) 指向相同型別的指標,或者指向具有較少限定符修飾的相同型別。
(3) 如果需初始化的指標不是函數指標,可以使用 void 指標進行初始化(同上,需初始化的指標可以是指向具有更多限定符型別的指標)。
指標如果不具有動態儲存週期,則必須用常數表示式來初始化,例如取址運算的結果,或者使用陣列或函數的名稱。
當初始化指標時,除非是上面說明的情況,否則不會發生隱式型別轉換。然而,
可以將一個指標值顯式地轉換成另一個指標型別。例如,若想一個位元組接著一個位元組地讀取物件,可以將其地址轉換成 char 指標,指向此物件的第一個位元組:
double x = 1.5;
char *cPtr = &x; // 錯誤:型別不匹配;沒有隱式轉換
char *cPtr = (char *)&x; // 正確:cPtr指向x的第一個位元組
所有指標在定義時如果沒有初始化,則空指標預設作為它們的初始化值。如果需初始化的指標不是函數指標,可以使用 void 指標進行初始化。那麼什麼是空指標和 void 指標呢?
空指標
當把一個空指標常數轉換為指標型別時,所得到的結果就是
空指標(null pointer)。
空指標常數(null pointer constant)是一個值為 0 的整數常數表示式,或者是一個 void* 型別的表示式。
在標頭檔案 stdlib.h、stdio.h 以及其他標頭檔案中,宏 NULL 被定義為空指標常數。
空指標有別於其他指向物件或函數的有效指標。因此,當返回值為指標的函數出現執行失敗的情況時,它通常會使用空指標作為返回值。標準函數 fopen()正是這樣的一個例子,如果在指定的模式下開啟某檔案失敗時,該函數會返回一個空指標。
#include <stdio.h>
/* ... */
FILE *fp = fopen( "demo.txt", "r" );
if ( fp == NULL ) // 也可以被寫成:if ( !fp )
{
// 錯誤:無法開啟demo.txt檔案進行讀取
}
如果有必要的話,空指標會被隱式地轉換成其他指標型別,以進行賦值運算或者是進行 == 或 != 的比較運算。因此,上述例子不需要使用轉型運算子。
void 指標
指向 void 的指標,或者簡稱為
void 指標(void pointer),是型別為 void* 的指標。因為沒有物件型別是 void,所以
void* 被稱為萬能指標型別。換句話說,
void 指標可以代表任何物件的地址,但不代表該物件的型別。若想獲取記憶體中的物件,必須先把 void 指標轉換為合適的物件指標。
若想宣告一個可以接收任何型別指標引數的函數,可以將所需的引數設定為指向 void 的指標。當呼叫這樣的函數時,編譯器會隱式地將物件指標引數轉換成 void 指標。常見的例子如標準函數 memset(),它被宣告在標頭檔案 string.h 中,其原型如下:
void *memset( void *s, int c, size_t n );
函數 memset()將 c 的值賦值到從地址 s 開始的 n 個記憶體位元組中。例如,下面的函數呼叫會將 0 值賦值到結構變數 record 中的每個位元組:
struct Data { /* ... */ } record;
memset( &record, 0, sizeof(record) );
實參 &record 具有 struct Data* 型別。在函數呼叫中,實參被轉換成形參型別,即 void*。
編譯器會在必要的地方把 void 指標轉換為物件指標。例如,在下面的語句中,函數 malloc()返回一個 void 指標,它的值是已分配記憶體的語句塊的地址。這樣的賦值操作會把 void 指標轉換成 int 指標:
int *iPtr = malloc( 1000 * sizeof(int) );