《C語言程序設計新視角》課件第6章_第1頁
《C語言程序設計新視角》課件第6章_第2頁
《C語言程序設計新視角》課件第6章_第3頁
《C語言程序設計新視角》課件第6章_第4頁
《C語言程序設計新視角》課件第6章_第5頁
已閱讀5頁,還剩185頁未讀, 繼續(xù)免費閱讀

下載本文檔

版權說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權,請進行舉報或認領

文檔簡介

第6章指針6.1地址和指針的關系6.2指針的定義 6.3指針變量的運算6.4指針和數(shù)組的關系6.5指針在函數(shù)中的應用6.6本章小結

【主要內(nèi)容】

指針的含義、使用規(guī)則及方法實例;

通過指針變量與普通變量的對比,說明其表現(xiàn)形式與本質含義;

指針變量與普通變量的不同之處以及使用上的相同之處;

指針與數(shù)組的關系;

指針偏移量的本質含義;

讀程序的訓練;

自頂向下算法設計的訓練;

指針調(diào)試要點。

【學習目標】

理解并掌握指針的概念;

理解指針、數(shù)組和字符串之間的關系;

掌握指針對變量、數(shù)組的引用方法;

能夠通過指針使用字符串數(shù)組。

要查看一本書中的相關內(nèi)容,我們的一般做法是先查看目錄,而不是直接在整本書中查找內(nèi)容。書的內(nèi)容在書中的位置是通過“頁碼”標示的。

6.1地址和指針的關系在計算機中,所有的數(shù)據(jù)都是存放在存儲器中的,為方便數(shù)據(jù)的查找,也有“數(shù)據(jù)存放位置”的概念,計算機中數(shù)據(jù)的位置是用“地址”標示的。

一般把存儲器中的一個字節(jié)(8

bit)稱為一個內(nèi)存單元,為了方便管理與訪問這些內(nèi)存單元,可為每個內(nèi)存單元編上號,根據(jù)一個內(nèi)存單元的編號即可準確地找到該內(nèi)存單元。內(nèi)存單元的編號也叫做地址,如圖6.1所示。

圖6.1內(nèi)存不同數(shù)據(jù)類型的數(shù)據(jù)所占用的內(nèi)存單元數(shù)不等,如整型量占2個單元,字符量占1個單元等。

變量a的地址是2000還是2001?

前面介紹的一個變量的三要素中的變量地址,是編譯或函數(shù)調(diào)用時系統(tǒng)為變量分配的內(nèi)存單元的編號。當一個變量占多個內(nèi)存單元時,其地址規(guī)定為單元編號中最小的那個。

內(nèi)存及地址

內(nèi)存(Memory)也被稱為內(nèi)存儲器,是計算機中重要的部件之一,用來暫時存放CPU中的運算數(shù)據(jù)以及與硬盤等外部存儲器交換的數(shù)據(jù),程序必須裝入內(nèi)存才能執(zhí)行。

有4個變量,均為整型,設它們在內(nèi)存中的狀態(tài)如圖6.2所示。其中,變量ptr的值比較特殊,是變量k的地址,我們稱變量ptr是指向k的指針,簡稱ptr指向k。

圖6.2內(nèi)存示意圖指針變量既然是變量,它就有和普通變量一樣的三個要素,即變量名、變量值和單元地址。它和普通變量的類比如表6.1所示。

表6.1變

指針的類型是什么?

由指針變量的含義可知,指針變量和普通變量唯一不同的地方在于,它的值只表示地址,而不能是有其他含義的數(shù)值。

既然指針變量的值是地址,而地址又是整數(shù),那么指針的類型是不是就是整型呢?

在這里特別需要注意,C語言規(guī)定,指針變量的類型,是它指向單元的數(shù)據(jù)的類型。如前面的ptr指針,它的類型就是變量k的類型,k是整型,則ptr的類型為int;若k是實型,則ptr的類型為float型。

指針是一種特殊的變量,和普通變量相比,它的值是地址;它的類型是它指向單元的數(shù)據(jù)的類型。

在指針變量名中包含字母ptr,這樣可以有很醒目的提示作用。

定義一個指針變量和普通變量一樣,只不過要在變量名前加上一個星號*,*表示這是一個指針變量,指針變量的定義形式如下:6.2指?針?的?定?義

類型說明符*變量名;

變量類型說明符表示本指針變量所指向的存儲單元的數(shù)據(jù)類型。

例如:int*p1;表示p1是一個指針變量,它的值是某個整型變量的地址,或者說p1指向一個整型變量。至于p1究竟指向哪一個整型變量,應由向p1賦予的地址來決定。

再如:

float*p3; /*p3是指向浮點變量的指針變量*/

char*p4; /*p4是指向字符變量的指針變量*/

應該注意的是,一個指針變量只能指向同類型的變量,如P3只能指向浮點變量,不能時而指向一個浮點變量,時而又指向一個字符變量。

空類型void問題

(1)空類型:其類型說明符為void。void類型不指定具體的數(shù)據(jù)類型,主要用于表示函數(shù)沒有返回值和通用指針。

(2)空類型函數(shù):在第5章“函數(shù)”中,我們已經(jīng)了解了空類型函數(shù)的含義。在調(diào)用函數(shù)值時,通常應向調(diào)用者返回一個函數(shù)值,這個返回的函數(shù)值是具有一定的數(shù)據(jù)類型的,應在函數(shù)定義及函數(shù)說明中給予說明。但是,也有一類函數(shù),調(diào)用后并不需要向調(diào)用者返回函數(shù)值,這種函數(shù)可以定義為“空類型”。

