《c和指针》之总结 八
16、标准函数库
标准函数库包含了许多有用的函数。第1组函数返回整型结果。abs和labs函数返回他们的参数的绝对值。div和ldiv 函数用于执行整数除法。和/操作符不同,当其中一个参数为负时,商的值是精确定义的。rand 函数返回一个伪随机数。调用srand 允许你从一串伪随机值中的任意一个位置开始产生随机数。atoi 和atol 函数把一个字符串转换为整型值。strtol 和strtoul 执行相同的转换,但他们可以给你更多的控制。
int abs(int value);
long int abs(long int value);
div_t div(int numerator, int denominator);
ldiv_t ldiv(long int numer, long int denom);
div 函数把它的第2个参数(分母)除以第1个参数(分子),产生商和余数,用一个div_t 结构返回。这个结构包含下面两个字段,int quot;// 商 int rem; // 余数
int rand(void);
void srand(unsigned int seed);
rand返回一个范围在0和RAND_MAX(至少为32,767)之间的为随机数。当它重复时函数返回这个范围的其他数。为了避免程序每次运行时获得相同的随机数序列,可以调用srand函数。它用它的参数值对随机数发生器进行初始化。一个常用的技巧是使用每天的时间作为随机数产生器的种子(seed)如:srand((unsigned int)time(0));
int atoi(char const *string);
long int atol(char const *string);
long int strtol(char const *string,char **unused,int base);
unsigned long int strtoul(char const *string,char **unused,int base);
下一组函数绝大部分接受一个double 参数返回double 结果。标准库提供了常用的三角函数sin、cos、tan、asin、acos、atan和atan2。头三个函数接受一个以弧度表示的角度参数,分别返回该角度对应的正弦、余弦、正切值。接下来的三个函数分别返回与他们的参数对应的反正弦、反余弦、反正切值。最后一个函数根据x和y参数计算反正切值。双曲正弦、双曲余弦和双曲正切分别由sinh、cosh、tanh函数计算。exp函数返回以e值为底,其参数为幂的指数值。log 函数返回其参数的自然对数,log10函数返回以10为底的对数。
double sin(double angle );
double cos(double angle );
double tan(double angle );
double asin(double angle );
double acos(double angle );
double atan(double angle );
double atan2(double x,double y);返回表达式y/x的反正切值,范围在-π和π之间。
double sinh(double angle );
double cosh(double angle );
double tanh(double angle );
double exp(double x ); 返回ex
double log(double x );
double log10(double x );
frexp和ldexp函数在创建与机器无关的浮点数表示形式方面是很有用的。 frexp函数用于计算一个给定值的表示形式。 ldexp函数用于解释一个表示形式,恢复它的原先值。modf 函数用于把一个浮点值分割成整数和小数部分。pow函数计算以第1个参数为底,第2个参数为幂的指数值。sqrt函数返回其参数的平方根。floor 函数返回不大于其参数的最大整数,ceil函数返回不小于其参数的最小整数。fabs 函数返回其参数的绝对值。fmod 函数接受两个参数,返回第2个参数除以第1个参数的余数。最后,atof 和strtod 函数把字符串转换为浮点值。后者能够在转换时提供更多的控制。
double frexp(double value,int *exponent );
double ldexp(double fraction,int exponent );
double modf(double value,double *ipart );
double pow(double x,double y );返回x y的值。
double sqrt(double x );
double floor(double x );
double ceil(double x );
double fabs(double x );
double fmod(double x ,double y);
double atof(double const *string );
double strtod(double const *string,char **unused );
接下来的一组函数用于处理日期和时间。clock 函数返回从程序执行开始到调用这个函数之间所花费的处理器时间。time 函数用一个time_t 值返回当前的日期和时间。ctime 函数把一个time_t 值转换为人眼可读的日期和时间的表示形式。difftime 函数计算两个time_t 值之间以秒为单位的时间差。gmtime 和localtime 函数把一个time_t 值转换为一个tm 结构,tm结构包含了日期和时间的所有组成部分。gmtime函数使用时间协调时间,localtime 函数使用本地时间。asctime和strftime函数把一个tm结构值转换为人眼可读的日期和时间的表示形式。strftime 函数对转换结果的格式提供了强大的控制。最后,mktime把存储于tm结构中的值进行规格化,并把它们转换为一个time_t值。
tm结构的字段
|
类型&名称 |
范围 |
含义 |
|
int tm_sec; |
0-61 |
分之后的秒数* |
|
int tm_min; |
0-59 |
小时之后的分数 |
|
int tm_hour; |
0-23 |
午夜之后的小时数 |
|
int tm_mday; |
1-31 |
当月的日期 |
|
int tm_mon; |
0-11 |
1月之后的月数 |
|
int tm_year; |
0-?? |
1900之后的年数 |
|
int tm_wday; |
0-6 |
星期天之后的天数 |
|
int tm_yday; |
0-365 |
|
|
int tm_isdat; |
|
夏令时标志 |
clock_t clock(void);
time_t time(time_t *returned_value);
char *ctime(time_t const *time_value);
double difftime(time_t time1, time_t time2)
struct tm *gmtime(time_t const *time_value);
struct tm *localtime(time_t const *time_value);
char *asctime(struct tm const *tm_ptr);
size_t strftime(char *string, size_t maxsize, char const *format, struct tm const *tm_ptr );
time_t mktime(struct tm *tm_ptr);
非本地跳转由setjmp和longjmp 函数提供。调用setjmp在一个jmp_buf 变量中保存处理器的状态信息。接着,后续的longjmp调用将恢复这个被保存的处理器状态。在调用setjmp的函数返回之后,可能无法再调用longjmp函数。
int setjmp(jmp_buf state);
void longjmp(jump_buf state, int value);
信号表示一个程序的执行期间可能发生的不可预料的事件,诸如用户中断程序或者发生一个算术错误。当一个信号发生时系统所采取的缺省反应时由编译器定义的,但一般都是终止程序。你可以通过定义一个信号处理函数并使用single 函数对其进行设置,从而该表信号的缺省行为。你可以在信号处理函数中执行的工作类型是受到严格限制的,因为程序在信号出现之后可能处于不一致的状态。volatile 数据的值可能会改变,而且很可能是由于自身所致。可以防止代码的优化。例如,一个在信号处理函数中修改的变量应该声明为volatile。raise 函数产生一个由它的参数指定的信号。
vprintf 、vfprintf和vsprintf函数和printf函数家族执行相同的任务,但需要打印的值以可变参数列表的形式传递给函数。abort 函数通过产生SIGABRT信号终止程序。atexit 函数用于注册退出函数,他们在程序退出前被调用。assert 宏用于断言,当一个应该为真的表达式实际为假时,它就会终止程序。当调试完成之后,你可以通过定义NDEBUG符号去除程序中的所有断言,而不必把他们物理性地从源代码中删除。getenv从操作系统环境中提取值。system 接受一个字符串参数,把它作为命令用本地命令处理器执行。
int vprintf(char const *format,va_list arg);
int vfprintf(FILE *stream ,char const *format, va_list arg);
int vsprintf(FILE *stream ,char const *format, va_list arg);
void abort(void);
void atexit(void (func)(void));
void exit(int status);
void assert(int expression); //#define NDEBUG 将在程序中消除断言
char *getenv(char const *name);//考虑程序的可移植性,最好避免使用它。
void system(char const *command);
qsort函数把一个数组中的值按照升序进行排序,bsearch 函数用于在一个已经排好序的数组中用二分法查找一个特定的值。由于这两个函数都是与类型无关的,所以他们可以用于任何数据类型的数组。
void qsort(void *base,size_t n_elements, size_t el_size, int (*compare)(void const *,void const *));
第1个参数指向需要排序的数组,第2个参数指定数组中元素的数目,第3个参数指定每个元素的长度(以字节为单位)。第4个参数是一个函数指针,用于对需要排序的元素类型进行比较。在排序时,qsort调用这个函数对数组中的数据进行比较。通过一个指向合适的比较函数的指针,你可以使用qsort排序任意类型的数组。比较函数接受两个参数,他们是指向两个需要进行比较的值的指针。函数应该返回一个大于零、等于零和小于零分别表示第1个参数大于、等于和小于第2个参数。
void *bsearch(void const *key,void const *base, size_tn_elements, size_t el_size, int (*compare)(void const *,void const *);
第1个参数指向你需要查找的值,第2个参数指向查找所在的数组,第3个参数指定数组中元素的数目,第4个参数是每个元素的长度。最后一个参数是和qsort 中相同的指向比较函数的指针。bsearch函数返回一个指向查找到的数组元素的指针。如果需要查找的值不存在,函数返回一个NULL 指针。
locale 就是一组参数,根据世界各国的约定差异对C程序的行为进行调整。setlocale函数用于修改整个或部分locale 。 locale包括了一些用于定义数值如果进行格式化的参数。他们的描述的值包括非货币值、本地货币值和国际货币值。locale本身并不执行任何形式的格式化,它只是简单地提供格式化的规范。locale 可以指定一个和机器的缺省序列不同的对照序列。在这种情况下,strxcoll用于根据当前的对照序列对字符串进行比较,它所返回的值类型类似strcmp 函数的返回值。strxfrm 函数把一个当前对照序列的字符串转换为一个位于缺省对照序列的字符串。用这种方式转换的字符串可以用strcmp 函数进行比较,比较的结构和用strxoll 比较原先的字符串的结果相同。
char *setlocate(int category,char const *locale);
category参数指定locale的哪个部分需要进行修改。允许出现的值如下
setlocate 类型
|
值 |
修改 |
|
LC_ALL |
整个locale |
|
LC_COLLATE |
对照序列,它将影响strcoll 和strxfrm函数的行为 |
|
LC_CTYPE |
定义于ctype.h中的函数所使用的字符类型分类信息 |
|
LC_MONETARY |
在格式化货币值时使用的字符 |
|
LC_NUMERIC |
在格式化非货币值时使用的字符。同时修改由格式化输入/输出函数和字符串转换函数所使用的小数点符号 |
|
LC_TIME |
strftime函数的行为 |
如果setlocate 的第2个参数为NULL,函数将返回一个指向给定类型的当前locale 的名字的指针。这个值可能被保存并在后续的setlocate 函数中使用,用来恢复以前的locale 。如果第2个参数不是NULL,它指定需要使用的新locale。 如果函数调用成功,它将返回新的locale 的值,否则返回一个NULL指针,原来的locale 不受影响。
struct lconv *localeconv(void);
lconv 结构包含两种类型的参数:字符和字符指针。字符参数为非负值。如果一个字符参数为CHAR_MAX,那么这个值就在当前的locale 中不可用(或不使用)。对于字符指针参数,如果它指向一个空字符串,它表示的意义和上面相同。
int strcoll(char const *sl,char const *s2);
size_t strxfrm(char *s1, char const *s2, size_t size);
strcoll 函数对两个根据当前locale 的LC_COLLATE 类型参数指定的字符串进行比较。它返回一个大于、等于、小于零的值,分别表示第1个参数大于、等于、小于第2个参数。
17、经典抽象数据类型
为ADT分配内存有三种技巧:静态数组、动态分配的数组和动态分配的链式结构。静态数组对结构施加了预先确定固定长度这个限制。动态数组的长度可以在运算时计算,如果需要数组也可以进行重新分配。链式结构对值的最大数量并未施加任何限制。
堆栈是一种后进先出的结构。它的接口提供了把新值压入堆栈的函数和从堆栈弹出值的函数。另一类接口提供了第3个函数,它返回栈顶元素的值但并不讲其中堆栈中弹出。堆栈很容易使用数组来实现,我们可以使用一个变量,初始化为-1,用它记住栈顶元素的下标。为了把一个新值压入到堆栈中,这个变量先进行增值,然后这个值被存储到数组中。当弹出一个值时,在访问栈顶元素之后,这个变量进行减值。我们需要两个额外的函数来使用动态分配的数组。一个用于创建指定长度的堆栈,另一个用于销毁它。单链表也能很好地实现堆栈。通过在链表的头部插入,可以实现堆栈的压入。通过删除第1个元素,可以实现堆栈的弹出。
队列是一种先进先出的结构。它的接口提供了插入一个新值和删除一个现有值的函数。由于队列对它的元素所施加的次数限制,用循环数组来实现队列要比使用普通数组合适得多。当一个变量被当作循环数组的下标使用时,如果它处于数组的末尾再增值时,它的值就“环绕”到零。为了判断数组是否已满,你可以使用一个用于计数已经插入到队列中的元素数量的变量。为了使用队列的front和rear 指针来检测这种情况,数组应始终至少保留一个空元素。
二叉搜索树(BST)是一种数据结构,它或者为空,或者具有一个值并拥有零个、一个或两个孩子(分别称为左孩子和右孩子),它的孩子本身也是一颗BST。BST 树节点的值大于它的左孩子所有节点的值,但小于它的右孩子所有节点的值。由于这种次序关系的存在,在BST中查找一个值是非常高效地------如果节点并未包含需要查找的值,你总是可以知道接下来应该查找它的哪棵子树。为了向BST插入一个值,你首先进行查找。如果值未找到,就把它插入到查找失败的位置。当你从BST 删除一个节点时,必须小心防止把它的子树同树的其他部分断开。树的遍历就是以某种次序处理它的所有节点。有4种常见的遍历次序。前序遍历先处理节点,然后遍历它的左子树和右子树。中序遍历先遍历节点的左子树,然后处理该节点,最后遍历节点的右子树。后序遍历先遍历节点的左子树和右子树,最后处理该节点。层次遍历从根到叶逐层从左向右处理每个节点。数组可以用于实现BST,但如果树不平衡,这种方法会浪费很多内存空间。链式BST可以避免这种浪费。
这些ADT 的简单实现方法带来了三个问题。首先,他们只允许拥有一个堆栈、一个队列或一棵树。这个问题可以通过把为这些结构分配内存的操作从操纵这些结构的函数中分离出来。但这样做导致封装性的损失,增加了出错的机会。第2个问题是无法声明不同类型的堆栈、队列和树。为每种类型单独创建一份ADT函数使代码的维护变得更为困难。一个更好的办法是用#define 宏实现代码,然后用目标类型对它进行扩展。不过,使用这种方法,你必须小心选择一种命名约定。另一种方法是通过把需要存储到ADT的值强制转换为void * 。这种策略的一个缺点是它绕过了类型检查。第3个问题是避免不同ADT之间以及同种ADT 用于处理不同类型数据的各个版本之间避免名字冲突。我们可以创建ADT的泛型实现,但为了正确使用它们,用户必须承担更多的责任。
使用断言检查内存是否分配成功是危险的。数组形式的二叉树节点位置计算公式假定数组的下标从1开始。

浙公网安备 33010602011771号