一、數學庫<math.h>
數學庫包含許多有用的數學函數。頭文件math.h提供了這些函數的函數聲明或原型。注意角度的單位為弧度。
原型 | 描述 |
double acos (double x) | 返回余弦值為x的角度值 |
double asin (double x) | 返回正弦值為x的角度值 |
double atan (double x) | 返回正切值為x的角度值 |
double atan2 (double y, double x) | 返回正切值為y/x的角度值 |
double cos (double x) | 返回x的余弦值 |
double sin (double x) | 返回x的正弦值 |
double tan (double x) | 返回x的正切值 |
double exp (double x) | 返回x的指數函數的值(e的x次方) |
double log (double x) | 返回x的自然對數值 |
double log10 (double x) | 返回x的以10為底的對數值 |
double pow (double x, double y) | 返回x的y次冪的值 |
double sqrt (double x) | 返回x的平方根 |
double ceil (double x) | 返回不小於x的最小整數值 |
double fabs (double x) | 返回x的絕對值 |
double floor (double x) | 返回不大於x的最大整數值 |
pi的值可以通過計算表達式4 * atan(1)得到:
const int PI = 4 * atan (1);
UNIX系統要求使用-lm標記以指示鏈接器搜索數學庫:
cc rect_pol.c -lm
Linux的gnu編譯器也使用相同的形式:
gcc rect_pol.c -lm
二、通用工具庫<stdlib.h>
通用工具庫包含各種函數,其中包括隨機數產生函數、搜索和排序函數、轉換函數和內存管理函數等。
1. exit( )和atexit( )函數
void exit (int) int atexit (void (*)(void))
程序從main( )返回時會自動調用exit( )函數。另外,ANSI C標准還增加了一些新功能。其中一個是,可以指定執行exit( )時調用的特定函數。通過對退出時調用的函數進行注冊,atexit( )函數也提供這項功能;atexit( )函數使用函數指針作為參數。示例程序如下:
// byebye.c -- atexit( )示例程序 #include <stdio.h> #include <stdlib.h> void sign_off (void); void too_bad (void); int main (void) { int n; atexit (sign_off); // 注冊sign_off( )函數 puts ("Enter an integer: "); if (scanf ("%d", &n) != 1) { puts ("That's no integer!"); atexit (too_bad); exit (EXIT_FAILURE); } printf ("%d is %s.\n", n, (n % 2 == 0) ? "even" : "odd"); return 0; } void sign_off(void) { puts ("Thus terminates another magnificent program from"); puts ("SeeSaw Software!"); } void too_bad (void) { puts ("SeeSaw Software extends its heartfelt condolences"); puts ("to you upon the failure of your program"); }
atexit( )把作為其參數的函數在調用exit( )時執行的函數列表中進行注冊。ANSI保證在這個列表中至少可放置32個函數。通過使用一個單獨的atexit( )調用把每個函數添加到列表中。最后,調用exit( )函數時,按先進后出的順序執行這些函數。
由atexit( )注冊的函數的類型應該為不接受任何參數的返回void的函數。通常它們執行內部處理事務。
注意,main( )終止時會隱式地調用exit( );因此,即使未顯式地調用eixt( ),也會調用經atexit( )注冊的函數。
對於exit( )函數:exit( )函數執行了atexit( )指定的函數之后,將做一些自身清潔工作。它會刷新所有輸出流、關閉所有打開的流,並關閉通過調用標准I/O函數tmpfile()創建的臨時文件。然后,exit( )把通知返回給主機環境。習慣上,UNIX程序用0表示終止,用非零值表示失敗。ANSI C定義了可移植的表示失敗的宏EXIT_FAILURE和表示成功的宏EXIT_SUCCESS,但是exit( )也接受用0表示成功。ANSI C中,在非遞歸的main( )函數中使用exit( )函數等價於使用關鍵字return。但是,在main( )之外的函數中使用exit( )也會終止程序。
2. qsort( )函數
void qsort (void *base, size_t nmemb, size_t size, int (*compar)(const void *, const void *));
快速排序法是最有效的排序算法之一,對大型數組而言更是如此。
函數的第一個參數為指向要排序的數組頭部的指針;第二個參數為需要排序的項目數量;第三個參數為每個數組元素的大小;最后一個參數為一個指向函數的指針,被指向的函數用於確定排序順序。
函數調用的結果是數組被按照遞增的順序被排序。
比較函數:
int (*compar) (const void *, const void *)
比較函數接受兩個參數,即分別指向進行比較的兩個項目的指針。如果第一個項目的值大於第二個項目的指,那么比較函數返回整數;如果兩個項目的值相等,那么返回0;如果第一個項目的值小於第二個項目的值,那么返回負數。示例程序如下:
// qsorter.c -- 使用qsort()對一組數字排序 #include <stdio.h> #include <stdlib.h> #define NUM 40 void fillarray (double ar[], int n); void showarray (const double ar[], int n); int mycomp (const void * p1, const void * p2); int main (void) { double vals[NUM]; fillarray (vals, NUM); puts ("Random list: "); showarray (vals, NUM); qsort (vals, NUM, sizeof(double), mycomp); puts ("Sorted list: "); showarray (vals, NUM); return 0; } void fillarray (double ar[], int n) { int index; for (index = 0; index < n; index++) ar[index] = (double)rand() / ((double)rand() + 0.1); } void showarray (const double ar[], int n) { int index; for (index = 0; index < n; index++) { printf ("%9.4f ", ar[index]); if (index % 6 == 5) putchar ('\n'); } if (index % 6 != 0) putchar('\n'); } int mycomp (const void * p1, const void * p2) { const double * a1 = (const double *)p1; const double * a2 = (const double *)p2; return *a1 - *a2; }
三、診斷庫<assert.h>
診斷庫是設計用於輔助調試程序的小型庫,有宏assert( )構成。該宏接受整數表達式作為參數。如果表達式值為假(非零),宏assert()向標准錯誤流(stderr)寫一條錯誤信息並調用abort( )函數以終止程序;值為真則什么也不做。錯誤信息包括失敗的判斷表達式、該判斷所在的文件名和行號。例如:
assert (z >= 0)
如果z < 0,則判斷失敗。assert會中斷程序,並且可能的輸出如下:
Assertion failed: z >= 0, file C:\use_assert.c, line 14
如果您認為已經排除了程序的漏洞不再需要assert調試,那么可以把宏定義#define NDEBUG放在assert.h包含語句的前面,並重新編譯該程序。編譯器將禁用文件中所有的assert()語句。去掉#define NDEBUG並重新編譯程序,就又重新啟動了assert( )語句。
四、string.h庫中的memcpy( )和memmove( )
不能把一個數組的值直接賦予另一個數組,因此,我們使用循環把數組中的元素逐個復制到另一個數組。一個例外情況是:可以使用strcpy( )和strncoy( )函數復制字符數組。memcpy( )和memmove( )函數為復制其他類型的數組提供了類似的便利工具。下面是這兩個函數的原型:
void *memcpy (void * restrict s1, const void * restrict s2, size_t n); void *memmove (void *s1, const void *s2, size_t n);
這兩個函數均從s2指向的位置復制n字節數據到s1指向的位置,且返回s1的指。兩者間的差異由關鍵字restrict造成,即memcpy( )可以假定兩個內存區域之間沒有重疊。memmove( )函數則不作這個假設,因此,復制過程類似於首先將所有字節復制到一個臨時緩沖區,然后再復制到最終目的地。對存在重疊的兩個區域使用memcpy( )的結果是不可欲知的。因此,使用memcpy( )時,您必須確保沒有重疊區域。第三個參數指定要復制的字節數。注意,字節數不一定等於元素的個數。一般情況下,n = nmemb * sizeof(type)。
五、可變參數:stdarg.h
頭文件stdarg.h為函數提供了接受可變個數的參數的能力。為使用這個特性,需要按如下步驟進行操作:
- 在函數原型中使用省略號。double sum(int lim, ...) {......}
- 在函數定義中創建一個va_list類型的變量。va_list ap;
- 用宏將該變量初始化為一個參數列表。va_start (ap, lim);
- 用宏訪問這個參數列表。double tic = va_arg(ap, double);
- 用宏完成清理工作。va_end(ap);
- 附加操作:復制va_list,應當在va_list被初始化后進行。va_copy(ls_list dest, ls_list src);
可變參數列表必須為函數的參數列表的最后一個,且之前需要至少存在一個普通參量:
void f1 (int n, ...); // 合法 int f2 (int n, char * s, ...); // 合法 char f3 (char c1, ..., char c2); // 非法,省略號不是最后一個參量 double f3 (...); //非法,沒有普通參量
需要使用到的數據類型和宏:
va_list ap; // 用於存放參數的變量
va_start(ap, lim); // 初始化參數列表。lim為函數列表中最后一個普通參量
double tic = va_arg(ap, double); // 依此取得一個參數,第二個參數為需要獲得的數據類型。
va_end(ap); // 最后的清理工作
va_copy(apcopy, ap); // 將參數列表ap復制到apcopy,注意要在ap被初始化后進行。
程序示例:
// varargs.c -- 使用可變個數的參數 #include <stdio.h> #include <stdarg.h> double sum (int, ...); int main (void) { double s, t; s = sum(3, 1.1, 2.5, 13.3); t = sum(6, 1.1, 2.1, 13.1, 4.1, 5.1, 6.1); printf("return value for " "sum(3, 1.1, 2.5, 13.3): %g\n",s); printf("return value for " "sum(6, 1.1, 2.1, 13.1, 4.1, 5.1, 6.1): %g\n", t); return 0; } double sum (int lim, ...) // 使用lim記錄傳入參數的個數 { va_list ap; // 聲明用於存放參數的變量 double tot = 0; int i; va_start(ap, lim); // 初始化為參數列表。注意:需要由最后一個普通參量來確定不定參數列表的地址。 for (i = 0; i < lim; i++) tot += va_arg(ap, double); // 訪問參數列表中的項目 va_end(ap); // 清理參數列表 return tot; }
va_copy的使用:
va_list ap; va_list apcopy; double tic; ... va_start(ap, lim); va_copy(apcopy, ap); // 將ap復制到apcopy tic = va_arg(ap, double); ...