(3)空類型指針:也稱通用類型指針或無確切類型指針,其含義是這個指針指向的內(nèi)存區(qū)域的數(shù)據(jù)可以是C允許的任何類型。

為什么要設置void類型的指針呢?這是由于指針使用時,在某些情形下,指針指向的存儲單元無法事前確定要存放什么類型的數(shù)據(jù),因此需要專門設計這種解決機制。

比如malloc庫函數(shù),功能是在程序運行的過程中,動態(tài)地申請一片連續(xù)的存儲區(qū)域,返回這個存儲區(qū)的起始地址。作為malloc函數(shù)的設計者,事前無法確定這個函數(shù)的調(diào)用者會在這片存儲區(qū)中存放什么類型的數(shù)據(jù),為適應所有可能的情形,只有把返回值設計成空類型指針才是合理的。有些語言的指針有專門的指針類型,這樣設計的好處是,不關心其指向單元的內(nèi)容究竟是什么類型的數(shù)據(jù)。

空類型的指針不能直接進行存取內(nèi)容的操作,必須先強制轉成具體的類型的指針才可以把內(nèi)容解釋出來。

指針變量可以進行某些運算,但其運算的種類是有限的,只能進行賦值運算和部分

算術運算及關系運算。6.3指針變量的運算6.3.1指針運算符

指針運算符有兩個,見表6.2。

表6.2指

(1)取地址運算符&。取地址運算符&是單目運算符,其結合性為自右至左,其功能是取變量的地址。在scanf函數(shù)中,我們已經(jīng)了解并使用了&運算符。

(2)取內(nèi)容運算符*。取內(nèi)容運算符*是單目運算符,其結合性為自右至左,用來表示指針變量所指的變量。在*運算符之后跟的變量必須是指針變量。需要注意的是指針運算符*和指針變量說明中的指針說明符*不是一回事。在指針變量說明中,“*”是類型說明符,表示其后的變量是指針類型。而表達式中出現(xiàn)的“*”則是一個運算符,用以表示指針變量所指的變量。

【例6-1】觀察指針的賦值、取指針指向單元的內(nèi)容。

1 #include<stdio.h>

2 intmain()

3 {

4 inta[5]={2,4,6,8};

5 int*aPtr,*bPtr; /*定義兩個字符類型的指針*/

6 aPtr=a; /*指針aPtr指向a數(shù)組的起始地址*/

7 bPtr=&a[3]; /*bPtr指向a[3]單元所在位置*/

8 *aPtr=*bPtr; /*把bPtr指向單元的值賦給aPtr指向的單元*/

9 return0;

10 }

說明:

(1)語句6:指針aPtr指向a數(shù)組的起始地址,數(shù)組名a是數(shù)組的起始地址。

(2)語句7:&a[3]表示取數(shù)組元素a[3]的地址,bPtr指向a[3]單元所在位置。指針aPtr、bPtr的指向如圖6.3所示。

(3)語句8:把bPtr指向單元的值賦給aPtr指向的單元,a[0]的值被改為8,如圖6.4所示。

圖6.3指針aPtr、bPtr的指向

圖6.4aPtr指向單元被修改

調(diào)試要點:

·指針的賦值;

·指針指向的單元;

·指針指向單元的內(nèi)容。

圖6.5~圖6.10所示分別為例6-1的調(diào)試步驟1~調(diào)試步驟6。

圖6.5中,數(shù)組a已初始化,指針aPtr、bPtr已定義,語句aPtr=a將要執(zhí)行。

圖6.5例6-1調(diào)試步驟1一般的情形是,編譯時正常,運行時卻報錯。圖6.6中,調(diào)試器給出的錯誤信息是expressioncannotbeevaluated,此時調(diào)試器里顯示變量的地址通常是0x000000或0xCCCCCC。圖6.6例6-1調(diào)試步驟2出現(xiàn)這樣的錯誤一般是由于對變量的初始化不正確或者還沒有初始化就直接引用變量。只要在對變量進行引用前確保變量已經(jīng)正確初始化就可以避免此類錯誤。

圖6.7中,執(zhí)行語句aPtr=a,指針aPtr指向a數(shù)組的起始地址,指向單元的內(nèi)容為a[0]。

圖6.8中,執(zhí)行語句bPtr=&a[3],其中&a[3]表示取數(shù)組元素a[3]的地址,bPtr指向a[3]單元;Watch中bPtr指向單元的地址是0x12ff78。圖6.9中的Memory查看a[3]的地址為0x12ff78,驗證了語句的功能。

圖6.7例6-1調(diào)試步驟3

圖6.8例6-1調(diào)試步驟4

圖6.9例6-1調(diào)試步驟5圖6.10中,語句*aPtr=*bPtr表示將bPtr指向單元的值賦給aPtr指向的單元,a[0]的值由起初的2被改為8。

圖6.10例6-1調(diào)試步驟6

例6-1程序中若無語句aPtr=a,即aPtr未賦值會出現(xiàn)什么情況?

答:編譯后,會出現(xiàn)下面的告警——

warningC4700:localvariable'aPtr'usedwithouthavingbeen

initialized

出現(xiàn)編譯告警,依然可以鏈接通過。

單步運行程序,在試圖執(zhí)行語句*aPtr=*bPtr時,將彈出如圖6.11所示的告警窗口,程序不再向下執(zhí)行。

圖6.11指針使用異常注:MyTest.exe為此例最后生成的可執(zhí)行文件;Unhandledexception意為未處理的異常;AccessViolation意為非法訪問。

