《C語言程序設(shè)計基礎(chǔ)》課件第8章_第1頁
《C語言程序設(shè)計基礎(chǔ)》課件第8章_第2頁
《C語言程序設(shè)計基礎(chǔ)》課件第8章_第3頁
《C語言程序設(shè)計基礎(chǔ)》課件第8章_第4頁
《C語言程序設(shè)計基礎(chǔ)》課件第8章_第5頁
已閱讀5頁,還剩199頁未讀, 繼續(xù)免費閱讀

下載本文檔

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

文檔簡介

第8章

指針——對存儲信息的引用機制8.1指針的概念8.2通過指針引用變量的值8.3通過指針引用一維數(shù)組8.4通過指針引用二維數(shù)組8.5通過指針引用字符串8.6通過指針調(diào)用函數(shù)8.7多重指針與指針數(shù)組8.8用于動態(tài)內(nèi)存分配的指針型函數(shù)實訓(xùn)任務(wù)十五

熟悉指針數(shù)據(jù)類型,掌握指針的正確使用實訓(xùn)任務(wù)十六

學(xué)習(xí)指針的應(yīng)用

8.1指

指針是什么?為什么通過指針可以訪問存儲器中的信息?

指針的一般意義大家都不會陌生,那就是建立一種指向,如儀器儀表的指針,路標(biāo)指針等。C語言指針也是建立一種指向,只是指向的是存儲器單元,按其所指訪問存儲器單元中的信息。要理解C語言中指針的內(nèi)涵,需了解對存儲器的訪問機制。計算機對存儲器是按地址進行訪問的。存儲器包含大量的信息單元,為能方便地訪問需要的信息單元,每一個單元都有一個編號,這個編號稱為地址。要訪問某一單元,只要給出該單元地址,就能準(zhǔn)確引用該單元信息。打個通俗的比方,一個樓房包含許多房間,為能方便地尋找某個房間,給每一個房間編一個號碼,知道了房號就能準(zhǔn)確地找到這個房間。由此可知,地址就是對存儲器單元的指向。即給出地址就能訪問存儲器單元,訪問存儲器單元必須有地址。C語言中的指針就是存儲器單元的地址,也可以反過來說,地址就是存儲器單元的指針。簡言之,指針就是地址,地址就是指針。因此,指針是對存儲器單元信息的一種引用機制。

地址屬于一個與存儲器硬件特性相關(guān)的概念。在高級語言中,編程人員不涉及計算機的硬件特性,對硬件資源的分配與處理由編譯系統(tǒng)來完成。在前面學(xué)習(xí)變量、數(shù)組、函數(shù)時,僅是按語言規(guī)定進行定義,按所定義的符號名稱進行引用,未涉及按地址引用的問題。事實上,程序在編譯時,系統(tǒng)給定義的數(shù)據(jù)對象或函數(shù)都要分配相應(yīng)的存儲單元,符號名稱也具有相應(yīng)的地址值,符號名實際上標(biāo)識了一個存儲單元的地址。比如,在一個程序中定義了3個整型變量i、j、k,程序編譯時,系統(tǒng)就會給3個變量分別分配4個字節(jié)的存儲單元,單元中存儲變量的值,i、j、k也分別具有相應(yīng)的地址值。假定3個變量的值分別為2、4、8,編譯時給i、j、k分配的地址值分別是1000、1004、1008,其存儲情況如圖8.1所示。

圖8.1數(shù)據(jù)引用示意圖對存儲器單元的訪問有兩種方式:一種是直接訪問,另一種是間接訪問。

直接訪問是直接引用地址所指向單元中的內(nèi)容。按變量名引用變量的值屬于直接訪問。

間接訪問是將欲訪問變量的地址存放在另一個變量中,先通過該變量取得欲訪問變量的地址,再按這個地址引用變量的值。打個通俗的比方,從一個抽屜中存取東西,人們也可采取兩種方式:一種是將一個抽屜A的鑰匙帶在身上,需要時直接用A抽屜鑰匙,打開抽屜,存取所需東西;另一種是將A抽屜鑰匙放在另一抽屜B中,鎖起來,需要打開A抽屜時,先要用B鑰匙打開B抽屜,取出A鑰匙后,再打開A抽屜,才能存取其中的東西。這就是一個間接訪問的過程。例如,我們將i變量所標(biāo)識的存儲單元的地址(1000)存放在i_pointer變量中,現(xiàn)要引用i變量的值,先要訪問i_pointer變量,從中取得i變量的地址,然后才能按此地址所指向的i變量取得所需要的值。訪問過程如圖8.1所示。

為了實現(xiàn)一個變量的間接訪問,需引入另外一種變量,稱之為指針變量。如果一個變量專門用來存放另一個變量的地址(即指針),則稱這個變量為指針變量。也就是說,指針變量的值是地址(即指針)。

從存儲器的訪問機制,我們引入了“指針”和“指針變量”,弄清楚這兩個概念的內(nèi)涵,對學(xué)習(xí)本章后續(xù)內(nèi)容是至關(guān)重要的。

8.2通過指針引用變量的值

8.2.1指針變量的定義與初始化

在程序中怎樣使用指針變量?

指針變量是有別于普通變量的,也必須先定義后使用。

指針變量定義的一般形式為

基類型符*指針變量名;

其中,基類型符是基本數(shù)據(jù)類型關(guān)鍵字,如int、float、char、double等,用來指定所定義指針變量可以指向的變量類型。簡單地說,就是指針變量所指的數(shù)據(jù)對象的類型?!?”規(guī)定了所定義的變量是指針型變量。反過來說,沒有此星號,那就成為普通變量的定義了。

指針變量名是所定義的指針變量的標(biāo)識符,其命名規(guī)則同普通變量定義中的變量名一樣。

注:*和指針變量名之間可以有空格,也可以沒有空格,兩者均可。

下面是指針變量定義的幾個例子:

int*pi1;

語句定義了一個指針變量pi1,它只可以指向整型變量。

char*pc1,*pc2;

語句定義了兩個指針變量pc1和pc2,它們只可以指向字符型變量。

float*pf1,*pf2,*pf3;

語句定義了三個指針變量pf1、pf2和pf3,它們只可以指向?qū)嵭妥兞俊?/p>

從以上舉例可以看出,一個定義語句可以定義一個指針變量,也可以定義多個指針變量,但只可以是同一類型的變量。定義了指針變量,僅聲明了所定義的變量是指針類型和可指向的變量類型,并沒有確定的指向。也就是說,沒有具體指向哪一個變量或存儲器單元。打個通俗的比方,我們加工出一些指針,在沒有安裝到某一個儀器儀表前,它們沒有具體的指向。要使所定義的指針變量指向某一變量,必須給指針變量初始化,將所要指向變量的地址賦給指針變量。指針變量中只能存放地址(即指針)。若要給指針變量賦數(shù)值則毫無意義。

指針變量初始化可有兩種方式:一是先定義指針變量,然后進行初始化;二是定義指針變量的同時初始化。

8.2.2指針變量的引用

通過指針變量怎樣間接引用變量的值?

在指針變量的引用中要使用兩個運算符?&?和?*?!?”是取地址運算符,例如&a是取變量a的地址;“*”是間接引用運算符,其后跟指針變量,是取該指針變量所指向的數(shù)據(jù),例如*p表示指針變量p所指向的數(shù)據(jù)。

指針變量有兩種引用:一是通過指針變量引用所指向變量的值,也就是說,通過指針變量實現(xiàn)變量值的間接引用;二是指針變量是變量,其值也可以引用,只不過引用的是地址(指針)值。下面通過兩個例子來說明兩種引用和指針變量的應(yīng)用。

例8.1

分析下面程序的運行結(jié)果:

運行結(jié)果:

分析:程序中第1個printf函數(shù)調(diào)用中,輸出變量a、b的值,屬于變量值的直接引用,對應(yīng)執(zhí)行結(jié)果的第1行;第2個printf函數(shù)調(diào)用中,輸出指針變量pointer_1、pointer_2所指向變量的值,pointer_1指向變量a,pointer_2指向變量b,屬于變量值的間接引用,對應(yīng)執(zhí)行結(jié)果的第2行;第3個輸出函數(shù)調(diào)用中,以十六進制格式輸出指針變量pointer_1、pointer_2的值,即變量a、b的地址,也屬于變量值的直接引用,其地址值是系統(tǒng)分配的。

