?? 第9章 數(shù) 組.txt
字號(hào):
C語(yǔ)言編程常見(jiàn)問(wèn)題解答
發(fā)表日期:2003年10月2日 已經(jīng)有1733位讀者讀過(guò)此文
第9章 數(shù) 組
C語(yǔ)言處理數(shù)組的方式是它廣受歡迎的原因之一。C語(yǔ)言對(duì)數(shù)組的處理是非常有效的,其原因有以下三點(diǎn):
第一,除少數(shù)翻譯器出于謹(jǐn)慎會(huì)作一些繁瑣的規(guī)定外,C語(yǔ)言的數(shù)組下標(biāo)是在一個(gè)很低的層次上處理的。但這個(gè)優(yōu)點(diǎn)也有一個(gè)反作用,即在程序運(yùn)行時(shí)你無(wú)法知道一個(gè)數(shù)組到底有多大,或者一個(gè)數(shù)組下標(biāo)是否有效。ANSI/ISOC標(biāo)準(zhǔn)沒(méi)有對(duì)使用越界下標(biāo)的行為作出定義,因此,一個(gè)越界下標(biāo)有可能導(dǎo)致這樣幾種后果:
(1) 程序仍能正確運(yùn)行;
(2) 程序會(huì)異常終止或崩潰;
(3) 程序能繼續(xù)運(yùn)行,但無(wú)法得出正確的結(jié)果;
(4) 其它情況。
換句話說(shuō),你不知道程序此后會(huì)做出什么反應(yīng),這會(huì)帶來(lái)很大的麻煩。有些人就是抓住這一點(diǎn)來(lái)批評(píng)C語(yǔ)言的,認(rèn)為C語(yǔ)言只不過(guò)是一種高級(jí)的匯編語(yǔ)言。然而,盡管C程序出錯(cuò)時(shí)的表現(xiàn)有些可怕,但誰(shuí)也不能否認(rèn)一個(gè)經(jīng)過(guò)仔細(xì)編寫(xiě)和調(diào)試的C程序運(yùn)行起來(lái)是非常快的。
第二,數(shù)組和指針能非常和諧地在一起工作。當(dāng)數(shù)組出現(xiàn)在一個(gè)表達(dá)式中時(shí),它和指向數(shù)組中第一個(gè)元素的指針是等價(jià)的,因此數(shù)組和指針幾乎可以互換使用。此外,使用指針要比使用數(shù)組下標(biāo)快兩倍(請(qǐng)參見(jiàn)9.5中的例子)。
第三,將數(shù)組作為參數(shù)傳遞給函數(shù)和將指向數(shù)組中第一個(gè)元素的指針傳遞給函數(shù)是完全·
等價(jià)的。將數(shù)組作為參數(shù)傳遞給函數(shù)時(shí)可以采用值傳遞和地址傳遞兩種方式,前者需要完整地拷貝初始數(shù)組,但比較安全;后者的速度要快得多,但編寫(xiě)程序時(shí)要多加小心。C++和ANSIC中都有const關(guān)鍵字,利用它可以使地址傳遞方式和值傳遞方式一樣安全。如果你想了解更多的細(xì)節(jié),請(qǐng)參見(jiàn)2.4,8.6和第7章“指針和內(nèi)存分配”開(kāi)頭部分的介紹。
數(shù)組和指針之間的這種聯(lián)系會(huì)引起一些混亂,例如以下兩種定義是完全相同的:
void f(chara[MAX])
{
/*... */
}
void f(char *a)
{ ·
/*... */
}
注意:MAX是一個(gè)編譯時(shí)可知的值,例如用#define預(yù)處理指令定義的值。
這種情況正是前文中提到的第三個(gè)優(yōu)點(diǎn),也是大多數(shù)C程序員所熟知的。這也是唯一一種數(shù)組和指針完全相同的情況,在其它情況下,數(shù)組和指針并不完全相同。例如,當(dāng)作如下定義 (可以出現(xiàn)在函數(shù)說(shuō)明以外的任何地方)時(shí):
char a[MAX];
系統(tǒng)將分配MAX個(gè)字符的內(nèi)存空間。當(dāng)作如下說(shuō)明時(shí):
char *a;
系統(tǒng)將分配一個(gè)字符指針?biāo)璧膬?nèi)存空間,可能只能容納2個(gè)或4個(gè)字符。如果你在源文件中作如下定義:
char a[MAX];
但在頭文件作如下說(shuō)明;
extern char *a;
就會(huì)導(dǎo)致可怕的后果。為了避免出現(xiàn)這種情況,最好的辦法是保證上述說(shuō)明和定義的一致性,例如,如果在源文件中作如下定義:
char a[MAX];
那么在相應(yīng)的頭文件中就作如下說(shuō)明,
externchar a[];
上述說(shuō)明告訴頭文件a是一個(gè)數(shù)組,不是一個(gè)指針,但它并不指示數(shù)組a中有多少個(gè)元素,這樣說(shuō)明的類(lèi)型稱(chēng)為不完整類(lèi)型。在程序中適當(dāng)?shù)卣f(shuō)明一些不完整類(lèi)型是很常見(jiàn)的,也是一種很好的編程習(xí)慣。
9.1 數(shù)組的下標(biāo)總是從0開(kāi)始嗎?
是的,對(duì)數(shù)組a[MAX](MAX是一個(gè)編譯時(shí)可知的值)來(lái)說(shuō),它的第一個(gè)和最后一個(gè)元素分別是a[o]和aLMAX-1)。在其它一些語(yǔ)言中,情況可能有所不同,例如在BASIC語(yǔ)言中數(shù)組a[MAX]的元素是從a[1]到a[MAX],在Pascal語(yǔ)言中則兩種方式都可行。
注意:a[MAX]是一個(gè)有效的地址,但該地址中的值并不是數(shù)組a的一個(gè)元素(見(jiàn)9。2)。
上述這種差別有時(shí)會(huì)引起混亂,因?yàn)楫?dāng)你說(shuō)“數(shù)組中的第一個(gè)元素”時(shí),實(shí)際上是指“數(shù)組中下標(biāo)為。的元素”,這里的“第一個(gè)”的意思和“最后一個(gè)”相反。
盡管你可以假造一個(gè)下標(biāo)從1開(kāi)始的數(shù)組,但在實(shí)際編程中不應(yīng)該這樣做。下文將介紹這種技巧,并說(shuō)明為什么不應(yīng)該這樣做的原因。
因?yàn)橹羔樅蛿?shù)組幾乎是相同的,因此你可以定義一個(gè)指針,使它可以象一個(gè)數(shù)組一樣引用另一個(gè)數(shù)組中的所有元素,但引用時(shí)前者的下標(biāo)是從1開(kāi)始的:
/*don't do this!!*/
int a0[MAX],
int *a1=a0-1; /*&a0[-1)*/
現(xiàn)在,a0[0]和a1[1)是相同的,而a0[MAX-1]和a1[MAX]是相同的。然而,在實(shí)際編程中不應(yīng)該這樣做,其原因有以下兩點(diǎn):
第一,這種方法可能行不通。這種行為是ANSI/ISOC標(biāo)準(zhǔn)所沒(méi)有定義的(并且是應(yīng)該避免的),而&a0[-1)完全有可能不是一個(gè)有效的地址(見(jiàn)9.3)。對(duì)于某些編譯程序,你的程序可能根本不會(huì)出問(wèn)題;在有些情況下,對(duì)于任何編譯程序,你的程序可能都不會(huì)出問(wèn)題;但是,誰(shuí)能保證你的程序永遠(yuǎn)不會(huì)出問(wèn)題呢?
第二,這種方式背離了C語(yǔ)言的常規(guī)風(fēng)格。人們已經(jīng)習(xí)慣了C語(yǔ)言中數(shù)組下標(biāo)的工作方式,如果你的程序使用了另外一種方式,別人就很難讀懂你的程序,而經(jīng)過(guò)一段時(shí)間以后,連你自己都可能很難讀懂這個(gè)程序了。
請(qǐng)參見(jiàn):
9.2 可以使用數(shù)組后面第一個(gè)元素的地址嗎?
9.3 為什么要小心對(duì)待位于數(shù)組后面的那些元素的地址呢?
9.2 可以使用數(shù)組后面第一個(gè)元素的地址嗎?
你可以使用數(shù)組后面第一個(gè)元素的地址,但你不可以查看該地址中的值。對(duì)大多數(shù)編譯程序來(lái)說(shuō),如果你寫(xiě)如下語(yǔ)句:
int i,a[MAX],j;
那么i和j都有可能存放在數(shù)組a最后一個(gè)元素后面的地址中。為了判斷跟在數(shù)組a后面的是i還是j,你可以把i或j的地址和數(shù)組a后面第一個(gè)元素的地址進(jìn)行比較,即判斷"&i==&a[MAX]"或"&j==&a[MAX]"是否為真。這種方法通常可行,但不能保證。
問(wèn)題的關(guān)鍵是:如果你將某些數(shù)據(jù)存入a[MAX]中,往往就會(huì)破壞原來(lái)緊跟在數(shù)組a后面的數(shù)據(jù)。即使查看a[MAX]的值也是應(yīng)該避免的,盡管這樣做一般不會(huì)引出什么問(wèn)題。
為什么在C程序中有時(shí)要用到&a[MAX]呢?因?yàn)楹芏郈程序員習(xí)慣通過(guò)指針遍歷一個(gè)數(shù)組中的所有元素,即用
for(i=0;i<MAX;++i)
{
/*do something*/
}
代替
for(p=a; p<&a[MAX];++p)
{
/*do something*/
}
這種方式在已有的C程序中是隨處可見(jiàn)的,因此ANSIC標(biāo)準(zhǔn)規(guī)定這種方式是可行的。
請(qǐng)參見(jiàn):
9.3 為什么要小心對(duì)待位于數(shù)組后面的那些元素的地址呢?
9.5 通過(guò)指針或帶下標(biāo)的數(shù)組名都可以訪問(wèn)數(shù)組中的元素,哪一種方式更好呢?
9.3 為什么要小心對(duì)待位于數(shù)組后面的那些元素的地址呢?
如果你的程序是在理想的計(jì)算機(jī)上運(yùn)行,即它的取址范圍是從00000000到FFFFFFFF,那么你大可以放心,但是,實(shí)際情況往往不會(huì)這么簡(jiǎn)單。
在有些計(jì)算機(jī)上,地址是由兩部分組成的,第一部分是一個(gè)指向某一塊內(nèi)存的起始點(diǎn)的指,針(即基地址),第二部分是相對(duì)于這塊內(nèi)存的起始點(diǎn)的地址偏移量。這種地址結(jié)構(gòu)被稱(chēng)為段地址結(jié)構(gòu),子程序調(diào)用通常就是通過(guò)在棧指針上加上一個(gè)地址偏移量來(lái)實(shí)現(xiàn)的。采用段地址結(jié)構(gòu)的最典型的例子是基于Intel 8086的計(jì)算機(jī),所有的MS-DOS程序都在這種計(jì)算機(jī)上運(yùn)行(在基于Pentium芯片的計(jì)算機(jī)上,大多數(shù)MS-DOS程序也在與8086兼容的模式下運(yùn)行)。即使是性能優(yōu)越的具有線性地址空間的RISC芯片,也提供了寄存器變址尋址方式,即用一個(gè)寄存器保存指向某一塊內(nèi)存的起始點(diǎn)的指針,用另一個(gè)寄存器保存地址偏移量。
如果你的程序使用段地址結(jié)構(gòu),而在基地址處剛好存放著數(shù)組a0(即基地址指針和&a0[0]相同),這會(huì)引出什么問(wèn)題呢?既然基地址無(wú)法(有效地)改變,而偏移量也不可能是負(fù)值,因此“位于a0[0]前面的元素”這種說(shuō)法就沒(méi)有意義了,ANSIC標(biāo)準(zhǔn)明確規(guī)定引用這個(gè)元素的行為是沒(méi)有定義的,這也就是9.1中所提到的方法可能行不通的原因。
同樣,如果數(shù)組a(其元素個(gè)數(shù)為MAX)剛好存放在某段內(nèi)存的尾部,那么地址&a[MAX]就是沒(méi)有意義的,如果你的程序中使用了&a[MAX],而編譯程序又要檢查&a[MAX]是否有效,那么編譯程序必然就會(huì)報(bào)告沒(méi)有足夠的內(nèi)存來(lái)存放數(shù)組a。
盡管在編寫(xiě)基于Windows,UNIX或Macintosh的程序時(shí)不會(huì)遇到上述問(wèn)題,但是C語(yǔ)言不僅僅是為這幾種情況設(shè)計(jì)的,C語(yǔ)言必須適應(yīng)各種各樣的環(huán)境,例如用微處理器控制的烤面包爐,防抱死剎車(chē)系統(tǒng),MS-DOS,等等。嚴(yán)格按C語(yǔ)言標(biāo)準(zhǔn)編寫(xiě)的程序能被順利地編譯并能服務(wù)于任何目的,但是,有時(shí)程序員也可以適度地背離C語(yǔ)言的標(biāo)準(zhǔn),這要視程序員、編譯程序和程序用戶三者的具體要求而定。
請(qǐng)參見(jiàn):
9. 1數(shù)組的下標(biāo)總是從0開(kāi)始嗎?
9.2可以使用數(shù)組后面第一個(gè)元素的地址嗎?
9.4 在把數(shù)組作為參數(shù)傳遞給函數(shù)時(shí),可以通過(guò)sizeof運(yùn)算符告訴函數(shù)數(shù)組的大小嗎?
不可以。當(dāng)把數(shù)組作為函數(shù)的參數(shù)時(shí),你無(wú)法在程序運(yùn)行時(shí)通過(guò)數(shù)組參數(shù)本身告訴函數(shù)該數(shù)組的大小,因?yàn)楹瘮?shù)的數(shù)組參數(shù)相當(dāng)于指向該數(shù)組第一個(gè)元素的指針。這意味著把數(shù)組傳遞給函數(shù)的效率非常高,也意味著程序員必須通過(guò)某種機(jī)制告訴函數(shù)數(shù)組參數(shù)的大小。
為了告訴函數(shù)數(shù)組參數(shù)的大小,人們通常采用以下兩種方法:
第一種方法是將數(shù)組和表示數(shù)組大小的值一起傳遞給函數(shù),例如memcpy()函數(shù)就是這樣做的:
char source[MAX],dest[MAX];
/*... */
memcpy(dest,source,MAX);
第二種方法是引入某種規(guī)則來(lái)結(jié)束一個(gè)數(shù)組,例如在C語(yǔ)言中字符串總是以ASCII字符NUL('\0')結(jié)束,而一個(gè)指針數(shù)組總是以空指針結(jié)束。請(qǐng)看下述函數(shù),它的參數(shù)是一個(gè)以空指
針結(jié)束的字符指針數(shù)組,這個(gè)空指針告訴該函數(shù)什么時(shí)候停止工作:
void printMany(char *strings口)
{
int i;
i=0;
while(strings[i]!=NULL)
{
puts(strings[i]);
++i;
}
}
正象9.5中所說(shuō)的那樣,C程序員經(jīng)常用指針來(lái)代替數(shù)組下標(biāo),因此大多數(shù)C程序員通常會(huì)將上述函數(shù)編寫(xiě)得更隱蔽一些:
void printMany(char *strings[])
{
while(*strings)
{
puts(*strings++);
}
}
盡管你不能改變一個(gè)數(shù)組名的值,但是strings是一個(gè)數(shù)組參數(shù),相當(dāng)于一個(gè)指針,因此可以對(duì)它進(jìn)行自增運(yùn)算,并且可以在調(diào)用puts()函數(shù)時(shí)對(duì)strings進(jìn)行自增運(yùn)算。在上例中,while(*strings)
就相當(dāng)于
while(*strings !=NULL)
在寫(xiě)函數(shù)文檔(例如在函數(shù)前面加上注釋?zhuān)蛘邔?xiě)一份備忘錄,或者寫(xiě)一份設(shè)計(jì)文檔)時(shí),寫(xiě)進(jìn)函數(shù)是如何知道數(shù)組參數(shù)的大小是非常重要的,例如,你可以非常簡(jiǎn)略地寫(xiě)上“以空指針結(jié)束”或“數(shù)組elephants中有numElephants個(gè)元素”(如果你在程序中用數(shù)字13表示數(shù)組的大小,你可以寫(xiě)進(jìn)“數(shù)組arr中有13個(gè)元素”這樣的描述,然而用確切的數(shù)字表示數(shù)組的大小不是一種好的編程習(xí)慣)。
請(qǐng)參見(jiàn):
9.5通過(guò)指針或帶下標(biāo)的數(shù)組名都可以訪問(wèn)數(shù)組中的元素,哪一種方式更好呢?
9.6可以把另外一個(gè)地址賦給一個(gè)數(shù)組名嗎?
9.5 通過(guò)指針或帶下標(biāo)的數(shù)組名都可以訪問(wèn)數(shù)組中的元素,哪一種方式更好呢?
與使用下標(biāo)相比,使用指針能使C編譯程序更容易地產(chǎn)生優(yōu)質(zhì)的代碼。
假設(shè)你的程序中有這樣一段代碼:
/* X la some type */
X a[MAX];
X *p; /*pointer*/
X x; /*element*/
int i; /*index*/
為了歷數(shù)組a中的所有元素,你可以采用這樣一種循環(huán)方式(方式a)
/*version (a)*/
for (i = 0; i<MAX; ++i)
{
x=a[i];
/* do something with x * /
}
你也可以采用這樣一種循環(huán)方式(方式b)
/*veraion(b)*/
?? 快捷鍵說(shuō)明
復(fù)制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號(hào)
Ctrl + =
減小字號(hào)
Ctrl + -