AccessViolation錯誤是計算機運行的用戶程序試圖存取未被指定使用的存儲區(qū)時常常遇到的狀況。此條錯誤,前面在介紹scanf輸入函數(shù)常見錯誤時已經(jīng)遇到過。例如:

inta;

scanf("%d",a);——變量a前少了取地址符&,造成存取地址錯誤

指針使用原則:

(1)永遠要清楚你用的指針指向了哪里;

(2)永遠要清楚指針指向的位置中放的是什么類型的數(shù)據(jù)。

指針使用的原則也是指針使用的關鍵點。

對一個沒有指向特定位置的指針進行賦值,其可能的危害有兩種情形:

(1)產(chǎn)生嚴重的運行錯誤,程序不能夠繼續(xù)運行,有可能造成系統(tǒng)崩潰。

(2)程序能夠繼續(xù)運行,對指向單元的數(shù)據(jù)修改將造成數(shù)據(jù)的非法修改,這種錯誤在程序跟蹤調(diào)試中很難查找。因為被修改的單元數(shù)據(jù)在被使用的時刻是不可預計的,如果此種錯誤環(huán)境很難再次重現(xiàn),那么這種錯誤將是程序跟蹤調(diào)試最難查找的問題之一。

編程中有一類比較容易犯的錯誤——對一個沒有指向特定位置的指針進行賦值,可能產(chǎn)生嚴重的運行期錯誤,即使程序能夠繼續(xù)運行,也得到不正確的結果;或者可能會無意中修改重要的數(shù)據(jù)。6.3.2指針的運算

1.空指針的概念

(1)NULL的含義:NULL是在<stdio.h>頭文件中定義的符號常量,其值為0;NULL也是一個標準規(guī)定的宏定義,用來表示空指針常量。

(2)空指針:如果把NULL賦給了一個有類型的指針變量,那么此時這個指針就叫空指針,此時它不指向任何對象或者函數(shù),也就是說,空指針不指向任何存儲單元。說明:

(1)空指針在概念上不同于未初始化的指針??罩羔樋梢源_保不指向任何對象或函數(shù),而未初始化指針則可能指向任何地方。

(2)空指針的機器內(nèi)部表示不等同于內(nèi)存單元地址0。

2.指針的運算

對指針的運算就是對地址的運算。由于這一特點,指針運算不同于普通變量,只允許進行以下幾種有限的運算。

(1)賦值運算:限于同類型指針。

①指針變量初始化;

②變量的地址;

③指針變量值;

④地址常量:如數(shù)組、字符串起始地址。

(2)算術運算:只限于加減運算,用于對數(shù)組的操作。

(3)關系運算:用于對數(shù)組的操作。

【例6-2】指針運算的例子1。設有變量定義如下:

intk,x,a[10];

int*ptrk=&k;

int*ptr,*ptr1,*ptr2;

說明語句:

int*ptrk=&k;

等價于:

int*ptrk;ptrk=&k;

指針運算的規(guī)則及例句見表6.3。

表6.3指針運算說明:

(1)指針不能被賦予不是地址的值,也不能被賦予與該指針類型不同的其他類型的對象的地址,這些都會導致編譯錯誤。有一個特例,空指針(void*)能夠被賦予任何類型的對象的地址。

(2)對于指向數(shù)組的指針變量,可以加上或減去一個整數(shù)n。指針變量加或減一個整數(shù)n的意義是把指針指向的當前位置(指向某數(shù)組元素)向前或向后移動n個位置。應該注意,指向數(shù)組的指針變量向前或向后移動一個位置和地址加1或減1在概念上是不同的。因為數(shù)組可以有不同的類型,各種類型的數(shù)組元素所占的字節(jié)長度是不同的。如指針變量加1,即向后移動1個位置,表示指針變量指向下一個數(shù)據(jù)元素的首地址,而不是在原地址基礎上加1。

數(shù)組作為一個包含若干相同類型元素的整體,在內(nèi)存中占一塊連續(xù)的存儲空間,其地址與容量在程序運行的有效期內(nèi)保持不變,只有數(shù)組的內(nèi)容可以改變,每個數(shù)組元素都有相應的存儲單元和地址,數(shù)組名代表數(shù)組的存儲地址。數(shù)組名本質上是常量指針。6.4指針和數(shù)組的關系指針變量的作用是可以隨時指向任何我們希望的存儲單元,目的是方便對存儲單元的數(shù)據(jù)施加操作。利用指針對數(shù)組進行操作,往往比直接引用數(shù)組元素要靈活和高效。

6.4.1指針與一維數(shù)組

【例6-3】指針與數(shù)組例子1。分析程序,填充表6.4中的各項。

1 #include<stdio.h>

2 intmain()

3 {

4 inta[]={0,1,2};

5 int*aPtr=a;

6 intb;

7 char*bPtr="abcde";

8 b=*(++aPtr);

9 return0;

10 }

表6.4程序分析表說明:

(1)語句

int*aPtr=a;

等價于:

int*aPtr;aPtr=a;

數(shù)組名代表了數(shù)組的起始地址。

(2)程序中aPtr和bPtr指針初始化時的指向如圖6.12所示。aPtr指向a數(shù)組的起始地址;bPtr指向字符串"abcde"的起始位置。

圖6.12指針指向

語句b=*(++aPtr)中,aPtr指針加1后會指向哪里?

答:可能的情形——(1)地址絕對值加1;(2)指向數(shù)組的下一個單元。

問題歸結為:指向數(shù)組的指針加1后會指向哪里?

a數(shù)組的存儲參見圖6.13。