例8.2利用指針方法實現(xiàn):輸入兩個整數(shù),按先大后小的順序輸出。

編程思路:輸入兩個整數(shù),分別賦給兩個變量,比較兩個變量的值,利用指針變量可以不交換兩個變量的值,而只交換兩個指針變量的值(即改變指向)。

運行結(jié)果:

分析:輸入值2、8,即a<b,指針變量p1和p2的值進行交換。交換前的情況如圖8.2(a)所示,交換后的情況如圖8.2(b)所示。

從圖可以看出,變量a、b的值并未交換,交換了指針變量p1、p2的值,實際上改變了指向,p1原指向a,交換值后指向b,p2原指向b,交換值后則指向a。這樣輸出*p1、*p2時,就輸出變量b和變量a的值。

if語句中采用了兩個指針變量值的交換{p=p1;p1=p2;p2=p;},可改為{p1=&b;p2=&a;},效果是一樣的。

圖8.2指針變化示意圖8.2.3指針變量作函數(shù)參數(shù)

指針變量作函數(shù)參數(shù)傳遞什么值?函數(shù)之間能建立什么聯(lián)系?

在函數(shù)一章的學(xué)習(xí)中,已經(jīng)知道,變量作函數(shù)的參數(shù),把實參變量的值傳遞給形參變量。指針變量也可作函數(shù)的參數(shù),由于指針變量的值是地址(即指針),因此實參與形參之間傳遞的是指針,在被調(diào)函數(shù)中按指針的指向可得到變量的值。下面通過一個例子來說明指針變量作函數(shù)的參數(shù),指針傳遞的過程。

例8.3例8.2(輸入兩個整數(shù),按先大后小的順序輸出)中,用指針變量作參數(shù)的函數(shù)來實現(xiàn),并分析參數(shù)傳遞及其處理過程。

編程思路:用一個子函數(shù)實現(xiàn)交換兩個變量的值,主函數(shù)中判斷兩數(shù)大小,需要交換時調(diào)用子函數(shù),指針變量作函數(shù)參數(shù),傳遞指針。

分析:程序運行先執(zhí)行主函數(shù),輸入2、8分別賦給變量a、b,兩個指針賦值語句使兩個指針變量分別指向變量a、b,如圖8.3(a)所示。執(zhí)行if語句,判a<b,調(diào)用swap函數(shù),兩個指針變量pointer_1、pointer_2的值(地址)傳遞給形參指針變量,使p1的值為&a,p2的值為&b,此時,對應(yīng)的實參和形參指向同一個變量,如圖8.3(b)所示。執(zhí)行swap函數(shù),使p1、p2指向的兩個變量的值交換,如圖8.3(c)所示。函數(shù)調(diào)用結(jié)束后,形參p1、p2被釋放,即解除對變量a、b的指向,但在swap函數(shù)中的操作結(jié)果卻保留在變量a、b中,調(diào)用返回后輸出的a、b是交換后的值,如圖8.3(d)所示。

圖8.3例8.3函數(shù)調(diào)用參數(shù)傳遞示意圖通過本例應(yīng)該深刻理解,指針變量作函數(shù)參數(shù),實參傳遞地址(指針)給形參,使對應(yīng)的實參指針變量和形參指針變量指向同一個變量(存儲單元),在被調(diào)函數(shù)中通過形參指針變量對所指向的變量(存儲單元)的操作結(jié)果,在主函數(shù)中引用。相當(dāng)于通過被操作的變量(存儲單元)傳送了值。這樣就給函數(shù)間增加了一種數(shù)據(jù)傳遞的渠道。需特別注意:通過實參和形參所指向變量值的傳遞是雙向的(在主調(diào)函數(shù)和被調(diào)函數(shù)中都可以引用變量的值),但實參指針變量向形參指針變量傳遞地址值仍是單向傳遞。也就是說,形參值不可能傳遞給實參。因為函數(shù)調(diào)用結(jié)束后,形參就不復(fù)存在了。變量作函數(shù)參數(shù),函數(shù)調(diào)用只能得到一個返回值(即函數(shù)值),使用指針變量作函數(shù)參數(shù),可以得到多個操作變量的值。如果想通過函數(shù)調(diào)用得到n個操作數(shù)據(jù),只需在函數(shù)中設(shè)置n個指針變量參數(shù),函數(shù)調(diào)用時,將n個變量的指針傳遞給形參,在被調(diào)函數(shù)中通過形參指針變量對所指向變量進行操作,在主調(diào)函數(shù)中就可得到操作結(jié)果。下面通過一個例子來說明。

例8.4輸入3個整數(shù),按由大到小的順序輸出。

編程思路:對3個數(shù)排序,必須兩兩比較,根據(jù)大小關(guān)系進行交換。這樣,需要3次比較,可能需要3次兩數(shù)的交換操作。為避免交換操作的重復(fù)書寫,可設(shè)計兩個函數(shù),一個函數(shù)實現(xiàn)兩數(shù)的交換功能,一個函數(shù)進行兩數(shù)比較,根據(jù)判定結(jié)果調(diào)用交換函數(shù)。

運行結(jié)果:

分析:在主函數(shù)中使指針變量pi1、pi2、pi3分別指向變量a、b、c,exchange(pi1,pi2,pi3)函數(shù)調(diào)用,將變量a、b、c的地址值分別傳遞給形參指針變量pj1、pj2、pj3。exchange(int*pj1,int*pj2,int*pj3)中,通過形參指針變量的間接引用來比較a與b、a與c、b與c,如果前者小于后者,調(diào)用swap函數(shù),將對應(yīng)變量的地址傳遞給形參指針變量pk1、pk2。在swap(int*pk1,int*pk2)中利用形參指針變量的間接引用來實現(xiàn)所指向變量值的交換。

8.3通過指針引用一維數(shù)組

8.3.1一維數(shù)組的存儲結(jié)構(gòu)與指針

如何建立數(shù)組元素與其指針的對應(yīng)關(guān)系?

一維數(shù)組的存儲是:系統(tǒng)根據(jù)所定義的類型,按元素順序分配一個連續(xù)的存儲空間。類型規(guī)定了數(shù)組元素的存儲長度,即一個元素所占的存儲字節(jié)數(shù)。char型數(shù)組一個元素占一個字節(jié),int型數(shù)組一個元素占2個或4個字節(jié),float型數(shù)組一個元素占4個字節(jié),double型數(shù)組一個元素占8個字節(jié)。例如,有語句“inta[5]={1,3,5,7,9};”,假定編譯系統(tǒng)分配的存儲區(qū)首地址是2000,則存儲結(jié)構(gòu)如圖8.4所示。圖8.4一維數(shù)組存儲結(jié)構(gòu)示意圖在C語言中,可以不使用存儲地址來引用數(shù)組元素,而以數(shù)組名代表數(shù)組存儲首地址(指針),以數(shù)組元素的引用符號取地址運算符代表元素地址(指針)。如a表示所定義數(shù)組首地址,&a[0]表示第0個元素地址,&a[1]表示第1個元素地址,…

知道數(shù)組及數(shù)組元素指針后,就可以定義指針變量來實現(xiàn)數(shù)組元素的間接引用。例如:

“p=a;”可以置換成“p=&a[0];”,這兩個語句是等效的,都使指針變量p指向所定義數(shù)組的首元素。注意,數(shù)組名就表示數(shù)組首地址,一個數(shù)組元素相當(dāng)于一個變量,必須用“&”才得到元素地址。

定義了指向數(shù)組的指針變量后,就可以利用指針變量及其相關(guān)運算,以方便靈活的方法引用數(shù)組元素。

8.3.2一維數(shù)組指針調(diào)整與指針變量的運算

1.使指針指向下一個元素:p+1,p++,++p

