一、數學庫<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); ...