因為數(shù)組a的類型是int,所以,一個元素占2byte,若a[0]的地址是2000,則a[1]的地址是2002。如果aPtr指針加1,是地址的絕對值加1,則aPtr指向2001,這個單元地址既不是a[0]的地址,也不是a[1]的地址,和指針變量的概念不符,從邏輯上看也是不合理的。

因此,aPtr指針加1,應該指向下一個元素地址才合理。此時a數(shù)組的類型是int類型,指針要移動2

byte,才能指向下一個元素;如果a的類型是float類型,就要移動4

byte。所以指針加1,移動的長度和指針類型是相關的。

圖6.13a數(shù)組的存儲

指針偏移規(guī)則:指向數(shù)組的指針增加或減少1時,實際移動的長度為sizeof(指針的類型)。

有了指向數(shù)組的指針做算術運算的移位規(guī)則,在例6-3的程序中,指針對數(shù)組的操作結果就不難得出了,參見圖6.14。

圖6.14指針對數(shù)組的操作說明:

(1)可以把數(shù)組名當指針用,如a+2,但不可以對a進行賦值操作。

(2)當指針指向數(shù)組或字符串時,指針的用法可以和數(shù)組名類似,如bPtr[2]。但要注意,指針是個可變量,如果有bPtr=bPtr+2,則此時bPtr[1]='d'。

【例6-4】指針與數(shù)組例子2。指針指向常量字符串的問題。

1 intmain()

2 {

3 chara[]="dinar##";

4 char*b="dollar##";

5

6 a[6]=':';

7 b[5]=':';

8 return0;

9 }

程序運行后,會出現(xiàn)“Accessviolation”告警,跟蹤一下,具體是執(zhí)行第7行時出現(xiàn)的,即對b指針指向的字符串內(nèi)容不能進行寫操作。其原因是,C語言中把給字符指針賦值的字符串規(guī)定為常量字符串,其內(nèi)容是不可被修改的;而給數(shù)組賦值的字符串則不是常量字符串。

指針與常量字符串:給字符指針賦值的字符串是常量字符串,不能對其進行寫操作。

【例6-5】指針與數(shù)組例子3。分析程序,給出結果。

1 intmain()

2 {

3 inta[10],b[10];

4 int*aPtr,*bPtr,i;

5 aPtr=a;bPtr=b;

6 for(i=0;i<6;i++,aPtr++,bPtr++)

7 {

8 *aPtr=i;

9 *bPtr=2*i;

10 printf("%d\t%d\n",*aPtr,*bPtr);

11 }

12 aPtr=&a[1];①

13 bPtr=&b[1];②

14 for(i=0;i<5;i++)

15 {

16 *aPtr+=i;③

17 *bPtr*=i;④

18 printf(“%d\t%d\n”,*aPtr++,*bPtr++);

19 }/*

*aPtr++的含義是先取aPtr內(nèi)容再使aPtr加1*/

20 return0;

21 }

【解】程序分析:

(1)第6行for循環(huán)結束后,數(shù)組a和b的值如表6.5所示。

表6.5數(shù)組a和b的值

(2)逐步填充表6.6中的各項。

根據(jù)步驟①、②指針的指向aPtr=&a[1]和bPtr=&b[1]可知,此時*aPtr等于1,*bPtr等于2,第14行為for循環(huán),從i=0開始,逐步將*aPtr和*bPtr迭代的值填入表6.6中。

程序結果:

00

12

24

36

表6.6數(shù)據(jù)分析

48

510

10

34

512

724

940

【例6-6】指針與數(shù)組的例子3。分析程序,給出結果。

1intmain()

2{

3chara[2][5]={"abc","defg"};

4char*pPtr=a[0],*sPtr=a[1];

5while(*pPtr)pPtr++;

6while(*sPtr)*pPtr++=*sPtr++;

7printf("%s%s\n",a[0],a[1]);

8 return0;

9}

【解】a數(shù)組的內(nèi)容見圖6.15。

說明:

(1)第4行語句

char*pPtr=a[0],*sPtr=a[1];

等價于:

char*pPtr,*sPtr;

pPtr=a[0];sPtr=a[1];

a[0]是字符串"abc"的起始地址;a[1]是字符串"defg"的起始地址。指針指向a的示意圖見圖6.16。

圖6.15a數(shù)組

圖6.16指針指向a

(2)二維數(shù)組的存儲是連續(xù)的,即a[0]行元素和a[1]行元素是按序連續(xù)存放的。

(3)第6行語句

while(*sptr)*pPtr++=*sPtr++;

等價于:

while(*sPtr)

{ *pPtr=*sPtr;

pPtr++;

sPtr++;

}

(1)第5行語句while(*pPtr)pPtr++;

執(zhí)行完的情形如圖6.17所示。

(2)循環(huán)執(zhí)行第6行語句:

while(*sPtr)*pPtr++=*sPtr++;

sPtr指向單元的內(nèi)容為“d”,循環(huán)條件為真,sPtr單元內(nèi)容賦值給pPtr單元,原來單元的值“\0”被改寫為“d”,如圖6.18所示,然后兩個指針均后移一位。

圖6.17讀程分析1

圖6.18讀程分析2

(3)循環(huán)執(zhí)行第6行語句:

while(*sPtr)*pPtr++=*sPtr++;

pPtr指向單元的值“\0”被改寫為“e”,如圖6.19所示。

(4)循環(huán)執(zhí)行第6行語句:

while(*sPtr)*pPtr++=*sPtr++;

pPtr指向單元的值“d”被改寫為“f”,如圖6.20所示。

