C現代方法(第23章)筆記——庫對數值和字符數據的支持

文章目錄

  • 第23章 庫對數值和字符數據的支持
    • 23.1 <float.h>: 浮點類型的特性
    • 23.2 <limits.h>: 整數類型的大小
    • 23.3 <math.h>: 數學計算(C89)
      • 23.3.1 錯誤
      • 23.3.2 三角函數
      • 23.3.3 雙曲函數
      • 23.3.4 指數函數和對數函數
      • 23.3.5 冪函數
      • 23.3.6 就近舍入、絕對值函數和取余函數
    • 23.4 <math.h>: 數學計算(C99)
      • 23.4.1 IEEE浮點標準
      • 23.4.2 類型
      • 23.4.3 宏
      • 23.4.4 錯誤
      • 23.4.5 函數
      • 23.4.6 分類宏
      • 23.4.7 三角函數
      • 23.4.8 雙曲函數
      • 23.4.9 指數函數和對數函數
      • 23.4.10 冪函數和絕對值函數
      • 23.4.11 誤差函數和伽馬函數
      • 23.4.12 就近舍入函數
      • 23.4.13 取余函數
      • 23.4.14 操作函數
      • 23.4.15 最大值函數、最小值函數和正差函數
      • 23.4.16 浮點乘加
      • 23.4.17 比較宏
    • 23.5 <ctype.h>: 字符處理
      • 23.5.1 字符分類函數
      • 23.5.2 字符大小寫映射函數
    • 23.6 <string.h>: 字符串處理
      • 23.6.1 復制函數
      • 23.6.2 拼接函數
      • 23.6.3 比較函數
      • 23.6.4 搜索函數
      • 23.6.5 其他函數
    • 問與答
    • 寫在最后

第23章 庫對數值和字符數據的支持

——與計算機過長時間的接觸會把數學家變成書記員,也會把書記員變成數學家。

本章會介紹5個函數庫的頭,這5個頭提供了對數值、字符和字符串的支持。23.1節23.2節分別介紹了<float.h><limits.h>頭,它們包含了用于描述數值和字符類型特性的宏23.3節23.4節描述<math.h>頭,它提供了數學函數23.3節討論C89版本的<math.h>頭,而23.4節則講述C99中新增的內容,因為內容很多,所以將分別介紹。23.5節23.6節分別討論<ctype.h><string.h>頭,這兩個頭分別提供了字符函數和字符串函數

C99增加了幾個也能處理數、字符和字符串的頭。<wchar.h><wctype.h>頭在第25章中討論。第27章討論<complex.h><fenv.h><inttypes.h><stdint.h><tgmath.h>


23.1 <float.h>: 浮點類型的特性

<float.h>中提供了用來定義floatdoublelong double類型的范圍及精度的宏。<float.h>中沒有類型和函數的定義

有兩個宏對所有浮點類型適用。FLT_ROUNDS表示當前浮點加法的舍入方向(23.4節)表23-1列出了FLT_ROUNDS的可能值。(對于表中沒有給出的值,舍入行為由實現定義。)

表23-1 舍入方向

取值含義
-1不確定
-0趨零截尾
-1向最近的整數舍入
-2向正無窮方向舍入
-3向負無窮方向舍入

<float.h>中的其他宏(表示常量表達式)不同,FLT_ROUNDS的值在執行期間可以改變。(fesetround函數允許程序改變當前的舍入方向。)另一個宏FLT_RADIX指定了指數表示中的基數,它的最小值為2(表明二進制表示)。

其他宏用來描述具體類型的特性,這里會用一系列的表格來描述。根據宏是針對floatdouble還是long double類型,每個宏都會以FLTDBLLDBL開頭C標準對這些宏給出了相當詳細的定義,因此這里的介紹會更注重通俗易懂,不追求十分精確。依據C標準,表中列出了部分宏的最大值和最小值。

表23-2列出了定義每種浮點類型的有效數字個數的宏:

表23-2 <float.h>中的有效數字宏

