内存管理:
有两个内存分配函数,calloc和realloc,函数原型如下:
Void *calloc(size_t num_elements, size_t element_size);
Void realloc(void *ptr, size_t new_size);
Calloc用于分配内存,malloc和calloc之间的区别是后者返回指向内存的指针之前把它初始化为0。
#define malloc 不要直接调用malloc
#define MALLOC(num, type) (type *)alloc((num)*sizeof(type))
Extern void *alloc(size_t size);
这个方法的难解之处是一个非比寻常的#define指令,它用于防止其它代码块直接塞入程序而导致的偶尔直接调用malloc的行为,如果程序偶尔调用了malloc,程序由于语法错误而无法编译。在alloc中必须加入#udef指令,这样才能调用malloc而不至于出错。
释放一块内存的一部分是不允许的,动态分配的内存必须整块一起释放。但是,realloc函数可以缩小一块动态分配的内存,有效地释放它尾部的部分内存。
高级指针:
Int (*f[ ])(); *f[ ]是一个元素为某种类型的指针的数组,数组元素是函数指针,它所指的函数的返回值是一个整形值。
Int *(*f[ ])(); 它与上面的区别是多了个间接访问操作符,所以这个声明创建了一个指针数组,指针所指向的类型是返回值为整形指针的函数。
函数指针的最常用的两个用途是转换表和作为参数传递给另一个函数。
和其它指针一样,对函数指针执行间接访问之前必须把它出初始化为指向某个函数。
Int (*pf)(int)=&f;
这个声明创建了函数指针pf,并初始化为指向函数f,取址符号是可选的。
我们有三种方式调用函数:
Int ans;
Ans=f(25);
Ans=(*pf)(25);
Ans=pf(25);
回调函数:把一个函数的指针作为参数传递给其他函数。
/*在一个链表中查找一个指定的函数,它的参数是一个指向链表第一个节点的指针,
一个指向我们需要查找的值和一个函数指针,它所指向的函数用于比较存储于链表中的类型的值。*/
#include <stdio.h>
#include “node.h”
Node *seratch_list(Node *node, void const *value,int (*compare)(void const *, void const *))
{
While(node !=null){
If(compare(&node->value,value)==0)
Break;
Node=node->link;
}
Return node;
}
转移表:
下面的程序用于实现一个袖珍式计算器。程序的其他部分已经读取两个数(op1和op2)和一个操作符(oper),下面的代码对操作符进行测试。
然后决定调用那个函数。
Switch(oper)
{
Case ADD:
Result=add(op1,op2);
Break;
Case SUB:
Result=sub(op1,op2);
Break;
Case MUL:
Result=mul(op1,op2);
Break;
Case DIV:
Result=div(op1,op2);
Break;
……
}
为了用转换表完成同样的任务,转换表就是一个函数指针数组。
创建一个转换表需要两步,首先,声明并初始化一个函数指针数组,唯一需要留心之处就是确保这些函数的原型出现在这个数组的声明之前。
Double add(double, double);
Double sub(double, double);
Double mul(double, double);
Double div(double, double);
……
Double (*oper_func[])(double, double)={
Add, sub, mul. Div,…
}
第二个步骤是利用下面的这条语句替换前面switch语句。
Result=oper_fun[oper](op1,op2);
把二进制值转换为字符。
下面的代码用一个不同的方法解决这个问题。
Putchar(“0123456789ABCDEF” [value%16]);
余数将是0~15的值,但这次用下标从字符串常量中选择一个字符进行打印。这个方法定义了一个字符串使字母和数字相邻,余数从字符串中选择一个正确的数字。
指针和联合:
在一个结构内部包含一个类型为结构体本身的成员是否合法?这有一个例子,可以说明这个想法。
Struct SELF_REF1 {
Int a;
Struct SELF_REF1 b;
Int c;
};
这种自引用是非法的,因为成员b是一个完整的结构。
Struct SELF_REF2 {
Int a;
Struct SELF_REF1 *b;
Int c;
};
这个声明和前面那个声明的区别在于b现在是一个指针而不是结构。
警惕下面的陷阱:
Typedef struct {
Int a;
SELF_REF3 *b;
Int c;
}SELF_REF3;
声明失败因为类型名知道声明结束才定义,所以在结构声明的内部它尚未定义。
不完整的声明:
当一个结构包含了另一个结构的一个或多个成员,和自引用结构一样,至少有一个结构必须在另一个结构内部以指针的形式存在。
这个问题的解决方案是使用不完整声明:它声明一个作为结构标签的标识符,然后后面可以把这个标签用在不需要知道这个结构的长度的声明中。
如:
Struct B;
Struct A {
Struct B *partner;
/*other declarations*/
};
Struct B{
Strcut A *partner;
/*other declarations*/
};
在A的成员列表中需要标签B的不完整声明,一旦A被声明之后,B的成员列表也可以被声明。
结构的初始化:
结构的初始化方式和数组的初始化方式相似,一个位于一对花括号内部、由逗号分隔的初始值列表可用于结构各个成员的初始化。
Struct INIT_EX{
Int a;
Short b[10];
Simple c;
}x={10,
{1,2,3,4,5},
{25,’x’,1.9}
};
位段:
位段的声明和任何普通的结构成员声明相同,但有两个例外,首先,位段成员必须声明为int、signed int或unsigned int。其次,成员名后面是一个冒号和一个整数,这个整数指定该位段所用的位的数目。
Strcut CHAR{
Unsigned ch:7;
Unsigned font:6;
Unsigned size:19;
};
Struct CHAR ch1;
数组:
数组名的值是一个指针常量,也就是数组第一个元素的地址。他的类型取决于数组元素的类型。那么数组名的类型就是指向其他类型的常量指针。
数组拥有一些与指针完全不同的特征。如数组具有确定数量的元素,而指针只是有个标量值。只有数组名在表达式中使用时,编译器才会为它产生一个指针常量。
只有两种场合下,数组名并不用纸指针常量,就是当数组名作为sizeof操作符或单目操作符&的操作数时,sizeof返回整个数组的长度,而不是指向数组的指针的长度。取一个数组名的地址所产生的是一个指向数组的指针。
当数组的初始化局部与一个函数(或代码块)时,你应该仔细考虑一下,在程序的执行流每次进入该函数时,每次对数组进行重新初始化是不是值得,答案是否定的,你就把数组声明为static,这样数组的初始化只需要在程序开始前执行一次。
函数:
C的规则很简单,所有的参数都是传值调用,但是,被传递的参数是一个数组名,并且在函数中用下标引用该数组的参数,那么在函数中对数组元素进行修改实际上修改的是调用程序中的数组元素。函数将访问调用程序的数组元素,数组并不会被复制,这个行为被称为“传值调用”;
可变参数列表:
可变参数列表时通过宏来实现的,这些宏定义于stdarg.h,它是标准库的一部分。
这个头文件声明了一个类型va_list和三个宏—va_start、va_arg、va_end,我们可以声明一个类型为va_list的变量,与这几个宏配合使用,访问参数的值。
函数声明一个名叫var_arg的变量,它用于访问参数列表的未确定部分,这个变量通过调用va_start初始化,它的第一个参数是va_list的变量的名字,第二个参数是省略号前最后一个有名字的参数。初始化过程把var_arg变量设置为指向可变参数部分的第一个参数。
为了访问参数,需要使用va_arg,这个宏接受两个参数:va_list变量和参数列表中下一个参数的类型,这个例子中,所有可变参数都是整型,在有些函数中,你可能要通过前面获得的数据来判断下一个参数的类型,va_arg返回这个参数的值,并使var_arg指向下一个可变参数。
注意:可变参数必须从头到尾按照顺序逐个访问。
/*计算指定数据值得平均值*/
#include <stdarg.h>
Float average(int n_values, …)
{
Va_list var_arg;
Int count;
Float sum=0;
//准备访问可变参数
Va_start(var_arg, n_values);
//添加取自可变参数列表的值。
For(count=0; count<n_values;count+=1)
{
Sum+=va_arg(var_arg, int);
}
//完成处理可变参数
Va_end(var_arg);
Return sem/n_values;
}
指针:
让我们分析一个表达式。假定变量a存储于位置100,下面的语句的作用是什么?
*100=25;
看上去是把25赋值给a,因为a是位置100所存储的变量。但是是错的。因为字面值100的类型是整形,而间接访问操作只能作用于指针类型的表达式。如果确实想把25存储于位置100,必须使用强制类型转换。
*(int *)100=25;
强制类型转换把值100从整型转换为“指向整型的指针”,这样对它间接访问就是合法的。这个技巧唯一有用之处是偶尔需要通过地址访问内存某个特定的位置,它不是用于访问某个变量,而是访问硬件本身。
For(vp=&values[N_VALUES-1]; vp>=&values[0]; vp--)
*vp=0;
在数组第一个元素被清除之后,vp的值还将减去1,而接下去的一次比较运算是用于结束循环的。但就是问题所在,比较表达式vp>=&values[0]的值是位定义的,因为vp移到了数组边界之外。标准允许指向数组元素的指针与指向数组最后一个元素后面那个内存位置的指针进行比较,但不允许指向数组第一个元素之前那个内存位置的指针进行比较。
操作符与表达式:
下面的程序使用右移操作符来计数一个值中值为1的位的个数。它接受一个无符号参数(这是为了避免右移位的歧义),并使用%操作符判断最右边的一位是否非零。
/*这个函数返回参数值中值为1的个数*/
Int count_ont_bits(unsigned value)
{
Int ones;
For(ones=0;value!=0;value=value>>1)
If(value%2!=0)
Ones=ones+1;
Return ones;
}
A[2 * (y-6*f(x))]=a[2*(y-6*f(x))]+1;
A[2 * (y-6*f(x))]==1;
在第一式中,用于选择增值位置的表达式必须书写两次,一次在赋值号左边,一次在赋值号右边,由于编译器无从知道函数f是否具有副作用,所以它必须两次计算下标表达式的值。第二种形式效率更高,因为下标只计算一次。
+=操作符更重要的优点是它使源码更容易阅读和书写。还可以避免书写错误,基于这些理由应该尽量使用复合赋值符。
(float)a
强制类型转换这个名字很容易记忆,它具有很高的优先级,所以把强制类型转换放在一个表达式前面只会改变表达式第1个项目的类型,如果要对整个表达式的结果进行强制类型转换,必须把整个表达式用括号括起来。
数据:
连接属性一共有3种---external(外部)、internal(内部)和one(无)。没有连接属性的标示符(none)总是被当作单独的个体,也就是说该标识符的多个声明被当作独立不同的实体。属于internal连接属性的标示符在同一个源文件内部都指向同一个实体,但位于不同源文件的多个声明则分属于不同的实体,最后,属于external连接属性的标示符不论声明多少次、位于几个源文件都表示同一个实体。
关键字extern和static用于在声明中修改标识符的连接属性。如果某个声明在正常情况下具有external连接属性,在前面加上static关键字使它的连接属性变为internal。如果第二个声明像下面这样书写:
Static int b;
那么变量b就将为这个源文件所私有。在其他源文件中,如果也连接到一个叫做b的变量,那么它所引用的是另一个不同的变量。类似,也可以把函数声明为static,如下:
Static int c(int d)
这可以防止它被其他源文件调用。
Static只对缺省连接属性为external的声明才有改变连接属性的效果。
Extern关键字的规则更为复杂。一般而言,它为一个标识符指定external连接属性,这样就可以访问其他任何位置定义的这个实体。当extern关键字用于源文件中一个标识符的第一次声明时,它指定标识符具有external连接属性。但是,如果用于标识符的第二次或以后声明时,它并不会改变由第一次声明所指定的连接属性。
存储类型:
变量的缺省存储类型取决于它的声明位置,凡是在任何代码块之外声明的变量总是存储于静态内存中,就是不属于堆栈的内存。这类声明称为静态(static)变量。在代码块内部声明的变量的缺省存储类型是自动auto的,但是极少使用。对于在代码块内部声明的变量,如果给它加上关键字static,可以使他的存储类型从自动变为静态。
函数的形式参数不能声明为静态,因为实参总是在堆栈中传递给函数。
Static关键字。当用于不同环境的关键字static有不同的意思。当它用于定义函数时,或用于代码块之外的变量声明时,static关键字用于修改标识符的连接属性,从external改为internal。但标识符存储类型不受影响。用这种方式声明的函数或变量只能在声明他们的源文件中访问。
当它用于代码块内部的变量声明时,static关键字用于修改变量的存储类型,从自动变量修改为静态变量。