(5)循環(huán)執(zhí)行第6行語句:

while(*sPtr)*pPtr++=*sPtr++;

pPtr指向單元的值“e”被改寫為“g”,如圖6.21所示。

圖6.19讀程分析3

圖6.20讀程分析4

圖6.21讀程分析5

(6)循環(huán)執(zhí)行第6行語句:

while(*sPtr)*pPtr++=*sPtr++;

此時*sPtr為“\0”,循環(huán)條件為假,循環(huán)結束,如圖6.22所示。

(7)第7行語句:

printf("%s%s\n",a[0],a[1])

按%s格式控制符輸出的功能是:從給定地址開始輸出字符,遇到空字符“\0”停止。從地址a[0]開始輸出字符串的結果為abcdefgfg,從地址a[1]開始輸出字符串的結果為fgfg,所以最后的輸出結果為abcdefgfgfgfg。

圖6.22讀程分析66.4.2指向指針的指針

創(chuàng)建一個指針,使它指向另一個指針,則這個指針稱為指向指針的指針。

如圖6.23所示:變量yPtr的值是變量C的地址,所以指針yPtr指向C;變量xPtr的值是變量yPtr的地址,所以指針xPtr指向yPtr。其中的xPtr就被稱為指向指針yPtr的指針,簡稱指向指針的指針。

圖6.23指針的指針由圖6.23可知:

*yPtr等于c;——yPtr指向單元的內(nèi)容是c的值。

*xPtr等于yPtr;——xPtr指向單元的內(nèi)容是yPtr,故*xPtr是指針。

*(*xPtr)等于*(yPtr)等于c;——*xPtr指向單元的內(nèi)容即是yPtr指向單元的內(nèi)容。

**xPtr等于c;——指針的指針對變量c的二級間接訪問。

一級指針:普通的指針,如上例中的yPtr。

二級指針:指向指針的指針,如上例中的xPtr。

二級指針變量說明的一般形式為:

類型說明符**變量名;

二級指針常用于二維數(shù)組。

【例6-7】指針與數(shù)組的例子4。分析各種指針和數(shù)組間的關系。

1 intmain()

2 {

3 char*a[]={"abcd","efghij","mnpq","rstv"};

4 charb[4][7]={"abcd","efghij","mnpq","rstv"};

5 char**xPtr;

6 xPtr=a;

7 ++xPtr;

8 return0;

9 }

說明:

(1)a[]是指針數(shù)組,因為其數(shù)組元素是指針,其定義形式是在普通數(shù)組前加一星號;

(2)a是指針,a中的內(nèi)容也是指針,故a為二級指針。

xPtr、a、b間的關系及內(nèi)容見表6.7。

表6.7指

數(shù)

說明:

(1)a[]、b[]為行指針;

(2)同級指針才能賦值,如xPtr=a。

圖6.24~圖6.26所示分別為例6-7的調(diào)試步驟1~調(diào)試步驟3。

從圖6.24中可以看到指針數(shù)組、二維數(shù)組二級指針的內(nèi)容。

圖6.24例6-7調(diào)試步驟1圖6.25中,在語句xPtr=a; ++xPtr;執(zhí)行完后,xPtr指向a[1],對元素的各種引用方法及結果如表6.8所示,讀者可自行上機驗證。

圖6.25例6-7調(diào)試步驟2

表6.8指針xPtr與數(shù)組a、b的關系

b[1][8]值為“n”的解釋:二維數(shù)組的元素是連續(xù)存儲的,如表6.9所示。從圖6.26的Memory窗口也可以看到b的連續(xù)存儲的情形。

表6.9二維數(shù)組元素下標從圖6.25的Watch窗口中可以看出,a數(shù)組中各字符串并不是連續(xù)存儲的。

圖6.26例6-7調(diào)試步驟36.4.3數(shù)組的指針和指針數(shù)組

1.數(shù)組的指針

數(shù)組的指針是指向數(shù)組的一個指針。

數(shù)組的指針定義形式如下:

類型說明符(*指針名)[常量];

注意:數(shù)組的指針定義形式中,括號中是一個指針量。例如:

int(*xPtr)[3];這里的xPtr是一個指針變量,并不是指針常量(數(shù)組名),它是和有3列的二維數(shù)組配合使用的。xPtr指針的偏移量大小為3*sizeof(int)。

2.指針數(shù)組

指針數(shù)組是指數(shù)組的元素是指針的數(shù)組。

指針數(shù)組的定義形式如下:

類型說明符*數(shù)組標識符[常量];

例如:

int*xPtr[3];這里的xPtr是一個數(shù)組名,是一個指針常量。xPtr數(shù)組中有3個元素,每個元素都是int類型的指針。

【例6-8】數(shù)組的指針的例子。

inta[3][4]={{1,2,3,4},{5,6,7,8},{9,10,11,12}};

int(*aPtr)[4];/*aPtr:一個指針,與包含4個列元素的二維數(shù)組配合使用*/

int*bPtr;

aPtr=a;

bPtr=a[0];

圖6.27中示出了aPtr、bPtr指針與數(shù)組a的關系。

圖6.27aPtr、bPtr指針與數(shù)組a的關系說明:

(1)語句aPtr=a;bPtr=a[0];執(zhí)行后,aPtr、bPtr的指向都是數(shù)組a的起始位置;

(2)aPtr+1指向a數(shù)組的第二行;

(3)bPtr+1指向a數(shù)組第一行的第二個元素。

從圖6.28的Watch和Memory窗口可以觀察到上面的結論。

