巨同升
(山東理工大學 山東淄博 255049)
“C語言程序設計”這門課程在國內(nèi)高校普遍開設已有近三十年,課程的建設和研究取得了長足的進步,涌現(xiàn)出了數(shù)量眾多、各具特色的C語言教材。盡管如此,在許多C語言教材中還或多或少地存在著一些不準確甚至是值得商榷的說法。下面將對國內(nèi)教材中常見的幾種說法進行辨析,并期望與廣大同行商榷。
有的教材中說:“C語言中沒有輸入輸出語句,只有輸入輸出函數(shù)。”果真如此嗎?
完整的C語言是由語言標準和標準庫兩部分組成的[1]。語言標準相當于C語言的內(nèi)核,標準庫(主體是庫函數(shù))相當于C語言的擴展部分。這種設計極大地增強了C語言實現(xiàn)的靈活性和程序的可移植性,因為可以根據(jù)某類計算機的硬件特點而單獨修改C語言標準庫部分的實現(xiàn)。
在C語言的語言標準部分的確沒有輸入輸出語句,也沒有輸入輸出函數(shù)。不過在C語言的標準庫部分,明確地定義了各種輸入輸出函數(shù),從而可以在C語言程序中以語句的形式來調(diào)用這些輸入輸出函數(shù)。
既然這種函數(shù)調(diào)用語句可以實現(xiàn)輸入輸出的功能,將它們稱為“輸入輸出語句”(而不必稱為輸入輸出函數(shù)調(diào)用語句)也是順理成章的。
因此正確的說法是:在C語言的語言標準部分沒有輸入輸出語句,但在C語言的標準庫部分有輸入輸出函數(shù),從而在完整的C語言中有輸入輸出語句。
有的教材中說:“在C語言中,八進制和十六進制整數(shù)只有正數(shù),沒有負數(shù)?!边@種說法是有問題的。實際上,在C語言中,八進制和十六進制整數(shù)既可以使用正數(shù),也可以使用負數(shù)。下面的程序就是一個很好的證據(jù)。
#include <stdio.h>
int main(void)
{int a,b,c,d;
a=0127;
b=-0127;
c=0x1af;
d=-0x1af;
printf("a=%d,b=%d,c=%d,d=%d ",a,b,c,d);
printf("a=%o,b=%o,c=%x,d=%x ",a,b,c,d);
return 0;
}
該程序的運行結(jié)果如下圖所示。
從該程序的運行結(jié)果可以發(fā)現(xiàn),程序中可以使用八進制和十六進制的負整數(shù),但是不能以負數(shù)形式輸出八進制和十六進制的整數(shù)。
因此正確的說法是:在C語言中,以八進制和十六進制形式輸出整數(shù)時,只能輸出正數(shù)和0,不能輸出負數(shù)。
有的教材中說:“在C語言中,整數(shù)是以二進制補碼的形式在內(nèi)存中存儲的。”其實,這種說法是不準確的。在C語言中,有符號整數(shù)的確是以二進制補碼形式在內(nèi)存中存儲的,其最高位為符號位,用以表示該整數(shù)的正負。但是對于無符號整數(shù)來說,由于不存在負數(shù),并不需要表示正負的符號位,因而它的每一位都是數(shù)值位,這種表示形式應稱為“無符號二進制形式”。
因此正確的說法是:在C語言中,有符號整數(shù)是以二進制補碼形式在內(nèi)存中存儲的,無符號整數(shù)是以無符號二進制形式在內(nèi)存中存儲的。
國內(nèi)絕大多數(shù)的C語言教材都說:“在C語言中,后自增(減)運算符的優(yōu)先級與前自增(減)運算符相同?!逼鋵崳@種說法是有問題的。
在傳統(tǒng)C語言(即C89標準之前的C語言)規(guī)范中,后自增(減)的優(yōu)先級的確是與前自增(減)相同的,都是2級。但是,從C89標準開始,已經(jīng)將后自增(減)的優(yōu)先級調(diào)整為1級,而前自增(減)的優(yōu)先級依然為2級,從而使得后自增(減)的優(yōu)先級高于前自增(減)[1-2]。遺憾的是,這種調(diào)整并未在國內(nèi)的絕大多數(shù)C語言教材中反映出來。
為什么scanf函數(shù)中的第二個參數(shù)只能是變量的地址,而不能是變量名本身呢?大多數(shù)教材中并未給出解釋,而有的教材則認為是為了提高編程的靈活性,使得scanf函數(shù)中的第二個參數(shù)既可以是若干個變量的地址,也可以是指向某些內(nèi)存單元的指針。其實,真正的原因在于C語言中函數(shù)參數(shù)的單向傳遞規(guī)則,即只能將實參的值傳遞給對應的形參,而不能將形參的值傳遞給對應的實參[3]。
在程序中調(diào)用scanf函數(shù)完成數(shù)據(jù)的輸入,是通過執(zhí)行該函數(shù)的函數(shù)體語句實現(xiàn)的。不過在執(zhí)行該函數(shù)的函數(shù)體時所輸入的數(shù)據(jù),首先存放于函數(shù)體中定義的局部變量中。調(diào)用scanf函數(shù)時,若直接以待存儲數(shù)據(jù)的變量名作為實參,則函數(shù)體中局部變量(包括形參)的值并不能直接傳遞給實參;若改用待存儲數(shù)據(jù)的變量的地址作為實參,就可以通過跨函數(shù)間接引用的方式,將函數(shù)體中局部變量的值傳遞給主調(diào)函數(shù)中的變量了。
在大多數(shù)教材中,在調(diào)用malloc函數(shù)時,總是要對其返回值進行強制類型轉(zhuǎn)換。例如:
int*p;
p=(int*)malloc(sizeof(int));
這些教材中認為這種強制類型轉(zhuǎn)換是必不可少的,其實并非如此。在傳統(tǒng)C語言(即C89標準之前的C語言)規(guī)范中,malloc函數(shù)的返回值為char*類型,由于這種類型與其他的指針類型都不是賦值兼容的,因此不能直接進行賦值運算,而必須先進行強制類型轉(zhuǎn)換,再進行賦值運算[2]。
不過,從C89標準開始,已經(jīng)將malloc函數(shù)的返回值類型改為void*類型。void*是C89標準中定義的通用指針類型,通用指針與所有其他類型的指針都是賦值兼容的,即可以不經(jīng)過強制類型轉(zhuǎn)換而直接相互賦值[2]。例如:
int*p;
p=malloc(sizeof(int));
此外,calloc函數(shù)和realloc函數(shù)的用法與malloc函數(shù)是類似的。
C語言中的各類文件打開方式均包括兩種,如讀方式包括“r”和“rb”,寫方式包括“w”和“wb”。在大多數(shù)C語言教材中,將文件的這兩種打開方式稱為“打開文本文件”與“打開二進制文件”。
這種叫法很容易使人誤解為用第一種打開方式創(chuàng)建的就是文本文件,用第二種打開方式創(chuàng)建的就是二進制文件。其實一個新創(chuàng)建的文件是文本文件還是二進制文件的決定因素,是向文件中寫入數(shù)據(jù)的函數(shù),而不是文件的打開方式[3]。一般而言,使用fprintf、fputc和fputs等函數(shù)創(chuàng)建的文件,是文本文件;而使用fwrite函數(shù)創(chuàng)建的文件,則是二進制文件。相應地,fscanf、fgetc和fgets等函數(shù)用于讀取文本文件;而fread函數(shù)則用于讀取二進制文件。
既然如此,為什么還要將文件的打開方式區(qū)分為這兩種方式呢?其實,這源于兩類操作系統(tǒng)對于回車換行的不同處理方式。在第一類操作系統(tǒng)(如UNIX和Linux)的文本編輯軟件中,采用與C語言相同的處理方式,只需用一個換行符,即可實現(xiàn)回車換行。而在第二類操作系統(tǒng)(如DOS和Windows)的文本編輯軟件中,則采用與C語言不同的處理方式,需要用一個回車符和一個換行符的組合,方可實現(xiàn)回車換行。
因此,在第二類操作系統(tǒng)中,當用第一種方式打開文件并對文件進行寫入操作時,將會把每一個換行符替換為一個回車符和一個換行符的組合;當用第一種方式打開文件并對文件進行讀出操作時,將會進行相反的替換。然而,當用第二種方式打開文件并對文件進行寫入和讀出操作時,將不會對字符進行任何替換。
而在第一類操作系統(tǒng)中,第一種打開方式與第二種打開方式是沒有區(qū)別的。不論用哪種方式打開文件,在對文件進行寫入和讀出操作時,都不會對字符進行任何替換??梢?,這兩種打開方式均是既可以打開文本文件,也可以打開二進制文件,區(qū)別在于對回車換行的處理方式不同。因此,正確叫法應該是“文本打開方式”與“二進制打開方式”[3]。