常用C库简介
一、数学库<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); ...