标准函数库

标准函数库

算术

       求商和余数的函数div

字符串转换

       函数atoiatol执行基数为10的转换;

       而函数strtolstrtoul允许你在转化时指定基数,同时还允许访问字符串的剩余部分。

浮点表示形式

       函数modf把一个浮点值分成整数和小数两个部分。

非本地跳转

       setjmplongjmp函数提供了一种类似goto语句的机制,但它并不局限于一个函数的作用域之内。这些函数常用于深层嵌套的函数调用链

信号

       信号表示一种事件,它可能异步地发生,也就是并不予程序执行过程的任何时间同步。

信号处理函数

       由于信号可能在任何时候发生,所以由信号处理函数修改的变量的值可能会在任何时候发生改变。因此,我们不能指望这些变量在两条相邻的程序语句中肯定具有相同的值。volatile关键字告诉编译器这个事实,防止它以一种可能修改程序含义的方式优化程序。例如:

clip_image002[4]

通常情况下,上面的程序会认为第二个测试和第一个测试具有相同的结果,就优化为如下:

clip_image004[4]

而如果把变量value声明为volatile类型的,就不会进行此类优化。

有时,如果不用volatile修饰符,可能无法编写多线程程序,要么编译器失去大量优化的机会。

终止执行

l  abort函数用于不正常地终止一个正在执行的程序;

l  atexit函数可以把一些函数注册为退出函数exit function

l  exit函数用于正常终止程序;

断言

       assert(test)用于检测test是否为真。用这种方法可以使调试变得更容易。并且我们可以在头文件assert.h被包含之前,添加#define NDEBUG皆可以禁用所有的断言。

总结

l  frexpldexp函数在创建与机器无关的浮点数表示形式方面是很有用的。frexp函数用于计算一个给定值的表示形式;ldexp函数用于解释一个表示形式,恢复它的原先值;

l  qsort函数把一个数组中的值按照升序进行排序;

l  bsearch函数用于在一个已经排好序的数组中用二分法查找一个特定的值;

l  locale就是一组函数,根据世界各国的约定差异对C程序的行为进行调整;

l  使用setjmplongjmp可能导致晦涩难懂的代码;

l  使用断言可以简化程序的调试;

 

输入/输出函数

输入/输出函数

标准I/O函数库

       ANSI C的一个主要优点就是对标准函数的修改将通过增加不同函数的方法实现,而不是通过对现存函数进行修改来实现。因此,程序的可移植性不会受到影响。

对于在调试中的打印语句,可以通过添加一条fflush(stdout)语句,来强制立即输出。

流分为两种类型:文本流text和二进制流binary

字符I/O

       fgetcfputc都是真正的函数,但getcputcgetcharputchar都是通过#define指令定义的宏。宏在执行时间上效率稍高,而函数在程序的长度方面更胜一筹。

二进制I/O

       把数据写到文件效率最高的方法是用二进制形式写入。二进制输出避免了在数值转换为字符串过程中所涉及的开销和精度损失。

刷新和定位函数

fflush函数会立即把输出缓冲区中的数据进行物理写入;

ftell函数返回流的当前位置;

fseek函数允许你在一个流中定位;

rewind函数将读写指针设置回指定流的起始位置,它同时清除流的错误提示标志;fgetposfsetpos函数分贝时ftellfseek函数的替代方案。

流错误函数

几个判断流状态的函数:

l  feof:如果流当前处于文件尾,feof函数返回真;

l  ferror:报告流的错误状态,如果出现任何读写错误函数就返回真;

l  clearer:对指定流的错误标志进行重置;

文件操纵函数

removerename函数。

总结

       一种编译器可以在它的函数库中提供额外的函数,但是不应该修改标准要求提供的函数。

预处理器

预处理器

       编译一个C程序涉及很多步骤,其中第一个步骤被称为预处理preprocessing阶段。C预处理器在源代码编译之前对其进行一些文本性质的操作,它的主要任务包括删除注释、插入被#include指令包含的文件的内容、定义和替换由#define指令定义的符号以及确定代码的部分内容时候应该根据一些条件编译指令进行编译

预定义符号

符号

含义

__FILE__

进行编译的原文件名

__LINE__

文件当前行的行号

__DATE__

文件被编译的日期

__TIME__

文件被编译的时间

__STDC__

如果编译器遵循ANSI C,其值就为1,否则未定义

 

#define

       对于多行的宏定义,可以使用反斜杠来换行。

clip_image002

       我们也可以使用#define指令把一序列语句插入到程序中,例如:

clip_image004

关于宏的使用,有诸多注意的事项。比如,下面定义一个宏:

clip_image006

       所以对于上面的例子,如果我们定义如下就没有问题了:

clip_image008

 

这里再说另外一种情况:

clip_image010

对于上面的情况,我们可以用如下的方法定义(多加一个括号):

clip_image012

#define替换

       宏参数和#define定义可以包含其他#define定义的符号,但是,宏不可以出现递归

clip_image014式中的#xxx会把xxx转换为一个字符串;而##结构则执行一种不同的任务,它把位于它两边的符号链接成一个符号,比如value=5;那么#define test(x)  sum##value += x;中的sum##value会改为sum5

宏与函数

       宏非常频繁地用于执行简单的计算,比如在两个表达式中寻找其中较大或较小的一个:

clip_image016

       从上式中,我们看到通篇的括号,哈哈clip_image017,就是为了方式出现上面曾说过的错误。

       为什么不用函数来完成上面的任务呢

l  首先,用于调用和从函数返回的代码很可能比实际执行这个小型计算工作的代码更大,所以使用宏比使用函数在程序的规模和速度方面都更胜一筹

