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