结构和数组都属于聚合数据类型aggregate data type。
结构是一些值的集合,这些值称为它的成员。
结构并不是一个它自身成员的数组。和数组名不同,当一个结构变量在表达式中使用时,它并不被替换成一个指针。结构变量也无法使用下表来选择特定的成员。
结构变量属于标量类型,所以可以向对待其他标量类型那样执行相同类型的操作。结构也可以作为传递给函数的参数,它们也可以作为返回值从函数返回,相同类型的结构变量相互之间可以复制。可以声明指向结构的指针,取一个结构变量的地址,也可以声明结构数组。
如果必须确定结构某个成员的实际位置,应该考虑边界对齐因素,可以使用定义与stddef.h的offsetof宏。
offsetof(type,member)
type就是结构的类型,member就是需要的成员名。表达式的结果是一个size_t类型的值,表示这个指定成员开始存储的位置距离结构开始存储的位置偏移几个字节。
注重可移植性的程序应该避免使用位段。
联合变量可以被初始化,但这个初始值必须是联合第1个成员的类型,而且它必须位于一对花括号里面。
malloc与free的声明在stdlib.h中
void *malloc(size_t size);
void free(void *pointer);
malloc的参数就是需要分配的内存字节数,如果内存池中的可用内存可以满足这个需求,malloc就返回一个指向被分配的内存块起始位置的指针。申请失败会返回一个NULL指针。
free的参数必须要么是NULL,要么是一个先前从malloc、clloc或realloc返回的值。向free传递一个NULL参数不会产生任何效果。
calloc和realloc
void *calloc(size_t num_elements,
size_t element_size);
void realloc(void *ptr,size_t new_size);
calloc也用于分配内存。malloc和calloc之间的主要区别是后者在返回指向内存的指针之前把它初始化为0-。calloc和malloc之间另外一个区别是他们请求内存数量的方式不同。calloc的参数包括所需元素的数量和每个元素的字节数。它会根据这些值计算出总共需要分配的内存。
realloc函数用于修改一个原先已经分配的内存块的大小。使用这个函数,你可以使一块内存扩大或缩小。如果它用于扩大一个内存块,那么这块内存原先的内容依然保留,新增加的内存添加到原先内存块的后面,新内存并未以任何方法进行初始化。如果它用于缩小一个内存块,该内存块尾部的部分内存便被拿掉,剩余部分内存的原先内容依然保留。
如果原先的内存块无法改变大小,realloc将分配另一块正确大小的内存,并把原先那块内存的内容复制到新的块上。因此,在使用realloc之后,就不能再使用指向旧内存的指针,而是应该改用realloc所返回的新指针。
如果realloc函数的第一个参数是NULL,那么他的行为就和malloc一模一样。
单链表:
单链表中,每个节点包含一个指向链表下一节点的指针。链表最后一个节点的指针字段的值为NULL,提示链表后面不再有其他节点。为了记住链表的起始位置,可以用一个根指针 root pointer。根指针指向链表的第一个节点。根指针只是一个指针,它不包含任何数据。
函数名被使用时总是由编译器把它转换为函数指针。
使查找函数与类型无关,这样它就能用于任何类型。首先必须改变比较的执行方式,这样函数就可以对任何类型的值进行比较。解决方案是使用函数指针。调用者编写一个函数,用于比较,然后把一个指向这个函数的指针作为参数传递给查找函数。然后查找函数调用这个函数来执行值的比较。使用这种方法,任何类型的值都可以进行比较。
第二个方面是向函数传递一个指向值的指针而不是值本身。函数有一个void *形参,用于接收这个参数。然后指向这个值的指针便传递给比较函数。这个修改使字符串和数组对象也可以被使用。字符串和数组无法作为参数传递给函数,但指向他们的指针却可以。
使用这种技巧的函数被称为回调函数callback function,因为用户吧一个函数指针作为参数传递给其他参数,后者将“回调”用户的函数。如果所编写的函数必须能够在不同的时刻执行不同类型的工作或者执行职能由函数调用者定义的工作,都可以使用回调函数来解决。
在使用比较函数中的指针之前,它们必须被强制转换为正确的类型。因为强制类型转换能够躲过一般的类型检查,所以在使用时要小心确保函数的参数类型是真确的。
main函数具有两个形参。第一个为argc,表示命令行参数的数目。第二个为argv,它指向一组参数值。由于参数的数目并没有内在的限制,所以argv指向这组参数值的第1个元素。这些元素的每个都是指向一个参数文本的指针。如果程序需要访问命令行参数,main函数在声明时就要加上这些参数:
int main(int argc,char **argv)
“xyz”+1 的结果是y。字符串常量实际上是个指针,这个表达式计算指针值+1的值是个指针,指向字符串中第2个字符y。
编译一个C程序第1个步骤称为预处理preprocessing阶段。C预处理器preprocessor在源代码编译之前对其进行一些文本性质的操作。它的主要任务包括删除注释、插入被#include指令包含的文件的内容、定义和替换由#define指令定义的符号以及确定代码的部分内容是否应该根据一些条件编译指令进行编译。
预处理符号
符号 | 样例 | 含义 |
_FILE_ | "name.c" | 进行编译的源文件名 |
_LINE_ | 25 | 文件当前行的行号 |
_DATE_ | "Jan 31 1997" | 文件被编译的日期 |
_TIME_ | "18:04:30" | 文件被编译的时间 |
_STDC_ | 1 | 编译器村讯ANSI C,其值为1,否则未定义 |
#define
#define name stuff
#define机制包括了一个规定,允许把参数替换到文本中,这种实现通常称为宏macro或定义宏defined macro。
#define name(parameter-list) stuff
parameter-list参数列表是一个由逗号分隔的符号列表,他们可能出现在stuff中。参数列表的左括号必须与name紧邻。如果两者之间有任何空白存在,参数列表就会被解释为stuff的一部分。
当宏被调用时,名字后面是一个由逗号分隔的值的列表,每个值都与宏定义中的一个参数相对应,整个列表用一对括号包围。当参数出现在程序中时,与每个参数对应的实际值豆浆杯替换到stuff中。
所有用于对数值表达式进行求值的宏定义都应该合理的加上括号,避免在使用宏时,由于参数中的操作符或邻近的操作符之间不可预料的相互作用。
宏不可以出现递归
#argument这种结构被预处理器翻译为“argument”
##结构把位于它两边的符号链接成一个符号。作为用途之一,它允许宏定义从分离的文本片段创建标识符。
宏是与类型无关的
和使用函数相比,使用宏的不利之处在于每次使用宏时,一份宏定义代码的拷贝都将插入到程序中。除非宏非常短,否则使用宏可能会大幅度增加程序的长度。
当宏参数在宏定义中出现的次数超过一次时,如果这个参数具有副作用,那么当你使用这个宏时就可能出现危险,导致不可预料的结果。副作用就是在表达式求值时出现的永久性效果。
宏和函数的不同之处
属性 | #define宏 | 函数 |
代码长度 | 每次使用时,宏代码都被插入到程序中。出了非常小的宏之外,程序的长度将大幅度增长 | 函数代码只出现于一个地方;每次使用这个函数时,都调用那个地方的同一份代码 |
执行速度 | 更快 | 存在函数调用/返回的额外开销 |
操作符优先级 | 宏参数的求值是在所有周围表达式的上下文环境里除非它们加上括号,否则邻近操作符的优先级可能会产生不可预料的结果 | 函数参数只在函数调用时求值一次,它的结果值传递给函数。表达式的求职结果更容易预测 |
参数求值 | 参数每次用于宏定义时,它们都将重新求职。由于多次求值,具有副作用的参数可能会产生不可预料的结果 | 参数在函数被调用前只求值一次。在函数中多次使用参数并不会导致多种求值过程。参数的副作用并不会造成任何特殊的问题 |
参数类型 | 宏与类型无关。只要对参数的操作是合法的,它可以使用于任何参数类型 | 函数的参数是与类型有关的。如果参数的类型不同,就需要使用不同的函数,即使他们执行的任务是相同的 |
#undef用于移除一个宏定义
#undef name
如果一个现存的名字需要被重新定义,那么它的旧定义首先必须用#undef移除。
条件编译conditional compilation
使用条件编译,可以选择代码的一部分是被正常编译还是完全忽略。
#if constan-expression
statements
#elif constant-expression
other statements...
#else
other statements
#endif
其中,constant-expression常量表达式由预处理器进行求值。如果它的值是非零值(真),那么statements部分就被正常编译,否则预处理器就删除它们。
是否被定义
#if defined(symbol)
#ifdef symbol
#if !defined(symbol)
#ifndef symbol
前面提到的这些指令可以嵌套于另一个指令内部
为每个#endif加上一个注释标签是很有帮助的
文件包含
当头文件被包含时,位于头文件内所有的内容都要被编译。这意味着每个头文件应该包含一组函数或数据声明。
嵌套文件包含是允许的。ANSI C标准要求编译器必须支持至少8层的头文件嵌套,没有限定嵌套深度的最大值。
#error生成错误信息
#error text of error message
#line通知预处理器number是下一行输入的行号
#line number “string”
可选部分string会被作为当前文件的名字,这条指令将修改_LINE_符号的值,如果加上可选部分,还将修改_FILE_符号的值。