l  再者,函数的参数必须声明为一种特定的类型,所以它只能在类型合适的表达式上使用,反之,上面的这个宏可以用于整型、长整型、单浮点型、双浮点型以及其他任何可以使用>操作符比较值大小的类型,也就是说,宏是与类型无关的

l  还有一些任务根本无法用函数实现,比如,下面的type参数类型:

clip_image019

 

和使用函数相比,使用宏的不利之处在于:一份宏定义代码的拷贝都将插入到程序中,除非宏比较短,否则使用宏可能会大幅度增加程序的长度

宏和函数的不同之处

属性

#define

函数

代码长度

每次使用时,宏代码都会插入到程序中,除了非常小的宏外,程序的长度将大幅度增长

函数代码值出现在一个地方;每次使用这个函数时,都调用那个地方的同一份代码

执行速度

更快

存在函数调用/返回的额外开销

操作符优先级

宏参数的求值是在所有周围表达式的上下文环境里,除非它们加上括号,否则邻近操作符的优先级可能会产生不可预料的结果

函数参数只在函数调用时求值一次,它的结果值传递给函数,表达式的求值结果更容易预测

参数求值

参数每次用于宏定义时,它们都将重新求值,由于多次求值,具有副作用的参数可能会产生不可预测的结果

参数在函数被调用前置求值一次,在函数中多次使用参数并不会导致多种求职过程,参数的副作用并不会造成任何特殊的问题

参数类型

宏与类型无关,只要对参数的操作是合法的,它可以使用于任何参数类型

函数的参数是与类型有关的,如果参数的类型不同,就需要使用不同的函数,即使它们执行的任务是相同的。

命令行定义

如果函数中有int array[ARRAY_SIZE];可以在编译时使用-D选项来指定该值的大小,比如

gcc –DARRAY_SIZE=1000   test.c就可以将程序中的该值设置为1000

文件包含

       对于#include指令,预处理会删除这条指令,并用包含文件的内容取而代之,这样,一个头文件如果被包含到10个源文件中,它实际上被编译了10次。

       #include有两种方式<>””,其实对于标准的类似stdio.h也可以使用””来引用,只是会浪费一些时间。

       多重包含多出现在大型程序中,此时如果多重复制同一份头文件会导致程序变大变慢,解决多重包含头文件的方法为:

clip_image021

       以后每个头文件都应该加上这个条件语句。

总结

l  宏与类型无关,这是一个优点;宏的执行速度快于函数,因为宏不存在函数调用/返回的开销,但是,使用宏会增加程序的长度,函数不会;同样,具有副作用的参数可能在宏的使用过程中产生不可预料的结果,而函数参数的行为更容易预测。

l  注意在宏定义中使用的参数,要在它们周围和整个宏定义的两边加上括号;

 

高级指针话题

高级指针话题

高级声明

clip_image002返回值类型是一个指向整型的指针;

clip_image004f是一个函数指针,它做指向的函数返回一个整型值;

clip_image006f是一个函数指针,只是所指向的函数的返回值是一个整型指针,必须对其进行间接访问操作才能得到一个整型值;

clip_image008clip_image009函数只能返回标量值,不能返回数组;

clip_image010clip_image009[1]数组元素必须具有相同的长度,但不同的函数显然可能具有不同的长度;

clip_image012括号内的表达式*f[]首先进行求值,所以f是一个元素为某种类型的指针的数组。表达式末尾的()是函数调用操作符,所以f肯定是一个数组,数组元素的类型是函数指针,它所指向的函数的返回值是一个整型值。

 

对于复杂的函数定义,可以参考http://cdecl.org

clip_image014

函数指针

       函数指针的两个用途:

l  转换表jump table

l  作为参数传递给另一个函数

 

in ans;

ans = f(25);

执行流程为:函数名f首先被转换为一个函数指针,该指针指定函数在内存中的位置,然后,函数调用操作符调用该函数,执行开始于这个地址的代码。

在我们需要函数能作用与任何类型的值是,可以把参数类型声明为void *,表示一个指向未知类型的指针

转移表

对于转换表的例子为:对于一个计算器,在只是实现了加、减、乘、除时,可以使用switch语句来实现,但是如果具有成千上万个操作符,我们可以通过右边的转换表来实现。

clip_image016clip_image018

字符串常量

对于字符串“xyz”

l  “xyz”+1 是字符y的地址,因为“xyz”代表的是一个指针,即x的地址;

l  *“xyz” 代表的是x

l  “xyz”[2] 代表的是字符z

 

我们需要把十进制value转为十六进制的,可以采用:

clip_image020很帅clip_image021的方法。

 

使用结构和指针

使用结构和指针

         链表linked list就是一些包含数据的独立数据结构(通称为节点)的集合。

clip_image002

         单链表是一种使用指针来存储值的数据结构。链表中的每个节点包含一个字段,用于指向链表的下一个节点。另外有一个独立的根指针指向链表的第一个节点。由于节点在创建时是采用动态分配内存的方式,所以他们可能分散于内存之中。但是,遍历链表时根据指针进行的,所以节点的物理排列无关紧要。单链表只能以一个方向进行遍历。

         单链表添加新节点的两个步骤:

l  新节点的link字段必须设置为指向它的目标后续节点;

l  前一个节点的link字段必须设置为指向这个新节点;

l  对于根节点,可以通过保存一个指向必须进行修改的link字段的指针,而不是保存一个指向前一个节点的指针,来消除对起始位置的影响。

clip_image004

         双链表的每个节点包含两个link字段,其中fwd为指向链表的下一个节点,bwd为指向链表的上一个节点。所以,为了把一个新节点插入到双链表中,我们必须修改4个指针。新节点的前向和后向link字段必须被设置,前一个节点的后向link和后一个节点的前向link也需要进行修改,使它们指向这个新节点。