這3種運算都可以使指針從當(dāng)前元素指向下一個元素。注意指針變量加1不是指針變量值加1的結(jié)果,而是下一個元素的地址值。如圖8.4所定義的數(shù)組,若“p=a;”或“p=&a[0];”,p的值是2000,p+1后使指針指向元素a[1],p+1的值是2004,而不是2001。若“p=&a[2];”,p的值是2008,p+1后使指針指向元素a[3],p+1的值是2012,而不是2009。由此可以理解為:指向數(shù)組指針變量的運算,是以元素個數(shù)為單位的邏輯指針運算,而不是純地址值的運算。

p++和++p都可以使指針指向下一個元素,但引用元素時就有區(qū)別了。若“p=&a[2];”,則*(p++)是先引用元素a[2],再使p指針指向a[3];*(++p)先使指針指向a[3],引用的元素是a[3]。

2.使指針指向前一個元素:p-1,p--,--p

這3種運算都可以使指針從當(dāng)前元素指向前一個元素。它們對指針的調(diào)整作用類似于加1運算。

p--、--p也是以元素個數(shù)為單位的邏輯指針運算,而不是純地址值的運算。

3.使指針指向第i個元素:a+i(a是所定義的數(shù)組名),p+i

這兩種運算都可以使指針指向i個元素,但有本質(zhì)的不同。a代表數(shù)組的首地址,也是首元素的地址,a屬于常量,a+i是數(shù)組中第i個元素的指針。p是指針變量,可以是數(shù)組中任一元素的指針,p+i是從當(dāng)前所指元素后移到第i個元素的指針。若“p=a;”或“p=&a[0];”,則a+i和p+i是一樣的,*(a+i)和*(p+i)都是引用數(shù)組a中的第i個元素。若“p=&a[2];”,則*(p+i)是引用a[2]后的第i個元素,即引用的元素是a[2+i]。

4.使指針指向前第i個元素:p-i

p-i是指針從p當(dāng)前所指的元素指向前第i個元素。例如p=&a[4],則*(p-2)引用的是a[2]。注意,a-i是沒有意義的。

5.p2-p1運算的意義

如果兩個指針變量p1、p2都指向同一數(shù)組,p2>p1,則p2-p1是兩個指針之間的元素個數(shù)。例如:8.3.3通過指針引用數(shù)組元素

例8.5一維數(shù)組的輸入與輸出。

下面用4種數(shù)組元素的引用方法編寫程序,請認(rèn)真分析并體會數(shù)組元素的引用方法與編程技巧。

(1)下標(biāo)法。

運行結(jié)果同下標(biāo)法。程序中使用了數(shù)組元素指針a+i和指針法引用數(shù)組元素:*(a+i)。

思考:將*(p++)改為*(p+i),影響程序的正確性嗎?兩個“p=a;”語句的作用是什么?如果去掉第2個“p=a;”語句,程序的執(zhí)行結(jié)果會正確嗎?為什么?

(4)指針變量法(使用p++和*(--p))。

運行結(jié)果:

顯然,程序運行結(jié)果以逆序輸出。這是因為,循環(huán)輸入后,p指向最后一個元素之后,輸出中使用*(--p)先使p減1,指向前一個元素,從而產(chǎn)生了從后向前的輸出順序。

例8.6將一個數(shù)組中序號為0,3,6,9,…的元素輸出。

8.3.4一維數(shù)組指針作函數(shù)參數(shù)

一維數(shù)組指針作函數(shù)參數(shù)傳遞什么值?函數(shù)間建立什么聯(lián)系?

用一維數(shù)組指針作函數(shù)參數(shù),實參向形參傳遞數(shù)組首地址,使形參指針和實參指針指向同一個數(shù)組存儲區(qū)。在調(diào)用函數(shù)期間,如果改變了形參數(shù)組的值,也就改變了實參數(shù)組的值,在主調(diào)函數(shù)中就可以引用改變后的值。如有下面定義的函數(shù)關(guān)系,則存儲區(qū)共享情況如圖8.5所示。

圖8.5函數(shù)調(diào)用共享數(shù)組存儲區(qū)示意圖數(shù)組指針可用數(shù)組名和指向數(shù)組的指針變量兩種形式傳遞。實參可以是數(shù)組名或指針變量,形參是接收實參傳遞的數(shù)組首元素地址的,應(yīng)該是指針變量。但因C語言程序編譯時,是把形參數(shù)組名作為指針處理的,這樣,形參也可以是數(shù)組名。數(shù)組指針作函數(shù)參數(shù),實參與形參可有表8.1列出的組合關(guān)系。表8.1函數(shù)實參與形參的組合關(guān)系下面通過例子來說明一維數(shù)組指針作函數(shù)參數(shù)的程序設(shè)計方法。

例8.7

將一個數(shù)組中n個元素按相反的順序存放。

編程思路:定義n個元素的數(shù)組a,將a[0]與a[n-1]對換,再將a[1]與a[n-2]對換,…,直到將a[int(n-1)/2-1]與a[int(n-1)/2]對換。顯然,兩元素對調(diào)用循環(huán)結(jié)構(gòu)程序很容實現(xiàn)。設(shè)“兩個位置指示”變量i和j。i指向首元素,其初值為0。j指向尾元素,其初值為n-1。使當(dāng)前所指示的兩個元素交換,然后調(diào)整指針,使i的值加1,j的值減1,如此循環(huán),直到i=int(n-1)/2,即直到兩指針指向中間兩相鄰的元素。其操作關(guān)系如圖8.6所示。

圖8.6例8.7操作關(guān)系圖運行結(jié)果:

分析:在定義函數(shù)inv時,形參是數(shù)組名x,形參數(shù)組沒有定義大小,用n來接收數(shù)組元素個數(shù)。因為編譯系統(tǒng)把形參數(shù)組名作為一個指針變量處理,并不開辟數(shù)組空間。在主函數(shù)中,函數(shù)調(diào)用語句“inv(a,10);”實參是數(shù)組名和數(shù)組元素個數(shù),把數(shù)組首元素地址傳遞給形參數(shù)組名x,數(shù)組元素的個數(shù)傳遞給形參n,使形參x指向?qū)崊?shù)組,在inv中操作數(shù)組,實際上就是實參數(shù)組。用函數(shù)名作為形參,可以用“下標(biāo)法”引用形參數(shù)組元素,有些人習(xí)慣這種直觀的引用方法。

運行結(jié)果同(1)。在定義函數(shù)inv時,指針變量作形參。引用形參數(shù)組時,要用指針變量法。在inv函數(shù)中,定義了3個指針變量,i、j是指向當(dāng)前交換的前元素和后元素的指針變量,p是指向終止交換操作元素的指針變量。函數(shù)調(diào)用過程及參數(shù)傳遞同(1)。

(3)用一個函數(shù)來實現(xiàn)交換,實參用指針變量,形參用數(shù)組名。

函數(shù)運行結(jié)果同(1)。與(1)對比,只是在主函數(shù)中定義了指針變量,使其指向數(shù)組首地址,作函數(shù)的實參。

(4)用一個函數(shù)來實現(xiàn)交換,實參用指針變量,形參用指針變量。運行結(jié)果同(1)。與(1)、(2)、(3)比較,很容易找到異同,請讀者自行分析。

上面通過一個問題,用函數(shù)指針作參數(shù)的4種組合關(guān)系來實現(xiàn),通過對比,可反映各自特點,并揭示程序設(shè)計的方法與技巧。

例8.8

用指針法對n個整數(shù)按由大到小的順序排序。

編程思路:定義一個一維數(shù)組,存放n個整數(shù)。定義一個函數(shù),實現(xiàn)對n個整數(shù)排序。主函數(shù)中的函數(shù)調(diào)用傳遞被排序數(shù)組的首地址和數(shù)據(jù)個數(shù)。函數(shù)參數(shù)的組合關(guān)系可采用4種組合關(guān)系中的任一種。下面通過實參是指針變量、形參是數(shù)組名來實現(xiàn)。運行結(jié)果:

