黃 宇
文章編號(hào):1672-5913(2009)10-0096-03
摘要:作者在多年的C語(yǔ)言教學(xué)過程中,對(duì)學(xué)生中經(jīng)常遇到的問題進(jìn)行了一些總結(jié)。本文就學(xué)生中經(jīng)常遇到的5個(gè)帶有普遍性的問題,通過C程序示例進(jìn)行了分析,指出了出現(xiàn)錯(cuò)誤的原因,給出了改正的方法。
關(guān)鍵詞:C語(yǔ)言;程序設(shè)計(jì);教學(xué)
中圖分類號(hào):G642
文獻(xiàn)標(biāo)識(shí)碼:B
1引言
隨著計(jì)算機(jī)應(yīng)用技術(shù)的普及,大學(xué)中很多專業(yè)都開設(shè)了計(jì)算機(jī)編程課程。在非計(jì)算機(jī)專業(yè)中,大多以教授C語(yǔ)言編程為主。本人長(zhǎng)期從事對(duì)非計(jì)算機(jī)專業(yè)學(xué)生的C語(yǔ)言編程課的教學(xué)工作,在長(zhǎng)期的教學(xué)實(shí)踐中,發(fā)現(xiàn)了一些學(xué)生在編程中經(jīng)常會(huì)遇到的問題。在此,就幾個(gè)典型的常見問題,展開一些探討。這些問題的解決,對(duì)于更深入的理解C語(yǔ)言,將起到一定的幫助作用。
2幾個(gè)常見問題
2.1無符號(hào)數(shù)運(yùn)算問題
大家知道,在C語(yǔ)言中,不同類型的數(shù)據(jù)一起運(yùn)算時(shí)是按照隱式類型轉(zhuǎn)換的規(guī)則進(jìn)行的,也就是將兩個(gè)類型不一致的數(shù)據(jù)首先轉(zhuǎn)換成一致的,然后再進(jìn)行運(yùn)算。其轉(zhuǎn)換的基本原則有兩點(diǎn),一是小數(shù)據(jù)類型向大數(shù)據(jù)類型轉(zhuǎn)換,二是有符號(hào)類型向無符號(hào)類型轉(zhuǎn)換。比如,當(dāng)一個(gè)float類型數(shù)據(jù)和一個(gè)double類型的數(shù)據(jù)進(jìn)行運(yùn)算時(shí),就是首先將float類型的數(shù)據(jù)轉(zhuǎn)換成double類型的數(shù)據(jù),然后再進(jìn)行運(yùn)算;而當(dāng)一個(gè)int類型的數(shù)據(jù)和一個(gè)unsigned int類型的數(shù)據(jù)進(jìn)行運(yùn)算時(shí),則首先把int類型的數(shù)據(jù)轉(zhuǎn)換為unsigned int類型的數(shù)據(jù),然后再進(jìn)行運(yùn)算。對(duì)于第一種情況,一般不會(huì)遇到問題,但是對(duì)于第二種情況,初學(xué)者往往注意不到這種轉(zhuǎn)換中可能會(huì)隱含的問題,導(dǎo)致程序運(yùn)行結(jié)果出現(xiàn)與自己設(shè)想不一致的情況。
下面給一個(gè)具體的例子:
#include
int main()
{
unsigned int n = 1;
int m = -1;
if (m < n)printf("m < n");
else printf("m >= n");
return 1;
}
在這段程序中,n = 1,m = -1,顯然應(yīng)該是m
為什么會(huì)出現(xiàn)這種情況呢?這是因?yàn)閚是unsigned int類型的,而m是int類型的,在m和n進(jìn)行比較運(yùn)算時(shí),由于二者的類型不一致,首先要進(jìn)行類型轉(zhuǎn)換。按照C語(yǔ)言隱式類型轉(zhuǎn)換規(guī)則,有符號(hào)類型的int轉(zhuǎn)換為無符號(hào)類型的unsigned int。這樣,m(=-1)中的符號(hào)位被當(dāng)成了“數(shù)字”進(jìn)行轉(zhuǎn)換,有符號(hào)的-1成為了無符號(hào)的4294967295(四字節(jié)的情況下。如果是二字節(jié)的則是65535)。而4294967295當(dāng)然要大于1了,所以就有了以上的運(yùn)行結(jié)果。
不單單是在比較運(yùn)算中會(huì)出現(xiàn)這種情況,在其他運(yùn)算中,比如加減乘除等,也一樣會(huì)有類似的情況出現(xiàn)。所以,當(dāng)有符號(hào)和無符號(hào)的數(shù)據(jù)混合運(yùn)算時(shí),一定要注意這個(gè)問題,除非特殊情況,應(yīng)盡量避免有符號(hào)和無符號(hào)的混合運(yùn)算。
2.2計(jì)算數(shù)組的長(zhǎng)度
在C語(yǔ)言中,操作符sizeof( )可以計(jì)算一個(gè)類型或者一個(gè)變量所占用的字節(jié)數(shù)。比如:sizeof(int)或者sizeof(x)(假定x是int類型的),當(dāng)一個(gè)整數(shù)占用4個(gè)字節(jié)時(shí),就可以得到4的結(jié)果。
再比如,一個(gè)整數(shù)數(shù)組:int a[8];
可以通過sizeof(a)/sizeof(int)得到數(shù)組a的元素個(gè)數(shù)。因?yàn)閟izeof(a)得到的是a數(shù)組占用的總字節(jié)數(shù),除以每個(gè)int所占用的字節(jié)數(shù)sizeof(int),就是該數(shù)組的長(zhǎng)度。
由于很多情況下需要知道一個(gè)數(shù)組的長(zhǎng)度,比如在對(duì)一個(gè)數(shù)組排序時(shí),因此,有些初學(xué)者就利用sizeof在函數(shù)中計(jì)算數(shù)組的長(zhǎng)度。舉例如下:
mysort(int a[])
{
int len;
len = sizeof(a)/sizeof(int); //得到數(shù)組a的長(zhǎng)度
//以下對(duì)a進(jìn)行排序
}
但是往往會(huì)發(fā)現(xiàn),這樣的結(jié)果并不正確,len經(jīng)常得到的是1(假定是32位系統(tǒng),一個(gè)整數(shù)占4個(gè)字節(jié))。這又是為什么呢?
這個(gè)問題,與C語(yǔ)言中數(shù)組參數(shù)的傳遞方式有關(guān)。在C語(yǔ)言中,當(dāng)一個(gè)數(shù)組當(dāng)作參數(shù)傳遞時(shí),數(shù)組被轉(zhuǎn)換為指針。在上面的例子中,無論你在函數(shù)定義是mysort(int a[])還是mysort(int a[100]),在函數(shù)內(nèi)部,a均被轉(zhuǎn)換成int *a類型,與定義mysort(int *a)是一致的。因此,在函數(shù)內(nèi)部,當(dāng)計(jì)算sizeof(a)時(shí),實(shí)際上計(jì)算的是sizeof(int *)。因此,當(dāng)作為形參時(shí),無論你的mysort(int a[N])定義中,有無N,或者N是多大,sizeof(a)得到的都是4(假定在32位系統(tǒng)中)。
因此,在一個(gè)函數(shù)內(nèi)部,是無法得到一個(gè)數(shù)組參數(shù)的長(zhǎng)度的,其長(zhǎng)度只能通過參數(shù)進(jìn)行傳遞。所以,上述的排序函數(shù)應(yīng)該定義成如下的形式:
mysort(int a[], int len)
{
//以下對(duì)a進(jìn)行排序
}
2.3常量字符串問題
在學(xué)習(xí)完字符串的操作之后,同學(xué)們往往會(huì)編寫一些簡(jiǎn)單的練習(xí)程序,這時(shí)經(jīng)常會(huì)遇到一些“莫名其妙”的情況,使得程序不能正確運(yùn)行。
比如下面這個(gè)程序,非常簡(jiǎn)單,就是把字符串"abcde"中的'a'換成'A':
int main()
{
char *p = "abcde";
p[0] = 'A';
return 1;
}
程序編譯沒有問題,一運(yùn)行就出現(xiàn)錯(cuò)誤。這是為什么呢?
在這個(gè)程序中,字符串"abcde"是一個(gè)常量,指針p指向了這個(gè)常量。而“常量”顧名思義是不能修改的,而該程序試圖通過指針p修改一個(gè)常量字符串,導(dǎo)致運(yùn)行錯(cuò)誤。
把程序修改如下,就沒有問題了:
int main()
{
char p[] = "abcde";
p[0] = 'A';
return 1;
}
這是因?yàn)閜是一個(gè)字符數(shù)組,并通過初始化的方法對(duì)該數(shù)組進(jìn)行了賦值。雖然字符數(shù)組p也是一個(gè)字符串,但是p不是常量,可以修改,因此就不會(huì)出現(xiàn)運(yùn)行錯(cuò)誤了。
同樣,如果是這樣,也不會(huì)出現(xiàn)錯(cuò)誤:
int main()
{
char p[] = "abcde";
char *q = p;
q[0] = 'A';
return 1;
}
因?yàn)樵谶@里q指向是字符數(shù)組p,而不是字符串常量"abcde"。
2.4文件結(jié)束判斷問題
在C語(yǔ)言中,函數(shù)feof( )可以判斷文件結(jié)束,但是初學(xué)者在使用feof( )時(shí),經(jīng)常會(huì)犯錯(cuò)誤。請(qǐng)看下面這個(gè)例子,該程序?qū)崿F(xiàn)將文件a.txt拷貝到b.txt的功能,通過feof判斷a.txt是否結(jié)束,在結(jié)束之前,每次讀一個(gè)字符,并寫到b.txt中。程序如下:
int main()
{
FILE *pi, *po;
char c;
pi = fopen("a.txt", "rb");
po = fopen("b.txt", "wb");
while (!feof(pi))
{
c = fgetc(pi);
fputc(c, po);
}
fclose(pi);
fclose(po);
return 1;
}
程序很簡(jiǎn)單,看似沒有什么錯(cuò)誤。但是這里卻隱含了一個(gè)初學(xué)者經(jīng)常會(huì)犯的錯(cuò)誤。運(yùn)行一下該程序,就會(huì)發(fā)現(xiàn)在b.txt的最后,會(huì)“奇怪”地多出了一個(gè)字符。
問題出現(xiàn)在什么地方呢?主要是對(duì)feof的認(rèn)識(shí)有誤造成的。
仔細(xì)看一下feof的功能,會(huì)發(fā)現(xiàn)當(dāng)讀完了最后一個(gè)字符后,feof還是保持“假”,只有當(dāng)讀完了最后一個(gè)字符再試圖讀文件時(shí),feof才為真。也就是說,feof判斷是否到達(dá)文件尾比實(shí)際情況要“晚”一步。按照上面的程序,當(dāng)fgetc從a.txt中讀完了最后一個(gè)字符后,feof并不馬上為真,還要循環(huán)再讀一次a.txt,并通過fputc函數(shù)將這次得到的結(jié)果(在字符c中)寫入到b.txt中,造成了b.txt中多了一個(gè)字符。而這時(shí),feof才變成了真,程序退出循環(huán)結(jié)束。
明白了這一點(diǎn),程序按照如下方式增加一個(gè)if語(yǔ)句就可以了:
int main()
{
FILE *pi, *po;
char c;
pi = fopen("a.txt", "rb");
po = fopen("b.txt", "wb");
while (!feof(pi))
{
c = fgetc(pi);
if (feof(pi)) break;//新增加的一個(gè)判斷
fputc(c, po);
}
fclose(pi);
fclose(po);
return 1;
}
2.5結(jié)構(gòu)體的大小問題
通過sizeof可以計(jì)算一個(gè)結(jié)構(gòu)體占用的字節(jié)數(shù),比如下面一段程序是計(jì)算結(jié)構(gòu)體S所占的字節(jié)數(shù)。程序的輸出應(yīng)該是多少呢?
int main()
{
struct S
{
char a;
int n;
};
int n;
n = sizeof(struct S);
printf("%d", n);
return 1;
}
char占1個(gè)字節(jié),int占4個(gè)字節(jié),初學(xué)者往往會(huì)回答S長(zhǎng)度是5。但是一運(yùn)行程序,發(fā)現(xiàn)輸出的結(jié)果卻是8。見到這樣的結(jié)果,初學(xué)者往往又不得其解,不知為什么會(huì)這樣。
這里涉及的就是所謂的地址對(duì)齊的問題,編譯程序,在默認(rèn)的情況下,會(huì)按照一定的原則,比如讓寬度為2的基本數(shù)據(jù)類型(short等)都位于能被2整除的地址上,讓寬度為4的基本數(shù)據(jù)類型(int等)都位于能被4整除的地址上等等,這樣做的原因是為了加快程序的運(yùn)行速度。也就是說,對(duì)結(jié)構(gòu)體進(jìn)行了一定的填充,使得它的成員的地址滿足一定的要求。關(guān)于如何對(duì)齊問題涉及的內(nèi)容比較多,這里就不詳細(xì)解釋了,有興趣的讀者可以參考有關(guān)計(jì)算機(jī)組成原理方面的書。
3結(jié)束語(yǔ)
我們?cè)贑語(yǔ)言教學(xué)過程中,經(jīng)常會(huì)遇到學(xué)生提出的各種問題,有些問題是個(gè)別性的,有些問題則是普遍性的,從學(xué)生遇到的最多的問題中,整理出了這5個(gè)具有普遍性的問題,希望對(duì)有關(guān)C語(yǔ)言的教與學(xué)能起到一定的幫助。
參考文獻(xiàn):
[1] 薛勝軍. 計(jì)算機(jī)組成原理[M]. 武漢:華中科技大學(xué)出版社,2000.
[2] 王誠(chéng), 劉衛(wèi)東,宋佳興. 計(jì)算機(jī)組成與設(shè)計(jì)[M]. 3版. 北京:清華大學(xué)出版社,2008.
[3] 嚴(yán)蔚敏,吳偉民.數(shù)據(jù)結(jié)構(gòu)(C語(yǔ)言版)[M].北京:清華大學(xué)出版社,2006.
[4] 譚浩強(qiáng). C程序設(shè)計(jì)[M]. 北京:清華大學(xué)出版社,2006.
[5] David J. Kruglinski. Visual. C++技術(shù)內(nèi)幕[M]. 4版. 北京:清華大學(xué)出版社,2001.
[6] H.M.Deitel,P.J.Deitel. C程序設(shè)計(jì)教程[M]. 北京:機(jī)械工業(yè)出版社,2001.