久久国产成人av_抖音国产毛片_a片网站免费观看_A片无码播放手机在线观看,色五月在线观看,亚洲精品m在线观看,女人自慰的免费网址,悠悠在线观看精品视频,一级日本片免费的,亚洲精品久,国产精品成人久久久久久久

分享

C標(biāo)準(zhǔn)庫函數(shù)淺析

 longsteg 2012-11-02

使用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ù),。

    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,,不代表本站觀點,。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,,謹防詐騙,。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊一鍵舉報,。
    轉(zhuǎn)藏 分享 獻花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多