分析:sort函數(shù)中用數(shù)組名作形參,用“下標(biāo)法”引用形參數(shù)組元素。如果形參改為指針變量,函數(shù)中也可以用與“下標(biāo)法”類似的形式引用形參數(shù)組元素,可采用x+i和x+j作指針來引用數(shù)組元素。

8.4通過指針引用二維數(shù)組

8.4.1二維數(shù)組的存儲結(jié)構(gòu)與指針

如何建立二維數(shù)組元素與其指針的對應(yīng)關(guān)系?

二維數(shù)組的邏輯結(jié)構(gòu)是由行、列組成的平面結(jié)構(gòu)。因存儲器對數(shù)據(jù)的存儲結(jié)構(gòu)是線性的,所以要將二維平面結(jié)構(gòu)數(shù)據(jù)變換成一維線性結(jié)構(gòu)來存儲。二維數(shù)組是按行依次線性展開存儲的。設(shè)有一個3行4列的二維整型數(shù)組,其定義為

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

其邏輯結(jié)構(gòu)和存儲結(jié)構(gòu)的對應(yīng)關(guān)系如圖8.7所示。從圖中可以看出,按元素順序存第0行元素、第1行元素、第2行元素。圖8.7二維數(shù)組存儲結(jié)構(gòu)示意圖設(shè)定義一個n行m列的數(shù)組,則a[i][j]相對應(yīng)數(shù)組首元素的位置計算公式為

i?×?m?+?j

若定義了指針變量p,“p=&a[0][0];”,則元素a[i][j]的指針為

p?+?(i?×?m+j)

通過指針對元素a[i][j]的引用形式為

*(p?+?(i?×?m?+?j))同一維數(shù)組指針一樣,上述表示的指針仍是以元素為單位的邏輯指針,不代表元素存儲的物理地址。如前面定義數(shù)組a的首地址為2000,a[2][1]的指針是&a[0][0]+(2×4+1),表示按存儲順序a數(shù)組的第9個元素,該元素的物理存儲地址應(yīng)為2000?+?9?×?4?=?2032。再一次強調(diào):C語言程序中,指向數(shù)組元素的指針是邏輯指針,并不是物理存儲地址。

根據(jù)二維數(shù)組的線性存儲結(jié)構(gòu),二維數(shù)組可以看成由一維行數(shù)組組成的一維數(shù)組,如圖8.8所示。

圖8.8二維數(shù)組一維行數(shù)組組成示意圖可以把二維數(shù)組看成由兩層一維數(shù)組組成。第一層是每一行的4個元素組成一個一維數(shù)組,數(shù)組名分別為a[0]、a[1]、a[2]。第2層是以a[0]、a[1]、a[2]為元素的一維數(shù)組。第2層僅僅是一個邏輯上的一維數(shù)組。因為二維數(shù)組的存儲結(jié)構(gòu)仍按元素存儲的線性結(jié)構(gòu),系統(tǒng)只給二維數(shù)組的元素分配存儲單元,a[0]、a[1]、a[2]雖是第2層一維數(shù)組的元素,但只表示每一行首元素的地址,并不占據(jù)存儲單元。這樣劃分的目的是把二維數(shù)組轉(zhuǎn)化為一維數(shù)組范圍內(nèi)的問題,可以按一維數(shù)組元素的引用方法來分析二維數(shù)組元素的引用。從一維數(shù)組元素的指針來看,a表示首元素地址,應(yīng)該有a=&a[0],但因a[0]只是邏輯層一維數(shù)組的元素,不占有存儲單元,談不上有地址,而a[0]表示第0行一維數(shù)組首元素的地址,即a[0]=&a[0][0]。又從a是二維數(shù)組的數(shù)組名,代表二維數(shù)組首元素地址,即有a=&a[0][0]。由此可推出a=a[0]=&a[0][0]。這正是問題理解的關(guān)鍵。我們可以從邏輯引用的角度來分析,*(a+0)=a[0],*(a[0]+0)=a[0][0]。為幫助理解,又引用本章前所打比方:從B抽屜取東西,將B鑰匙放在A抽屜,隨身帶A抽屜鑰匙,用A鑰匙打開A抽屜,取出B鑰匙,再用B鑰匙打開B抽屜?,F(xiàn)在改為B抽屜鑰匙放在桌面上,只指示從桌面上拿到B鑰匙就可打開B抽屜,即不需要用A鑰匙打開A抽屜來得到B鑰匙,如圖8.9所示。也就是說,從第2層數(shù)組直接得到第1層數(shù)組元素的地址,再按地址引用第1層的數(shù)組元素。

圖8.9二維數(shù)組引用示意圖對于任意元素,*(a+i)==a[i],*(a[i]+j)==a[i][j]。即由*(a+i)引用第2層一維數(shù)組元素a[i],得到第1層行一維數(shù)組首元素地址,再由*(a[i]+j)引用第1層行一維數(shù)組元素a[i][j]。例如,*(a+1)==a[1],*(a[1]+2)==a[1][2]。

從以上分析可知,a、a+i、a[i]、*(a+i)、*(a+i)+j、a[i]+j都是地址(指針),而*(a[i]+j),*(*(a+i)+j)是對二維數(shù)組元素a[i][j]的引用。

由于把二維數(shù)組看成兩層一維數(shù)組的特殊性,定義指針變量也有別于直接指向二維數(shù)組元素的指針變量定義。其定義的一般形式為

基類型符(*指針變量名)[m];其中,m表示行一維數(shù)組元素個數(shù),即二維數(shù)組的列數(shù);基類型符是二維數(shù)組元素的類型。這樣定義,表示所定義的指針變量指向包含m個基類型元素的一維數(shù)組,即指向邏輯層上的行一維數(shù)組。注意,指針變量兩側(cè)的括號不能缺省。

例如,int(*p)[4],定義了指向包含4個整型元素的一維數(shù)組的指針變量p。若“p=&a;”,則*(p+i)==a[i],*(*(p+i)+j)==a[i][j]。

在*(p+i)的指針調(diào)整中,是以第2層數(shù)組元素為單位的,而第2層數(shù)組中的一個元素表示二維數(shù)組的一行,所以(p+i)就使指針指向第i行。例如,(p+1)使指針指向第1行,(p+2)使指針指向第2行。在*(p+i)+j的指針調(diào)整中,由于經(jīng)過*(p+i)運算,得到a[i],即&a[i][0],它已經(jīng)轉(zhuǎn)化為指向列元素的指針了,因此加j是以元素為單位的,即加j就使指針指向第i行的第j個元素。例如,*(p+1)+1使指針指向第1行的第1列元素,*(p+1)+2使指針指向第1行的第2列元素。

8.4.2通過指針引用二維數(shù)組元素

1.通過指向元素的指針變量引用二維數(shù)組元素

設(shè)定義了n?×?m二維數(shù)組a,并定義了指向數(shù)組元素的指針變量p,則對數(shù)組元素的引用形式為

*(p?+?(i?×?(m-1)?+?j))

(i?×?(m-1)?+?j)是以元素為單位的相對位移量。若對p賦首元素指針,則引用a[i][j]元素;若p非首元素地址,則引用p當(dāng)前所指元素后的第i?×?(m-1)?+?j個元素。這種引用方式就是把二維數(shù)組的元素看作一個普通變量,通過定義指向變量的指針變量來間接引用數(shù)組元素。

2.通過指向一維數(shù)組的指針變量引用二維數(shù)組元素

設(shè)定義了二維數(shù)組a,并定義了指向一維數(shù)組的指針變量p,則對數(shù)組元素的應(yīng)用形式為

*(*(p?+?i)?+?j)

若對p賦首元素指針,則引用a[i][j]元素;若p非首元素地址,則引用p當(dāng)前所指元素后的第i?×?m?+?j個元素。這種引用方式是把二維數(shù)組看作兩層一維數(shù)組來引用的。先定義指向行一維數(shù)組的指針變量p,*(p+i)取得第i行地址,*(p+i)+j就是第i行第j列元素的地址。

例8.9

設(shè)有一個3?×?4二維數(shù)組,要求用指向元素的指針變量以平面矩陣的形式輸出二維數(shù)組各元素的值。

