输入/输出函数

输入/输出函数

标准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也需要进行修改,使它们指向这个新节点。

 

结构和联合

结构和联合

结构基础知识

       聚合数据类型(aggregate data type)能够同时存储超过一个的单独数据。C提供了两种类型的聚合数据类型:数组和结构

       数组:是相同类型的元素的集合,它的每个元素时通过下标引用或指针间接访问来选择的;

       结构:也是一些值的集合,这些值成为它的成员member,但一个结构的各个成员可能具有不同的类型。

结构声明

       结构声明为:

       struct tag {member-list} variable-list;

       红色部分为可选部分,可选部分不能全部省略,至少要出现两个

 

struct {

       int a;

       char b;

       float c;

}x;

 

struct {

       int a;

       char b;

       float c;

}y[10],*z;

如上面所示,即使两个结构的成员列表完全相同,但是编译器仍然会把它们当做完全不同的两种类型来对待。

结构成员的直接访问

       直接访问通过点操作符.访问。点操作符结构两个操作数,左操作数就是结构变量的名字,右操作数就是需要访问的成员的名字。

结构成员的间接访问

对于结构

struct data {

       int a;

       char b;

       float c;

};

那么struct data *x,我们应该使用(*x).a来访问a,但是这样写太繁琐。C语言提供了一种更方便的操作符,即箭头操作符。使用x->a即可访问a

结构的自引用

       在一个结构内部包含一个类型为该结构本身的成员是非法的clip_image001,比如:

struct data {

       int a;

       char b;

       float c;

       struct data x;

};

其中的struct data会无限递归。我们可以通过包含一个该结构的指针来解决

struct data {

       int a;

       char b;

       float c;

       struct data *x;

};

正确的原因是,编译器在结构的长度确定之前就已经知道了指针的长度,所以这种类型的自引用是合法的。

如果你觉得一个结构内部包含一个指向该结构本身的指针有些奇怪,请记住它事实上所指向的是同一种类型的不同结构,比如更高级的数据结构,链表和树,都是采用这种技巧实现的。

警惕陷阱

typedef struct {

       int a;

       char b;

       float c;

       DATA *x;

}DATA;

上面这个结构是失败的,因为类型名DATA直到声明的末尾才定义,所以在结构声明的内部它尚未定义。解决方法为定义一个结构标签:

typedef struct DATA_TAG {

       int a;

       char b;

       float c;

       struct DATA_TAG *x;

}DATA;

不完整的声明

       当两个结构互相引用时,肯定存在一些依赖的结构。此时应该首先声明哪一个呢?我们可以使用不完整声明(incomplete declaration)来解决这个问题,它声明一个作为结构标签的标识符。然后,我们可以把这个标签用在不需要知道这个结构的长度的声明中,如声明指向这个结构的指针。接下来的声明把这个标签与成员列表联系在一起。例如:

struct B;

 

struct A {

       struct B *partner;

       int a;

       float b;

}

 

struct B {

       struct A *partner;

       int a;

       float b;

}

访问结构成员

       比如对结构:

struct data {

       int a;

       char b;

       float c;

       struct data *x;

};

我们定义一个struct data m

pa = &m

pi = &(m.a);

我们可以知道papi的值其实是相同的,因为pa是整个结构的地址,而pia的地址,而a是结构的第一个元素,所以值相同。但是两个是不等同的,因为pa是指向结构的指针,而pi是指向int的指针。可以通过强制类型转换pi = (int *)pa来令两者相等,但是有很大危险性。

作为函数参数的结构

       当把结构作为参数产地给函数时,最好使用结构的指针,而不是结构本身。比如:

struct data {

       int a;

       char b;

       float c;

       struct data *x;

}DATA;

 

l  void test(DATA x);   这种方法是不提倡的;复制DATA,如果DATA结构十分庞大,会占用很大的空间;

l  void test(DATA *x);   这种方法是提倡的clip_image002只是传递一个指针。

l  void test(register DATA const*x);  这种方法是大力提倡的clip_image002[1]clip_image002[2]clip_image002[3],这种模式可以提高速度,并且可以防止指针被修改。

 

什么时候应该向函数传递一个结构而不是指针呢?很少有这种情况。只有当一个结构特别的小(长度和指针相同或更小)时,结构传递方案的效率才不会输给指针传递方案。但是对于绝大多数的结构,传递指针显然效率更高。如果你希望修改结构的任何成员,也应该使用指针传递方案。

位段

       位段的声明和结构类似,但它的成员时一个或多个位的字段,这些不同长度的字段实际上存储于一个或多个整型变量中

       位段的声明和任何普通的结构成员声明相同,但有两个例外:

l  首先,位段成员必须声明为intsigned intunsigned int类型;

l  其次,在成员们的后面是一个冒号和一个整数,这个整数指定该位段所占用的位的数目。

 

Mark:用signedunsigned整数显式地声明位段是个好主意,因为int在不同的机器上解释可能不同,指明了signedunsigned类型有利于移植。

       使用位段的好理由是,位段能够把长度为奇数的数据包装在一起,节省存储空间;使用位段还可以很方便地访问一个整型值的部分内容。

联合

       联合的所有成员引用的是内存中的相同位置。当你想在不同的时刻把不同的东西存储于同一个位置时,就可以使用联合。

clip_image004

       上述联合在我的机器上共占用8个字节。

l  如果成员f被使用,这个字就作为浮点型访问;

l  如果成员i被使用,这个字就作为整型值访问;

l  如果成员d被使用,这个字就作为双精度浮点型访问;

如果联合的各个成员具有不同的长度,联合的长度就是它最长成员的长度。比如,上述的联合fid的联合长度就应该是double类型做占用的字节数。这样的话,联合就足以容纳它最大的成员,但是如果这些成员的长度相差悬殊,当存储长度较短的成员时,浪费的空间是相当可观的。在这种情况下,更好的方法是在联合中存储指向不同成员的指针而不是直接存储成员本身,这样就解决了内存浪费的问题,当它需要使用哪个成员时,就分配正确数量的内存来存储它。

联合的初始化

       联合可以被初始化,但这个初始值必须是第一个成员的类型(如果被赋予其他值,有可能会被强制类型转换),而且它必须位于一对花括号里面。例如:

clip_image006

总结

l  不同的结构声明即使它们的成员列表相同也被认为是不同的类型;

l  结构不能包含类型也是这个结构的成员,但是它的成员可以是一个指向这个结构的指针,这个技巧长用于链式数据结构中;

l  为了声明两个结构,每个结构都包含一个指向对方的指针的成员,我们需要使用不完整的声明来定义一个结构标签名;

l  结构可以作为参数传递给函数,也可以作为返回值从函数返回。但是,向函数传递一个指向结构的指针往往效率更高。在结构指针参数的声明中可以加上const关键字防止函数修改指针所指向的结构。

l  向函数传递结构参数是低效的,最好传递指针。