明白了aPtr和bPtr的指向及特點,就容易看清它們對數(shù)組a中元素值的引用了,讀者可自己驗證表6.10中的內(nèi)容。

圖6.28Watch和Memory窗口

表6.10aPtr與bPtr指針

指針的賦值規(guī)則及數(shù)組的指針的偏移:

(1)同級、同類型指針才能賦值。

(2)數(shù)組的指針的偏移大小與指針的類型及其列參數(shù)相關。例如int(*aPtr)[4],其中4為列參數(shù)。

指針偏移量=sizeof(int)*4

6.5.1函數(shù)的參數(shù)是指針

下面再討論一下“傳址調(diào)用”的另一種形式——參數(shù)是指針變量的情形,例子是由例5-7“函數(shù)間信息傳遞的例子2”改變而來的。

【例6-9】函數(shù)的參數(shù)是指針的例子1。求數(shù)組中下標為m到n項的和。主函數(shù)負責提供數(shù)組、m和n的值及結果的輸出,求和功能由子函數(shù)func完成。6.5指針在函數(shù)中的應用程序如下:

1 #include"stdio.h"

2 #defineSIZE10

3 intfunc(int*bPtr,intm,intn);

4

5 /*求數(shù)組中下標為m到n項的和*/

6 intfunc(int*bPtr,intm,intn)

7 {

8 inti,sum=0;

9

10 bPtr=&bPtr[m];/*將bPtr指針指向要操作的元素地址*/

11 for(i=m;i<=n;i++,bPtr++)

12 {

13 sum=sum+*bPtr;

14 }

15 returnsum;

16 }

17

18 intmain()

19 {

20 intx;

21 inta[SIZE]={1,2,3,4,5,6,7,8,9,0};

22 int*aPtr=a;

23 intp=3,q=7;/*指定求和下標的位置*/

24

25 x=func(aPtr,p,q);

26 printf("%d\n",x);

27 return0;

28 }

調(diào)試要點:

·指針aPtr、bPtr是否各分單元;

·bPtr指向發(fā)生改變,aPtr是否跟著改變。

圖6.29~圖6.41所示分別為例6-9調(diào)試步驟1~調(diào)試步驟13。

說明:&aPtr表示指針變量aPtr的存儲地址是0x12ff50。

func調(diào)用前,實參變量aPtr、p、q的地址及值都在圖6.29的Watch窗口中顯示出來。圖6.30中,從地址0x12ff54開始存放的內(nèi)容是數(shù)組a的值。

圖6.29例6-9調(diào)試步驟1圖6.30例6-9調(diào)試步驟2

圖6.31例6-9調(diào)試步驟3觀察可知:圖6.29中,實參指針aPtr的單元地址是0x12ff50,單元的內(nèi)容是0x12ff54;圖6.32中,形參指針bPtr單元地址是0x12fef0,單元的內(nèi)容是0x12ff54。二者的單元地址是不一樣的,即它們不是“共用地址”,而是“各分單元”——這一點特別提請注意,也就是說地址值0x12ff54是當做一個整型值被傳遞了。func要處理的數(shù)據(jù)存放在bPtr指向的存儲區(qū)域里,只要按序去取即可。

圖6.32例6-9調(diào)試步驟4圖6.33例6-9調(diào)試步驟5

圖6.34例6-9調(diào)試步驟6下面再來看看指針實參值被修改,是否會對形參有影響。

圖6.35中,在調(diào)用func前,各實參的地址和值與前面跟蹤的情形是一樣的。

圖6.36中,aPtr、p、q分別和bPtr、m、n對應,各分單元。

圖6.37中,讓指針bPtr指向要讀取的下標為m的元素地址0x12ff60,其值為4。

圖6.35例6-9調(diào)試步驟7

圖6.36例6-9調(diào)試步驟8

圖6.37例6-9調(diào)試步驟9圖6.39中,當bPtr++被執(zhí)行后,其指向從原來的0x12ff60變成了0x12ff64,在圖6.38中可查看到此時bPtr指向單元的值是5。此處bPtr++移動了一個int的長度(4

byte)。

圖6.40中,循環(huán)結束,bPtr指向下標為7的元素,地址為0x12ff74,值為9,sum=30。

圖6.41中,func調(diào)用完畢,回到主函數(shù),可看到aPtr、p、q的地址和值沒有改變。

圖6.38例6-9調(diào)試步驟10

圖6.39例6-9調(diào)試步驟11

圖6.40例6-9調(diào)試步驟12

圖6.41例6-9調(diào)試步驟13

表6.11函數(shù)間傳遞參數(shù)的方式

表6.12函數(shù)間傳遞參數(shù)的方式注意:在形參實參各分單元時,實參值可在子函數(shù)中被改變,形參值不會被改變。

在C語言中,函數(shù)調(diào)用為值調(diào)用與模擬引用調(diào)用,模擬引用調(diào)用歸為值調(diào)用一類。

【例6-10】函數(shù)的參數(shù)是指針的例子2。讀程序,分析結果。

1 #include<stdio.h>

2

3 voidsstrlen(char*sPtr);

4

5 voidsstrlen(char*sPtr)

6 {

7 intn;

8 for(n=0;*sPtr!='#';sPtr++)

9 {

10 n++;

11 }

12 *sPtr++=':';

13 *sPtr++='0'+n;

14 }

15

16 intmain()

17 {

18 chara[4][10]={"dinar##","dollar##","dong##","drachma##"};

19 char(*ptr)[10];

20 inti;

21

22 ptr=a;

23 for(i=0;i<4;i++,*ptr++)

24 {

25 sstrlen(*ptr);

26 printf("%s\n",ptr);

27 }

28 return0;

29 }