編程思路:按二維數(shù)組的線性存儲結(jié)構(gòu),指向變量的指針變量可指向任一元素,可用單重循環(huán)結(jié)構(gòu)程序來實現(xiàn);也可結(jié)合邏輯結(jié)構(gòu)的行、列下標(biāo),對指針變量進行運算調(diào)整來引用任一元素,可用雙重循環(huán)程序結(jié)構(gòu)來實現(xiàn)。

(1)按存儲結(jié)構(gòu)引用元素。

運行結(jié)果:

分析:a數(shù)組包含12個元素,循環(huán)變量i從0到11變化,(p+i)就依次指向每一個元素。

(2)結(jié)合邏輯結(jié)構(gòu)下標(biāo),對指針調(diào)整,用雙重循環(huán)實現(xiàn)程序。分析:運行結(jié)果同(1)。外循環(huán)控制行標(biāo),即控制行的順序;內(nèi)循環(huán)控制列標(biāo),即控制列的順序。(p+i+j)是指向a[i][j]的指針,依次指向二維數(shù)組的每一元素。外循環(huán)每循環(huán)一次,p的值為&a[0][0]+i×3。第1次外循環(huán)時,p的值為&a[0][0];第2次外循環(huán)時,p的值為&a[0][0]+3;第3次外循環(huán)時,p的值為&a[0][0]+6。分析清楚p值的變化,是理解使用指針變量引用數(shù)組元素的關(guān)鍵。

例8.10

輸出二維數(shù)組任一行任一列元素的值。

編程思路:由鍵盤輸入數(shù)組元素的行、列號,根據(jù)行、列號,使指針指向該元素輸出??墒褂弥赶蛐幸痪S數(shù)組的指針變量。

運行結(jié)果:

分析:定義了指向由4個整型元素組成的一維數(shù)組指針變量p,輸入2、3,*(p+i)+j=a[2]+3,即第2行的第3個元素。如果將第7行“p=a;”置換為“p=&a[0];”,或置換為“p=a+0;”,程序能正確編譯執(zhí)行。如果將第7行“p=a;”置換為“p=a[0];”,或置換為“p=&a[0][0];”,或置換為“p=*(a+0);”,程序編譯時都將出現(xiàn)如下出錯信息。

前面已介紹,a、a+0、a[0]、&a[0][0]、*(a+0)都是數(shù)組首元素的地址。那么,上述情況為什么會不一樣呢?問題的關(guān)鍵是:定義了“int(*p)[4];”,系統(tǒng)是按兩層一維數(shù)組進行引用的,p是指向第2層由a[0]、a[1]、a[2]組成的一維數(shù)組的指針,只能指向這3個元素,而不能指向第1層每一行的數(shù)組元素。下面對幾種情況進行分析。

(1)數(shù)組名a既表示二維數(shù)組首元素地址,又表示第2層數(shù)組首元素地址,“p=a;”使p指向第2層數(shù)組首元素。

(2)?a+0表示第2層一維數(shù)組首元素指針,“p=a+0;”同樣使p指向第2層數(shù)組首元素。

(3)?a[0]、a[1]、a[2]表示3個行一維數(shù)組的數(shù)組名,組成邏輯層的一維數(shù)組,雖然不占據(jù)存儲單元,不具有物理地址,但p=&a[0]屬于一種邏輯操作,其意義使p指向邏輯層的一維數(shù)組的首元素。

(4)?a[0]雖表示第0行首元素的地址,但它畢竟屬于第2層一維數(shù)組的元素,將數(shù)組元素直接賦指針變量是不符合規(guī)定的,所以出錯。

(5)語句“int(*p)[4];”定義了p是指向行一維數(shù)組的指針,&a[0][0]表示二維數(shù)組首元素地址,“p=&a[0][0];”使p指向了二維數(shù)組元素,所以出現(xiàn)指向類型錯誤。

8.4.3二維數(shù)組指針作函數(shù)參數(shù)

例8.11一個班有n個學(xué)生,開設(shè)m門課程,計算總平均分?jǐn)?shù),并輸出任一學(xué)生各門課成績。

編程思路:

(1)為了突出程序設(shè)計方法和程序結(jié)構(gòu),而不被眾多的數(shù)據(jù)干擾,先設(shè)有3個學(xué)生、4門課程。在此基礎(chǔ)上,只要添加數(shù)據(jù),設(shè)置相應(yīng)變量的初值,就能實現(xiàn)更多學(xué)生、更多課程的處理。

(2)班級成績表顯然屬于二維數(shù)據(jù)表格,按功能設(shè)計處理函數(shù),按求總平均分?jǐn)?shù)設(shè)計一個處理函數(shù),按輸出一個學(xué)生各門課成績設(shè)計一個處理函數(shù),數(shù)組指針作函數(shù)參數(shù),減少數(shù)據(jù)傳遞數(shù)量,提高程序效率。

運行結(jié)果:

分析:

(1)求平均分?jǐn)?shù)函數(shù)average定義中,第一個形參p定義為指向?qū)嵭蛿?shù)組元素的指針,第2個形參n是二維數(shù)組元素的個數(shù),即全班學(xué)生所有課程的成績數(shù)目。函數(shù)調(diào)用時,第1個實參*score,是第2層邏輯一維數(shù)組首元素的引用,即score[0],也就是&score[0][0],即score[0][0]的地址,使形參p指向成績數(shù)組首元素。

(2)查找并輸出任一個學(xué)生成績函數(shù)search定義中,第1個參數(shù)定義p為指向第2層行一維數(shù)組的指針,第2個參數(shù)n表示學(xué)號。函數(shù)調(diào)用時,第1實參是score,也代表第2層行一維數(shù)組首元素score[0]的指針,使形參p指向第0個學(xué)生的第0門課程成績。*(*(p+n)+i)表示第n個學(xué)生的第i門課程分?jǐn)?shù),即元素score[n][i]的值。

特別強調(diào):實參和形參如果是指針類型,應(yīng)當(dāng)注意它們類型的一致性。不能把int*型的指針(即元素的地址)傳給int(*)[]型(指向一維數(shù)組)的指針變量,反之亦然。例如,函數(shù)調(diào)用score(score,2)置換成score(*score,2)就產(chǎn)生錯誤。因為*score==score[0]==&score[0][0]是二維數(shù)組首元素地址,而形參是int(*)[]型(指向一維數(shù)組)的指針變量。

例8.12在例8.11的基礎(chǔ)上,查找有一門以上課程不及格的學(xué)生,并輸出這些學(xué)生全部課程的成績。運行結(jié)果:

分析:search函數(shù)定義中,第一個形參p定義為指向第2層一維數(shù)組的指針,第2個形參n是班級學(xué)生數(shù)。在函數(shù)中定義了一個標(biāo)志變量flag,在內(nèi)循環(huán)外賦初值0,在內(nèi)循環(huán)中若查出當(dāng)前查找的學(xué)生有不及格課程,則賦值1。結(jié)束內(nèi)循環(huán)后,檢查標(biāo)志,如果flag為1,則通過一個循環(huán)輸出該學(xué)生的號碼和各門課程成績。函數(shù)調(diào)用時,傳遞實參score,即score[0]的地址,使形參p指向score數(shù)組的第0行。

8.5通過指針引用字符串

8.5.1字符串的存儲結(jié)構(gòu)與指針

如何建立字符串中字符與其指針的對應(yīng)關(guān)系?

在字符數(shù)組一節(jié)中已經(jīng)介紹過,字符串是按字符數(shù)組進行存儲的,只是在最后一個字符后加存一個字符串結(jié)束符“\0”。例如,charstring[]="IloveChina!",其存儲結(jié)構(gòu)如圖8.10所示。根據(jù)數(shù)組指針的概念,字符串指針就是其存儲的首地址,字符數(shù)組名也表示字符串的首地址。

圖8.10字符串存儲結(jié)構(gòu)與指針8.5.2通過指針引用字符串

1.通過數(shù)組名(指針)引用

在字符數(shù)組一節(jié)中已經(jīng)介紹,通過字符數(shù)組名可以引用字符串,使用“%s”格式描述符,可以整體輸入或輸出,只不過沒有引入指針的概念。字符數(shù)組名就表示字符串指針,通過指針可以對字符串及字符串中的字符進行引用。下面通過一個例子說明引用方法。

