曾凡舒
指針是C語(yǔ)言中一種廣泛使用的數(shù)據(jù)類型,也是C語(yǔ)言的重要特性。在C語(yǔ)言中,使用指針能夠編寫出高效、精煉、簡(jiǎn)潔的程序代碼。因此,在C語(yǔ)言的學(xué)習(xí)過(guò)程中,能否正確理解和使用指針是檢驗(yàn)是否掌握C語(yǔ)言的一個(gè)重要標(biāo)志,但是,指針也是C語(yǔ)言中最為困難的部分之一。指針的學(xué)習(xí)像其他內(nèi)容一樣也必須從理解基本概念開始。
一、計(jì)算機(jī)基本原理
半個(gè)多世紀(jì)以來(lái),雖然計(jì)算機(jī)制造技術(shù)發(fā)生了巨大變化,但仍然沿用馮·諾依曼體系結(jié)構(gòu)。在馮·諾依曼計(jì)算機(jī)體系結(jié)構(gòu)理論中,有三個(gè)基本思想:1)計(jì)算機(jī)處理的數(shù)據(jù)和指令采均用二進(jìn)制數(shù)表示;2)計(jì)算機(jī)運(yùn)行過(guò)程中,指令和數(shù)據(jù)首先存入主存儲(chǔ)器(內(nèi)存),計(jì)算機(jī)將自動(dòng)地并按順序從主存儲(chǔ)器中取出指令一條一條地執(zhí)行,這一概念稱作順序存儲(chǔ)程序;3)計(jì)算機(jī)硬件由運(yùn)算器、控制器、存儲(chǔ)器、輸入設(shè)備和輸出設(shè)備五大部分組成。
計(jì)算機(jī)的主存儲(chǔ)器是由一系列連續(xù)編號(hào)或編碼的存儲(chǔ)單元組成,要運(yùn)行的程序指令以及相關(guān)的數(shù)據(jù)都按照一定的順序存儲(chǔ)在內(nèi)存中,如圖1所示。存儲(chǔ)單元中存儲(chǔ)的內(nèi)容有三種:1)指令,如0x03000、0x03001存儲(chǔ)單元,2)直接數(shù)據(jù),如0x05027、0x05028、0x05029、0x05030存儲(chǔ)單元,3)地址數(shù)據(jù),如0x03002、0x03003存儲(chǔ)單元,它們存儲(chǔ)的是存儲(chǔ)單元0x05030、0x05027的地址值。
二、指針的基本概念
C語(yǔ)言的設(shè)計(jì)者Brian W.Kernighan在《C程序設(shè)計(jì)語(yǔ)言》一書中給指針的定義是:指針是一種保存變量地址的變量,因此要真確理解指針概念,先要理解幾個(gè)指針相關(guān)的概念,如地址、變量等。
地址:在計(jì)算機(jī)中,通過(guò)尋址機(jī)構(gòu)將物理存儲(chǔ)介質(zhì)映射成一維線性空間,并以字節(jié)為單位進(jìn)行統(tǒng)一編碼,使得每個(gè)字節(jié)都具有唯一的編碼,類似于街道的門牌號(hào)碼,該編碼稱為字節(jié)的地址,也稱內(nèi)存地址。內(nèi)存地址采用無(wú)符號(hào)整數(shù)來(lái)表示,例如在32位計(jì)算機(jī)中,內(nèi)存地址編碼為0x00000000 ~0xFFFFFFFF,能夠支持最大4GB的內(nèi)存空間,每個(gè)字節(jié)的地址采用32位的無(wú)符號(hào)整數(shù)表示。
存儲(chǔ)單元:在計(jì)算機(jī)系統(tǒng)中,絕大多數(shù)的數(shù)據(jù)往往需要多個(gè)字節(jié)來(lái)存儲(chǔ),如Unicode字符、整數(shù)、浮點(diǎn)小數(shù)、字符串等。所以,需要分配一個(gè)或連續(xù)的多個(gè)字節(jié)來(lái)存儲(chǔ)這些基本數(shù)據(jù),我們把一個(gè)或連續(xù)的多個(gè)字節(jié)的存儲(chǔ)空間稱為一個(gè)存儲(chǔ)單元,每個(gè)存儲(chǔ)單元的地址用該存儲(chǔ)單元的第一個(gè)字節(jié)的地址來(lái)表示。例如,C語(yǔ)言中的short int、long int、char、double等基本數(shù)據(jù)類型在32位計(jì)算機(jī)系統(tǒng)中分別占用2、4、1、8個(gè)字節(jié),如果在內(nèi)存0x0F00地址處依次存儲(chǔ)short int、long int、char、double類型數(shù)據(jù)各一個(gè),那么這四個(gè)存儲(chǔ)單元的地址則分別為0x0F00、0x0F02、0x0F06、0x0F07,而下一個(gè)存儲(chǔ)單元的地址則為0x0F0F,如圖2所示。
變量:是計(jì)算機(jī)存儲(chǔ)空間或存儲(chǔ)單元的具體化,通過(guò)一個(gè)易辨別的字符序列來(lái)標(biāo)識(shí)一個(gè)存儲(chǔ)空間或存儲(chǔ)單元,這樣能夠大大提高編程效率和代碼的可讀性。變量具有三個(gè)要素:1)名稱,2)類型,3)值,此外,變量還有一個(gè)隱含屬性,即地址。例如,圖1中的變量s、i三個(gè)變量,它們的要素及屬性如表1所示。
指針:回頭再看Brian W.Kernighan的指針定義,指針本質(zhì)上也是一個(gè)變量,只不過(guò)它存儲(chǔ)的數(shù)據(jù)是一個(gè)表示地址的無(wú)符號(hào)整數(shù)。如圖1所示,變量pi、ps就是指針變量,我們把表1加上變量pi、ps后得到表2。
三、指針定義及運(yùn)算
在C語(yǔ)言中,表2中各變量的定義如下:
char s = ‘a(chǎn), *ps; //定義并初始化char類型變量s,定義一個(gè)指向char類型變量的指針
int i = 56, *pi; //定義并初始化int類型變量i,定義一個(gè)指向int類型變量的指針
ps = &s; //將變量s的地址保存到ps變量中,則ps將指向s變量
pi = &i; //將變量i的地址保存到pi變量中,則pi將指向i變量
或者
char s = ‘a(chǎn), *ps = &s ; //指針定義與賦值同時(shí)進(jìn)行
int i = 56, *pi = &i;
其中,*ps稱為指針,指針變量的定義格式為:
數(shù)據(jù)類型 *指針變量名;
在上述代碼中,通過(guò)ps = &s賦值后,*ps等價(jià)于s。結(jié)合圖1,對(duì)于0x05027存儲(chǔ)單元中的‘a(chǎn)有兩種訪問(wèn)方式:1)通過(guò)變量s來(lái)訪問(wèn)內(nèi)存地址0x05027的‘a(chǎn),這種訪問(wèn)方式稱為直接訪問(wèn)方式;2)指針變量ps中存放的是變量s的地址,通過(guò)*ps來(lái)訪問(wèn)0x05027的‘a(chǎn)需要兩個(gè)步驟,首先要從指針變量ps中讀取變量s的地址0x05027,再?gòu)牡刂?x05027中讀出‘a(chǎn),這種通過(guò)指針變量存取數(shù)據(jù)的訪問(wèn)方式稱為間接訪問(wèn)方式。
跟指針密切相關(guān)的運(yùn)算符是*和&,它們均為一元運(yùn)算符,且互為逆運(yùn)算。
&運(yùn)算符:取地址運(yùn)算符。放在普通變量的前面,作用是獲取對(duì)應(yīng)變量的存儲(chǔ)地址,例如變量i的值為56,而表達(dá)式&i的值則為0x05030,可使用以下語(yǔ)句進(jìn)行測(cè)試:
inti = 56;
printf ("%d, %p", i, &i);
其運(yùn)行結(jié)果為:56, 0060FF34
*運(yùn)算符:指針運(yùn)算符,又稱間接訪問(wèn)運(yùn)算符。放在指針變量的前面,作用是獲取指針變量中的地址所對(duì)應(yīng)的存儲(chǔ)單元中的數(shù)據(jù)??捎孟率龃a進(jìn)行驗(yàn)證。
inti = 56, *pi; // 第1行
pi = &i; // 第2行
printf ("%d, %p, %p, %d", i, &i, pi, *pi); // 第3行
運(yùn)行結(jié)果為: 56, 0060FF34, 0060FF34, 56
其中,第1行的*表示定義指針變量,第3行中的*則表示指針運(yùn)算。
四、指針的應(yīng)用
學(xué)習(xí)指針的難點(diǎn)除了概念上的理解比較難以外,在應(yīng)用上有以下幾個(gè)比較難理解的地方。
1.指針的初始化、釋放與NULL指針
指針在使用中容易出問(wèn)題的主要環(huán)節(jié)就是指針的初始化和釋放后的處理。指針必須先申明,再賦值,然后才能使用,使用完后,需要用free()函數(shù)釋放所占用的存儲(chǔ)空間,最后還要對(duì)指針變量進(jìn)行置空,否則會(huì)出現(xiàn)意想不到的問(wèn)題。
首先,在聲明指針變量后,如果不進(jìn)行初始化,那么該指針就是一個(gè)未初始化的指針,指針變量中的數(shù)據(jù)為內(nèi)存殘存數(shù)據(jù),如果不進(jìn)行初始化,就會(huì)指向一個(gè)未知的地方,得到的是一個(gè)無(wú)效數(shù)據(jù)。如下面的程序代碼。
int *pt;
printf ("%p, %p, %d\n", &pt, pt, *pt);
運(yùn)行結(jié)果:0060FF30, 004013A0, -2082109099
結(jié)果中第一個(gè)數(shù)字0060FF30表示的是指針變量pt本身的地址值;第二個(gè)數(shù)字004013A0是表示指針變量pt中的地址值,由于指針pt沒有初始化,所以指針pt變量中的地址值是原來(lái)內(nèi)存中的殘存數(shù)據(jù);第三個(gè)數(shù)字-2082109099則是殘存數(shù)據(jù)作為地址值所指內(nèi)存中的數(shù)據(jù),不是我們想要的數(shù)據(jù),屬于垃圾數(shù)據(jù)。
其次,在指針使用完畢后,第一,要釋放指針?biāo)嫉膬?nèi)存空間,通過(guò)free()函數(shù)完成,第二,需要對(duì)不用的指針變量置空,即設(shè)置不再使用的指針變量的值為NULL,否則釋放空間后,指針中的地址值不會(huì)變,但是指針原來(lái)所指的存儲(chǔ)單元將成為未使用空間或分配給其他的變量使用。
char *pta = (char *) malloc(30); //第1行
strcpy(pta, "Hello C Language!"); //第2行
printf ("%p \n", pta); //第3行
free(pta); //第4行
printf ("%p \n", pta); //第5行
//if(pta != NULL); //第6行
//strcpy(pta, "zfs"); //第7行
pta = NULL; //第8行
printf ("%p \n", pta); //第9行
結(jié)果為:
009C0DC0
009C0DC0
00000000
第一個(gè)數(shù)字009C0DC0為malloc()函數(shù)申請(qǐng)30個(gè)字符空間的首個(gè)字節(jié)的地址,并將該地址值保存到了指針變量pta中;第二個(gè)數(shù)字009C0DC0是對(duì)指針變量pta進(jìn)行釋放處理后其中的值,可以看出,雖然釋放了pta所指向的存儲(chǔ)空間,但pta中的地址值仍然不變,pta所指向的存儲(chǔ)空間已經(jīng)回收或另作他用,pta指向了不可用內(nèi)存區(qū)域,成了野指針,而且不能使用(pta != NULL)進(jìn)行檢測(cè),所以第6、7行運(yùn)行會(huì)出錯(cuò);第三個(gè)數(shù)字00000000是將pta變量置空后pta中的值,即將NULL賦值給pta指針變量,pta即成為NULL指針(空指針),NULL的定義在C語(yǔ)言標(biāo)準(zhǔn)庫(kù)頭文件stddef.h中。宏定義如下:
#undef NULL
#if defined(__cplusplus)
#define NULL 0
#else
#define NULL ((void *)0)
#endif
2.指針與數(shù)組
C語(yǔ)言的數(shù)組表示的是一段連續(xù)的內(nèi)存空間,用來(lái)存儲(chǔ)多個(gè)指定類型的數(shù)據(jù),每個(gè)數(shù)組成員稱為一個(gè)數(shù)組元素,每個(gè)數(shù)組元素占用的字節(jié)數(shù)相等。數(shù)組和指針不是同一種結(jié)構(gòu),不可以互相轉(zhuǎn)換,但是數(shù)組變量則是指向了數(shù)組的第一個(gè)元素的內(nèi)存地址,如圖3所示。
short int ai[5]= {27, 36, 51, 110, 97}; //第1行
short int *ptai; //第2行
ptai = ai; //第3行
在C語(yǔ)言中可以把數(shù)組變量直接賦值給指針,但不能把指針變量賦值給數(shù)組。如果把一個(gè)數(shù)組變量值賦給指針,實(shí)際上是把指向數(shù)組第一個(gè)元素的地址賦給指針。第2、3行可以理解為:
short int *ptai = &ai[0];
或者
short int *ptai;
ptai = &ai[0];
所以ai[0]與*ptai是等價(jià),由于數(shù)組中的元素是等長(zhǎng)的,因此,ai[1]與*(ptai + 1)、ai[2]與*(ptai + 2)也是等價(jià)的,以此類推。但是,要注意的是ptai + 1、ptai + 2中的1、2并不是按字節(jié)計(jì)算的偏移量,而是按short int類型計(jì)算的偏移量。
五、總結(jié)
指針雖然比較難以理解和掌握,需要許多計(jì)算機(jī)底層的知識(shí)作支撐,但它卻是C語(yǔ)言的精髓所在,是C語(yǔ)言的重要特性,能夠充分展現(xiàn)C語(yǔ)言的強(qiáng)大魅力。實(shí)際上,我們只要從變量的本質(zhì)、內(nèi)存地址、計(jì)算機(jī)尋址原理等多方面進(jìn)行了解和貫通,指針概念以及指針的相關(guān)應(yīng)用也就不難理解了。而且,通過(guò)對(duì)指針的深入了解和學(xué)習(xí),能夠更加高效和靈活地使用數(shù)組、字符串、結(jié)構(gòu)體、各種線性非線性數(shù)據(jù)結(jié)構(gòu)、內(nèi)存的動(dòng)態(tài)分配以及文件的存取等。所以說(shuō),學(xué)好了指針,才能夠?qū)W好C語(yǔ)言。
責(zé)任編輯朱守鋰