main函數(shù)中的指針ptr和子函數(shù)sstrlen中的指針sPtr與二維數(shù)組a的關系如圖6.42所示。

圖6.42例6-10指針指向示意圖

圖6.43~圖6.51所示分別為例6-10的調(diào)試步驟1~調(diào)試步驟9。

圖6.43的主函數(shù)中,sstrlen子函數(shù)調(diào)用前,實際參數(shù)*ptr指向a數(shù)組第一個字符串"dinar##"。

圖6.43例6-10調(diào)試步驟1說明:出現(xiàn)圖6.44中這個錯誤顯示的原因是,sPtr的作用域不在main函數(shù)中。

圖6.44例6-10調(diào)試步驟2

圖6.45中,轉到執(zhí)行子函數(shù)sstrlen,形式參數(shù)sPtr接收到了實際參數(shù)傳遞過來的地址值0x12ff58,通過這個地址,可以得到a數(shù)組的字符串數(shù)據(jù)。

注意:在子函數(shù)執(zhí)行時,主函數(shù)的局部量也是不可見的。

圖6.45例6-10調(diào)試步驟3圖6.46中,sPtr指針后移一位,其值由前面的0x12ff58變?yōu)?x12ff59,偏移量=0x12ff59-0x12ff58=1(byte),原因是sPtr的類型為char。

圖6.47中,for語句統(tǒng)計出字符串‘#’前的字符個數(shù),即n=5。循環(huán)結束時,sPtr指向“dinar##”中的第一個‘#’。

圖6.48中,for循環(huán)后,對sPtr指向單元的二條賦值操作結果已經(jīng)無法在Watch窗口中看到了,這時可以通過字符串"dinar##"的起始地址0x12ff58,在Memory窗口中查看字符串被修改的情形。圖6.46例6-10調(diào)試步驟4

圖6.47例6-10調(diào)試步驟5

圖6.48例6-10調(diào)試步驟6圖6.49中,“dinar##”被改成了“dinar:5”。注意,這里顯示的5是字符,而非數(shù)值,其ASCII碼值在Memory里顯示的是0x35。

圖6.50中,子函數(shù)sstrlen調(diào)用結束,返回主函數(shù),此時子函數(shù)中定義的變量在Watch窗口中不可見,原因是函數(shù)內(nèi)定義的局部量,其作用域只在本函數(shù)內(nèi)。i++變?yōu)?;*ptr++后,指向a數(shù)組的第二個字符串“dollar##”。

在圖6.51的Memory窗口中可以看到a數(shù)組中的字符串全部處理完后的結果。

圖6.49例6-10調(diào)試步驟7

圖6.50例6-10調(diào)試步驟8

圖6.51例6-10調(diào)試步驟9程序結果:

dinar:5

dollar:6

dong:4

drachma:7

實際參數(shù)指針的移動是否改變了形參指針的指向?為什么?

答:實際參數(shù)指針的移動并未改變形參指針的指向,原因是兩個指針變量有各自的存儲單元。讀者可自行跟蹤查看。6.5.2函數(shù)的返回值是指針

指針值也可以作為函數(shù)的返回值。這種情況下函數(shù)的返回值類型需要定義成指針變量類型。返回指針值的函數(shù)的一般定義格式為:

類型標識符﹡函數(shù)名(參數(shù)表)

{函數(shù)體}

例如:

float*Func(floatx,floaty);該函數(shù)的形式參數(shù)是兩個float型的變量,返回一個float型變量的地址。

注意:將指針值作為函數(shù)的返回值時,一定要保證該指針值是一個有效的指針,即該指針不是局部變量的指針。一個常犯的錯誤是試圖返回一個局部變量的地址。

1.函數(shù)返回的指針值是全局量地址

【例6-11】返回指針值的函數(shù)的例子1。讀程序,分析結果。

1 #include<stdio.h>

2 long*GetMax();

3 longscore[10]={1,2,3,4,5,6,7,8,9,10};/*score數(shù)組是全局量*/

4

5 intmain()

6 {

7 long*p;

8 p=GetMax();

9 printf("Maxvalueinarrayis%d",*p);

10 return0;

11 }

12

13 long*GetMax()

14 {

15 longtemp; /*temp記錄全局數(shù)組score里最大的元素*/

16 intpos; /*pos記錄最大的元素的數(shù)組下標號*/

17 inti;

18

19 temp=score[0]; /*取數(shù)組的第一個元素做比較基準*/

20 pos=0;

21 for(i=0;i<10;i++) /*在數(shù)組score中循環(huán)找最大值*/

22 if(score[i]>temp)

23 {

24 temp=score[i];

25 pos=i;

26 }

27 return&score[pos]; /*返回數(shù)組中值最大的元素的地址值*/

28 }

程序結果:

Maxvalueinarrayis10

2.函數(shù)返回的指針值是局部量地址

【例6-12】返回指針值的函數(shù)的例子2。讀程序,分析結果。

1 #include<stdio.h>

2 long*GetMax();

3

4 intmain()

5 {

6 long*p;

7 p=GetMax();

8 printf("Maxvalueinarrayis%d",*p);

9 return0;

10 }

11

12 long*GetMax()

13 {

14 longscore[10]={1,2,3,4,5,6,7,8,9,10};

15 longtemp; /*temp記錄全局數(shù)組score里最大的元素*/

16 intpos; /*pos記錄最大的元素的數(shù)組下標號*/

17 inti;

18

19 temp=score[0]; /*取數(shù)組的第一個元素做比較基準*/

20 pos=0;

21 for(i=0;i<10;i++) /*在數(shù)組score中循環(huán)找最大值*/

22 if(score[i]>temp)

23 {

24 temp=score[i];

25 pos=i;

26 }

27 return&score[pos]; /*返回數(shù)組中值最大的元素的地址值*/

28 }

