天天看點

c指針

http://blog.jobbole.com/25409/

指針、引用和取值

什麼是指針?什麼是記憶體位址?什麼叫做指針的取值?指針是一個存儲計算機記憶體位址的變量。在這份教程裡“引用”表示計算機記憶體位址。從指針指向的記憶體讀取資料稱作指針的取值。指針可以指向某些具體類型的變量位址,例如int、long和double。指針也可以是void類型、NULL指針和未初始化指針。本文會對上述所有指針類型進行探讨。

根據出現的位置不同,操作符 * 既可以用來聲明一個指針變量,也可以用作指針的取值。當用在聲明一個變量時,*表示這裡聲明了一個指針。其它情況用到*表示指針的取值。

&是位址操作符,用來引用一個記憶體位址。通過在變量名字前使用&操作符,我們可以得到該變量的記憶體位址。

// 聲明一個int指針
int *ptr;
// 聲明一個int值
int val = 1;
// 為指針配置設定一個int值的引用
ptr = &val;
// 對指針進行取值,列印存儲在指針位址中的内容
int deref = *ptr;
printf("%d\n", deref);      

第2行,我們通過*操作符聲明了一個int指針。接着我們聲明了一個int變量并指派為1。然後我們用int變量的位址初始化我們的int指針。接下來對int指針取值,用變量的記憶體位址初始化int指針。最終,我們列印輸出變量值,内容為1。

第6行的&val是一個引用。在val變量聲明并初始化記憶體之後,通過在變量名之前使用位址操作符&我們可以直接引用變量的記憶體位址。

第8行,我們再一次使用*操作符來對該指針取值,可直接獲得指針指向的記憶體位址中的資料。由于指針聲明的類型是int,是以取到的值是指針指向的記憶體位址存儲的int值。

這裡可以把指針、引用和值的關系類比為信封、郵箱位址和房子。一個指針就好像是一個信封,我們可以在上面填寫郵寄位址。一個引用(位址)就像是一個郵件位址,它是實際的位址。取值就像是位址對應的房子。我們可以把信封上的位址擦掉,寫上另外一個我們想要的位址,但這個行為對房子沒有任何影響。

void指針、NULL指針和未初始化指針

一個指針可以被聲明為void類型,比如void *x。一個指針可以被指派為NULL。一個指針變量聲明之後但沒有被指派,叫做未初始化指針。

int *uninit; // int指針未初始化
int *nullptr = NULL; // 初始化為NULL
void *vptr; // void指針未初始化
int val = 1;
int *iptr;
int *castptr;
 
// void類型可以存儲任意類型的指針或引用
iptr = &val;
vptr = iptr;
printf("iptr=%p, vptr=%p\n", iptr, vptr);
 
// 通過顯示轉換,我們可以把一個void指針轉成
// int指針并進行取值
castptr = (int *)vptr;
printf("*castptr=%d\n", *castptr);
 
// 列印null和未初始化指針
printf("uninit=%p, nullptr=%p\n", uninit, nullptr);
// 不知道你會得到怎樣的傳回值,會是随機的垃圾位址嗎?
// printf("*nullptr=%d\n", nullptr);
// 這裡會産生一個段錯誤
// printf("*nullptr=%d\n", nullptr);      

執行上面的代碼,你會得到類似下面對應不同記憶體位址的輸出。

iptr=0x7fff94b89c6c, vptr=0x7fff94b89c6c
*castptr=1
uninit=0x7fff94b89d50, nullptr=(nil)      

第1行我們聲明了一個未初始化int指針。所有的指針在指派為NULL、一個引用(位址)或者另一個指針之前都是未被初始化的。第2行我們聲明了一個NULL指針。第3行聲明了一個void指針。第4行到第6行聲明了一個int值和幾個int指針。

第9行到11行,我們為int指針指派為一個引用并把int指針指派為void指針。void指針可以儲存各種其它指針類型。大多數時候它們被用來存儲資料結構。可以注意到,第11行我們列印了int和void指針的位址。它們現在指向了同樣的記憶體位址。所有的指針都存儲了記憶體位址。它們的類型隻在取值時起作用。

第15到16行,我們把void指針轉換為int指針castptr。請注意這裡需要顯示轉換。雖然C語言并不要求顯示地轉換,但這樣會增加代碼的可讀性。接着我們對castptr指針取值,值為1。

第19行非常有意思,在這裡列印未初始化指針和NULL指針。值得注意的是,未初始化指針是有記憶體位址的,而且是一個垃圾位址。不知道這個記憶體位址指向的值是什麼。這就是為什麼不要對未初始化指針取值的原因。最好的情況是你取到的是垃圾位址接下來你需要對程式進行調試,最壞的情況則會導緻程式崩潰。

NULL指針被初始化為o。NULL是一個特殊的位址,用NULL指派的指針指向的位址為0而不是随機的位址。隻有當你準備使用這個位址時有效。不要對NULL位址取值,否則會産生段錯誤。

指針和數組

C語言的數組表示一段連續的記憶體空間,用來存儲多個特定類型的對象。與之相反,指針用來存儲單個記憶體位址。數組和指針不是同一種結構是以不可以互相轉換。而數組變量指向了數組的第一個元素的記憶體位址。

一個數組變量是一個常量。即使指針變量指向同樣的位址或者一個不同的數組,也不能把指針指派給數組變量。也不可以将一個數組變量指派給另一個數組。然而,可以把一個數組變量指派給指針,這一點似乎讓人感到費解。把數組變量指派給指針時,實際上是把指向數組第一個元素的位址賦給指針。

int myarray[4] = {1,2,3,0};
int *ptr = myarray;
printf("*ptr=%d\n", *ptr);
 
// 數組變量是常量,不能做下面的指派
// myarray = ptr
// myarray = myarray2
// myarray = &myarray2[0]      

第1行初始化了一個int數組,第2行用數組變量初始化了一個int指針。由于數組變量實際上是第一個元素的位址,是以我們可以把這個位址指派給指針。這個指派與int *ptr = &myarray[0]效果相同,顯示地把數組的第一個元素位址指派到了ptr引用。這裡需要注意的是,這裡指針需要和數組的元素類型保持一緻,除非指針類型為void。

指針與結構體

就像數組一樣,指向結構體的指針存儲了結構體第一個元素的記憶體位址。與數組指針一樣,結構體的指針必須聲明和結構體類型保持一緻,或者聲明為void類型。

struct person {
  int age;
  char *name;
};
struct person first;
struct person *ptr;
 
first.age = 21;
char *fullname = "full name";
first.name = fullname;
ptr = &first;
 
printf("age=%d, name=%s\n", first.age, ptr->name);      

第1至6行聲明了一個person結構體,一個變量指向了一個person結構體和指向person結構體的指針。第8行為age成員賦了一個int值。第9至10行我們聲明了一個char指針并指派給一個char數組并指派給結構體name成員。第11行我們把一個person結構體引用指派給結構體變量。

第13行我們列印了結構體執行個體的age和name。這裡需要注意兩個不同的符号,’.’ 和 ‘->’ 。結構體執行個體可以通過使用 ‘.’ 符号通路age變量。對于結構體執行個體的指針,我們可以通過 ‘->’ 符号通路name變量。也可以同樣通過(*ptr).name來通路name變量。