使用C語言的一半價值在于使用其標(biāo)準(zhǔn)庫函數(shù),。當(dāng)然,,靈活的for循環(huán)以及數(shù)組和
指針之間的相似性也是C語言的重要價值。在解決實際問題時,能方便地操作字符串和文件等對象是最重要的,,有些語言能出色地完成其中的一部分工作,,另一些語
言能出色地完成其中的另一部分工作,然而,,沒有幾種語言能象C語言那樣能出色地完成全部工作,。
c標(biāo)準(zhǔn)庫中還缺少很多函數(shù),例如投有圖形函數(shù),,甚至沒有全屏幕文本操作函數(shù),,signal機制也相當(dāng)弱(見12.10),并且根本沒有對多任務(wù)或使用常規(guī)
內(nèi)存以外的內(nèi)存提供支持,。盡管C標(biāo)準(zhǔn)庫存在上述缺陷,,但它畢竟為所有的程序都提供了一套基本功能,不管這些程序是運行在多任務(wù),、多窗口的環(huán)境下,,還是運行
在簡單的終端上,或者是運行在一臺昂貴的烤面包機上,。 C標(biāo)準(zhǔn)庫中所缺的函數(shù)可以從其它途徑獲得,,例如編譯程序開發(fā)商和第三方的函數(shù)庫都會提供一些函數(shù),這些函數(shù)都是事實上的標(biāo)準(zhǔn)函數(shù),。然而,,標(biāo)準(zhǔn)庫中的函數(shù)已經(jīng)為程序設(shè)計提供了一個非常堅實的基礎(chǔ)。
12.1 為什么應(yīng)該使用標(biāo)準(zhǔn)庫函數(shù)而不要自己編寫函數(shù)? 標(biāo)準(zhǔn)庫函數(shù)有三點好處:準(zhǔn)確性,、高效性和可移植性,。 準(zhǔn)確性:編譯程序的開發(fā)商通常會保證標(biāo)準(zhǔn)庫函數(shù)的準(zhǔn)確性。更重要的是,。至少開發(fā)商做了全面的檢測來證實其準(zhǔn)確性,,這比你所能做到的更加全面(有些昂貴的測試工具能使這項工作更加容易)。
高效性:優(yōu)秀的C程序員會大量使用標(biāo)準(zhǔn)庫函數(shù),,而內(nèi)行的編譯程序開發(fā)商也知道這一點,。如果開發(fā)商能提供一套出色的標(biāo)準(zhǔn)庫函數(shù),他就會在競爭中占優(yōu)勢,。當(dāng)對
相互競爭的編譯程序的效率進行比較時,,一套出色的標(biāo)準(zhǔn)庫函數(shù)將起到?jīng)Q定性的作用。因此,,開發(fā)商比你更有動力,,并且有更多的時間,去開發(fā)一套高效的標(biāo)準(zhǔn)庫函
數(shù),。 可移植性:在軟件要求不斷變化的情況下,,標(biāo)準(zhǔn)庫函數(shù)在任何計算機上,,對任何編譯程序都具有同樣的功能,并且表達同樣的含義,,因此它們是C程序員屈指可數(shù)的幾種依靠之一,。
有趣的是,你很難找到一項關(guān)于標(biāo)準(zhǔn)庫函數(shù)的最標(biāo)準(zhǔn)的信息,。對于每一個函數(shù),,都需要有一個(在極少數(shù)情況下需要兩個)保證能將該函數(shù)的原型提供給你的頭文件
(在調(diào)用任何一個函數(shù)時,都應(yīng)該包含其原型,,見8.2),。有趣的是什么呢?這個頭文件可能并不是真正包含該函數(shù)原型的文件,在有些(非常糟糕!)情況下,,
甚至由編譯程序手冊推薦的頭文件都不一定正確,。對于宏定義,typedef和全局變量,,同樣會發(fā)生這種情況,。 為了找到“正確的”頭文件,你可以在一份ANSI/ISO c標(biāo)準(zhǔn)的拷貝中查閱相應(yīng)的函數(shù),。如果你手頭沒有這樣一份拷貝,,你可以使用表12.2。 請參見: 8.2為什么要使用函數(shù)原型? 12.2 為了定義我要使用的標(biāo)準(zhǔn)庫函數(shù),,我需要使用哪些頭文件?
12.2 為了定義我要使用的標(biāo)準(zhǔn)庫函數(shù),,我需要使用哪些頭文件? 你需要使用ANSI/ISO標(biāo)準(zhǔn)規(guī)定的你應(yīng)該使用的那些頭文件,見表12.2,。
有趣的是,,這些文件并不一定定義你要使用的函數(shù),。例如,如果你要使用宏EDOM,你的編譯程序保證你能通過包含(errno.h)得到這個宏,,而
(errno.h)可能定義了宏EDOM,,也可能只包含定義這個宏的頭文件,。更糟的是,,編譯程序的下一個版本可能會在另一個地方定義宏EDOM。 因此,,你不用去尋找真正定義一個函數(shù)的頭文件并使用這個文件,,而應(yīng)該使用那個被假定為定義了該函數(shù)的頭文件,這樣做是肯定可行的,。 有幾個名字在多個頭文件中被定義:NULL,,size_t和wchar_t。如果你需要其中一個名字的定義,,可以使用任意一個定義了該名字的頭文件((stddef.h>是一個較好的選擇,,它不僅小,,而且包含了常用的宏定義和類型定義),。
表12.2標(biāo)準(zhǔn)庫函數(shù)的頭文件 ---------------------------------------------------------------------- 函數(shù) 頭文件 ---------------------------------------------------------------------- abort stdlib. h abs stdlib. h acos math. h asctime time. h asin math. h assert assert.h atan math. h atan2 math. h atexit stdlib. h atof stdlib. h atoi stdlib. h atol stdlib. h bsearch stdlib. h BUFSIZ stdio. h calloc stdlib. h ceil math. h clearerr stdio. h clock time. h CLOCKS-PER-SEC time. h clock_t time. h cos math. h cosh math. h ctime time. h difftime time. h div stdlib. h div_t stdlib. h EDOM errno. h EOF stdio. h ERANGE errno. h errno errno. h exit stdlib. h EXIT_FAILURE stdlib. h EXIT_SUCCESS stdlib. h exp math. h fabs math. h fclose stdio. h feof stdio.h ferror stdio.h fflush stdio. h fgetc stdio.h fgetpos stdio. h fgets stdio.h FILE stdio. h FILENAME-MAX stdio. h floor math. h fmod math. h fopen stdio. h FOPEN_MAX stdio. h fpos_t stdio. h fpnntf stdio. h fputc stdio.h fputs stdio. h head stdio. h free stdlib. h freopen stdio. h frexp math. h fscanf stdio. h fseek stdio. h fsetpos stdio. h ftell stdio. h fwrite stdio. h getc stdio.h getchar stdio. h getenv stdlib. h gets stdio.h gmtime time. h HUGE-VAL math.h _IOFBF stdio. h _IOLBF stdio. h _IONBF stdio. h isalnum ctype. h isalpha ctype. h iscntrl ctype. h isdigit ctype. h isgraph ctype. h islower ctype. h isprint ctype. h ispunct ctype. h isspace ctype. h isupper ctype. h isxdigit ctype. h jmp_buf setjmp. h labs stdlib. h LC_ALL locale. h LC_COLLATE locale. h LC_CTYPE locale. h LC_MONETARY locale. h LC_NUMERIC locale. h LC_TIME locale. h struct lconv locale. h ldexp math. h ldiv stdlib. h ldiv_t stdlib. h localeconv locale. h localtime time. h log math. h log10 math. h longjmp setjmp. h L_tmpnam stdio. h malloc stdlib. h mblen stdlib. h mbstowcs stdlib. h mbtowc stdlib. h MB_CUR_MAX stdlib. h memchr string. h memcmp string. h memcpy string. h memmove string. h memset string. h mktime time. h modf math. h NDEBUG assert. h NULL locale. h.stddef. h.stdio. h.stdlib. h.string. h.time. h offsetof stddef. h perror stdio.h pow math. h printf stdio.h ptrdiff_t stddef. h putc stdio. h putchar stdio. h puts stdio. h qsort stdlib. h raise signal. h rand stdlib. h RAND_MAX stdlib. h realloc stdlib. h remove stdio. h rename stdio. h rewind stdio. h scanf stdio.h SEEK_CUR stdio. h SEEK_END stdio. h SEEK_SET stdio. h setbuf stdio. h setjmp setjmp. h setlocale locale. h setvbuf stdio. h SIGABRT signal. h SIGFPE signal. h SIGILL signal. h SIGINT signal. h signal signal. h SIGSEGV signal. h SIGTERM signal. h sig_atomic_t signal. h SIG_DFL signal. h SIG_ERR signal. h SIG_IGN signal. h sin math. h sinh math. h size_t stddef. h.stdlib. h.string. h sprintf stdio. h sqrt math. h srand stdlib. h sscanf stdio. h stderr stdio.h stdin stdio. h stdout stdio. h strcat string. h strchr string. h strcmp string. h strcoll string. h strcpy string. h strcspn string. h strerror string.h strftime time. h strlen string. h strncat string. h strncmp string. h strncpy string. h strpbrk string. h strrchr string. h strspn string. h strstr string. h strtod stdlib. h strtok string. h strtol stdlib. h strtoul stdlib. h strxfrm string. h system stblib. h tan math. h tanh math. h time time. h time_t time. h struct tm time. h tmpfile stdio. h tmpnam stdio. h TMP_MAX stdio. h tolower ctype. h toupper ctype. h ungetc stdio. h va_arg stdarg. h va_end stdarg. h valist stdarg. h va_ start stdarg. h vfprintf stdio. h vprintf stdio. h vsprintf stdio. h wchar_t stddef. h. stdlib. h wcstombs stdlib. h wctomb stdlib. h -------------------------------------------------------------------------
請參見: 5.12 #include(file~和#include“file”有什么不同? 12.1 為什么應(yīng)該使用標(biāo)準(zhǔn)庫函數(shù)而不要自己編寫函數(shù)?
12.3 怎樣編寫參數(shù)數(shù)目可變的函數(shù)? 你可以利用(stdarg.h)頭文件,,它所定義的一些宏可以讓你處理數(shù)目可變的參數(shù)。 注意:這些宏以前包含在名為(varargs.h)或類似的一個頭文件中,。你的編譯程序中可能還有這樣一個文件,,也可能沒有;即使現(xiàn)在有,,下一個版本中可能就沒有了,。因此,還是使用(stadrg.h)為好,。
如果對傳遞給c函數(shù)的參數(shù)不加約束,,就沒有一種可移植的方式讓c函數(shù)知道它的參數(shù)的數(shù)目和類型。如果一個c函數(shù)的參數(shù)數(shù)目不定(或類型不定),,就需要引入
某種規(guī)則來約束它的參數(shù),。例如,printf()函數(shù)的第一個參數(shù)是一個字符串,,它將指示其后都是一些什么樣的參數(shù): printf(" Hello, world! /n" ); /* no more arguments */ printf("%s/n" , "Hello, world!"); /* one more string argument */ printf("%s, %s/n" , "Hello" , "world!"); /* two more string arguments */ printf("%s, %d/n", "Hello", 42); /* one string, one int */
例12.3給出了一個簡單的類似printf()的函數(shù),,它的第一個參數(shù)是格式字符串,根據(jù)該字符串可以確定其余參數(shù)的數(shù)目和類型,。與真正的
printf()函數(shù)一樣,,如果格式字符串和其余參數(shù)不匹配,那么結(jié)果是沒有定義的,,你無法知道程序此后將做些什么(但很可能是一些糟糕的事情),。 例12.3一個簡單的類似printf()的函數(shù) # include <stdio. h> # include <stdlib. h> # include <string. h> # include <stdarg. h>
static char * int2str (int n) { int minus = (n < 0) ; static char buf[32]; char * p = &buf[3l]; if (minus) n = —n; *P = '/0', do { *---p = '0'+n%10; n/=10; } while (n>0); if (minus) *- - p = '-'; return p; } /* * This is a simple printf-like function that handles only * the format specifiers %%, %s, and %d. */ void simplePrintf(const char * format, . . . ) { va_list ap; / * ap is our argument pointer. * / int i; char * s ; /* * Initialize ap to start with the argument * after "format" */ va_start(ap, format); for (; * format; format + + ) { if (* format !='%'){ putcharC * format); continue; } switch ( * ++format) { case 's' : / * Get next argument (a char * ) * / s = va_arg(ap, char * ); fputs(s, stdout); break; case 'd':/ * Get next argument (an int) * / i = va_arg(ap, int); s = int2str(i) ; fputs(s, stdout) ; break s case ' /0' : format---; breaks default :putchar ( * format) ; break; } } / * Clean up varying arguments before returning * / va_end(ap); } void main() { simplePrintK "The %s tax rate is %d%%. /n" , "sales", 6); }
請參見: 12.2為了定義我要使用的標(biāo)準(zhǔn)庫函數(shù),我需要使用哪些頭文件?
12.4 獨立(free—standing)環(huán)境和宿主(hosted)環(huán)境之間有什么區(qū)別?
并不是所有的C程序員都在編寫數(shù)據(jù)庫管理系統(tǒng)和字處理軟件,,有些C程序員要為嵌入式系統(tǒng)(embedded
system)編寫代碼,,例如防抱死剎車系統(tǒng)和智能型的烤面包機。嵌入式系統(tǒng)可以不要任何類型的文件系統(tǒng),,也可以基本上不要操作系統(tǒng),。ANSI/1SO標(biāo)
準(zhǔn)稱這樣的系統(tǒng)為“獨立(free—standing)”系統(tǒng),并且不要求它們提供除語言本身以外的任何東西,。與此相反的情況是程序運行在RC機,、大型機
或者介于兩者之間的計算機上,這被稱為“宿主(hosted)”環(huán)境,。
即使是開發(fā)獨立環(huán)境的程序員也應(yīng)該重視標(biāo)準(zhǔn)庫:其一,,獨立環(huán)境往往以與標(biāo)準(zhǔn)兼容的方式提供某種功能(例如求平方根函數(shù),重新設(shè)計該函數(shù)顯然很麻煩,,因而毫
無意義),;其二,,在將嵌入式程序植入烤面包機這樣的環(huán)境之前,通常要先在PC機上測試該程序,,而使用標(biāo)準(zhǔn)庫函數(shù)能增加可同時在測試環(huán)境和實際環(huán)境中使用的
代碼的總量,。
請參見: 12.1為什么應(yīng)該使用標(biāo)準(zhǔn)庫函數(shù)而不要自己編寫函數(shù)?
12.5 對字符串進行操作的標(biāo)準(zhǔn)庫函數(shù)有哪些? 簡單的回答是:(string.h)中的函數(shù)。 C語言沒有固有的字符串類型,,但c程序可以用以NUL(’\O’)字符結(jié)束的字符數(shù)組來代替字符串,。 C程序(以及c程序員)應(yīng)該保證數(shù)組足夠大,以容納所有將要存入的內(nèi)容,。這一點可以通過以下三種方法來實現(xiàn): (1)分配大量的空間,,并假定它足夠大,不考慮它不夠大時將產(chǎn)生的問題(這種方法效率高,,但在空間不足時會產(chǎn)生嚴(yán)重的問題),; (2)總是分配并重新分配所需大小的空間(如果使用realloc()函數(shù),這種方法的效率不會太低,;這種方法需要使用大量代碼,,并且會耗費大量運行時間); (3)分配應(yīng)該足夠的空間,,并禁止占用更多的空間(這種方法既安全又高效,,但可能會丟失數(shù)據(jù))。 注意:C++提供了第4種方法:直接定義一種string類型,。由于種種原因,,用C++完成這項工作要比用C簡單得多。即便如此,,用C++還是顯得有點麻煩,。幸運的是,盡管定義一個標(biāo)準(zhǔn)的C++ string類型并不簡單,,但這種類型使用起來卻非常方便,。
有兩組函數(shù)可用于C語言的字符串處理。第一組函數(shù)(strcpy,,strcat,,等等)按第一種或第二種方法工作。這組函數(shù)完全按需要拷貝字符串或使用內(nèi)
存,,因此最好留出所需的全部空間,,否則程序就可能出錯。大多數(shù)C程序員使用第一組函數(shù),。第二組函數(shù)(strncpy,,strncat,等等)按第三種方法
工作,。這組函數(shù)需要知道應(yīng)該使用多大的空間,,并且永遠不會占用更多的空間,,因此它們會忽略所有已無法容納的數(shù)據(jù)。 函數(shù)strncpy()和strncat()中的參數(shù)“n”(第三個)的意義是不同的: 對strncpy()函數(shù)來說,,它意味著只能使用“n”個字符的空間,,包括末尾的NUL字符。 strncpy()
函數(shù)也恰好只拷貝“n”個字符,。如果第二個參數(shù)沒有這么多字符,,strncpy()函數(shù)會用NUL字符填充剩余的空間,。如果第二個參數(shù)有多于“n”個的字
符,,那么strncpy()函數(shù)在還沒有拷貝到NUL字符之前就結(jié)束工作了。這意味著,,在使用strncpy()函數(shù)時,,你應(yīng)該總是自己在目標(biāo)字符串的末
尾加上NUL字符,而不要指望strncpy()函數(shù)為你做這項工作,。 對strncat()函數(shù)來說,,它意味著最多只能拷貝“n”個字符,如果需要還要加上一個NUL字符,。因為你真正知道的是目標(biāo)字符串能存放多少個字符,,所以通常你要用strlen()函數(shù)來計算可以拷貝的字符數(shù)。 函數(shù)strncpy()和strncat()之間的區(qū)別是“歷史性”的(這是一個技術(shù)用語,,指的是“它對某些人確實起到了一定的作用,,并且它可能是處理問題的正確途徑,但為什么正確至今仍然說不清楚”),。 例12.5a給出了一個使用strncpy()和strncat()函數(shù)的程序,。
.注意:你應(yīng)該去了解一下"string-n”函數(shù),雖然它們使用起來有些困難,,但用它們編寫的程序兼容性更好,,錯誤更少。
如果你愿意的話,,可以用函數(shù)strcpy()和strcat()重新編寫例12.5a中的程序,,并用很長的足以溢出緩沖區(qū)的參數(shù)運行它。會出現(xiàn)什么現(xiàn)象
呢?計算機會掛起嗎?你會得到"GeneralProtection Exception”或內(nèi)存信息轉(zhuǎn)儲這樣的消息嗎?請參見7.24中的討論,。 例12.5a使用"string—n”函數(shù)的一個例子 # include <stdio. h> # include <string. h> /* Normally, a constant like MAXBUF would be very large, to help ensure that the buffer doesn't overflow. Here, it's very small, to show how the "string-n" functions prevent it from ever overflowing. */ # define MAXBUF 16 int main (int argc, char* * argv) { char buf[MAXBUF]; int i; buf[MAXBUF - 1] = '/0'; strncpy(buf, argv[0], MAXBUF-1); for (i = 1; i<argc; ++i) { strncat(buf, " " , MAXBUF -1 - strlen (buf) ) ; strncat(buf, argv[i], MAXBUF -1 - strlen (buf ) ) ; } puts (buf ); return 0; }
注意:許多字符串函數(shù)都至少有兩個參數(shù),,在描述它們時,與其稱之為“第一個參數(shù)”和“第二個參數(shù)”,,還不如稱之為“左參數(shù)”和“右參數(shù)”,。 函數(shù)strcpy()和strncpy()用來把字符串從一個數(shù)組拷貝到另一個數(shù)組,即把右參數(shù)的值拷貝到左參數(shù)中,,這與賦值語句的順序是一樣的,。 函數(shù)strcat()和strncat()用來把一個字符串連接到另一個字符串的末尾,。例如,如果數(shù)組a1的內(nèi)容為“dog”,,數(shù)組a2的內(nèi)容為“wood”,,那么在調(diào)用strcat(al,a2)后,,a1將變?yōu)椤癲ogwood”,。 函數(shù)strcmp()和strncmp()用來比較兩個字符串。當(dāng)左參數(shù)小于,、等于或大于右參數(shù)時,,它們都分別返回一個小于、等于或大于零的值,。常見的比較兩個字符串是否相等的寫法有以下兩種: if (strcmp(sl, s2)) { / * si !=s2 * / } 和 if (! strcmp(s1, s2)) { /* s1 ==s2 * / } 上述代碼可能并不易讀,,但它們是完全有效并且相當(dāng)常見的c代碼,你應(yīng)該記住它們,。如果在比較字符串時還需要考慮當(dāng)前局部環(huán)境(locale,,見12.8),則要使用strcoll()函數(shù),。
有一些函數(shù)用來在字符串中進行檢索(在任何情況下,,都是在左參數(shù)或第一個參數(shù)中進行檢索)。函數(shù)strchr()和strrchr()分別用來查找某個字
符在一個字符串中第一次和最后一次出現(xiàn)的位置(如果函數(shù)strchr()和strrchr()有帶“n”字母的版本,,那么函數(shù)memchr()和
memrchr()是最接近這種版本的函數(shù)),。函數(shù)strspn()、strcspn()(“c”表示"complement")和strpbrk()用
來查找包含指定字符或被指定字符隔開的子字符串: n = strspn("Iowa" , "AEIOUaeiou"); / * n = 2( "Iowa" starts with 2 vowels * / n=strcspn("Hello world" ,"/t" ) ; / * n = 5; white space after 5 characters * / p = strbrk("Hellb world" ,"/t" ) ; / * p points to blank * /
函數(shù)strstr()用來在一個字符串中查找另一個字符串: p = strstr("Hello world", "or"); / * p points to the second "or" * /
函數(shù)strtok()按照第二個參數(shù)中指定的字符把一個字符串分解為若干部分,。函數(shù)strtok()具有“破壞性”,,它會在原字符串中插入NUL字符(如
果原字符串還要做其它的改變,應(yīng)該拷貝原字符串,,并將這份拷貝傳遞給函數(shù)strtok()),。函數(shù)strtok()是不能“重新進入”的,你不能在一個信
號處理函數(shù)中調(diào)用strtok()函數(shù),,因為在下一次調(diào)用strtok()函數(shù)時它總是會“記住”上一次被調(diào)用時的某些參數(shù),。strtok()函數(shù)是一個
古怪的函數(shù),但它在分解以逗號或空白符分界的數(shù)據(jù)時是非常有用的,。例12.5b給出了一個程序,,該程序用strtok()函數(shù)把一個句子中的單詞分解出
來: 例12.5b一個使用strtok()的例子 # include <stdio. h> # include <string. h> static char buf[] = "Now is the time for all good men . . . " ; int main() { char * p; p = strtok(buf, " ") ; while (p ) { printf("%s/n" ,p); p = strtok(NULL, " "); } return 0; }
請參見: 4.18怎樣讀寫以逗號分界的文本? 第6章字符串操作 7.23 NULL和NUI。有什么不同? 9.9 字符串和數(shù)組有什么不同? 12.8 什么是“局部環(huán)境(10cale)”? 12.10 什么是信號(signal)?用信號能做什么?
12.6 對內(nèi)存進行操作的標(biāo)準(zhǔn)庫函數(shù)有哪些? 有些函數(shù)可用來拷貝,、比較和填寫任意的內(nèi)存塊,,它們都帶有void。類型(并不指向任何具體類型的指針)的參數(shù),可以處理指向任何類型的指針,。
有兩個函數(shù)(有點象strncpy()函數(shù))可用來拷貝信息,。第一個函數(shù)是memmove(),它把內(nèi)存中的內(nèi)容從一個地方拷貝到另一個地方,,不管源區(qū)域
和目標(biāo)區(qū)域是否有相互覆蓋的部分,。為什么要提到這兩個區(qū)域是否相互覆蓋呢?假設(shè)緩沖區(qū)中已有部分數(shù)據(jù),而你要把它們移到“后面”,,以騰出緩沖區(qū)前面的空
間,。例12.6給出了一個試圖進行這項工作的程序,但它做得并不正確: 例12.6一個試圖移動數(shù)據(jù),,結(jié)果毀掉數(shù)據(jù)的程序 static char buf[] = {'R','I','G','H','T','/0','-','-','-'}; int main() { int i; for (i = 0; i<6; ++i) { buf[i + 3] = buf[i]i } }
上述程序的意圖是把buf從"RIGHT"改為“RIGRIGHT”,,這樣就可以在前面三個字節(jié)中存入其它數(shù)據(jù)。不幸的是,,程序并沒有真正實現(xiàn)這個意圖,。如果把for循環(huán)展開(或者通過調(diào)試程序來觀察程序正在做什么),,你就會發(fā)現(xiàn)程序?qū)嶋H上是在這樣做: buf[3] = buf[0]; buf[4] = buf[l]; buf[5] = buf[2]; buf[6] = buf[3]; buf[7] = buf[4]; buf[8] = buf[5]; buf[9] = buf[6];
數(shù)據(jù)的移動效果如圖12.6a所示(新拷貝的數(shù)據(jù)用粗黑體表示)——該程序毀掉了它原來想移動的某些數(shù)據(jù),。
R I G H T /0 - - - R I G R T /0 - - - R I G R I /0 - - - R I G R I G - - - R I G R I G R - - R I G R I G R I - R I G R I G R I G
圖12·6a“移動”相互覆蓋的數(shù)據(jù)的錯誤方法
在移動或拷貝相互覆蓋的數(shù)據(jù)時,有這樣一個簡單的原則:如果源區(qū)域和目標(biāo)區(qū)域相互覆蓋,,并且源區(qū)域在目標(biāo)區(qū)域的前面,,則應(yīng)該從源區(qū)域的末尾開始按逆向順序
依次移動數(shù)據(jù),直到達到源區(qū)域的頭部,;如果源區(qū)域在目標(biāo)區(qū)域的后面,,則應(yīng)該從源區(qū)域的頭部開始移動數(shù)據(jù),直到達到源區(qū)域的末尾,。請看圖12.6b,。 R I G H T /0 - - - R I G H T /0 - - /n R I G H T /0 - T /0 R I G H T /O H T /0 R I G H T G H T /0 R I G H I G H T /O R I G R I G H T /O < < < L E F T /O L < < L E F T /O L E < L E F T /O L E F L E F T /0 L E F T E F T /O L E F T /0 F T /O
圖12.6b“移動”相互覆蓋的數(shù)據(jù)的正確方法
解釋這些情況的目的是為了指出這樣一點:memmove()函數(shù)知道上述原則,它能保證用正確的方法拷貝數(shù)據(jù),,不管數(shù)據(jù)是否相互覆蓋,。如果在拷貝或移動數(shù)
據(jù)時你并不知道源區(qū)域和目標(biāo)區(qū)域是否相互覆蓋,你就應(yīng)該使用memmove()函數(shù),。如果你能確定它們并沒有相互覆蓋,,那么可以使用memcpy()函
數(shù),這樣能稍快一些,。 memcmp()函數(shù)與strncmp()函數(shù)基本相似,,只是它在遇到NUL字符時不會結(jié)束。memcmp()函數(shù)不能用來比較結(jié)構(gòu)的值,。假設(shè)你有下面這樣一個結(jié)構(gòu): struct foo{ short s,; long 1; } 并
且假設(shè)你的程序?qū)⑦\行在一個short類型為兩個字節(jié)(16位),long類型為4個字節(jié)(32位)的系統(tǒng)上,。在32位的計算機中,,許多編譯程序會在s和
l之間加入兩個字節(jié)的“無用信息”,以使I從下一個字的邊界開始,。如果你的程序運行在低位優(yōu)先(低位字節(jié)存放在低位地址中)的計算機上,,那么上述結(jié)構(gòu)展開
后可能會如下所示: struct foo byte[O] s的低位字節(jié) struct foo byte[1] s的高位字節(jié) struct foo byte[2] 無用信息(使l從一個long類型邊界開始) struct foo byte[3] 無用信息(使l從一個long類型邊界開始) struct foo byte[4] l的最低位字節(jié) struct foo byte[5] l的次低位字節(jié) struct foo byte[6] l的次高位字節(jié) struct foo byte[7] 1的最高位字節(jié) 用memcmp()函數(shù)比較具有相同的s和l值的兩個foo結(jié)構(gòu)時,其結(jié)果并不一定相等,,因為所加入的“無用信息”并不一定相同,。 memchr()函數(shù)與strchr()函數(shù)基本相似,只不過它是在指定的一塊內(nèi)存空間中查找一個字符串,,并且它在遇到第一個NUL字符時不會結(jié)束,。 memset()函數(shù)對所有的C程序員都是很有用的,它能把某種字節(jié)拷貝到指定的內(nèi)存空間中,。memset()函數(shù)的一種常見的用法是把某種結(jié)構(gòu)全部初始化為零字節(jié),。如果p是指向一個結(jié)構(gòu)的指針,那么語句memset(p,,'/0',,size01 * p); 將把p所指向的對象全部改寫為零(NUL或'/O')字節(jié)(那些使結(jié)構(gòu)成員從字邊界開始的“無用信息”也會被改寫,,但這樣做沒有關(guān)系,,因為這些信息沒有用,所以誰也不會在乎它們被改寫成什么樣子),。
請參見: 4.1當(dāng)errno為一個非零值時,,是否有錯誤發(fā)生? 4.3怎樣重定向一個標(biāo)準(zhǔn)流? 9.9字符串和數(shù)組有什么不同?
12.7 怎樣判斷一個字符是數(shù)字、字母或其它類別的符號? 在頭文件ctype.h中定義了一批函數(shù),,它們可用來判斷一個字符屬于哪一類別,。下面列出了這些函數(shù): --------------------------------------------------------------------------------------- 函數(shù) 字符類別 返回非零值的字符 --------------------------------------------------------------------------------------- isdigit() 十進制數(shù) 0--9 isxdigit() 十六進制數(shù) 0--9,a—f,,或A--F isalnum() 字母數(shù)字符號 0--9,,a--Z,或A--Z isalpha() 字母 a--Z或A--Z islower() 小寫字母 a--Z isupper() 大寫字母 A--Z isspace() 空白符 空格符,,水平制表符,,垂直制表符,換行符,,換頁符,,或回車符 isgraph() 非空白字符 任何打印出來不是空白的字符(ASCII碼從21到7E) isprint() 可打印字符 所有非空白字符,加上空格符 ispunct() 標(biāo)點符 除字母數(shù)字符號以外的所有非空白字符 iscntrl() 控制字符 除可打印字符外的所有字符(ASCII碼從00到1F,,加上7F) ---------------------------------------------------------------------------------------- 與前文提到過的使用標(biāo)準(zhǔn)庫函數(shù)的好處相似,,調(diào)用上述這些宏而不是自己編寫測試字符類別的程序也有三點好處。首先,這些宏運算速度快,,因為它們的實現(xiàn)方式通常都是利用位屏蔽技術(shù)來檢查一個表,,所以即使是進行一項相當(dāng)復(fù)雜的檢查,也比真正去比較字符的值要快得多,。 其次,,這些宏都是正確的。如果你自己編寫一個測試程序,,你很容易犯邏輯上或輸入上的錯誤,,例如引入了一個錯誤的字符(或漏掉了一個正確的字符)。
第三,,這些宏是可移植的,。信不信由你,并非所有的人都使用同樣的含PC擴充字符的ASCII字符集,。也許今天你還不太在意,,但是,當(dāng)你發(fā)現(xiàn)你的下一臺計算
機使用的是Unicode字符集而不是ASCII字符集,,你就會慶幸自己原來沒有按照字符集中的字符值來編寫程序,。
頭文件ctype.h中還定義了兩個可以對字母進行大小寫轉(zhuǎn)換的函數(shù),即函數(shù)toupper()和tolower(),。如果toupper()函數(shù)的參數(shù)
不是小寫字母或tolOWel"()函數(shù)的參數(shù)不是大寫字母,,那么這兩個函數(shù)的行為是沒有定義的,因此,,在調(diào)用這兩個函數(shù)之前,你應(yīng)該用函數(shù)
islower()或isupper()來檢查一下,。
請參見: 5.1什么是宏(macro)?怎樣使用宏? 6.2怎樣刪去字符串尾部的空格? 6.3怎樣刪去字符串頭部的空格? 20.18怎樣判斷一個字符是不是字母? 20.19怎樣判斷一個字符是不是數(shù)字?
12.8 什么是“局部環(huán)境(locale)”? 局部環(huán)境是對特定環(huán)境下程序要遵循的特定規(guī)則的一種描述,,它對程序的國際化很有幫助。
如果你要打印一筆錢的數(shù)目,,你總是使用美元符號嗎?不,,如果你的程序要在英國運行,你就要使用英鎊符號,。在有些國家,,貨幣符號要寫在錢數(shù)的前面,而在有些
國家,,貨幣符號要寫在錢數(shù)的后面,。一個負數(shù)的負號要放在哪里呢?在美國寫成1,234.56的一個數(shù)字,,在另外一些國家中可能要寫成1.234,,56。同
樣的值在不同的國家中會有不同的表示規(guī)則。時間和日期又是如何表示的呢?簡而言之,,也是因國而異,。如果一個程序員要編寫一個必須在全世界運行的程序,那么
這些情況就是使他頭疼的部分技術(shù)原因,。
幸運的是:部分差異已經(jīng)被標(biāo)準(zhǔn)化了,。C編譯程序支持不同的“局部環(huán)境”,即程序在不同地方的不同表示規(guī)則,。例如,,函數(shù)strcoll()(string
collate,字符串的依序整理)和strcmp()函數(shù)相似,,但它能反映出不同國家和語言對字符串值進行排序和整理(collate)的方式,。函數(shù)
setlocale()和localeconv()提供了這方面的支持。
不幸的是:并沒有一種標(biāo)準(zhǔn)化了的關(guān)于這些有趣的局部環(huán)境的清單,。你的編譯程序唯一能保證提供的只有“C”局部環(huán)境,。這是一種通用的美式英語規(guī)則,對于碼值
在32和127之間的ASCII字符,,這種規(guī)則工作得最好,。盡管如此,如果你想正確地編寫一個能在全世界運行的程序,,那么從局部規(guī)則這個角度來考慮問題就
是一個好的開端(接下來,,如果你能再找到幾種你的編譯程序能支持的局部環(huán)境,或者讓你的編譯程序接受你定義的幾種局部環(huán)境,,那就更好了),。
12.9 有沒有辦法從一個或多個函數(shù)中跳出? 在極少數(shù)確實需要這樣做的情況下,可以利用標(biāo)準(zhǔn)庫函數(shù)setjmp()和longjmp()實現(xiàn)一種能從一個或多個函數(shù)中跳出的跳轉(zhuǎn)(goto),。要正確地使用setjmp()和longjmp()函數(shù),,必須滿足幾個條件。
首先,,你必須包含setjmp.h頭文件,,該文件提供了setjmp()和longimp()函數(shù)的原型,并定義了jmp—buf類型,。你需要把一個
jmp—bur類型的變量作為一個參數(shù)傳遞給setjmp()和longjmp()函數(shù),,這個變量將包含使跳轉(zhuǎn)發(fā)生所需的信息。
其次,,你必須調(diào)用setjmp()函數(shù)來初始化jmp—bur變量,。如果setjmp()函數(shù)返回0,則說明jmp_buf變量已被初始化,;如果
setjmp()函數(shù)返回其它值,,則說明程序剛才通過調(diào)用longjmp()函數(shù)跳轉(zhuǎn)到了對應(yīng)于該值的位置,。在后一種情況下,setjmp()函數(shù)的返回
值就是程序傳遞給longjmp()函數(shù)的第二個參數(shù),。
從概念上講,,longjmp()函數(shù)的作用就好象是這樣:當(dāng)它被調(diào)用時,當(dāng)前正在執(zhí)行的函數(shù)便會返回,;然后,,調(diào)用這個函數(shù)的函數(shù)將返回;依此類推,,直到調(diào)
用setjmp()的函數(shù)成為正在執(zhí)行的函數(shù),。程序的執(zhí)行將跳轉(zhuǎn)到調(diào)用setjmp()函數(shù)的位置,并從setjmp()函數(shù)返回那一點繼續(xù)往下執(zhí)行,,但
此時setjmp()函數(shù)的返回值已被置為傳遞給longjmp()函數(shù)的第二個參數(shù),。
換句話說,如果函數(shù)f()調(diào)用了setjmp(),,然后又調(diào)用了函數(shù)g(),,而函數(shù)g()調(diào)用了函數(shù)h(),函數(shù)h()調(diào)用了longjmp(),,那么程
序運行起來就好象h()立即返回了,,然后g()立即返回,然后f()執(zhí)行一次回到調(diào)用setjmp()的位置的跳轉(zhuǎn),。
這就是說,,為了使對10ngjmp()的調(diào)用能正常工作,程序必須已經(jīng)調(diào)用setjmp(),,并且還沒有從調(diào)用setjmp()的函數(shù)中返回,。如果這些條
件得不到滿足,那么longjmp()的行為是沒有定義的(這意味著你的程序很可能會崩潰),。例12.9中的程序說明了setjmp()和
longjmp()的用法,。這個程序顯然是為此而設(shè)計的,因為如果不使用setjmp()和longjmp(),,程序就會更簡潔些??偟膩碚f,,當(dāng)你想使用
setjmp()和longjmp()時,最好先找一種可以不使用它們的編程方法,,因為它們?nèi)菀妆徽`用,,并且會使程序難于閱讀和維護。 例12.9 一個使用setjmp()和longjmp()的例子 # include <setjmp. h> # include <stdio. h> # include <string. h> # include <stdlib. h> # define RETRY_PROCESS 1 # define QUIT_PROCESS 2 jmp_buf env; int nitems; int procItem() { char buf[256]; if (gets (buf) &&.strcmp(buf, "done")) { if (strcmp(buf, "quit") ==0) longjmp (env, QUIT_PROCESS ); if (strcmp(buf, "restart") ==0) longjmp(env, RETRY_PROCESS); nitems+ + ; return 1; } return 0; } void process() { printf ("Enter items, followed by 'done'. /n") ; printf("At any time, you can type 'quit' to exit/n"); printf ("or 'restart' to start over again/n"); nitems = 0; while (procItem()) } void main() { for (; ;) { switch (setjmp(env)) { case 0: case RETRY_PROCESS: process () ; printf("You typed in %d items. /n" , nitems); break ; case QUIT_PROCESS: default: exit(O); } } }
請參見: 1.8 goto,,longjmp()和setjmp()之間有什么區(qū)別? 7.20 什么是棧(stack)?
12.10 什么是信號(signal)?用信號能做什么? 信號是程序執(zhí)行過程中出現(xiàn)的異常情況,。它可能是由程序中的錯誤造成的,,例如引用內(nèi)存中的一個非法地址;或者是由程序數(shù)據(jù)中的錯誤造成的,,例如浮點數(shù)被0除,;或者是由外部事件引發(fā)的,例如用戶按了Ctrl+Break鍵,。 你可以利用標(biāo)準(zhǔn)庫函數(shù)signal()指定要對這些異常情況采取的處理措施(實施處理措施的函數(shù)被稱為“信號處理函數(shù)”),。signal()的原型為: #include <signal.h> void(*signal(int hum,void(*func)(int)))(int),; 這恐怕是你在C標(biāo)準(zhǔn)函數(shù)庫中能見到的最復(fù)雜的說明了,。如果你先定義一個typedef,理解起來就容易一些了,。下面給出的sigHandler_t類型是指向一個程序的指針,,該函數(shù)有一個int類型的參數(shù),并且返回一個void類型: typedef void(*sigHandler_t)(int),; sigHandler_t signal(int num , sigHandler_t func),;
signal()有兩個參數(shù),分別為int類型和sigHandler_t類型,,其返回值為sigHandler_t類型,。以func參數(shù)形式傳遞給
signal()的那個函數(shù)將成為第num號異常情況的新的信號處理函數(shù)。signal()的返回值是信號hum原來的信號處理函數(shù),。在設(shè)置了一個暫時的
信號處理函數(shù)之后,,你可以利用該值恢復(fù)程序先前的行為。num的可能值依賴于系統(tǒng),,并且在signal.h中列出,。func的可能值可以是你的程序中的任
意函數(shù),或者是SIG_DFL和SLG_IGN這兩個特別定義的值之一,。SIG_DFL是指系統(tǒng)的缺省處理措施,,通常是暫停執(zhí)行程序;SIG_IGN表示
信號將被忽略,。
當(dāng)下面這行代碼被執(zhí)行后,,程序?qū)⒉蝗ロ憫?yīng)按Ctrl+Break鍵這個信號,除非修改signal()函數(shù),,使其重新響應(yīng)該信號,。盡管hum的可能值依賴
于系統(tǒng),但SIGINT這個值通常用來表示用戶試圖中斷程序運行的信號(在DOS下,,為Ctrl+C或Ctrl+Break),。 signal(SIGINT,SIG_IGN)
請參見: 20.16 怎樣使Ctrl+Break失效?
12.11 為什么變量名不能以下劃線開始? 凡是以兩個或一個下劃線開始,,后面緊跟著一個大寫字母的標(biāo)識符,,不管它出現(xiàn)在哪里,,都是保留給編譯程序或標(biāo)準(zhǔn)庫函數(shù)使用的。此外,,凡是以一個下劃線開始,,后面不管跟著什么內(nèi)容的標(biāo)識符,如果它出現(xiàn)在文件范圍內(nèi)(即它不是出現(xiàn)在一個函數(shù)內(nèi)),,那么它也是被保留的,。
如果你用一個保留的標(biāo)識符來作一個變量的名稱,結(jié)果是沒有定義的(程序可能無法編譯,,或者可以編譯但會崩潰),。即使你能非常幸運地找到一個目前還沒有被你
的編譯程序或函數(shù)庫使用的標(biāo)識符,你也應(yīng)該記住這樣的標(biāo)識符是保留起來供將來使用的,。因此,,最好還是避免使用以下劃線開始的變量名或函數(shù)名。
請參見: 19.1可以在變量名中使用下劃線嗎?
12.12 為什么編譯程序提供了兩個版本的malloc()函數(shù)?
包含了頭文件stdlib.h后,,你就可以在程序中使用malloc()和free()函數(shù)了,。這些函數(shù)是編譯程序從C函數(shù)庫中包含到你的程序中的。有些
編譯程序還提供了一個獨立的庫,,你可以要求編譯程序用其中的版本來代替標(biāo)準(zhǔn)庫中的malloc()和free()版本(只需在命令行中加入類似一
lmalloc這樣的標(biāo)志),。
malloc()和free()的替代版本和標(biāo)準(zhǔn)版本的功能完全一樣,只不過前者被認為在對內(nèi)存分配錯誤不那么寬容的代價下,,能產(chǎn)生更好的執(zhí)行效果,。筆者
在15年的C語言編程經(jīng)歷中從未使用過這些替代版本,但為了回答這個問題,,筆者編寫了一個大量使用malloe()和free()的簡單的測試程序,,并用
一種非常著名的C編譯程序,分使用和不使用malloc庫兩種情況對其進行了編譯,。結(jié)果筆者沒有發(fā)現(xiàn)明顯的差異,,并且筆者懷疑該開發(fā)商在實現(xiàn)這兩種版本時
使用了相同的代碼,因為兩個版本的程序的大小是一樣的,。正因為如此,,筆者也就不便指出該開發(fā)商的名字了。
以上的情況說明,,也許不必去使用malloc()的其它版本,,并且也不要指望它們會提高程序的性能。如果剖視(profiling)表明程序把大量時間花
費在malloc()和free()上,,并且通過改進算法也無法解決這個問題,那么你可以自己編寫一個“緩沖池(pool)”分配函數(shù),,也許能提高程序的
性能,。
大量調(diào)用malloc()和free()函數(shù)的程序往往是為相同類型的數(shù)據(jù)分配內(nèi)存和釋放內(nèi)存,,這些數(shù)據(jù)具有固定的長度。當(dāng)知道要分配和釋放的數(shù)據(jù)的大小
后,,自己編寫的緩沖池分配函數(shù)會比malloc()和free()運行得更快,。一個緩沖池分配函數(shù)的工作方式是這樣的:調(diào)用malloc()一次分配許多
大小相同的結(jié)構(gòu),然后每次交付一個供使用,。該函數(shù)通常從來不調(diào)用free(),,它所使用的內(nèi)存將一直保留到程序退出。例12.12給出了一個用于自定義類
型struct foo的緩沖池分配函數(shù),。 例12.12一個緩沖池分配函數(shù)的例子 # include <stdio. h> / * declaration of hypothetical structure "foo" * / struct foo { int dummy1; char dummy2; long dummy3; }; / * start of code for foo pool allocator * / # include <stdlib. h> / * number of foos to mallocO at a time * / # define NFOOS 64 /* * A union is used to provide a linked list that * can be overlaid on unused foos. */ union foo_u { union foo_u *next; struct foo f; }; static union foo_u * free_list ; struct foo * alloc_foo() { struct foo * ret = 0; if (!free_list) { int i; free_list = (union foo_u * ) malloc(NFOOS * sizeof (union foo_u)); if (free_list) { for (i = 0; i<NFOOS-1; i+ + ) free_list[i]. next = &iree_list[i + 1]; free_list [NFOOS -1 ]. next = NULL; if (free_list) { ret = &free_list ->f; free_list = free_list ->next; } return ret; } void free_foo(struct foo * fp) { union foo_u * up= (union foo_u * ) fp; up ->next = free_list) free_list = up; } int main(int argc, char * * argv) { int i; int n; struct foo ** a ; if (argc <2) { fprintf(stderr, "usage: %s f/n" , argv[0]); fprintf(stderr. "where f is the number of"); fprintf(stderr, "'foo's to allocate/n" ) ; exit(1); } i = atoi(argv[l]); a = (struct foo * * ) malloc(sizeof (struct foo * ) * i); for (n = 0; n<i; n+ + ) a[n] = alldc-foo() ; for (n = 0j n<i; n+ + ) free_foo(a[n]); return 0; }
筆者用30000這樣一個參數(shù)編譯并運行了上述程序,,并將其結(jié)果與用malloc()和free()代替alloc_foo()和free_foo()的一個類似的程序進行比較,發(fā)現(xiàn)前者使用的CPU時間為O.46秒,,而后者為0.92秒,。 需要注意的是,使用緩沖池分配函數(shù)只能是最后的選擇,,它也許能提高速度,,但它會造成內(nèi)存的巨大浪費。此外,,如果你不調(diào)用free(),,而又沒能小心地把從緩沖池中申請到的內(nèi)存返回去,就會導(dǎo)致微妙的內(nèi)存分配錯誤,。 請參見: 7.21什么是堆(heap)? 7.26 free()函數(shù)是怎樣知道要釋放的內(nèi)存塊的大小的?
12.13 適用于整數(shù)和浮點數(shù)的數(shù)學(xué)函數(shù)分別有哪些? 運算符+,,-,*和/(加,、減,、乘和除)對整數(shù)和浮點數(shù)都適用,而運算符%(求余)僅適用于整數(shù),。
適用于浮點數(shù)的大多數(shù)函數(shù)在頭文件math.h中說明,。為了提高精確度,這些函數(shù)大多以雙精度浮點數(shù)的精度進行操作,。如果傳遞過來的參數(shù)不在其定義域內(nèi)
(函數(shù)的定義域是指函數(shù)參數(shù)有效值的集合),,這些函數(shù)會返回一些不確定的值,并將變量errno置為EDOM,。如果返回值太大或太小,,無法用一個
double類型表示(造成上溢或下溢),這些函數(shù)會返回HUGEVAL(表示上溢)或O(表示下溢),,并將errno置為
ERANGE,EDOM,,ERANGE和HUGEVAL都在math.h中定義。 下面列出了在math.h中說明的函數(shù)的描述: ·double COS(double),,double sin(double)和double tan(double)的參數(shù)都是一個弧度值,,其返回值分別為該值的正弦值,、余弦值和正切值。 ·double acos(double),,double asin(double)和double atan(double)的參數(shù)都是一個值,,其返回值分別為該值的反正弦值、反余弦值和反正切值,。傳遞給acos()和asin()的值必須在-1和1之間,。 ·double atan2(double x,double y)返回x/y的反正切值,,不管x/y是否能表示成double類型(例如y為0時),。 ·double cosh(double),double sinh(double)和double tanh(double)的參數(shù)都是一個弧度值,,其返回值分別為該值的雙曲正弦值,、雙曲余弦值和雙曲正切值。
·double exp(double x),,double log(double x)和double logl0(double
x)的參數(shù)都是一個值,,其返回值分別為e。,,x的自然對數(shù)值和x的以10為底的對數(shù)值,。當(dāng)x為0或一個負數(shù)時,后兩個函數(shù)都將分別導(dǎo)致一個范圍錯誤
(ERANGE)或一個定義域錯誤(EDOM),。 ·double sqrt(double)將返回其參數(shù)的平方根值,。當(dāng)該參數(shù)為負數(shù)時,該函數(shù)將導(dǎo)致一個定義域錯誤(EDOM),。 ·double ldexp(double n,,double e)返回n*2e。這與整數(shù)的“<<”運算符有些相似,。 ·double pow(double b,,double e)返回be。當(dāng)b為O而e小于等于0時,,或者當(dāng)b小于O而e不是一個整數(shù)值時,,該函數(shù)將導(dǎo)致一個定義域錯誤(EDOM)。
·double frexp(double
n,,int*i)返回n的尾數(shù)(mantissa),,并將n的指數(shù)(exponent)存放在i所指向的整型變量中。尾數(shù)在o.5和1之間(不包括1本
身),,而指數(shù)是這樣一個數(shù),,它將使n=mantissa*2exponent。 ·double modl(double n,int *i)返回n的小數(shù)部分,,并將n的整數(shù)部分存放在i所指向的整型變量中,。 ·double celt(double)和double floor(double)分別返回大于其參數(shù)的最小整數(shù)和小于其參數(shù)的最大整數(shù)。例如,,ceil(-1.1)返回-1.O,而floor(-1.1)返回-2.0,。 ·double fmod(double x,,double y)返回x/y的余數(shù)。這與整數(shù)的%運算符相似,,但該函數(shù)的參數(shù)和返回值并不局限于整數(shù),。當(dāng)y為O時,該函數(shù)將導(dǎo)致一個定義域錯誤(EDOM),。 ·double fabs(double)返回其參數(shù)的絕對值(一個數(shù)量相同的數(shù)字,,但永遠是正數(shù))。例如,,labs(-3.14)返回3.14,。
請參見: 2.11 對不同類型的變量進行算術(shù)運算會有問題嗎?
12.14 什么是多字節(jié)字符(multibyte characters)? 多字節(jié)字符是使國際化的程序更容易編寫的另一種途徑。具體地說,,它們有助于支持永遠無法納入8位字符的語言,,例如漢語和日語。如果你的程序永遠不需要使用除英語之外的其它任何語言,,你可以不必了解多字節(jié)字符,。 你不得不承認這樣一個事實:可能到處都有人想使用你的軟件,但并不是人人都懂英語,。幸運的是,,已經(jīng)有了可以把歐洲語言的各種特殊字符納入8位字符集的標(biāo)準(zhǔn)(不幸的是,這樣的標(biāo)準(zhǔn)有好幾種,,并且它們相互并不一致),。 到了亞洲,這個問題變得更復(fù)雜,。有些語言的字符超過256個,,例如漢語和日語,它們永遠無法納入8位字符集中(一個8位字符能存放O和255之間的一個數(shù)字,,因此它能只有256種不同的值),。
幸運的是,C標(biāo)準(zhǔn)庫已經(jīng)開始解決這個問題,。<stddef.h>定義了wchar_t類型,,它的長度足以存放c程序能處理的任何語言中的任何
字符。根據(jù)到目前為止的所有協(xié)議,16位已經(jīng)足夠了,。這通常就是short類型,,但最好還是相信編譯程序開發(fā)商所提供的wchar_t的正確性,以免在
short類型的長度發(fā)生變化時遇到麻煩,。 函數(shù)mblen(),,mbtowc()和wctomb()能將單字節(jié)字符串轉(zhuǎn)換為多字節(jié)字符。如果你想了解更多的有關(guān)這些函數(shù)的信息,,請查閱你的編譯程序手冊,。
請參見: 12.15怎樣操作由多字節(jié)字符組成的字符串?
12.15 怎樣操作由多字節(jié)字符組成的字符串?
假設(shè)你的程序既要處理英文文本(很容易納As位字符,并且還能空出一位),,又要處理日文文本(需要16位才能包含所有的可能性),。如果你用相同的代碼來處
理這兩種不同國家的文本,你是否需要給每個字符,,甚至英文符都分配16位呢?也許不必這樣做,,因為有些多字節(jié)字符的編碼方法會保存關(guān)于是否需要多于一個字
節(jié)的空間的信息。 mbstowcs()(“多字節(jié)字符串到寬字符串”)和wcstombs()(“寬字符串到多字節(jié)字符串”)用于wchar—t類型的數(shù)組(其中每個字符占16位或兩個字節(jié))和多字節(jié)字符串(可能的話,,一個字符會被存入一個字節(jié)中),。 你無法保證你的編譯程序能以緊縮的方式存儲多字節(jié)字符串(因為沒有一種普遍接受的方法)。如果你的編譯程序能幫助你處理多字節(jié)字符串,,mbstowcs()和wcstombs()就是完成這部分工作的函數(shù),。
請參見: 12.14什么是多字節(jié)字符(multibyte characters)?
如果你愿意的話,可以用函數(shù)strcpy()和strcat()重新編寫例12.5a中的程序,,并用很長的足以溢出緩沖區(qū)的參數(shù)運行它,。會出現(xiàn)什么現(xiàn)象
呢?計算機會掛起嗎?你會得到"GeneralProtection Exception”或內(nèi)存信息轉(zhuǎn)儲這樣的消息嗎?請參見7.24中的討論。 例12.5a使用"string—n”函數(shù)的一個例子 # include <stdio. h> # include <string. h> /* Normally, a constant like MAXBUF would be very large, to help ensure that the buffer doesn't overflow. Here, it's very small, to show how the "string-n" functions prevent it from ever overflowing. */ # define MAXBUF 16 int main (int argc, char* * argv) { char buf[MAXBUF]; int i; buf[MAXBUF - 1] = '/0'; strncpy(buf, argv[0], MAXBUF-1); for (i = 1; i<argc; ++i) { strncat(buf, " " , MAXBUF -1 - strlen (buf) ) ; strncat(buf, argv[i], MAXBUF -1 - strlen (buf ) ) ; } puts (buf ); return 0; }
注意:許多字符串函數(shù)都至少有兩個參數(shù),,在描述它們時,,與其稱之為“第一個參數(shù)”和“第二個參數(shù)”,還不如稱之為“左參數(shù)”和“右參數(shù)”,。 函數(shù)strcpy()和strncpy()用來把字符串從一個數(shù)組拷貝到另一個數(shù)組,,即把右參數(shù)的值拷貝到左參數(shù)中,這與賦值語句的順序是一樣的,。 函數(shù)strcat()和strncat()用來把一個字符串連接到另一個字符串的末尾,。例如,如果數(shù)組a1的內(nèi)容為“dog”,,數(shù)組a2的內(nèi)容為“wood”,,那么在調(diào)用strcat(al,a2)后,,a1將變?yōu)椤癲ogwood”,。 函數(shù)strcmp()和strncmp()用來比較兩個字符串,。當(dāng)左參數(shù)小于、等于或大于右參數(shù)時,,它們都分別返回一個小于,、等于或大于零的值。常見的比較兩個字符串是否相等的寫法有以下兩種: if (strcmp(sl, s2)) { / * si !=s2 * / } 和 if (! strcmp(s1, s2)) { /* s1 ==s2 * / } 上述代碼可能并不易讀,,但它們是完全有效并且相當(dāng)常見的c代碼,,你應(yīng)該記住它們。如果在比較字符串時還需要考慮當(dāng)前局部環(huán)境(locale,,見12.8),,則要使用strcoll()函數(shù)。
有一些函數(shù)用來在字符串中進行檢索(在任何情況下,,都是在左參數(shù)或第一個參數(shù)中進行檢索)。函數(shù)strchr()和strrchr()分別用來查找某個字
符在一個字符串中第一次和最后一次出現(xiàn)的位置(如果函數(shù)strchr()和strrchr()有帶“n”字母的版本,,那么函數(shù)memchr()和
memrchr()是最接近這種版本的函數(shù)),。函數(shù)strspn()、strcspn()(“c”表示"complement")和strpbrk()用
來查找包含指定字符或被指定字符隔開的子字符串: n = strspn("Iowa" , "AEIOUaeiou"); / * n = 2( "Iowa" starts with 2 vowels * / n=strcspn("Hello world" ,"/t" ) ; / * n = 5; white space after 5 characters * / p = strbrk("Hellb world" ,"/t" ) ; / * p points to blank * /
函數(shù)strstr()用來在一個字符串中查找另一個字符串: p = strstr("Hello world", "or"); / * p points to the second "or" * /
函數(shù)strtok()按照第二個參數(shù)中指定的字符把一個字符串分解為若干部分,。函數(shù)strtok()具有“破壞性”,,它會在原字符串中插入NUL字符(如
果原字符串還要做其它的改變,應(yīng)該拷貝原字符串,,并將這份拷貝傳遞給函數(shù)strtok()),。函數(shù)strtok()是不能“重新進入”的,你不能在一個信
號處理函數(shù)中調(diào)用strtok()函數(shù),,因為在下一次調(diào)用strtok()函數(shù)時它總是會“記住”上一次被調(diào)用時的某些參數(shù),。strtok()函數(shù)是一個
古怪的函數(shù),但它在分解以逗號或空白符分界的數(shù)據(jù)時是非常有用的,。例12.5b給出了一個程序,,該程序用strtok()函數(shù)把一個句子中的單詞分解出
來: 例12.5b一個使用strtok()的例子 # include <stdio. h> # include <string. h> static char buf[] = "Now is the time for all good men . . . " ; int main() { char * p; p = strtok(buf, " ") ; while (p ) { printf("%s/n" ,p); p = strtok(NULL, " "); } return 0; }
請參見: 4.18怎樣讀寫以逗號分界的文本? 第6章字符串操作 7.23 NULL和NUI。有什么不同? 9.9 字符串和數(shù)組有什么不同? 12.8 什么是“局部環(huán)境(10cale)”? 12.10 什么是信號(signal)?用信號能做什么?
12.6 對內(nèi)存進行操作的標(biāo)準(zhǔn)庫函數(shù)有哪些? 有些函數(shù)可用來拷貝,、比較和填寫任意的內(nèi)存塊,,它們都帶有void。類型(并不指向任何具體類型的指針)的參數(shù),,可以處理指向任何類型的指針,。
有兩個函數(shù)(有點象strncpy()函數(shù))可用來拷貝信息。第一個函數(shù)是memmove(),,它把內(nèi)存中的內(nèi)容從一個地方拷貝到另一個地方,,不管源區(qū)域
和目標(biāo)區(qū)域是否有相互覆蓋的部分。為什么要提到這兩個區(qū)域是否相互覆蓋呢?假設(shè)緩沖區(qū)中已有部分數(shù)據(jù),,而你要把它們移到“后面”,,以騰出緩沖區(qū)前面的空
間,。例12.6給出了一個試圖進行這項工作的程序,但它做得并不正確: 例12.6一個試圖移動數(shù)據(jù),,結(jié)果毀掉數(shù)據(jù)的程序 static char buf[] = {'R','I','G','H','T','/0','-','-','-'}; int main() { int i; for (i = 0; i<6; ++i) { buf[i + 3] = buf[i]i } }
上述程序的意圖是把buf從"RIGHT"改為“RIGRIGHT”,,這樣就可以在前面三個字節(jié)中存入其它數(shù)據(jù)。不幸的是,,程序并沒有真正實現(xiàn)這個意圖,。如果把for循環(huán)展開(或者通過調(diào)試程序來觀察程序正在做什么),你就會發(fā)現(xiàn)程序?qū)嶋H上是在這樣做: buf[3] = buf[0]; buf[4] = buf[l]; buf[5] = buf[2]; buf[6] = buf[3]; buf[7] = buf[4]; buf[8] = buf[5]; buf[9] = buf[6];
數(shù)據(jù)的移動效果如圖12.6a所示(新拷貝的數(shù)據(jù)用粗黑體表示)——該程序毀掉了它原來想移動的某些數(shù)據(jù),。
R I G H T /0 - - - R I G R T /0 - - - R I G R I /0 - - - R I G R I G - - - R I G R I G R - - R I G R I G R I - R I G R I G R I G
圖12·6a“移動”相互覆蓋的數(shù)據(jù)的錯誤方法
在移動或拷貝相互覆蓋的數(shù)據(jù)時,,有這樣一個簡單的原則:如果源區(qū)域和目標(biāo)區(qū)域相互覆蓋,并且源區(qū)域在目標(biāo)區(qū)域的前面,,則應(yīng)該從源區(qū)域的末尾開始按逆向順序
依次移動數(shù)據(jù),,直到達到源區(qū)域的頭部;如果源區(qū)域在目標(biāo)區(qū)域的后面,,則應(yīng)該從源區(qū)域的頭部開始移動數(shù)據(jù),,直到達到源區(qū)域的末尾。請看圖12.6b,。 R I G H T /0 - - - R I G H T /0 - - /n R I G H T /0 - T /0 R I G H T /O H T /0 R I G H T G H T /0 R I G H I G H T /O R I G R I G H T /O < < < L E F T /O L < < L E F T /O L E < L E F T /O L E F L E F T /0 L E F T E F T /O L E F T /0 F T /O
圖12.6b“移動”相互覆蓋的數(shù)據(jù)的正確方法
解釋這些情況的目的是為了指出這樣一點:memmove()函數(shù)知道上述原則,,它能保證用正確的方法拷貝數(shù)據(jù),不管數(shù)據(jù)是否相互覆蓋,。如果在拷貝或移動數(shù)
據(jù)時你并不知道源區(qū)域和目標(biāo)區(qū)域是否相互覆蓋,,你就應(yīng)該使用memmove()函數(shù)。如果你能確定它們并沒有相互覆蓋,,那么可以使用memcpy()函
數(shù),,這樣能稍快一些。 memcmp()函數(shù)與strncmp()函數(shù)基本相似,,只是它在遇到NUL字符時不會結(jié)束,。memcmp()函數(shù)不能用來比較結(jié)構(gòu)的值。假設(shè)你有下面這樣一個結(jié)構(gòu): struct foo{ short s,; long 1,; } 并
且假設(shè)你的程序?qū)⑦\行在一個short類型為兩個字節(jié)(16位),long類型為4個字節(jié)(32位)的系統(tǒng)上,。在32位的計算機中,,許多編譯程序會在s和
l之間加入兩個字節(jié)的“無用信息”,以使I從下一個字的邊界開始,。如果你的程序運行在低位優(yōu)先(低位字節(jié)存放在低位地址中)的計算機上,,那么上述結(jié)構(gòu)展開
后可能會如下所示: struct foo byte[O] s的低位字節(jié) struct foo byte[1] s的高位字節(jié) struct foo byte[2] 無用信息(使l從一個long類型邊界開始) struct foo byte[3] 無用信息(使l從一個long類型邊界開始) struct foo byte[4] l的最低位字節(jié) struct foo byte[5] l的次低位字節(jié) struct foo byte[6] l的次高位字節(jié) struct foo byte[7] 1的最高位字節(jié) 用memcmp()函數(shù)比較具有相同的s和l值的兩個foo結(jié)構(gòu)時,其結(jié)果并不一定相等,,因為所加入的“無用信息”并不一定相同,。 memchr()函數(shù)與strchr()函數(shù)基本相似,,只不過它是在指定的一塊內(nèi)存空間中查找一個字符串,并且它在遇到第一個NUL字符時不會結(jié)束,。 memset()函數(shù)對所有的C程序員都是很有用的,,它能把某種字節(jié)拷貝到指定的內(nèi)存空間中。memset()函數(shù)的一種常見的用法是把某種結(jié)構(gòu)全部初始化為零字節(jié),。如果p是指向一個結(jié)構(gòu)的指針,,那么語句memset(p,'/0',,size01 * p),; 將把p所指向的對象全部改寫為零(NUL或'/O')字節(jié)(那些使結(jié)構(gòu)成員從字邊界開始的“無用信息”也會被改寫,但這樣做沒有關(guān)系,,因為這些信息沒有用,,所以誰也不會在乎它們被改寫成什么樣子)。
請參見: 4.1當(dāng)errno為一個非零值時,,是否有錯誤發(fā)生? 4.3怎樣重定向一個標(biāo)準(zhǔn)流? 9.9字符串和數(shù)組有什么不同?
12.7 怎樣判斷一個字符是數(shù)字,、字母或其它類別的符號? 在頭文件ctype.h中定義了一批函數(shù),它們可用來判斷一個字符屬于哪一類別,。下面列出了這些函數(shù): --------------------------------------------------------------------------------------- 函數(shù) 字符類別 返回非零值的字符 --------------------------------------------------------------------------------------- isdigit() 十進制數(shù) 0--9 isxdigit() 十六進制數(shù) 0--9,a—f,,或A--F isalnum() 字母數(shù)字符號 0--9,,a--Z,或A--Z isalpha() 字母 a--Z或A--Z islower() 小寫字母 a--Z isupper() 大寫字母 A--Z isspace() 空白符 空格符,,水平制表符,,垂直制表符,換行符,,換頁符,,或回車符 isgraph() 非空白字符 任何打印出來不是空白的字符(ASCII碼從21到7E) isprint() 可打印字符 所有非空白字符,加上空格符 ispunct() 標(biāo)點符 除字母數(shù)字符號以外的所有非空白字符 iscntrl() 控制字符 除可打印字符外的所有字符(ASCII碼從00到1F,,加上7F) ---------------------------------------------------------------------------------------- 與前文提到過的使用標(biāo)準(zhǔn)庫函數(shù)的好處相似,,調(diào)用上述這些宏而不是自己編寫測試字符類別的程序也有三點好處。首先,,這些宏運算速度快,,因為它們的實現(xiàn)方式通常都是利用位屏蔽技術(shù)來檢查一個表,所以即使是進行一項相當(dāng)復(fù)雜的檢查,,也比真正去比較字符的值要快得多,。 其次,這些宏都是正確的,。如果你自己編寫一個測試程序,,你很容易犯邏輯上或輸入上的錯誤,,例如引入了一個錯誤的字符(或漏掉了一個正確的字符)。
第三,,這些宏是可移植的,。信不信由你,并非所有的人都使用同樣的含PC擴充字符的ASCII字符集,。也許今天你還不太在意,,但是,當(dāng)你發(fā)現(xiàn)你的下一臺計算
機使用的是Unicode字符集而不是ASCII字符集,,你就會慶幸自己原來沒有按照字符集中的字符值來編寫程序,。
頭文件ctype.h中還定義了兩個可以對字母進行大小寫轉(zhuǎn)換的函數(shù),即函數(shù)toupper()和tolower(),。如果toupper()函數(shù)的參數(shù)
不是小寫字母或tolOWel"()函數(shù)的參數(shù)不是大寫字母,,那么這兩個函數(shù)的行為是沒有定義的,因此,,在調(diào)用這兩個函數(shù)之前,,你應(yīng)該用函數(shù)
islower()或isupper()來檢查一下。
請參見: 5.1什么是宏(macro)?怎樣使用宏? 6.2怎樣刪去字符串尾部的空格? 6.3怎樣刪去字符串頭部的空格? 20.18怎樣判斷一個字符是不是字母? 20.19怎樣判斷一個字符是不是數(shù)字?
12.8 什么是“局部環(huán)境(locale)”? 局部環(huán)境是對特定環(huán)境下程序要遵循的特定規(guī)則的一種描述,,它對程序的國際化很有幫助,。
如果你要打印一筆錢的數(shù)目,你總是使用美元符號嗎?不,,如果你的程序要在英國運行,,你就要使用英鎊符號。在有些國家,,貨幣符號要寫在錢數(shù)的前面,,而在有些
國家,貨幣符號要寫在錢數(shù)的后面,。一個負數(shù)的負號要放在哪里呢?在美國寫成1,,234.56的一個數(shù)字,在另外一些國家中可能要寫成1.234,,56,。同
樣的值在不同的國家中會有不同的表示規(guī)則。時間和日期又是如何表示的呢?簡而言之,,也是因國而異,。如果一個程序員要編寫一個必須在全世界運行的程序,那么
這些情況就是使他頭疼的部分技術(shù)原因,。
幸運的是:部分差異已經(jīng)被標(biāo)準(zhǔn)化了,。C編譯程序支持不同的“局部環(huán)境”,即程序在不同地方的不同表示規(guī)則,。例如,,函數(shù)strcoll()(string
collate,,字符串的依序整理)和strcmp()函數(shù)相似,但它能反映出不同國家和語言對字符串值進行排序和整理(collate)的方式,。函數(shù)
setlocale()和localeconv()提供了這方面的支持,。
不幸的是:并沒有一種標(biāo)準(zhǔn)化了的關(guān)于這些有趣的局部環(huán)境的清單。你的編譯程序唯一能保證提供的只有“C”局部環(huán)境,。這是一種通用的美式英語規(guī)則,,對于碼值
在32和127之間的ASCII字符,這種規(guī)則工作得最好,。盡管如此,,如果你想正確地編寫一個能在全世界運行的程序,那么從局部規(guī)則這個角度來考慮問題就
是一個好的開端(接下來,,如果你能再找到幾種你的編譯程序能支持的局部環(huán)境,,或者讓你的編譯程序接受你定義的幾種局部環(huán)境,那就更好了),。
12.9 有沒有辦法從一個或多個函數(shù)中跳出? 在極少數(shù)確實需要這樣做的情況下,,可以利用標(biāo)準(zhǔn)庫函數(shù)setjmp()和longjmp()實現(xiàn)一種能從一個或多個函數(shù)中跳出的跳轉(zhuǎn)(goto)。要正確地使用setjmp()和longjmp()函數(shù),,必須滿足幾個條件,。
首先,你必須包含setjmp.h頭文件,,該文件提供了setjmp()和longimp()函數(shù)的原型,,并定義了jmp—buf類型。你需要把一個
jmp—bur類型的變量作為一個參數(shù)傳遞給setjmp()和longjmp()函數(shù),,這個變量將包含使跳轉(zhuǎn)發(fā)生所需的信息。
其次,,你必須調(diào)用setjmp()函數(shù)來初始化jmp—bur變量,。如果setjmp()函數(shù)返回0,則說明jmp_buf變量已被初始化,;如果
setjmp()函數(shù)返回其它值,,則說明程序剛才通過調(diào)用longjmp()函數(shù)跳轉(zhuǎn)到了對應(yīng)于該值的位置。在后一種情況下,,setjmp()函數(shù)的返回
值就是程序傳遞給longjmp()函數(shù)的第二個參數(shù),。
從概念上講,longjmp()函數(shù)的作用就好象是這樣:當(dāng)它被調(diào)用時,,當(dāng)前正在執(zhí)行的函數(shù)便會返回,;然后,調(diào)用這個函數(shù)的函數(shù)將返回,;依此類推,,直到調(diào)
用setjmp()的函數(shù)成為正在執(zhí)行的函數(shù),。程序的執(zhí)行將跳轉(zhuǎn)到調(diào)用setjmp()函數(shù)的位置,并從setjmp()函數(shù)返回那一點繼續(xù)往下執(zhí)行,,但
此時setjmp()函數(shù)的返回值已被置為傳遞給longjmp()函數(shù)的第二個參數(shù),。
換句話說,如果函數(shù)f()調(diào)用了setjmp(),,然后又調(diào)用了函數(shù)g(),,而函數(shù)g()調(diào)用了函數(shù)h(),函數(shù)h()調(diào)用了longjmp(),,那么程
序運行起來就好象h()立即返回了,,然后g()立即返回,然后f()執(zhí)行一次回到調(diào)用setjmp()的位置的跳轉(zhuǎn),。
這就是說,,為了使對10ngjmp()的調(diào)用能正常工作,程序必須已經(jīng)調(diào)用setjmp(),,并且還沒有從調(diào)用setjmp()的函數(shù)中返回,。如果這些條
件得不到滿足,那么longjmp()的行為是沒有定義的(這意味著你的程序很可能會崩潰),。例12.9中的程序說明了setjmp()和
longjmp()的用法,。這個程序顯然是為此而設(shè)計的,因為如果不使用setjmp()和longjmp(),,程序就會更簡潔些,。總的來說,,當(dāng)你想使用
setjmp()和longjmp()時,,最好先找一種可以不使用它們的編程方法,因為它們?nèi)菀妆徽`用,,并且會使程序難于閱讀和維護,。 例12.9 一個使用setjmp()和longjmp()的例子 # include <setjmp. h> # include <stdio. h> # include <string. h> # include <stdlib. h> # define RETRY_PROCESS 1 # define QUIT_PROCESS 2 jmp_buf env; int nitems; int procItem() { char buf[256]; if (gets (buf) &&.strcmp(buf, "done")) { if (strcmp(buf, "quit") ==0) longjmp (env, QUIT_PROCESS ); if (strcmp(buf, "restart") ==0) longjmp(env, RETRY_PROCESS); nitems+ + ; return 1; } return 0; } void process() { printf ("Enter items, followed by 'done'. /n") ; printf("At any time, you can type 'quit' to exit/n"); printf ("or 'restart' to start over again/n"); nitems = 0; while (procItem()) } void main() { for (; ;) { switch (setjmp(env)) { case 0: case RETRY_PROCESS: process () ; printf("You typed in %d items. /n" , nitems); break ; case QUIT_PROCESS: default: exit(O); } } }
請參見: 1.8 goto,longjmp()和setjmp()之間有什么區(qū)別? 7.20 什么是棧(stack)?
12.10 什么是信號(signal)?用信號能做什么? 信號是程序執(zhí)行過程中出現(xiàn)的異常情況,。它可能是由程序中的錯誤造成的,,例如引用內(nèi)存中的一個非法地址;或者是由程序數(shù)據(jù)中的錯誤造成的,,例如浮點數(shù)被0除,;或者是由外部事件引發(fā)的,例如用戶按了Ctrl+Break鍵,。 你可以利用標(biāo)準(zhǔn)庫函數(shù)signal()指定要對這些異常情況采取的處理措施(實施處理措施的函數(shù)被稱為“信號處理函數(shù)”),。signal()的原型為: #include <signal.h> void(*signal(int hum,void(*func)(int)))(int); 這恐怕是你在C標(biāo)準(zhǔn)函數(shù)庫中能見到的最復(fù)雜的說明了,。如果你先定義一個typedef,,理解起來就容易一些了。下面給出的sigHandler_t類型是指向一個程序的指針,,該函數(shù)有一個int類型的參數(shù),,并且返回一個void類型: typedef void(*sigHandler_t)(int); sigHandler_t signal(int num , sigHandler_t func),;
signal()有兩個參數(shù),,分別為int類型和sigHandler_t類型,其返回值為sigHandler_t類型,。以func參數(shù)形式傳遞給
signal()的那個函數(shù)將成為第num號異常情況的新的信號處理函數(shù),。signal()的返回值是信號hum原來的信號處理函數(shù)。在設(shè)置了一個暫時的
信號處理函數(shù)之后,,你可以利用該值恢復(fù)程序先前的行為,。num的可能值依賴于系統(tǒng),并且在signal.h中列出,。func的可能值可以是你的程序中的任
意函數(shù),,或者是SIG_DFL和SLG_IGN這兩個特別定義的值之一。SIG_DFL是指系統(tǒng)的缺省處理措施,,通常是暫停執(zhí)行程序,;SIG_IGN表示
信號將被忽略。
當(dāng)下面這行代碼被執(zhí)行后,,程序?qū)⒉蝗ロ憫?yīng)按Ctrl+Break鍵這個信號,,除非修改signal()函數(shù),使其重新響應(yīng)該信號,。盡管hum的可能值依賴
于系統(tǒng),,但SIGINT這個值通常用來表示用戶試圖中斷程序運行的信號(在DOS下,為Ctrl+C或Ctrl+Break),。 signal(SIGINT,,SIG_IGN)
請參見: 20.16 怎樣使Ctrl+Break失效?
12.11 為什么變量名不能以下劃線開始? 凡是以兩個或一個下劃線開始,后面緊跟著一個大寫字母的標(biāo)識符,,不管它出現(xiàn)在哪里,都是保留給編譯程序或標(biāo)準(zhǔn)庫函數(shù)使用的,。此外,,凡是以一個下劃線開始,后面不管跟著什么內(nèi)容的標(biāo)識符,,如果它出現(xiàn)在文件范圍內(nèi)(即它不是出現(xiàn)在一個函數(shù)內(nèi)),,那么它也是被保留的。
如果你用一個保留的標(biāo)識符來作一個變量的名稱,,結(jié)果是沒有定義的(程序可能無法編譯,,或者可以編譯但會崩潰),。即使你能非常幸運地找到一個目前還沒有被你
的編譯程序或函數(shù)庫使用的標(biāo)識符,你也應(yīng)該記住這樣的標(biāo)識符是保留起來供將來使用的,。因此,,最好還是避免使用以下劃線開始的變量名或函數(shù)名。
請參見: 19.1可以在變量名中使用下劃線嗎?
12.12 為什么編譯程序提供了兩個版本的malloc()函數(shù)?
包含了頭文件stdlib.h后,,你就可以在程序中使用malloc()和free()函數(shù)了,。這些函數(shù)是編譯程序從C函數(shù)庫中包含到你的程序中的。有些
編譯程序還提供了一個獨立的庫,,你可以要求編譯程序用其中的版本來代替標(biāo)準(zhǔn)庫中的malloc()和free()版本(只需在命令行中加入類似一
lmalloc這樣的標(biāo)志),。
malloc()和free()的替代版本和標(biāo)準(zhǔn)版本的功能完全一樣,只不過前者被認為在對內(nèi)存分配錯誤不那么寬容的代價下,,能產(chǎn)生更好的執(zhí)行效果,。筆者
在15年的C語言編程經(jīng)歷中從未使用過這些替代版本,但為了回答這個問題,,筆者編寫了一個大量使用malloe()和free()的簡單的測試程序,,并用
一種非常著名的C編譯程序,分使用和不使用malloc庫兩種情況對其進行了編譯,。結(jié)果筆者沒有發(fā)現(xiàn)明顯的差異,,并且筆者懷疑該開發(fā)商在實現(xiàn)這兩種版本時
使用了相同的代碼,因為兩個版本的程序的大小是一樣的,。正因為如此,,筆者也就不便指出該開發(fā)商的名字了。
以上的情況說明,,也許不必去使用malloc()的其它版本,,并且也不要指望它們會提高程序的性能。如果剖視(profiling)表明程序把大量時間花
費在malloc()和free()上,,并且通過改進算法也無法解決這個問題,,那么你可以自己編寫一個“緩沖池(pool)”分配函數(shù),也許能提高程序的
性能,。
大量調(diào)用malloc()和free()函數(shù)的程序往往是為相同類型的數(shù)據(jù)分配內(nèi)存和釋放內(nèi)存,,這些數(shù)據(jù)具有固定的長度。當(dāng)知道要分配和釋放的數(shù)據(jù)的大小
后,,自己編寫的緩沖池分配函數(shù)會比malloc()和free()運行得更快,。一個緩沖池分配函數(shù)的工作方式是這樣的:調(diào)用malloc()一次分配許多
大小相同的結(jié)構(gòu),然后每次交付一個供使用,。該函數(shù)通常從來不調(diào)用free(),,它所使用的內(nèi)存將一直保留到程序退出。例12.12給出了一個用于自定義類
型struct foo的緩沖池分配函數(shù)。 例12.12一個緩沖池分配函數(shù)的例子 # include <stdio. h> / * declaration of hypothetical structure "foo" * / struct foo { int dummy1; char dummy2; long dummy3; }; / * start of code for foo pool allocator * / # include <stdlib. h> / * number of foos to mallocO at a time * / # define NFOOS 64 /* * A union is used to provide a linked list that * can be overlaid on unused foos. */ union foo_u { union foo_u *next; struct foo f; }; static union foo_u * free_list ; struct foo * alloc_foo() { struct foo * ret = 0; if (!free_list) { int i; free_list = (union foo_u * ) malloc(NFOOS * sizeof (union foo_u)); if (free_list) { for (i = 0; i<NFOOS-1; i+ + ) free_list[i]. next = &iree_list[i + 1]; free_list [NFOOS -1 ]. next = NULL; if (free_list) { ret = &free_list ->f; free_list = free_list ->next; } return ret; } void free_foo(struct foo * fp) { union foo_u * up= (union foo_u * ) fp; up ->next = free_list) free_list = up; } int main(int argc, char * * argv) { int i; int n; struct foo ** a ; if (argc <2) { fprintf(stderr, "usage: %s f/n" , argv[0]); fprintf(stderr. "where f is the number of"); fprintf(stderr, "'foo's to allocate/n" ) ; exit(1); } i = atoi(argv[l]); a = (struct foo * * ) malloc(sizeof (struct foo * ) * i); for (n = 0; n<i; n+ + ) a[n] = alldc-foo() ; for (n = 0j n<i; n+ + ) free_foo(a[n]); return 0; }
筆者用30000這樣一個參數(shù)編譯并運行了上述程序,,并將其結(jié)果與用malloc()和free()代替alloc_foo()和free_foo()的一個類似的程序進行比較,,發(fā)現(xiàn)前者使用的CPU時間為O.46秒,而后者為0.92秒,。 需要注意的是,,使用緩沖池分配函數(shù)只能是最后的選擇,它也許能提高速度,,但它會造成內(nèi)存的巨大浪費,。此外,如果你不調(diào)用free(),,而又沒能小心地把從緩沖池中申請到的內(nèi)存返回去,,就會導(dǎo)致微妙的內(nèi)存分配錯誤。 請參見: 7.21什么是堆(heap)? 7.26 free()函數(shù)是怎樣知道要釋放的內(nèi)存塊的大小的?
12.13 適用于整數(shù)和浮點數(shù)的數(shù)學(xué)函數(shù)分別有哪些? 運算符+,,-,,*和/(加、減,、乘和除)對整數(shù)和浮點數(shù)都適用,,而運算符%(求余)僅適用于整數(shù)。
適用于浮點數(shù)的大多數(shù)函數(shù)在頭文件math.h中說明,。為了提高精確度,,這些函數(shù)大多以雙精度浮點數(shù)的精度進行操作。如果傳遞過來的參數(shù)不在其定義域內(nèi)
(函數(shù)的定義域是指函數(shù)參數(shù)有效值的集合),,這些函數(shù)會返回一些不確定的值,,并將變量errno置為EDOM。如果返回值太大或太小,,無法用一個
double類型表示(造成上溢或下溢),,這些函數(shù)會返回HUGEVAL(表示上溢)或O(表示下溢),并將errno置為
ERANGE,EDOM,,ERANGE和HUGEVAL都在math.h中定義,。 下面列出了在math.h中說明的函數(shù)的描述: ·double COS(double),double sin(double)和double tan(double)的參數(shù)都是一個弧度值,,其返回值分別為該值的正弦值,、余弦值和正切值。 ·double acos(double),,double asin(double)和double atan(double)的參數(shù)都是一個值,,其返回值分別為該值的反正弦值、反余弦值和反正切值,。傳遞給acos()和asin()的值必須在-1和1之間。 ·double atan2(double x,double y)返回x/y的反正切值,,不管x/y是否能表示成double類型(例如y為0時),。 ·double cosh(double),double sinh(double)和double tanh(double)的參數(shù)都是一個弧度值,,其返回值分別為該值的雙曲正弦值,、雙曲余弦值和雙曲正切值。
·double exp(double x),,double log(double x)和double logl0(double
x)的參數(shù)都是一個值,,其返回值分別為e。,,x的自然對數(shù)值和x的以10為底的對數(shù)值,。當(dāng)x為0或一個負數(shù)時,后兩個函數(shù)都將分別導(dǎo)致一個范圍錯誤
(ERANGE)或一個定義域錯誤(EDOM),。 ·double sqrt(double)將返回其參數(shù)的平方根值,。當(dāng)該參數(shù)為負數(shù)時,該函數(shù)將導(dǎo)致一個定義域錯誤(EDOM),。 ·double ldexp(double n,,double e)返回n*2e。這與整數(shù)的“<<”運算符有些相似,。 ·double pow(double b,,double e)返回be。當(dāng)b為O而e小于等于0時,,或者當(dāng)b小于O而e不是一個整數(shù)值時,,該函數(shù)將導(dǎo)致一個定義域錯誤(EDOM)。
·double frexp(double
n,,int*i)返回n的尾數(shù)(mantissa),,并將n的指數(shù)(exponent)存放在i所指向的整型變量中。尾數(shù)在o.5和1之間(不包括1本
身),,而指數(shù)是這樣一個數(shù),,它將使n=mantissa*2exponent。 ·double modl(double n,,int *i)返回n的小數(shù)部分,,并將n的整數(shù)部分存放在i所指向的整型變量中。 ·double celt(double)和double floor(double)分別返回大于其參數(shù)的最小整數(shù)和小于其參數(shù)的最大整數(shù),。例如,,ceil(-1.1)返回-1.O,而floor(-1.1)返回-2.0,。 ·double fmod(double x,,double y)返回x/y的余數(shù),。這與整數(shù)的%運算符相似,但該函數(shù)的參數(shù)和返回值并不局限于整數(shù),。當(dāng)y為O時,,該函數(shù)將導(dǎo)致一個定義域錯誤(EDOM)。 ·double fabs(double)返回其參數(shù)的絕對值(一個數(shù)量相同的數(shù)字,,但永遠是正數(shù)),。例如,labs(-3.14)返回3.14,。
請參見: 2.11 對不同類型的變量進行算術(shù)運算會有問題嗎?
12.14 什么是多字節(jié)字符(multibyte characters)? 多字節(jié)字符是使國際化的程序更容易編寫的另一種途徑,。具體地說,它們有助于支持永遠無法納入8位字符的語言,,例如漢語和日語,。如果你的程序永遠不需要使用除英語之外的其它任何語言,你可以不必了解多字節(jié)字符,。 你不得不承認這樣一個事實:可能到處都有人想使用你的軟件,,但并不是人人都懂英語。幸運的是,,已經(jīng)有了可以把歐洲語言的各種特殊字符納入8位字符集的標(biāo)準(zhǔn)(不幸的是,,這樣的標(biāo)準(zhǔn)有好幾種,并且它們相互并不一致),。 到了亞洲,,這個問題變得更復(fù)雜。有些語言的字符超過256個,,例如漢語和日語,,它們永遠無法納入8位字符集中(一個8位字符能存放O和255之間的一個數(shù)字,因此它能只有256種不同的值),。
幸運的是,,C標(biāo)準(zhǔn)庫已經(jīng)開始解決這個問題。<stddef.h>定義了wchar_t類型,,它的長度足以存放c程序能處理的任何語言中的任何
字符,。根據(jù)到目前為止的所有協(xié)議,16位已經(jīng)足夠了,。這通常就是short類型,,但最好還是相信編譯程序開發(fā)商所提供的wchar_t的正確性,以免在
short類型的長度發(fā)生變化時遇到麻煩,。 函數(shù)mblen(),,mbtowc()和wctomb()能將單字節(jié)字符串轉(zhuǎn)換為多字節(jié)字符。如果你想了解更多的有關(guān)這些函數(shù)的信息,,請查閱你的編譯程序手冊,。
請參見: 12.15怎樣操作由多字節(jié)字符組成的字符串?
12.15 怎樣操作由多字節(jié)字符組成的字符串?
假設(shè)你的程序既要處理英文文本(很容易納As位字符,,并且還能空出一位),又要處理日文文本(需要16位才能包含所有的可能性),。如果你用相同的代碼來處
理這兩種不同國家的文本,,你是否需要給每個字符,甚至英文符都分配16位呢?也許不必這樣做,,因為有些多字節(jié)字符的編碼方法會保存關(guān)于是否需要多于一個字
節(jié)的空間的信息。 mbstowcs()(“多字節(jié)字符串到寬字符串”)和wcstombs()(“寬字符串到多字節(jié)字符串”)用于wchar—t類型的數(shù)組(其中每個字符占16位或兩個字節(jié))和多字節(jié)字符串(可能的話,,一個字符會被存入一個字節(jié)中),。 你無法保證你的編譯程序能以緊縮的方式存儲多字節(jié)字符串(因為沒有一種普遍接受的方法)。如果你的編譯程序能幫助你處理多字節(jié)字符串,,mbstowcs()和wcstombs()就是完成這部分工作的函數(shù),。
|