例8.13定義一個字符數(shù)組,存放字符串“Ilovechina!”,輸出該字符串和第8個元素。

分析:在“printf("%s\n",string);”中使用%s格式描述符和數(shù)組名(指針)對字符串整體輸出,這里與數(shù)值型數(shù)組不同。在C語言中,“%s”格式描述符與指針配合,能實現(xiàn)字符串的整體輸出或輸入。先使指針指向串的首字符輸出,指針自動加1,指向下一個字符,依次輸出,直到字符串結(jié)束符“\0”。在“printf("No.8%c\n",*(string+7);”中的“*(string+7)”是用指針引用字符串中第7個元素string[7],因數(shù)組元素編號從0開始,實際上就是串中第8個字符。

2.通過字符串指針變量引用

指向字符串的指針變量,也就是指向字符數(shù)組的指針變量。指向字符數(shù)組的指針變量也必須先定義后使用,而且指針變量只有賦初值后才能建立指向。

字符型指針變量定義的一般形式為

char*指針變量名;

其中,char屬于基類型,規(guī)定了指針變量指向字符型數(shù)據(jù)(即指向字符串),其他部分同指針變量定義的意義一樣。

給字符型指針變量賦初值的方式也有兩種:一是定義的同時賦初值;二是先定義,再賦初值。

分析:與例8.13對比,只是定義了字符型指針變量,用指針變量來引用字符串和字符串中的字符。運行結(jié)果也完全一樣。但仍要注意,用數(shù)組名作指針,其地址值是固定的,而用字符型指針變量,其值是可以變化的。在本例中體現(xiàn)得不是太明顯,但在比較復(fù)雜的問題中就會表現(xiàn)出來。

例8.15將字符串a(chǎn)復(fù)制到字符串b中,再輸出字符串b。

編程思路:對字符串的輸入或輸出,可以使用%s格式描述符,進行整體的輸入或輸出。但對字符串的其他操作,只能對字符串中的字符逐個進行處理,即對字符數(shù)組中的元素逐個進行處理。所以,對字符串的處理,常采用循環(huán)結(jié)構(gòu)。下面分別通過指針引用法和指針變量引用法來編寫程序。運行結(jié)果:

分析:

①數(shù)組b的長度應(yīng)大于等于數(shù)組a,否則無法完整復(fù)制。

②復(fù)制循環(huán)中使用了指針引用法,把a數(shù)組中的字符依次逐個復(fù)制到b數(shù)組中,循環(huán)結(jié)束的判定條件是字符串結(jié)束符“\0”,不能把結(jié)束符復(fù)制到b數(shù)組中。所以循環(huán)結(jié)束后,向b數(shù)組后添加結(jié)束符“\0”。

③輸出循環(huán)中使用了數(shù)組下標(biāo)法,可以與指針引用法比較。分析:該程序的運行結(jié)果同(1)。循環(huán)復(fù)制中使用指針變量法實現(xiàn)逐個字符復(fù)制,指針變量既作指針,又作循環(huán)控制變量,使兩個p1、p2同步移動。這也是指針變量使用的一種技巧。兩字符串的輸出仍采用了數(shù)組名(指針法),體現(xiàn)了通過指針引用字符串的多種方法。8.5.3字符指針作函數(shù)參數(shù)

例8.16用函數(shù)調(diào)用實現(xiàn)字符串的復(fù)制。

編程思路:定義一個函數(shù)來實現(xiàn)字符串復(fù)制的功能,在主函數(shù)中提供源串,調(diào)用復(fù)制函數(shù)后,輸出源串和目標(biāo)串。函數(shù)參數(shù)可以是字符數(shù)組名(字符串指針)或字符指針變量。下面分別給出實參和形參4種組合關(guān)系的程序,以供分析比較,體會編程方法與技巧。

(1)實參、形參都用字符數(shù)組名(指針)。

運行結(jié)果:

分析:程序中函數(shù)實參和形參都是數(shù)組名,系統(tǒng)仍把形參數(shù)組名作指針變量處理。在函數(shù)調(diào)用時,將a和b第1個字符的地址分別傳給形參from和to,使from指向字符串a(chǎn),to指向字符串b。函數(shù)調(diào)用后,字符串b產(chǎn)生變化。函數(shù)調(diào)用前后字符串?dāng)?shù)組的狀態(tài)如圖8.11(a)和(b)所示。從圖中可以看到,由于b數(shù)組原來長度大于a數(shù)組,在a數(shù)組復(fù)制到b數(shù)組后,未能全部覆蓋b數(shù)組原有內(nèi)容。b數(shù)組最后3個元素仍保留原狀。在輸出b時,圖8.11函數(shù)調(diào)用前后的字符數(shù)組狀態(tài)由于按%s格式輸出,遇到“\0”即結(jié)束,因此第1個“\0”后的字符不輸出。如果采用%c格式,則后面的字符是可以輸出的。

(2)實參用字符數(shù)組名,形參用字符指針變量。分析:程序的運行結(jié)果同(1)。形參是指針變量,調(diào)用時接收參數(shù)傳遞的源串和目標(biāo)串的首地址,利用指針變量加1調(diào)整運算,能很方便地使用for循環(huán)來實現(xiàn)逐個字符的復(fù)制。

(3)實參用字符指針變量,形參用字符數(shù)組名。

分析:程序的運行結(jié)果同(1)。參數(shù)傳遞的是指針變量,但被賦給字符數(shù)組名,調(diào)用時,傳遞的是字符串首地址。形參是數(shù)組名,編譯系統(tǒng)按指針變量看待,可接收實參傳遞的字符串首地址,在字符復(fù)制循環(huán)中用“下標(biāo)法”實現(xiàn)逐個字符復(fù)制。

分析:程序的運行結(jié)果同(1)。參數(shù)傳遞及函數(shù)調(diào)用過程請讀者自行分析。

字符型指針作函數(shù)參數(shù),引用字符串元素的方法有多種,給程序設(shè)計帶來許多技巧。下面通過對copy_string函數(shù)的改寫來說明程序設(shè)計的方法與技巧。

對copy_string函數(shù)可有如下一些改寫,都能保持功能不變。

8.6通過指針調(diào)用函數(shù)

8.6.1函數(shù)指針與指針變量的定義

何謂函數(shù)指針?在程序中如何使用函數(shù)指針或函數(shù)指針變量?

函數(shù)的指針就是函數(shù)代碼存儲的起始地址,也就是函數(shù)調(diào)用時的入口地址。可以通過指向函數(shù)入口的指針變量(或簡稱指向函數(shù)的指針變量)來調(diào)用函數(shù)。

指向函數(shù)的指針變量又是一種新類型的指針變量,也必須先定義后使用。指向函數(shù)的指針變量定義的一般形式為

基類型符(*變量名)(函數(shù)參數(shù)表列)其中,基類型符是指函數(shù)返回值的類型。變量名前加*表示是指針變量,兩側(cè)再加括號,連同后面的括號內(nèi)的函數(shù)參數(shù)表列一起,表示所定義的變量是指向返回值為基類型的函數(shù)的指針變量。也就是說,變量名兩側(cè)的括號和其后的括號與參數(shù)列表都不能遺漏。

例如:

int(*p)(int,int);

定義了指向返回值為整型、有兩個整型參數(shù)的函數(shù)的指針變量p。與一般指針變量定義和使用一樣,定義了一個指向函數(shù)的指針變量后,并沒有指向哪一個函數(shù),只有當(dāng)賦初值后,才能建立起明確的指向。如果把一個已定義的函數(shù)名賦給已定義的函數(shù)的指針變量,則使指針變量與函數(shù)建立了明確的指向。8.6.2通過函數(shù)指針調(diào)用函數(shù)

如何通過函數(shù)指針或指針變量調(diào)用函數(shù)?