宏名取值宏的描述
FLT_MANT_DIG有效數字的個數(基數FLT_RADIX
DBL_MANT_DIG
LDBL_MANT_DIG
FLT_DIG≥6有效數字的個數(十進制)
DBL_DIG≥10
LDBL_DIG≥10

表23-3列出了與指數相關的宏:

表23-3 <float.h>中的指數宏

宏名取值宏的描述
FLT_MIN_EXPFLT_RADIX的最小(負的)次冪
DBL_MIN_EXP
LDBL_MIN_EXP
FLT_MIN_10_EXP≤-3710的最小(負的)次冪
DBL_MIN_10_EXP≤-37
LDBL_MIN_10_EXP≤-37
FLT_MAX_EXPFLT_RADIX的最大次冪
DBL_MAX_EXP
LDBL_MAX_EXP
FLT_MAX_10_EXP≥+3710的最大次冪
DBL_MAX_10_EXP≥+37
LDBL_MAX_10_EXP≥+37

表23-4列出的宏描述了最大值、最接近0的值以及兩個連續的數之間的最小差值:

表23-4 <float.h>中的最大值、最小值和差值宏

宏名取值宏的描述
FLT_MAX≥10^37最大的有限值
DBL_MAX≥10^37
LDBL_MAX≥10^37
FLT_MIN≤10^-37最小的正值
DBL_MIN≤10^-37
LDBL_MIN≤10^-37
FLT_EPSILON≤10^-5兩個數之間可表示的最小差值
DBL_EPSILON≤10^-9
LDBL_EPSILON≤10^-9

C99提供了另外兩個宏:DECIMAL_DIGFLT_EVAL_METHODDECIMAL_DIG表示所支持的最大浮點類型的有效數字個數(以10為基數)。FLT_EVAL_METHOD的值說明具體的實現中是否用到了超出實際需要的范圍和精度的浮點運算。例如,如果該宏的值為0,那么對兩個float類型的值相加就按照正常的方法進行;但如果該宏的值為1,在執行加法之前需要先把float類型的值轉換為double類型的值。表23-5列出了FLT_EVAL_METHOD可能的取值。(表中沒有給出的負值表示由實現定義的行為。)

表23-5 求值方法

取值含義
-1不確定
0根據類型的范圍和精度對所有運算和常量求值
1根據double類型的范圍和精度對所有float類型和double類型的運算和常量求值
2根據long double類型的范圍和精度對所有類型的運算和常量求值

<float.h>中定義的大多數宏只有數值分析領域的專家才會感興趣,因此這可能是標準庫中最不常用的頭


23.2 <limits.h>: 整數類型的大小

<limits.h>中提供了用于定義每種整數類型(包括字符類型)取值范圍的宏。在<limits.h>中沒有聲明類型或函數。

<limits.h>中的一組宏用于字符類型:charsigned charunsigned char表23-6列舉了這些宏以及它們的最大值或最小值。

表23-6 <limits.h>中的字符類型宏

宏名取值宏的描述
CHAR_BIT≥8每個字符包含位的位數
SCHAR_MIN≤-127最小的signed char類型值
SCHAR_MAX≥+127最大的signed char類型值
UCHAR_MAX≥255最大的unsigned char類型值
CHAR_MIN最小的char類型值
CHAR_MAX最大的char類型值
MB_LEN_MAX≥1多字節字符最多包含的字節數

①如果char類型被當作有符號類型,則CHAR_MINSCHAR_MIN相等,否則CHAR_MIN0

②根據char類型被當作有符號類型還是無符號類型,CHAR_MAX分別與SCHAR_MAXUCHAR_MAX相等。

其他在<limits.h>中定義的宏針對整數類型:short intunsigned short intintunsigned intlong int以及unsigned long int表23-7列舉了這些宏以及它們的最大值或最小值,并給出了計算各個值的公式。注意!!C99及之后的標準提供了三個宏來描述long long int類型的特性:

表23-7 <limits.h>中整數類型的宏

宏名取值公式宏的描述
SHRT_MIN≤-32767-(2^15-1)最小的short int類型值
SHRT_MAX≥+327672^15-1最大的shor tint類型值
USHRT_MAX≥655352^16-1最大的unsigned short int類型值
INT_MIN≤-32767-(2^15-1)最小的int類型值
INT_MAX≥+327672^15-1最大的int類型值
UINT_MAX≥655352^16-1最大的unsigned int類型值
LONG_MIN≤-2147483647-(2^31-1)最小的long int類型值
LONG_MAX≥+21474836472^31-1最大的long int類型值
ULONG_MAX≥42929672952^32-1最大的unsigned long int類型值
LLONG_MIN①≤-9223372036854775807-(2^63-1)最小的long long int類型值
LLONG_MAX①≥+92233720368547758072^63-1最大的long long int類型值
ULLONG_MAX①≥184467440737095516152^64-1最大的unsigned long long int類型值

①僅C99及之后的標準才有。

<limits.h>中定義的宏在查看編譯器是否支持特定大小的整數時十分方便。例如,如果要判斷int類型是否可以用來存儲像100000一樣大的數,可以使用下面的預處理指令:

#if INT_MAX < 100000 
#error int type is too small 
#endif

如果int類型不適用,#error指令(14.5節)會導致預處理器顯示一條出錯消息。

進一步講,可以使用<limits.h>中的宏來幫助程序選擇正確的類型定義。假設Quantity類型的變量必須可以存儲像100000一樣大的整數。如果INT_MAX至少為100000,就可以將Quantity定義為int;否則,要定義為long int

#if INT_MAX >= 100000 
typedef int Quantity; 
#else 
typedef long int Quantity; 
#endif 

23.3 <math.h>: 數學計算(C89)

C89<math.h>中定義的函數包含下面5種類型:

  • 三角函數;
  • 雙曲函數;
  • 指數和對數函數;
  • 冪函數;
  • 就近舍入函數、絕對值函數和取余函數。

C99在這5種類型中增加了許多函數,并且新增了一些其他類型的數學函數。C99中對<math.h>所做的改動很大,下一節將專門討論相關內容。

在深入討論<math.h>提供的函數之前,先來簡單地了解一下這些函數是如何處理錯誤的。


23.3.1 錯誤

<math.h>中的函數對錯誤的處理方式與其他庫函數不同。當發生錯誤時,<math.h>中的大多數函數會將一個錯誤碼存儲到[<errno.h>(24.2節)中聲明的]一個名為errno的特殊變量中。此外,一旦函數的返回值大于double類型的最大取值,<math.h>中的函數會返回一個特殊的值,這個值由HUGE_VAL宏定義(這個宏在<math.h>中定義)。HUGE_VALdouble類型的,但不一定是普通的數。[IEEE浮點運算標準定義了一個值叫“無窮數”(23.4節),這個值是HUGE_VAL的一個合理的選擇。]

<math.h>中的函數檢查下面兩種錯誤:

  • 定義域錯誤。函數的實參超出了函數的定義域。當定義域錯誤發生時,函數的返回值是由實現定義的,同時EDOM(“定義域錯誤”)會被存儲到errno中。在<math.h>的某些實現中,當定義域錯誤發生時,函數會返回一個特殊的值NaN(“非數”)
  • 取值范圍錯誤。函數的返回值超出了double類型的取值范圍。如果返回值的絕對值過大(上溢出),函數會根據正確結果的符號返回正的或負的HUGE_VAL。此外,值ERANGE(“取值范圍錯誤”)會被存儲到errno中。如果返回值的絕對值太小(下溢出),函數返回零;一些實現可能也會將ERANGE存儲到errno中。

本節不討論取余時可能發生的錯誤。附錄D中的函數描述會解釋導致每種錯誤的情況。


23.3.2 三角函數

double acos(double x); 
double asin(double x); 
double atan(double x); 
double atan2(double y, double x); 
double cos(double x); 
double sin(double x); 
double tan(double x); 

cossintan函數分別用來計算余弦、正弦和正切。假定PI被定義為3.14159265,那么以PI/4為參數調用cossintan函數會產生如下的結果:

cos(PI/4) ? 0.707107 
sin(PI/4) ? 0.707107 
tan(PI/4) ? 1.0 
//注意!!傳遞給 cos、sin和tan函數的實參是以弧度表示的,而不是以角度表示的。

acosasinatan函數分別用來計算反余弦、反正弦和反正切:

acos(1.0) ? 0.0 
asin(1.0) ? 1.5708 
atan(1.0) ? 0.785398

cos函數的計算結果直接調用acos函數不一定會得到最初傳遞給cos函數的值,因為acos函數始終返回一個0~π的值。asin函數與atan函數會返回-π/2~π/2的值。

atan2函數用來計算y/x的反正切值,其中y是函數的第一個參數,x是第二個參數。atan2函數的返回值在-π~π范圍內。調用atan(x)與調用atan2(x,1.0)等價。


23.3.3 雙曲函數

double cosh(double x); 
double sinh(double x); 
double tanh(double x); 

coshsinhtanh函數分別用來計算雙曲余弦、雙曲正弦和雙曲正切:

cosh(0.5) ? 1.12763 
sinh(0.5) ? 0.521095 
tanh(0.5) ? 0.462117

傳遞給coshsinhtanh函數的實參必須以弧度表示,而不能以角度表示


23.3.4 指數函數和對數函數

double exp(double x); 
double frexp(double value, int *exp); 
double ldexp(double x, int exp); 
double log(double x); 
double log10(double x); 
double modf(double value, double *iptr);

exp函數返回e的冪:

exp(3.0) ? 20.0855 

log函數是exp函數的逆運算,它計算以e為底的對數。log10計算“常用”(以10為底)對數:

log(20.0855) ? 3.0 
log10(1000) ? 3.0

對于不以e10為底的對數,計算起來也不復雜。例如,下面的函數對任意的xb,計算以b為底x的對數:

double log_base(double x, double b) 
{ return log(x) / log(b); 
} 

modf函數和frexp函數將一個double型的值拆解為兩部分modf將它的第一個參數分為整數部分和小數部分,返回其中的小數部分,并將整數部分存入第二個參數所指向的對象中:

modf(3.14159, &int_part) ? 0.14159 
//(int_part被賦值為3.0)

雖然int_part的類型必須為double,但我們始終可以隨后將它強制轉換成intlong int

frexp函數將浮點數拆成小數部分?和指數部分n,使得原始值等于?×2^n,其中0.5≤?≤1?=0。函數返回?,并將n存入第二個參數所指向的(整數)對象中:

frexp(12.0, &exp) ? 0.75 //(exp被賦值為 4)
frexp(0.25, &exp) ? 0.5 //(exp被賦值為-1)

ldexp函數會抵消frexp產生的結果,將小數部分和指數部分組合成一個數:

ldexp(.75, 4) ? 12.0 
ldexp(0.5, -1) ? 0.25

一般而言,調用ldexp(x, exp)將返回x × 2^exp

modffrexpldexp函數主要供<math.h>中的其他函數使用,很少在程序中直接調用。


23.3.5 冪函數

double pow(double x, double y); 
double sqrt(double x);

pow函數計算第一個參數的冪,冪的次數由第二個參數指定:

pow(3.0, 2.0) ? 9.0 
pow(3.0, 0.5) ? 1.73205 
pow(3.0, -3.0) ? 0.037037

sqrt函數計算平方根:

sqrt(3.0) ? 1.73205 

由于通常sqrt函數比pow函數的運行速度快得多,因此使用sqrt計算平方根更好。


23.3.6 就近舍入、絕對值函數和取余函數

double ceil(double x); 
double fabs(double x); 
double floor(double x); 
double fmod(double x, double y); 

ceil函數返回一個double類型的值,這個值是大于或等于其參數的最小整數。floor函數則返回小于或等于其參數的最大整數:

ceil(7.1) ? 8.0 
ceil(7.9) ? 8.0 
ceil(-7.1) ? -7.0 
ceil(-7.9) ? -7.0 floor(7.1) ? 7.0 
floor(7.9) ? 7.0 
floor(-7.1) ? -8.0 
floor(-7.9) ? -8.0 

換言之,ceil“向上舍入”到最近的整數,floor“向下舍入”到最近的整數C89沒有標準庫函數可以用來舍入到最近的整數,但我們可以簡單地使用ceil函數和floor函數來實現一個這樣的函數:

double round_nearest(double x) 
{ return x < 0.0 ? ceil(x - 0.5) : floor(x + 0.5); 
} 

fabs函數計算參數的絕對值:

fabs(7.1) ? 7.1 
fabs(-7.1) ? 7.1 

fmod函數返回第一個參數除以第二個參數所得的余數:

fmod(5.5, 2.2) ? 1.1 

注意!!C語言不允許對%運算符使用浮點操作數,不過fmod函數足以用來替代%運算符。


23.4 <math.h>: 數學計算(C99)

C99<math.h>包含了所有C89版本的內容,同時增加了許多的類型、宏和函數。相關的改動很多,我們將分別介紹。標準委員會為<math.h>增加這么多內容,有以下幾個原因:

  • 更好地支持IEEE浮點標準C99不強制使用IEEE標準,其他表示浮點數的方法也是允許的。但是,大多數C程序運行于支持IEEE標準的系統上。
  • 更好地控制浮點運算。對浮點運算加以更好的控制可以使程序達到更高的精度和速度。
  • 使CFortran程序員更具吸引力。增加了許多數學函數,并在C99中做了一些增強(例如,加入了對復數的支持),可以增強C語言對曾經使用過其他編程語言的程序員(主要是Fortran程序員)的吸引力。

小補充:普通的C程序員可能對這一節并不很感興趣。把C語言用于傳統應用程序(包括系統編程和嵌入式系統)的人可能不需要用到C99提供的新函數。但是,開發工程、數學或科學應用程序的程序員可能會覺得這些函數非常有用


23.4.1 IEEE浮點標準

改動<math.h>頭的動機之一是為了更好地支持IEEE 754標準,這是應用最廣的浮點數表示方法。這個標準完整的名稱為“IEEE Standard for Binary Floating-Point Arithmetic”(ANSI/IEEE 標準754-1985),也叫作3IEC 60599,這是C99標準中的叫法。

7.2節描述了IEEE標準的一些基本性質。該標準提供了兩種主要的浮點數格式:單精度(32位)雙精度(64位)。數值按科學記數法存儲,每個數包括三個部分:符號、指數和小數。對IEEE標準的這一有限了解足以有效地使用C89<math.h>了。但是,要了解C99的<math.h>,則需要更詳細地了解IEEE標準。下面是一些我們需要了解的信息。

  • 正零/負零。在浮點數的IEEE表示中有一位代表數的符號。因此,根據該位的不同取值,零既可以是正數也可以是負數。零具有兩種表示這一事實有時要求我們把它與其他浮點數區別對待。

  • 非規范化的數進行浮點運算的時候,結果可能會太小以至于不能表示,這種情況稱為下溢出。考慮使用計算器反復除以一個數的情況:結果最終為零,這是因為數值會變得太小,以至于計算器無法顯示。IEEE標準提供了一種方法來減弱這種現象的影響。通常浮點數按“規范”格式存儲,二進制小數點的左邊恰好只有一位數字。當數變得足夠小時,就按另一種非規范化的形式來存儲。這些非規范化的數(subnormal number也叫作denormalized number或denormal)可以比規范化的數小很多,代價是當數變得越來越小時精度會逐漸降低。

  • 特殊值。每個浮點格式允許表示三種特殊值:正無窮數、負無窮數和NaN(非數)。正數除以零產生正無窮數,負數除以零產生負無窮數,數學上沒有定義的運算(如零除以零)產生的結果是NaN(更準確的說法是“結果是一種NaN”而不是“結果是NaN”,因為IEEE標準有多種表示NaN的方式。NaN的指數部分全為1,但小數部分可以是任意的非零位序列)。后續的運算中可以用特殊值作為操作數。對無窮數的運算與通常的數學運算是一樣的。例如,正數除以正無窮數結果為零(需要注意,算術表達式的中間結果可能會是無窮數,但最終結果不是無窮數)。對NaN進行任何運算,結果都為NaN

  • 舍入方向。當不能使用浮點表示法精確地存儲一個數時,當前的舍入方向(或者叫舍入模式)可以確定選擇哪個浮點值來表示該數。一共有4種舍入方向:

    1. 向最近的數舍入,向最接近的可表示的值舍入,如果一個數正好在兩個數值的中間就向“偶”值(最低有效位為0)舍入;
    2. 趨零截尾;
    3. 向正無窮方向舍入;
    4. 向負無窮方向舍入。

    默認的舍入方向是向最近的數舍入

  • 異常。有5種類型的浮點異常:上溢出、下溢出、除零、無效運算(算術運算的結果是NaN)和不精確(需要對算術運算的結果舍入)。當檢查到其中任何一個條件時,我們稱拋出異常。


23.4.2 類型

C99<math.h>中加入了兩種類型:float_tdouble_tfloat_t類型至少和float型一樣“寬”(意思是說有可能是float型,也可能是double等更寬的類型)。同樣地,double_t要求寬度至少是double類型的(至少和float_t一樣寬)。這些類型提供給程序員以最大限度地提高浮點運算的性能。float_t應該是寬度至少為float的最有效的浮點類型,double_t應該是寬度至少為double的最有效的浮點類型。

float_tdouble_t類型與宏FLT_EVAL_METHOD(23.1節)相關,如表23-8所示。

表23-8 float_tdouble_t類型與FLT_EVAL_METHOD宏的關系

FLT_EVAL_METHOD的值float_t的含義double_t的含義
0floatdouble
1doubledouble
2long doublelong double
其他由實現定義由實現定義

23.4.3 宏

C99<math.h>增加了許多宏,這里只介紹其中的兩個:INFINITY表示正無窮數和無符號無窮數的float版本(如果實現不支持無窮數,那么INFINITY表示編譯時會導致上溢出的float類型值);NAN宏表示“非數”的float版本,更具體地說,它表示“安靜的”NaN(用于算術表達式時不會拋出異常)。如果不支持安靜的NaNNAN宏不會被定義。

本節后面將介紹<math.h>中類似于函數的宏以及普通的函數。只和具體函數相關的宏與該函數一起討論。


23.4.4 錯誤

在大多數情況下,C99版本的<math.h>在處理錯誤時和C89版本的相同,但有幾點需要討論。

首先,C99提供的一些宏允許在實現時選擇如何提示出錯消息:通過存儲在errno中的值、通過浮點異常,或者兩者都有。宏MATH_ERRNOMATH_ERREXCEPT分別表示整型常量12。另一個宏math_errhandling表示一個int表達式,其值可以是MATH_ERRNOMATH_ERREXCEPT或者兩者按位或運算的結果(math_errhandling也可能不是一個真正的宏,它可能是一個具有外部鏈接的標識符)。在程序內math_errhandling的值不會改變。

其次,我們來看看在調用<math.h>的函數時出現定義域錯誤的情形。C89會把EDOM存放在errno中。在C99標準中,如果表達式math_errhandling&MATH_ERRNO非零(即設置了MATH_ERRNO位),那么會把EDOM存放在errno中;如果表達式math_errhandling&MATH_ERREXCEPT非零,會拋出無效運算浮點異常。根據math_errhandling取值的不同,這兩種情況都有可能出現。

最后,討論一下在函數調用過程中出現取值范圍錯誤的處理方式。根據返回值的大小有2種情形。

上溢出(overflow。如果返回的值太大,C89標準要求函數根據正確結果的符號返回正的或負的HUGE_VAL。另外,把ERANGE存儲在errno中。C99標準在發生上溢出時會有更復雜的處理方式。

  • 如果采用默認的舍入方向或返回值是“精確的無窮數”(如log(0.0)),根據返回類型的不同,函數會返回HUGE_VALHUGE_VALF或者HUGE_VALLHUGE_VALFHUGE_VALLC99新增的,分別表示HUGE_VALfloatlong double版本。與HUGE_VAL一樣,它們可以表示正無窮數)。返回值與正確結果的符號相同。
  • 如果math_errhandling&MATH_ERRNO的值非零,把ERANGE存于errno中。
  • 如果math_errhandling&MATH_ERREXCEPT的值非零,當數學計算的結果是精確的無窮數時拋出除零浮點異常,否則拋出上溢出異常。

下溢出(underflow。如果返回的值太小而無法表示,C89要求函數返回0,一些實現可能也會將ERANGE存入errnoC99中的處理有點不同。

  • 函數返回值小于或等于相應返回類型的最小規范化正數。(這個值可以是0或者非規范化的數。)
  • 如果math_errhandling&MATH_ERRNO的值非零,實現中有可能把ERANGE存于errno中。
  • 如果math_errhandling&MATH_ERREXCEPT的值非零,實現中有可能拋出下溢出浮點異常。

注意后兩種情況中的“有可能”,為了執行的效率,實現不要求修改errno或拋出下溢出異常。


23.4.5 函數

現在可以討論C99<math.h>中新增的函數了。本節將使用C99標準中的分類方法把函數分組討論,這種分類和23.3節中來自C89的分類有些不一致。

C99版本中,對<math.h>的最大改動是大部分函數都新增了兩個或兩個以上的版本。在C89中,每個數學函數只有一種版本,通常至少有一個double類型的參數或返回值是double類型。C99另外新增了兩個版本:float類型和long double類型。這些函數名和原本的函數名相同,只不過增加了后綴fl。例如,原來的sqrt函數對double類型的值求平方根,現在就有了sqrtf(float版本)sqrtl(long double版本)。本節將列出新版本的原型,但不會深入討論相應的函數,因為它們本質上與C89中的對應函數一樣。

C99版本的<math.h>中也有許多全新的函數(以及類似函數的宏)。將會對每一個函數進行簡要的介紹。與23.3節一樣,本節不會討論這些函數的錯誤條件,但是在附錄D(按字母序列出了所有的標準庫函數)中會給出相關信息。本節沒有對所有新函數進行詳細描述,而只是描述主要的函數。例如,有三個函數可以計算反雙曲余弦,即acoshacoshfacoshl,將只描述acosh

一定要記住:很多新的函數是非常特別的。因此描述看起來可能會很粗略,暫時不討論對這些函數具體用法。


23.4.6 分類宏

int fpclassify(實浮點 x); 
int isfinite(實浮點 x); 
int isinf(實浮點 x); 
int isnan(實浮點 x); 
int isnormal(實浮點 x); 
int signbit(實浮點 x); 

我們介紹的第一類包括類似函數的宏,它們用于確定浮點數的值是“規范化”的數還是無窮數NaN之類的特殊值。這組宏的參數都是任意的實浮點類型(floatdouble或者long double)。

fpclassify宏對參數分類,返回表23-9中的某個數值分類宏。具體的實現可以通過定義以FP_和大寫字母開頭的其他宏來支持其他分類。

表23-9 數值分類宏

名稱含義
FP_INFINITE無窮數(正或負)
FP_PAN非數
FP_NORMAL規范化的數(不是0、非規范化的數、無窮數或 NaN)
FP_SUBNORMAL非規范化的數
FP_ZERO0(正或負)

如果isfinite宏的參數具有有限值(0、非規范化的數,或是除無窮數與NaN之外的規范化的數),該宏返回非零值。如果isinf的參數值為無窮數(正或負),該宏返回非零值。如果isnan的參數值是NaN,該宏返回非零值。如果isnormal的參數是一個正常值(不是0、非規范化的數、無窮數或NaN),該宏返回非零值。(非零值=真)

最后一個宏與其他幾個有點區別。如果參數的符號為負,signbit返回非零值。參數不一定是有限數,signbit也可以用于無窮數和NaN


23.4.7 三角函數

float acosf(float x);               見 acos 
long double acosl(long double x);   見 acos float asinf(float x);               見 asin 
long double asinl(long double x);   見 asin float atanf(float x);               見 atan   
long double atanl(long double x);   見 atan float atan2f(float y float x);      見 atan2 
long double atan2l(long double y, 
long double x);                     見 atan2 float cosf(float x);                見 cos 
long double cosl(long double x);    見 cos float sinf(float x);                見 sin 
long double sinl(long double x);    見 sin float tanf(float x);                見 tan 
long double tanl(long double x);    見 tan

C99中的新三角函數與C89中的函數相似,具體描述見23.3節的對應函數。


23.4.8 雙曲函數

double acosh(double x); 
float acoshf(float x); 
long double acosh1(long double x); double asinh(double x); 
float asinhf(float x); 
long double asinhl(long double x);double atanh(double x);
float atanhf(float x);
long double atanhl(long double x);float coshf(float x);                   見 cosh 
long double coshl(long double x);       見 cosh float sinhf(float x);                   見 sinh 
long double sinhl(long double x);       見 sinh float tanhf(float x);                   見 tanh 
long double tanhl(long double x);       見 tanh

這一組的6個函數與C89函數中的coshsinhtanh相對應。新的函數acosh計算雙曲余弦,asinh計算雙曲正弦,atanh計算雙曲正切。


23.4.9 指數函數和對數函數

float expf(float x);                                        見 exp 
long double expl(long double x);                            見 exp double exp2(double x);
float exp2f(float x);
long double exp21(long double x);double expm1(double x);
float expm1f(float x);
long double expm1l(long double x);float frexpf(float value, int *exp);                        見 frexp 
long double frexpl(long double value, int *exp);            見 frexp int ilogb(double x);
int ilogbf(float x) ;
int ilogbl(long double x);float ldexpf(float x, int exp);                             見 ldexp 
long double ldexpl(long double x, int exp);                 見 ldexp float logf(float x);                                        見 log 
long double logl(long double x);                            見 log float log10f(float x);                                      見 log10 
long double log10l(long double x);                          見 log10 double log1p(double x); 
float log1pf(float x);
long double log1pl(long double x);double log2(double x);
float log2f(float x);
long double log2l(long double x);double logb(double x);
float logbf(float x);
long double logbl(long double x);float modff(float value, float *iptr);                      見 modf 
long double modfl(long double value, long double *iptr);    見 modf double scalbn(double x, int n);
float scalbnf(float x, int n);
long double scalbnl(long double x, int n);double scalbln(double x, long int n);
float scalblnf(float x, long int n);
long double scalblnl(long double x, long int n);

除了expfrexpldexploglog10modf的新版本以外,這一類中還有一些全新的函數。其中exp2expm1exp函數的變體。當應用于參數x時,exp2函數返回 2 x {2^x} 2xexpm1返回 e x ? 1 {e^x-1} ex?1

logb函數返回參數的指數。更準確地說,調用logb(x)返回log(r為底)(|x|),其中r是浮點算術的基數(由宏FLT_RADIX定義,通常值為2)。ilogb函數把logb的值強制轉換為int類型并返回loglp函數返回ln(1+x),其中x是參數。log2函數以2為底計算參數的對數。

函數scalbn返回x乘FLT_RADIX^n,這個函數能有效地進行計算(不會顯式地計算FLT_RADIXn次冪)。scalbln除第二個參數是long int類型之外,其他和scalbn函數相同。


23.4.10 冪函數和絕對值函數

double cbrt(double x);
float cbrtf(float x);
long double cbrtl(long double x);float fabsf(float x);                               見 fabs 
long double fabsl(long double x);                   見 fabs double hypot(double x, double y);
float hypotf(float x, float y);
long double hypotl(long double x, long double y);float powf(float x, float y);                       見 pow 
long double powl(long double x, long double y);     見 pow float sqrtf(float x);                               見 sqrt 
long double sqrtl(long double x);                   見 sqrt

這一組中的大部分函數是已有函數(fabspowsqrt)的新版,只有cbrthypot(以及它們的變體)是全新的。

cbrt函數計算參數的立方根pow函數同樣可用于這個目的,但pow不能處理負參數(負參數會導致定義域錯誤)。cbrt既可以用于正參數也可以用于負參數,當參數為負時返回負值。

hypot函數應用于參數xy時返回 x 2 + y 2 \sqrt{x^2+y^2} x2+y2 ?。換句話說,這個函數計算的是邊長為xy的直角三角形的斜邊。


23.4.11 誤差函數和伽馬函數

double erf(double x);
float erff(float x);
long double erfl(long double x);double erfc(double x);
float erfcf(float x);
long double erfcl(long double x);double lgamma(double x);
float lgammaf(float x);
long double lgammal(long double x);double tgamma(double x);
float tyammaf(float x);
long double tgammal(long double x);

函數erf計算誤差函數erf(通常也叫高斯誤差函數),常用于概率、統計和偏微分方程。erf的數學定義如下:

e r f ( x ) = 2 π ∫ 0 x e ? t 2 d t erf(x) = \frac{2}{\sqrt{\pi}}\int_0^x{e^{-t^2}}dt erf(x)=π ?2?0x?e?t2dt

erfc計算余誤差函數(complementary error function) e r f c ( x ) = 1 ? e r f ( x ) erfc(x)=1-erf(x) erfc(x)=1?erf(x)

伽馬函數(gammafunction) Γ \Gamma Γ是階乘函數的擴展,不僅可以應用于整數,還可以應用于實數。當應用于整數n時, Γ ( n ) = ( n ? 1 ) ! {\Gamma(n)=(n-1)!} Γ(n)=(n?1)!。用于非整數的 Γ \Gamma Γ函數定義更為復雜。tgamma函數計算 Γ \Gamma Γlgamma函數計算 l n ( ∣ Γ ( x ) ∣ ) {ln(|\Gamma(x)|)} ln(∣Γ(x)),它是伽馬函數絕對值的自然對數。lgamma有些時候會比伽馬函數本身更有用,因為伽馬函數增長太快,計算時容易導致溢出


23.4.12 就近舍入函數

float ceilf(float x);                               見 ceil 
long double ceill(long double x);                   見 ceil float floorf(float x);                              見 floor 
long double floorl(long double x);                  見 floor double nearbyint(double x);
float nearbyintf(float x);
long double nearbyintl(long double x);double rint(double x);
float rintf(float x);
long double rintl(long double x);long int lrint (double x);
long int lrintf(float x);
long int lrintl(long double x);
long long int llrint(double x);
long long int llrintf(float x);
long long int llrintl(long double x);double round(double x);
float roundf(float x); 
long double roundl(long double x); long int lround (double x); 
long int lroundf(float x); 
long int lroundl(long double x); 
long long int llround(double x); 
long long int llroundf(float x); 
long long int llroundl(long double x); double trunc(double x); 
float truncf(float x); 
long double truncl(long double x);

除了ceilfloor的新增版本,C99還新增了許多函數,用于把浮點值轉換為最接近的整數。在使用這些函數時需要注意:盡管它們都返回整數,但一些函數按浮點格式(如floatdoublelong double值)返回,一些函數按整數格式(如long intlong long int值)返回

nearbyint函數對參數舍入,并以浮點數的形式返回nearbyint使用當前的舍入方向,且不會拋出不精確浮點異常。rintnearbyint相似,但當返回值與參數不相同時,有可能拋出不精確浮點異常。

lrint函數根據當前的舍入方向對參數向最近的整數舍入lrint返回long int類型的值。llrintlrint相似,但返回long long int類型的值。

round函數對參數向最近的整數舍入,并以浮點數的形式返回round函數總是向遠離零的方向舍入(如3.5舍入為4.0)。

lround函數對參數向最近的整數舍入,并以long int類型值的形式返回。和round函數一樣,它總是向遠離零的方向舍入。llroundlround相似,但返回long long int類型的值。

trunc函數對參數向不超過參數的最近的整數舍入。(換句話說,它把參數趨零截尾。)trunc以浮點數的形式返回結果。


23.4.13 取余函數

float fmodf(float x, float y);                                  見 fmod 
long double fmodl(long double x, long double y);                見 fmod double remainder(double x, double y);
float remainderf(float x, float y);
long double remainderl(long double x, long double y);double remquo(double x, double y, int *quo);
float remquof(float x, float y, int *quo);
long double remquol(long double x, long double y, int *quo);

除了fmod的新版本之外,這一類還包含兩種新增的函數:remainderremquo

remainder返回的是xREMy的值,其中REMIEEE標準定義的函數。當y不等于0時,xREMy的值為r=x-ny,其中n是與x/y的準確值最接近的整數。(如果x/y的值恰好位于兩個整數的中間,n取偶數。)如果r=0,則與x的符號相一致。

remquo函數的前兩個參數值與remainder的相等時,其返回值也與remainder的相等。另外,remquo函數會修改參數quo指向的對象,使其包含整數商|x/y|n個低位字節,其中n依賴于具體的實現但至少為3。如果x/y<0,存儲在該對象中的值為負。


23.4.14 操作函數

double copysign(double x, double y);
float copysignf(float x, float y);
long double copysignl(long double x, long double y);double nan(const char *tagp);
float nanf(const char *tagp);
long double nanl(const char *tagp);double nextafter(double x, double y);
float nextafterf(float x, float y);
long double nextafterl(long double x, long double y);double nexttoward(double x, long double y);
float nexttowardf(float x, long double y);
long double nexttowardl(long double x, long double y);

這些神秘的“操作函數”都是C99新增的。它們提供了對浮點數底層細節的訪問

  • copysign函數復制一個數的符號到另一個數。函數調用copysign(x,y)返回的值大小與x相等,符號與y一樣。

  • nan函數將字符串轉換為NaN。調用nan("n個字符的序列")等價于strtod("NAN(n個字符的序列)",(char**)NULL)。[討論strtod函數(26.2節)時描述了n個字符的序列的格式。]調用nan("")等價于strtod("NAN()",(char**)NULL)。如果nan的參數既不是"n個字符的序列"又不是"",那么該調用等價于strtod("NAN",(char**)NULL)。如果系統不支持安靜的NaN,那么nan返回0。對nanfnanl的調用分別等價于對strtofstrtold調用。這個函數用于構造包含特定二進制模式的NaN值。(回憶一下本節前面的論述,NaN值的小數部分是任意的。)

  • nextafter函數用于確定數值x之后的可表示的值(如果x類型的所有值都按序排列,這個值將恰好在x之前或x之后)。y的值確定方向:如果y<x,則函數返回恰好在x之前的那個值;如果x<y,則返回恰好在x之后的那個值;如果xy相等,則返回y

  • nexttoward函數和nextafter函數相似,區別在于參數y的類型為long double而不是double。如果xy相等,nexttoward將返回被轉換為函數的返回類型的ynexttoward函數的優勢在于,任意(實)浮點類型都可以作為第二個參數,而不用擔心會錯誤地將其轉換為較窄的類型。


23.4.15 最大值函數、最小值函數和正差函數

double fdim(double x, double y);
float fdimf(float x, float y);
long double fdiml(long double x, long double y);double fmax(double x, double y);
float fmaxf(float x, float y);
long double fmaxl(long double x, long double y);double fmin(double x, double y);
float fminf(float x, float y);
long double fmainl(long double x, long double y);

函數fdim計算xy的正差:

f ( x ) = { x ? y , x > y + 0 , x ≤ y f(x) = \begin{cases} x-y,\,\,x>y\\ +0,\,\,x\le{y}\\ \end{cases} f(x)={x?y,x>y+0,xy?

fmax函數返回兩個參數中較大的一個,fmin返回較小的一個。


23.4.16 浮點乘加

double fma(double x, double y, double z);
float fmaf(float x, float y, float z);
long double fmal(long double x, long double y, long double z);

fma函數是將它的前兩個參數相乘再加上第三個參數。換句話說,我們可以將語句

a = b * c + d;

替換為

a = fma(b, c, d);

C99中增加這個函數是因為一些新的CPU具有“融合乘加”(fused multiply-add)指令,該指令既執行乘法也執行加法。調用fma告訴編譯器使用這個指令(如果可以的話),這樣比分別執行乘法指令和加法指令要快。而且,融合乘加指令只進行一次舍入,而不是兩次,所以可以產生更加精確的結果。融合乘加指令特別適用于需要執行一系列乘法和加法運算的算法,如計算兩個向量點積的算法或兩個矩陣相乘的算法。

為了確定是否可以調用fma函數,C99程序可以測試FP_FAST_FMA宏是否有定義。如果有定義,那么調用fma應該會比分別進行乘法運算和加法運算要快(至少一樣快)。對于fmaf函數和fmal函數,FP_FAST_FMAFFP_FAST_FMAL宏分別扮演著同樣的角色。

把乘法和加法合并成一條指令來執行是C99標準中所說的“緊縮”(contraction)的一個例子。緊縮把兩個或多個數學運算合并起來,當成一條指令來執行。從fma函數可以看出,緊縮通常可以獲得更快的速度和更高的精度。但是,因為緊縮可能會導致結果發生細微的變化,所以程序員希望能控制緊縮是否自動進行(上面的fma是顯式要求進行緊縮的)。極端情況下,緊縮可以避免拋出浮點異常。

C99中可以用包含FP_CONTRACT#pragma指令來實現對緊縮的控制,用法如下:

#pragma STDC FP_CONTRACT 開關

開關的值可以是ONOFFDEFAULT。如果選擇ON,編譯器允許對表達式進行緊縮;如果選擇OFF,編譯器禁止對表達式進行緊縮;DEFAULT用于恢復默認設置(ONOFF)。如果在程序的外層(所有函數定義的外部)使用該指令,該指令將持續有效,直到在同一個文件中遇到另一條包含FP_CONTRACT#pragma指令或者到達文件末尾。如果在復合語句(包括函數體)中使用該指令,必須將其放在所有聲明和語句之前;在到達復合語句的末尾之前,該指令都是有效的,除非被另一條#pragma覆蓋。即便用FP_CONTRACT禁止了對表達式的自動緊縮,程序仍然可以調用fma執行顯式的緊縮。


23.4.17 比較宏

int isgreater(實浮點 x, 實浮點 y);
int isgreaterequal(實浮點 x, 實浮點 y);
int isless(實浮點 x, 實浮點 y);
int islessequal(實浮點 x, 實浮點 y);
int islessgreater(實浮點 x, 實浮點 y);
int isunordered(實浮點 x, 實浮點 y);

最后一類類似函數的宏對兩個數進行比較,它們的參數可以是任意實浮點類型。

增加比較宏是因為使用普通的關系運算符(如<>)比較浮點數時會出現問題。如果任一操作數(或兩個)是NaN,那么這樣的比較就可能導致拋出無效運算浮點異常,因為NaN的值(不同于其他浮點數的值)被認為是無序的。比較宏可以用來避免這種異常。這些宏可以稱作關系運算符的“安靜”版本,因為它們在執行時不會拋出異常。

isgreaterisgreaterequalislessislessequal宏分別執行與>>=<<=相同的運算,區別在于,當參數無序時它們不會拋出無效運算浮點異常

調用islessgreater(x,y)等價于(x)<(y)||(x)>(y),唯一的區別在于前者不會對xy求兩次值,而且(與之前提到的宏一樣)當xy無序時不會導致拋出無效運算浮點異常。

isunordered宏在參數無序(其中至少一個是NaN)時返回1,否則返回0


23.5 <ctype.h>: 字符處理

<ctype.h>提供了兩類函數:字符分類函數(如isdigit函數,用來檢測一個字符是否是數字)和字符大小寫映射函數(如toupper函數,用來將一個小寫字母轉換成大寫字母)。

雖然C語言并不要求必須使用<ctype.h>中的函數來測試字符或進行大小寫轉換,但我們仍建議使用<ctype.h>中定義的函數來進行這類操作:

  • 第一,這些函數已經針對運行速度進行過優化(實際上,大多數都是用宏實現的);
  • 第二,使用這些函數會使程序的可移植性更好,因為這些函數可以在任何字符集上運行;
  • 第三,當地區(locale 25.1節)改變時,<ctype.h>中的函數會相應地調整其行為,使我們編寫的程序可以正確地運行在世界上不同的地點。

<ctype.h>中定義的函數都具有int類型的參數,并返回int類型的值。許多情況下,參數事先存放在一個int型的變量中(通常是調用fgetcgetcgetchar讀取的結果)。當參數類型為char時,需要小心。C語言可以自動將char類型的參數轉換為int類型;如果char是無符號類型或者使用ASCII之類的7位字符集,轉換不會出問題,但如果char是有符號類型且有些字符需要用8位來表示,那么把這樣的字符從char轉換為int就會得到負值。當參數為負時,<ctype.h>中的函數行為是未定義的(EOF除外),這樣可能會造成一些嚴重的問題。這種情況下應把參數強制轉換為unsigned char類型以確保安全。(為了最大化可移植性,一些程序員在使用<ctype.h>中的函數之前總是把char類型的參數強制轉換為unsigned char類型。)


23.5.1 字符分類函數

int isalnum(int c); 
int isalpha(int c); 
int isblank(int c);
int iscntrl(int c); 
int isdigit(int c); 
int isgraph(int c); 
int islower(int c); 
int isprint(int c); 
int ispunct(int c); 
int isspace(int c); 
int isupper(int c); 
int isxdigit(int c); 

如果參數具有某種特定的性質,字符分類函數會返回非零值表23-10列出了每個函數所測試的性質。

表23-10 字符分類函數

取值取值對應的舍入模式
isalnum?c是否是字母或數字
isalpha?c是否是字母
isblank?c是否是標準空白字符①
iscntrl?c是否是控制字符②
isdigit?c是否是十進制數字
isgraph?c是否是可顯示字符(除空格外)
islower?c是否是小寫字母
isprint?c是否是可打印字符(包括空格)
ispunct?c是否是標點符號③
isspace?c是否是空白字符④
isupper?c是否是大寫字母
isxdigit?c是否是十六進制數字

①標準空白字符是空格水平制表符(\t)。這是C99中的新函數。

②在ASCII字符集中,控制字符包括\x00\x1f,以及\x7f

③標點符號包括所有可打印字符,但要除掉使isspaceisalnum為真的字符。

④空白字符包括空格換頁符(\f)換行符(\n)回車符(\r)水平制表符(\t)垂直制表符(\v)

ispunctC99中的定義與在C89中的定義略有不同。在C89中,ispunct(c)測試c是否為除空格符和使isalnum(c)為真的字符以外的可打印字符。在C99中,ispunct(c)測試c是否為除了使isspace(c)isalnum(c)為真的字符以外的可打印字符。


23.5.2 字符大小寫映射函數

int tolower(int c); 
int toupper(int c); 

tolower函數返回與作為參數傳遞的字母相對應的小寫字母,而toupper函數返回與作為參數傳遞的字母相對應的大寫字母。對于這兩個函數,如果所傳參數不是字母,那么將返回原始字符,不加任何改變

下面的程序對字符串"aA0!"中的字符進行大小寫轉換:

/*
tcasemap.c
--Tests the case-mapping functio
*/
#include <ctype.h> 
#include <stdio.h> 
int main(void) 
{ char *p; for (p = "aA0!"; *p != '\0'; p++) { printf("tolower('%c') is '%c'; ", *p, tolower(*p)); printf("toupper('%c') is '%c'\n", *p, toupper(*p)); } return 0; 
} 
/*
這段程序產生的輸出如下:
tolower('a') is 'a'; toupper('a') is 'A' 
tolower('A') is 'a'; toupper('A') is 'A' 
tolower('0') is '0'; toupper('0') is '0' 
tolower('!') is '!'; toupper('!') is '!'
*/

23.6 <string.h>: 字符串處理

我們第一次見到<string.h>是在13.5節,那一節中討論了最基本的字符串操作:字符串復制字符串拼接字符串比較以及字符串長度計算。接下來我們將看到,除了用于字符數組(不需要以空字符結尾)的字符串處理函數之外,<string.h>中還有許多其他字符串處理函數。前一類函數的名字以mem開頭,以表明它們處理的是內存塊而不是字符串。這些內存塊可以包含任何類型的數據,因此mem函數的參數類型為void *而不是char *

<string.h>提供了5種函數:

  • 復制函數,將字符從內存中的一處復制到另一處。
  • 拼接函數,向字符串末尾追加字符。
  • 比較函數,用于比較字符數組。
  • 搜索函數,在字符數組中搜索一個特定字符、一組字符或一個字符串。
  • 其他函數,初始化字符數組或計算字符串的長度。

23.6.1 復制函數

void *memcpy(void * restrict s1, const void * restrict s2, size_t n); 
void *memmove(void * s1, const void * s2, size_t n); 
char *strcpy(char * restrict s1, const char * restrict s2); 
char *strncpy(char * restrict s1, const char * restrict s2, size_t n);

這一類函數將字符(字節)從內存的一處(源)移動到另一處(目的地)。每個函數都要求第一個參數指向目的地,第二個參數指向源。所有的復制函數都會返回第一個參數(即指向目的地的指針)。

  • memcpy函數從源向目的地復制n個字符,其中n是函數的第三個參數。如果源和目的地之間有重疊,memcpy函數的行為是未定義的。memmove函數與memcpy函數類似,只是在源和目的地重疊時它也可以正常工作。

  • strcpy函數將一個以空字符結尾的字符串從源復制到目的地strncpystrcpy類似,只是它不會復制多于n個字符,其中n是函數的第三個參數。(如果n太小,strncpy可能無法復制結尾的空字符。)如果strncpy遇到源字符串中的空字符,它會向目的字符串不斷追加空字符,直到寫滿n個字符為止。與memcpy類似,strcpystrncpy不保證當源和目的地相重疊時可以正常工作。

下面的例子展示了所有的復制函數,注釋中給出了哪些字符會被復制:

char source[] = {'h', 'o', 't', '\0', 't', 'e', 'a'}; 
char dest[7]; 
memcpy(dest, source, 3); /* h, o, t */ 
memcpy(dest, source, 4); /* h, o, t, \0 */ 
memcpy(dest, source, 7); /* h, o, t, \0, t, e, a */ 
memmove(dest, source, 3); /* h, o, t */ 
memmove(dest, source, 4); /* h, o, t, \0 */ 
memmove(dest, source, 7); /* h, o, t, \0, t, e, a */ 
strcpy(dest, source); /* h, o, t, \0 */ 
strncpy(dest, source, 3); /* h, o, t */ 
strncpy(dest, source, 4); /* h, o, t, \0 */ 
strncpy(dest, source, 7); /* h, o, t, \0, \0, \0, \0 */ 

注意!!memcpymemmovestrncpy都不要求使用空字符結尾的字符串,它們對任意內存塊都可以正常工作。而strcpy函數則會持續復制字符,直到遇到一個空字符為止,因此strcpy僅適用于以空字符結尾的字符串

13.5節給出了strcpystrncpy的常見用法示例。這兩個函數都不完全安全,但至少strncpy提供了一種方法來限制所復制字符的個數。


23.6.2 拼接函數

char *strcat(char * restrict s1, const char * restrict s2); 
char *strncat(char * restrict s1, const char * restrict s2, size_t n); 

strcat函數將它的第二個參數追加到第一個參數的末尾。兩個參數都必須是以空字符結尾的字符串。strcat函數會在拼接后的字符串末尾添加空字符。考慮下面的例子:

char str[7] = "tea"; 
strcat(str, "bag"); /* adds b, a, g, \0 to end of str */ 

字母b會覆蓋"tea"中字符a后面的空字符,因此現在str包含字符串"teabag"strcat函數會返回它的第一個參數(指針)。

strncat函數與strcat函數基本一致,只是它的第三個參數會限制所復制字符的個數:

char str[7] = "tea"; 
strncat(str, "bag", 2); /* adds b, a, \0 to str */ 
strncat(str, "bag", 3); /* adds b, a, g, \0 to str */ 
strncat(str, "bag", 4); /* adds b, a, g, \0 to str */

正如上面的例子所示,strnact函數會保證其結果字符串始終以空字符結尾

在13.5節中我們發現,strncat的調用通常具有如下形式:

strncat(str1, str2, sizeof(str1)strlen(str1)1); 

第三個參數計算str1中剩余的空間大小(由表達式sizeof(str1) – strlen(str1)給定),然后減1以確保給空字符留出空間。


23.6.3 比較函數

int memcmp(const void *s1, const void *s2, size_t n); 
int strcmp(const char *s1, const char *s2); 
int strcoll(const char *s1, const char *s2); 
int strncmp(const char *s1, const char *s2, size_t n); 
size_t strxfrm(char * restrict s1, const char * restrict s2, size_t n); 

比較函數分為2組。第一組中的函數(memcmpstrcmpstrncmp)比較兩個字符數組的內容,第二組中的函數(strcoll函數和strxfrm函數)在需要考慮地區(25.1節)時使用。

memcmpstrcmpstrncmp函數有許多共性。這三個函數都需要以指向字符數組的指針作為參數,然后用第一個字符數組中的字符逐一地與第二個字符數組中的字符進行比較。這三個函數都是在遇到第一個不匹配的字符時返回。另外,這三個函數都根據比較結束時第一個字符數組中的字符是小于、等于還是大于第二個字符數組中的字符,而相應地返回負整數0正整數

這三個函數之間的差異在于,如果數組相同,則何時停止比較。memcmp函數包含第三個參數nn會用來限制參與比較的字符個數,但memcmp函數不會關心空字符。strcmp函數沒有對字符數設定限制,因此會在其中任意一個字符數組中遇到空字符時停止比較。(因此,strcmp函數只能用于以空字符結尾的字符串。)strncmp結合了memcmpstrcmp,當比較的字符數達到n在其中任意一個字符數組中遇到空字符時停止比較。

下面的例子展示了memcmpstrcmpstrncmp的用法:

char s1[] = {'b', 'i', 'g', '\0', 'c', 'a', 'r'}; 
char s2[] = {'b', 'i', 'g', '\0', 'c', 'a', 't'}; 
if (memcmp(s1, s2, 3) == 0) ... /* true */ 
if (memcmp(s1, s2, 4) == 0) ... /* true */ 
if (memcmp(s1, s2, 7) == 0) ... /* false */ 
if (strcmp(s1, s2) == 0)... /* true */ 
if (strncmp(s1, s2, 3) == 0) ... /* true */ 
if (strncmp(s1, s2, 4) == 0) ... /* true */ 
if (strncmp(s1, s2, 7) == 0) ... /* true */ 

strcoll函數與strcmp函數類似,但比較的結果依賴于當前的地區。

大多數情況下,strcoll都足夠用來處理依賴于地區的字符串比較。但有些時候,我們可能需要多次進行比較(strcoll的一個潛在問題是,它不是很快),或者需要改變地區而不影響比較的結果。在這些情況下,strxfrm函數(“字符串變換”)可以用來代替strcoll使用。

strxfrm函數會對它的第二個參數(一個字符串)進行變換,將變換的結果放在第一個參數所指向的字符串中。第三個參數用來限制向數組輸出的字符個數,包括最后的空字符。用兩個變換后的字符串作為參數調用strcmp函數所產生的結果應該與用原始字符串作為參數調用strcoll函數所產生的結果相同(0)。

strxfrm函數返回變換后字符串的長度,因此strxfm函數通常會被調用兩次:一次用于判斷變換后字符串的長度,一次用來進行變換。下面是一個例子:

size_t len; 
char *transformed;
len = strxfrm(NULL, original, 0); 
transformed = malloc(len + 1); 
strxfrm(transformed, original, len);

23.6.4 搜索函數

void *memchr(const void *s, int c, size_t n); 
char *strchr(const char *s, int c); 
size_t strcspn(const char *s1, const char *s2); 
char *strpbrk(const char *s1, const char *s2); 
char *strrchr(const char *s, int c); 
size_t strspn(const char *s1, const char *s2); 
char *strstr(const char *s1, const char *s2); 
char *strtok(char * restrict s1, const char * restrict s2); 

strchr函數在字符串中搜索特定字符。下面的例子說明了如何使用strchr函數在字符串中搜索字母f

char *p, str[] = "Form follows function."; 
p = strchr(str, 'f'); /* finds first 'f' */ 

strchr函數會返回一個指針,這個指針指向str中出現的第一個f(即單詞follows中的f)。如果需要多次搜索字符也很簡單,例如,可以使用下面的調用搜索str中的第二個f(即單詞function中的f):

p = strchr(p + 1, 'f'); /* finds next 'f' */ 
//如果不能定位所需的字符,strchr返回空指針。

memchr函數與strchr函數類似,但memchr函數會在搜索了指定數量的字符后停止搜索,而不是當遇到首個空字符時才停止memchr函數的第三個參數用來限制搜索時需要檢測的字符總數。當不希望對整個字符串進行搜索或搜索的內存塊不是以空字符結尾時,memchr函數會十分有用。下面的例子用memchr函數在一個沒有以空字符結尾的字符數組中進行搜索:

char *p, str[22] = "Form follows function."; 
p = memchr(str, 'f', sizeof(str));

strchr函數類似,memchr函數也會返回一個指針指向該字符第一次出現的位置。如果找不到所需的字符,memchr函數返回空指針。

strrchr函數與strchr類似,但它會反向搜索字符:

char *p, str[] = "Form follows function."; 
p = strrchr(str, 'f'); /* finds last 'f' */ 

在此例中,strrchr函數會首先找到字符串末尾的空字符,然后反向搜索字母f(單詞function中的f)。與strchrmemchr一樣,如果找不到指定的字符,strrchr函數也返回空指針。

strpbrk函數比strchr函數更通用,它返回一個指針,該指針指向第一個參數中與第二個參數中任意一個字符匹配的最左邊一個字符:

char *p, str[] = "Form follows function.";
p = strpbrk(str, "mn"); /* finds first 'm' or 'n' */
/*
在此例中,p最終會指向單詞Form中的字母m。
當找不到匹配的字符時,strpbrk函數返回空指針。
*/ 

strspn函數和strcspn函數與其他的搜索函數不同,它們會返回一個表示字符串中特定位置的整數(size_t類型)。當給定一個需要搜索的字符串以及一組需要搜索的字符時,strspn函數返回字符串中第一個不屬于該組字符的字符的下標。對于同樣的參數,strcspn函數返回第一個屬于該組字符的字符的下標。下面是使用這兩個函數的例子:

size_t n; 
char str[] = "Form follows function."; 
n = strspn(str, "morF"); /* n = 4 */ 
n = strspn(str, " \t\n"); /* n = 0 */ 
n = strcspn(str, "morF"); /* n = 0 */ 
n = strcspn(str, " \t\n"); /* n = 4 */

strstr函數在第一個參數(字符串)中搜索第二個參數(也是字符串)。在下面的例子中,strstr函數搜索單詞fun

char *p, str[] = "Form follows function."; 
p = strstr(str, "fun"); /* locates "fun" in str */

strstr函數返回一個指向待搜索字符串第一次出現的地方的指針。如果找不到,則返回空指針。在上例的調用后,p會指向function中的字母f

strtok函數是最復雜的搜索函數。它的目的是在字符串中搜索一個“記號”——就是一系列不包含特定分隔字符的字符。調用strtok(s1,s2)會在s1中搜索不包含在s2中的非空字符序列。strtok函數會在記號末尾的字符后面存儲一個空字符作為標記,然后返回一個指針指向記號的首字符。

strtok函數最有用的特點是以后可以調用strtok函數在同一字符串中搜索更多的記號。調用strtok(NULL,s2)就可以繼續上一次的strtok函數調用。和上一次調用一樣,strtok函數會用一個空字符來標記新的記號的末尾,然后返回一個指向新記號的首字符的指針。這個過程可以持續進行,直到strtok函數返回空指針,這表明找不到符合要求的記號。

這里就不討論strtok函數的工作原理了,要明白,strtok有幾個眾所周知的問題,這些問題限制了它的使用。這里只說以下兩個問題。首先strtok每次只能處理一個字符串,不能同時搜索兩個不同的字符串。其次strtok把一組分隔符與一個分隔符同等看待;因此,如果字符串中有些字段用分隔符(例如逗號)分開,有些字段為空,那么strtok就不適用了。


23.6.5 其他函數

void *memset(void *s, int c, size_t n); 
size_t strlen(const char *s); 

memset函數會將一個字符的多個副本存儲到指定的內存區域。假設p指向一塊N字節的內存,調用

memset(p, ' ', N);

會在這塊內存的每個字節中存儲一個空格。memset函數的一個用途是將數組全部初始化為0

memset(a, 0, sizeof(a));

memset函數會返回它的第一個參數(指針)。

strlen函數返回字符串的長度,字符串末尾的空字符不計算在內strlen函數的調用示例見13.5節

此外還有一個字符串函數——strerror函數(24.2節),會和<errno.h>一起討論。


問與答

問1expml函數的作用僅僅是從exp函數的返回值里減去1,為什么需要這個函數呢?

答:把exp函數應用于接近0的數時,其返回結果非常接近1。因為舍入誤差的存在,從exp的返回值里減去1可能不精確。這種情況下expml可以用來獲得更精確的結果。

loglp函數的作用也是類似的。對于接近0x值,loglp(x)log(1+x)更精確。

問2:計算伽馬函數的函數為什么命名為tgamma而不是gamma呢?

答:起草C99標準的時候,有些編譯器已提供了名為gamma的函數,但計算的是伽馬函數的對數。這個函數后來重命名為lgamma。把伽馬函數的名字選為gamma可能會和已有的程序相沖突,所以C99委員會決定改用tgamma(意為“truegamma”)。

問3:描述nextafter函數時,為什么說當xy相等時返回y呢?如果xy相等,返回x與返回y有區別嗎?

答:考慮調用nextafter(-0.0,+0.0),從數學上講兩個參數是相等的。如果返回y而不是x,函數的返回值為+0.0(而不是-0.0,那樣有違直覺)。類似地,調用nextafter(+0.0,-0.0)返回-0.0

問4:為什么<string.h>中提供了那么多方法來做同一件事呢?真的需要4個復制函數(memcpymemmovestrcpystrncpy)嗎?

答:我們先看memcpy函數和strcpy函數,使用這兩個函數的目的是不同的:strcpy函數只會復制一個以空字符結尾的字符數組(也就是字符串),memcpy函數可以復制沒有這一終止字符的內存塊(如整數數組)。

另外兩個函數可以使我們在安全性和運行速度之間做出選擇。strncpy函數比strcpy函數更安全,因為它限制了復制字符的個數。當然安全也是有代價的,因為strncpy函數比strcpy函數慢一點。使用memmove函數也需要做出類似的抉擇。memmove函數可以將字符從一塊內存區域復制到另一塊可能會與之相重疊的內存區域中。在同樣的情況下,memcpy函數無法保證能夠正常工作;然而,如果可以確保沒有重疊,memcpy函數很可能會比memmove函數要快一些。

問5:為什么strspn函數有這么一個奇怪的名字?

答:不要將strspn函數的返回值理解為不屬于指定字符集合的第一個字符的下標,而要將它的返回值理解為屬于指定字符集合的字符的最長“跨度”(span)


寫在最后

本文是博主閱讀《C語言程序設計:現代方法(第2版·修訂版)》時所作筆記,日后會持續更新后續章節筆記。歡迎各位大佬閱讀學習,如有疑問請及時聯系指正,希望對各位有所幫助,Thank you very much!

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/167655.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/167655.shtml
英文地址,請注明出處:http://en.pswp.cn/news/167655.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

設計模式——行為型模式(一)

行為型模式用于描述程序在運行時復雜的流程控制,即描述多個類或對象之間怎樣相互協作共同完成單個對象都無法單獨完成的任務,它涉及算法與對象間職責的分配。 行為型模式分為類行為模式和對象行為模式,前者采用繼承機制來在類間分派行為,后者采用組合或聚合在對象間分配行…

醫院預約管理系統開發 代碼展示 九價疫苗接種預約功能(含小程序源代碼)

基于微信小程序的疫苗預約系統讓疫苗信息&#xff0c;疫苗預約信息等相關信息集中在后臺讓管理員管理&#xff0c;讓用戶在小程序端預約疫苗&#xff0c;查看疫苗預約信息&#xff0c;該系統讓信息管理變得高效&#xff0c;也讓用戶預約疫苗&#xff0c;查看疫苗預約等信息變得…

MySQL 優化器 Index Condition Pushdown下推(ICP)

ICP 測試 準備數據 CREATE TABLE icp (employee_id int(6) NOT NULL AUTO_INCREMENT,first_name varchar(20) DEFAULT NULL,last_name varchar(25) DEFAULT NULL,email varchar(25) DEFAULT NULL,phone_number varchar(20) DEFAULT NULL,PRIMARY KEY (employee_id) );insert i…

額溫槍方案,MS8551,MS8601;MS1112,MS1100

鑒于測溫的傳感器信號非常微弱&#xff0c;需要用高精度、低噪聲的運算放大器和高精度、低功耗的ADC。 運算放大器可供選擇&#xff1a;MS8551 or MS8601&#xff0c;具有低失調&#xff08;1uV&#xff09;、低噪&#xff08;22nV√Hz &#xff09;、封裝小等優點&#xff0c…

Redis并發問題解決方案

目錄 前言 1.分布式鎖 1.基于單個節點 2.基于多個節點 3.watch(樂觀鎖) 2.原子操作 1.單命令操作 2.Lua 腳本(多命令操作) 3.事務 1.執行步驟 2.錯誤處理 3.崩潰處理 總結 前言 在多個客戶端并發訪問Redis的時候&#xff0c;雖然Redis是單線程執行指令&#xff…

【間歇振蕩器2片555時基仿真】2022-9-24

緣由multisim出現這個應該怎么解決吖&#xff0c;急需解決-嵌入式-CSDN問答 輸出一定要有電阻分壓才能前后連接控制否則一定報錯。

Python自動化生成漂亮的測試報告

&#x1f4e2;專注于分享軟件測試干貨內容&#xff0c;歡迎點贊 &#x1f44d; 收藏 ?留言 &#x1f4dd; 如有錯誤敬請指正&#xff01;&#x1f4e2;交流討論&#xff1a;歡迎加入我們一起學習&#xff01;&#x1f4e2;資源分享&#xff1a;耗時200小時精選的「軟件測試」資…

五種多目標優化算法(MOJS、NSGA3、MOGWO、NSWOA、MOPSO)求解微電網多目標優化調度(MATLAB代碼)

一、多目標優化算法簡介 &#xff08;1&#xff09;多目標水母搜索算法MOJS 多目標優化算法&#xff1a;多目標水母搜索算法MOJS&#xff08;提供MATLAB代碼&#xff09;_水母算法-CSDN博客 &#xff08;2&#xff09;NSGA3 NSGA-III求解微電網多目標優化調度&#xff08;M…

acwing算法基礎之數學知識--求卡特蘭數

目錄 1 基礎知識2 模板3 工程化 1 基礎知識 題目&#xff1a;給定n個0和n個1&#xff0c;它們將按照某種順序排成長度為2n的序列&#xff0c;求它們能排成的所有序列中&#xff0c;能夠滿足任意前綴序列中0的個數都不少于1的個數的序列有多少個&#xff1f; 輸出的答案對 1 0 …

【云原生 Prometheus篇】Prometheus的動態服務發現機制與認證配置

目錄 一、Prometheus服務發現的方式1.1 基于文件的服務發現1.2 基于consul的服務發現1.3 基于 Kubernetes API 的服務發現1.3.1 簡介1.3.2 基于Kurbernetes發現機制的部分配置參數 二、實例一&#xff1a;部署基于文件的服務發現2.1 創建用于服務發現的文件2.2 修改Prometheus的…

yo!這里是c++11重點新增特性介紹

目錄 前言 列表初始化 { }初始化 initializer_list類 類型推導 auto decltype 范圍for 右值引用與移動語義 左值引用和右值引用 移動語義 1.移動構造 2.移動賦值 3.stl容器相關更新 右值引用和萬能引用 完美轉發 關鍵字 default delete final和override …

西米支付:簡單介紹一下支付公司的分賬功能體系

隨著互聯網的普及和電子商務的快速發展&#xff0c;支付已經成為人們日常生活的重要組成部分。支付公司作為第三方支付平臺&#xff0c;為消費者和商家提供了便捷、安全的支付方式。而在支付領域中&#xff0c;分賬功能是一個非常重要的功能&#xff0c;它可以幫助企業實現資金…

SpringBoot——攔截器

優質博文&#xff1a;IT-BLOG-CN 一、登錄時可能會出現重復提交問題。我們可以通過重定向解決此問題。例如&#xff1a;用戶提交的請求為&#xff1a;/user/login&#xff0c;通過redirect&#xff1a;重定向至 main.html請求。 PostMapping("/user/login") public …

C語言——從終端(鍵盤)將 5 個整數輸入到數組 a 中,然后將 a 逆序復制到數組 b 中,并輸出 b 中 各元素的值。

#define _CRT_SECURE_NO_WARNINGS 1#include<stdio.h> int main() {int i;int a[5];int b[5];printf("輸入5個整數&#xff1a;\n");for(i0;i<5;i){scanf("%d",&a[i]);}printf("數組b的元素值為&#xff1a;\n");for(i4;i>0;i--…

Windows任務管理器內存性能界面各個參數含義

任務管理器的內存性能界面提供了一些關鍵參數&#xff0c;這些參數可以幫助你了解系統中內存的使用情況。以下是一些常見的參數及其含義&#xff1a; 已提交&#xff08;Committed&#xff09;&#xff1a; 表示已分配的物理內存和虛擬內存的總和。已提交的內存包括當前正在使…

Javascript每天一道算法題(十五)——輪轉數組_中等(一行解決輪轉數組)

文章目錄 1、問題2、示例3、解決方法&#xff08;1&#xff09;方法1——while遍歷&#xff08;較為復雜&#xff0c;不推薦&#xff09;&#xff08;2&#xff09;方法2&#xff08;直接截取后插入&#xff0c;推薦&#xff09;&#xff08;3&#xff09;方法3——優化方法2&a…

jQuery_03 dom對象和jQuery對象的互相轉換

dom對象和jQuery對象 dom對象 jQuery對象 在一個文件中同時存在兩種對象 dom對象: 通過js中的document對象獲取的對象 或者創建的對象 jQuery對象: 通過jQuery中的函數獲取的對象。 為什么使用dom或jQuery對象呢&#xff1f; 目的是 要使用dom對象的函數或者屬性 以及呢 要…

python -opencv 輪廓檢測(多邊形,外接矩形,外接圓)

python -opencv 輪廓檢測(多邊形&#xff0c;外接矩形&#xff0c;外接圓) 邊緣檢測步驟: 第一步&#xff1a;讀取圖像為灰度圖 第二步&#xff1a;進行二值化處理 第三步&#xff1a;使用cv2.findContours對二值化圖像提取輪廓 第三步&#xff1a;將輪廓繪制到圖中 代碼如下…

Hibernate的三種狀態

1.瞬時狀態(Transient) 通過new創建對象后&#xff0c;對象并沒有立刻持久化&#xff0c;他并未對數據庫中的數據有任何的關聯&#xff0c;此時java對象的狀態為瞬時狀態&#xff0c;Session對于瞬時狀態的java對象是一無所知的&#xff0c;當對象不再被其他對象引用時&#xf…

【TL431+場效應管組成過壓保護電路】2022-3-22

緣由這個穩壓三極管是構成的電路是起到保護的作用嗎&#xff1f;-硬件開發-CSDN問答