程序返回一個局部變量的指針,運行中會出現(xiàn)什么樣的問題呢?

返回一個局部變量的地址。圖6.52~圖6.55所示分別為例6-12調(diào)試步驟1~調(diào)試步驟4。

圖6.52中,GetMax函數(shù)找到score數(shù)組中的最大元素值10,返回這個局部量元素score[pos]的地址0x12ff24。

圖6.52例6-12調(diào)試步驟1圖6.53中,一個score數(shù)組元素的長度=0x12ff04-0x12ff00=4(byte)(long型占的存儲空間大小)。score[9]的值為0xA,地址為0x12ff24。

圖6.54的main函數(shù)中,p指針接收了GetMax返回的指針值0x12ff24,指向單元的值是10,說明地址傳遞的過程是正確的。

圖6.55中,printf結果顯示:

Maxvalueinarrayis10

圖6.53例6-12調(diào)試步驟2

圖6.54例6-12調(diào)試步驟3

圖6.55例6-12調(diào)試步驟4注意:在printf執(zhí)行完后,p指針指向單元的內(nèi)容被改變。原因是這里的數(shù)組score是一個局部變量,函數(shù)調(diào)用結束后該數(shù)組所占用的內(nèi)存空間將被釋放,跟蹤觀察得知,子函數(shù)雖然返回了數(shù)組元素的指針,但該數(shù)組元素在引用一次后被釋放,這個指針也就成了一個無效的指針。此時,再引用指針單元的內(nèi)容,將導致程序的異常。

將指針值作為函數(shù)的返回值時,不要返回一個局部變量的地址。

3.函數(shù)返回的指針是空類型指針

下面先介紹兩個庫函數(shù):malloc函數(shù)和free函數(shù)。

1)內(nèi)存分配函數(shù)malloc()

函數(shù)格式:void*malloc(unsignedsize);

函數(shù)功能:從內(nèi)存中分配一大小為size字節(jié)的塊。

參數(shù)說明:size為無符號整型,用于指定需要分配的內(nèi)存空間的字節(jié)數(shù)。

返回值:新分配內(nèi)存的地址,如無足夠的內(nèi)存可分配,則返回NULL。說明:

(1)當size為0時,返回NULL。

(2)void*為無類型指針,可以指向任何類型的數(shù)據(jù)存儲單元,無類型指針需強制類型轉換后賦給其他類型的指針。

2)釋放內(nèi)存函數(shù)free()

函數(shù)格式:voidfree(void*block);

函數(shù)功能:將calloc()、malloc()及realloc()函數(shù)所分配的內(nèi)存空間釋放為自由空間。

參數(shù)說明:block為void類型的指針,指向要釋放的內(nèi)存空間。返回值:無。

例如:

voidfree(void*p);

從動態(tài)存儲區(qū)釋放p指向的內(nèi)存區(qū),p是調(diào)用malloc返回的值。free函數(shù)沒有返回值。

動態(tài)存儲分配

在C語言中有一種稱為“動態(tài)存儲分配”的內(nèi)存空間分配方式:程序在執(zhí)行期間需要存儲空間時,通過“申請”分配指定的內(nèi)存空間;當閑置不用時,可隨時將其釋放,由系統(tǒng)另做它用。相關的庫函數(shù)有malloc()、calloc()、free()、realloc()等,使用這些函數(shù)時,必須在程序開頭包含文件stdlib.h或malloc.h。

【例6-13】返回指針值的函數(shù)的例子3——malloc和free的配合使用方法。在程序運行之前,不能確定數(shù)組大小時,可以用此方法進行動態(tài)分配。

1#include<stdio.h>

2intmain()

3{

4 float*p;

5 p=malloc(sizeof(float));

6 *p=5.0;

7 printf("\n*p=%f",*p);

8 *p=*p+5.0;

9 printf("\n*p=%f",*p);

10 free(p);

11 return0;

12}

程序結果:

*p=5.000000

*p=10.000000說明:

(1)

sizeof(數(shù)據(jù)類型符)算出數(shù)據(jù)類型所占用的字節(jié)數(shù)。

(2)

malloc(sizeof(float))在系統(tǒng)的靜態(tài)存儲區(qū)里分配了4個字節(jié)的內(nèi)存空間。malloc函數(shù)返回一個指針值,該指針就是上述4個字節(jié)的首地址。

(3)語句free(p)釋放指針p所在的內(nèi)存塊。

(4)malloc函數(shù)和free函數(shù)應該搭配使用,忘記釋放應該釋放的內(nèi)存會導致程序內(nèi)存泄漏,影響程序的效率。

內(nèi)存泄漏

應用程序一般使用malloc、realloc等函數(shù)從堆中分配到一塊內(nèi)存,使用完后,程序必須負責相應的調(diào)用free釋放該內(nèi)存塊;否則,這塊內(nèi)存就不能被再次使用,我們就說這塊內(nèi)存泄漏了。

內(nèi)存泄漏指造成了內(nèi)存的浪費,這會降低計算機的性能。最終,在最糟糕的情況下,過多的可用內(nèi)存被泄漏掉導致全部或部分設備停止正常工作,或者應用

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
  • 4. 未經(jīng)權益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負責。
  • 6. 下載文件中如有侵權或不適當內(nèi)容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論