通過函數(shù)指針調(diào)用函數(shù)有兩種方式:一是通過函數(shù)指針(函數(shù)名);二是通過指向函數(shù)的指針變量。通過函數(shù)名調(diào)用已經(jīng)介紹過,因函數(shù)名僅表示一個固定地址,故只能調(diào)用一個固定的函數(shù)。指向函數(shù)的指針變量的值可以變化,可使其指向不同的函數(shù),分別實現(xiàn)不同函數(shù)的調(diào)用。

例8.17用函數(shù)求兩整數(shù)中的大數(shù)。

編程思路:定義一個函數(shù),通過比較求出兩個整數(shù)中的大數(shù)。在主函數(shù)中定義指向函數(shù)的指針變量,通過指針變量調(diào)用函數(shù),得到兩數(shù)中的大數(shù)。

分析:程序第5行定義了指向返回值為整型、有兩個整型參數(shù)函數(shù)的指針變量p。第7行將函數(shù)名表示的函數(shù)起始地址賦給指針變量p,使其指向函數(shù)max。第10行“c=(*p)(a,b);”是指向函數(shù)的指針變量對函數(shù)的調(diào)用,a、b是實參,把函數(shù)的返回值賦給變量c。函數(shù)調(diào)用過程及參數(shù)傳遞同函數(shù)名調(diào)用。如果將“c=(*p)(a,b);”置換為“c=max(a,b);”且不需定義指向函數(shù)的指針變量和賦初值語句,程序的執(zhí)行結(jié)果完全一樣。這個例子說明了通過指向函數(shù)的指針變量調(diào)用函數(shù)的方法,但看不出通過指向函數(shù)的指針變量調(diào)用函數(shù)的優(yōu)越性。在后面將會看到,利用指針變量值的可變性,可以實現(xiàn)一類函數(shù)的調(diào)用。

8.6.3用指向函數(shù)的指針作函數(shù)的參數(shù)

例8.18實現(xiàn)兩個整數(shù)的加、減、乘、除運算,用戶通過輸入1、2、3、4分別進行加法、減法、乘法、除法運算并輸出結(jié)果。

編程思路:分別設(shè)計實現(xiàn)兩整數(shù)加、減、乘、除的函數(shù),再設(shè)計一個參數(shù)為指針的函數(shù),在主函數(shù)中根據(jù)用戶的選擇,傳遞相應(yīng)運算函數(shù)的指針,形參接收函數(shù)指針后,調(diào)用不同的運算函數(shù)。

分析:在arith函數(shù)定義中,x、y是兩個接收運算數(shù)的形參,int(*p)(int,int)是指向函數(shù)的指針變量,接收函數(shù)的指針。在函數(shù)體中,由指針調(diào)用相應(yīng)運算函數(shù),相當(dāng)于構(gòu)建了一個四則運算類函數(shù)。

程序運行時,在主函數(shù)中接收用戶的運算選擇,switch語句依據(jù)選擇調(diào)用arith函數(shù),把兩運算數(shù)和相應(yīng)功能運算函數(shù)指針傳遞給形參,由“result=(*p)(x,y);”語句調(diào)用相應(yīng)功能運算函數(shù)。顯然,p是指向函數(shù)的指針變量,可以接收不同函數(shù)的指針(函數(shù)入口地址),通過它來實現(xiàn)多個函數(shù)的調(diào)用。參數(shù)傳遞及p的指向如圖8.12所示。

圖8.12例8.17參數(shù)傳遞及指針變量的指向8.6.4返回指針值的函數(shù)

函數(shù)返回指針值,調(diào)用函數(shù)與被調(diào)函數(shù)間建立什么聯(lián)系?

函數(shù)的返回值可以是指針。指針作為函數(shù)的返回值,可以減少函數(shù)間傳遞的數(shù)據(jù)量,是提高程序效率的有效方法。特別是在主調(diào)函數(shù)和被調(diào)函數(shù)都對一個數(shù)組進行操作的情況下,數(shù)組指針作函數(shù)的返回值,在主調(diào)函數(shù)中,通過返回指針就可以引用操作后的數(shù)組元素。

返回指針值的函數(shù),要將返回值類型定義為指針型。其定義的一般形式為

基類型符*函數(shù)名(參數(shù)表列);顯然,返回指針值函數(shù)的定義和返回基本類型值函數(shù)定義的區(qū)別僅在于基類型符和函數(shù)名之間加了一個“*”。這樣,就定義了該函數(shù)是返回指向一種基類型數(shù)據(jù)的指針。從C語言運算符優(yōu)先級來講,因()優(yōu)先級高于*,所以數(shù)組名先與()結(jié)合,顯然這是函數(shù)形式。函數(shù)前面有一個*,表示此函數(shù)是指針型函數(shù)(函數(shù)值是指針)。最前面的基類型是返回的指針?biāo)赶驍?shù)據(jù)的類型。

下面通過例子來說明使用返回指針值函數(shù)的程序設(shè)計方法。

例8.19

編程實現(xiàn)一個班學(xué)生課程成績的查詢。要求輸入一個學(xué)生的序號后,能輸出該學(xué)生全部課程的成績。用指針型函數(shù)來實現(xiàn)。

編程思路:一個班學(xué)生成績表用二維數(shù)組來表示。定義一個查詢函數(shù),根據(jù)學(xué)生序號,確定一個指向該學(xué)生各門課成績行的指針,此指針作為函數(shù)返回值。在主函數(shù)中,通過返回指針可輸出該學(xué)生的各門課成績。為簡化數(shù)據(jù)而突出程序的設(shè)計方法,假設(shè)一個班有3個學(xué)生、4門課成績。

分析:函數(shù)search定義為指針型函數(shù),形參pointer是指向包含4個實型數(shù)據(jù)元素的一維數(shù)組的指針變量。函數(shù)中,定義pt是指向變量的指針變量。*(pointer+n)是引用第2層邏輯一維數(shù)組的第n個元素,即得到第n行首元素地址,把此地址賦給變量pt,作為函數(shù)返回值。主函數(shù)中,調(diào)用查詢函數(shù),將返回指針賦給p,在for循環(huán)中,通過p輸出所指向?qū)W生的各門課成績。請注意程序中所定義的指針變量p、pt和pointer的區(qū)別。

例8.20

在一個班學(xué)生成績表中,查找不及格課程的學(xué)生,輸出序號及成績。

編程思路:在例8.19的基礎(chǔ)上,定義查詢函數(shù)為查找一個學(xué)生有無不及格成績。如有則確定一個指向該學(xué)生成績行首的指針;如無,則設(shè)置指針為空。指針作為函數(shù)返回值。在主函數(shù)中,要按學(xué)生序號逐一調(diào)用查詢函數(shù),根據(jù)返回指針值判定是否有不及格成績。若有,則通過返回指針輸出該學(xué)生各門課成績。若無則不輸出。分析:定義函數(shù)search檢查一個學(xué)生有無不及格成績。形參pointer是指向包含4個實型數(shù)據(jù)元素的一維數(shù)組的指針變量。函數(shù)中,定義pt是指向?qū)嵭妥兞康闹羔樧兞俊Qh(huán)實現(xiàn)檢查一個學(xué)生每門課成績是否低于60。若是,則將該學(xué)生成績行首地址賦給pt;若無,則pt為NULL。函數(shù)調(diào)用后,返回pt值。在主函數(shù)中將pt值賦給p,if語句判定p的值是否等于一個學(xué)生成績行首地址*(score+i)。若是,則該學(xué)生有不及格成績,用for循環(huán)輸出該學(xué)生各門課成績。若不是,則該學(xué)生無不及格成績,不輸出。

8.7多重指針與指針數(shù)組

何謂多重指針?怎樣通過多重指針引用數(shù)據(jù)?

在本章開頭已經(jīng)介紹,通過指針變量來引用另一變量值的方式是間接訪問。這屬于“單級間址”。如果再有一個指針變量,使其指向目標(biāo)變量的指針變量,就形成了“二級間址”的方法,即形成了二重指針。從理論上講,間址方法可以延伸到更多的層級,即多重指針。多重指針及其指向關(guān)系如圖8.13所示。在實際應(yīng)用中,很少使用超過二級的間址。間址層級越多,越難理解,出錯機會也會增多。

圖8.13多重指針示意圖8.7.1指針數(shù)組

若一個數(shù)組的元素均為指針類型數(shù)據(jù),則稱為指針數(shù)組。也就是說,指針數(shù)組中每一個元素都存放一個地址,相當(dāng)于一個指針變量。

使用指針數(shù)組也必須先定義。指針數(shù)組定義的一般形式為

基類型符*數(shù)組名[數(shù)組長度];

容易看出,指針數(shù)組與基本數(shù)據(jù)類型數(shù)組定義的區(qū)別在于:基類型符與數(shù)組名之間加了一個“*”,表示數(shù)組元素是指針類型,這些指針都指向基類型數(shù)據(jù)。從C語言運算符優(yōu)先級看,由于[]比*優(yōu)先級高,因此數(shù)組名先與[數(shù)組長度]結(jié)合,這顯然是數(shù)組形式,再與*結(jié)合,表示此數(shù)組是指針類型。

例如:

int*a[4];

定義了數(shù)組a,包含4個指針類型元素,這些指針元素都指向整型數(shù)據(jù)。請注意指針數(shù)組定義與指向一維數(shù)組的指針變量定義的區(qū)別。如果寫成“int(*a)[4];”,就成為指向一維數(shù)組的指針變量的定義了。

指針數(shù)組最典型的應(yīng)用是:指向若干個字符串,使字符串處理更加方便靈活。例如,圖書館有若干本書,把書名放在一起組成書目,經(jīng)常需要對書目進行排序和查詢。因字符串本身就是一個字符數(shù)組,存放書目的多個字符串需要設(shè)計一個二維字符數(shù)組。但在定義二維數(shù)組時,需要確定列數(shù),一旦列數(shù)設(shè)定,二維數(shù)組中每一行包含的元素個數(shù)就是相等的。實際上,書名字符串的長度一般是不相等的。如果按最長的字符串來定義列數(shù),則會浪費許多存儲單元,而且在進行字符串處理時也會降低效率。使用指針數(shù)組,可以分別定義一些字符串,用指針數(shù)組的元素分別指向各字符串,如圖8.14所示。這樣能有效解決上述問題。

圖8.14多個字符串的存儲及與指針數(shù)組的指向示意圖

例8.21將書目字符串按字母順序輸出。

編程思路:定義一個指針數(shù)組,用各字符串對其進行初始化,即把各字符串首字符地址賦給指針數(shù)組各元素。用選擇法排序,不移動字符串,而只改變指針數(shù)組各元素的指向。

分析:定義sort函數(shù)的作用是對字符串排序。形參name是指針數(shù)組名,接收實參傳遞的name數(shù)組0行首字符地址,使形參name和實參name指向同一個數(shù)組。用選擇法對字符串排序。strcmp是系統(tǒng)提供的字符串比較函數(shù),name[k]和name[j]是第k個和第j個字符串首字符的地址。strcmp(name[k],name[j])的值為:如果name[k]所指的字符串大于name[j]所指的字符串,其值為正;若相等,其值為0;若小于,其值為負。if語句的作用是比較兩個字符串,并將小串的序號記錄在變量k中。當(dāng)執(zhí)行完內(nèi)循環(huán)for語句后,第k串最小。若k≠i,就表示最小串不是第i串。故將name[i]和name[k]對換,也就是將其指向互換。執(zhí)行完sort函數(shù)后,指針數(shù)組元素的指向如圖8.15所示。圖8.15排序后指針數(shù)組元素的指向

printf的作用是輸出排好序的字符串。因name[0]~name[4]分別指向從小到大排好序的字符串,所以按指針數(shù)組元素順序,用“%s”格式控制,就能輸出排好序的字符串。

8.7.2指向指針數(shù)據(jù)的指針

指針數(shù)組的存儲同基本類型數(shù)組一樣,按元素順序連續(xù)存儲在一個存儲區(qū),每個元素都有一個存儲地址。所以,可定義一個指向指針數(shù)組元素的指針變量,通過該指針變量可以引用指針數(shù)組中的元素。這種指針變量稱為指向指針數(shù)據(jù)的指針變量。指向指針數(shù)據(jù)的指針變量定義的一般形式為

基類型符**變量名;

從C語言運算符的優(yōu)先級來看,*運算符的結(jié)合性是從右到左,因此,“**變量名”相當(dāng)于*(*變量名),顯然“*變量名”是指針變量的定義形式,前面再加一個“*”,表示該指針變量指向指針型數(shù)據(jù)。

在前面定義書目指針數(shù)組的基礎(chǔ)上,看下面語句的功能作用:

char**p;

p=name+2;

printf("%d\n",*p);

printf("%s\n",*p);

其中,第1個printf函數(shù)語句中,“*p”是引用字符型指針數(shù)組元素name[2]的值,輸出的是一個地址。第2個printf函數(shù)語句中,“*p”與“%s”配合,輸出的是name[2]所指向的字符串。

例8.22使用指向指針數(shù)據(jù)的指針變量輸出多個字符串。

編程思路:定義一個指針數(shù)組,并對其初始化,使數(shù)組中的每一個元素分別指向5個字符串。定義一個指向指針型數(shù)據(jù)的指針變量,使其先后指向指針數(shù)組各元素,輸出這些元素所指向的字符串。

運行結(jié)果:

分析:在主函數(shù)的開頭,定義了指向字符的指針數(shù)組name,同時用字符串賦初值的方式初始化,分別將5個字符串的首地址賦給數(shù)組元素。又定義指向指針數(shù)據(jù)的指針變量p,在for循環(huán)中,賦值語句“p=name+i;”使p指向指針數(shù)組的第i元素,即第i個字符串的首地址。在“printf("%s\n",*p);”語句中使用“%s”,輸出p所指向的字符串。上面針對指針數(shù)組元素指向字符串的典型應(yīng)用舉了兩個例子。指針數(shù)組元素也可指向基本類型數(shù)據(jù)。下面通過一個簡單例子來說明這種用法。

例8.23使用指針數(shù)組元素指向一個整型數(shù)組的元素,用指向指針數(shù)據(jù)的指針變量輸出整型數(shù)組各元素的值。

運行結(jié)果:

分析:主函數(shù)中定義了一個整型數(shù)組a,又定義了一個指向整型數(shù)據(jù)的指針數(shù)組,同時對其初始化,將5個整型數(shù)組元素的地址賦給指針數(shù)組元素。在“printf("%d",**p);”語句中,“**p”屬于二重間址訪問,從p所指向的指針數(shù)組得到數(shù)據(jù)元素的地址,再由元素地址從數(shù)據(jù)數(shù)組中取得元素值輸出。

8.8用于動態(tài)內(nèi)存分配的指針型函數(shù)

8.8.1內(nèi)存動態(tài)分配的函數(shù)

1.開辟動態(tài)存儲空間函數(shù)(malloc)

該函數(shù)原型為

void*malloc(unsignedintsize);

其作用是在內(nèi)存的動態(tài)存儲區(qū)中分配一個長度為size字節(jié)的連續(xù)空間。此函數(shù)是指針型函數(shù),返回值是所分配存儲區(qū)域的起始地址,如果分配未成功,則返回空指針(NULL)。利用返回指針可以使用該存儲區(qū)域中的數(shù)據(jù)。例如:

malloc(100);

開辟100字節(jié)的動態(tài)存儲區(qū),函數(shù)返回該區(qū)域首字節(jié)地址。

2.開辟數(shù)組動態(tài)存儲空間函數(shù)(calloc)

該函數(shù)原型為

void*calloc(unsignedn,unsignedsize);

其作用是在內(nèi)存的動態(tài)存儲區(qū)中分配n個長度為size的連續(xù)空間,即可為n個元素的數(shù)組開辟一個動態(tài)存儲區(qū),每個元素的存儲長度為size。函數(shù)的返回值是所分配存儲區(qū)域的起始地址,如果分配未成功,則返回空指針(NULL)。利用返回指針可以使用該存儲區(qū)域中的數(shù)據(jù)元素。例如:

p=calloc(50,4);

開辟

溫馨提示

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

評論

0/150

提交評論