第三章 分析C语言的声明
3.1 只有编译器才会喜欢的语法
char (*j) [20];/*j是一个指向数组的指针,数组内有20个char元素*/
j = (char (*)[20]) malloc(20);
如果把星号两边看上去明显多余的括号拿掉,代码会变成非法的。
涉及指针和const的声明可能会出现几种不同的顺序:
const int *grape;
int const *grape;
int * constgrape_jelly;
在最后一种情况下,指针是只读的,而在另外两种情况下,指针所指向的对象是只读的。当对象和指针都是只读的,下面两种声明方法能做到这一点:
const int * const grape_jam;
int const * const grape_jam;
3.2 声明是如何形成的
声明器就是标识符以及与它组合在一起的任何指针、函数括号、数组下标等
3.2.1 关于结构
结构的通常形式是:
struct 结构标签(可选){
类型1 标识符1;
类型2 标识符2;
……
类型N 标识符N;
}变量定义(可选);
struct vegonion, radish, turnip;
有些C语言书记声称“在调用函数时,参数按照从右到左的次序压到堆栈里。”这种说法过于简单。参数在传递时首先尽可能地存放到寄存器中(追求速度)。注意,int型变量i跟只包含一个int型成员的结构变量s在参数传递时的方式可能完全不同。一个int型参数一般会被传递到寄存器中,而结构参数则很可能被传递到堆栈中。
在结构中放置数组,如
struct s_tag {int a[100]; };
现在,可以把数组当做第一等级的类型,用赋值语句拷贝整个数组,以传值调用的方式把它传递到函数,或者把它作为函数的返回类型。
在典型情况下,并不会频繁地堆整个数组进行赋值操作。但是如果需要这样做,可以通过把它放入结构中来实现。
3.2.2 关于联合
联合的外表与结构相似,但在内存布局上存在关键性的区别。在结构中,每个成员依次存储,而在联合中,所有的成员都从偏移地址零开始存储。这样,每个成员的位置都重叠在一起:在某一时刻,只有一个成员真正存储于该地址。
联合既有一些优点,也有一些缺点。它的缺点就是那些所谓的优点其实并不怎么出色。
union 可选的标签 {
类型1 标识符1;
类型2 标识符2;
……
类型N 标识符N;
}可选的变量定义;
联合一般是作为大型结构的一部分存在的。
联合一般被用来节省空间,因为有些数据项是不可能同时存在的。例如,如果我们想要存储一些关于动物种类的信息,首先想到的方法可能是:
sttuct creature{
char has_backbone;
char has_fur;
short num_of_legs_in_excess_of_4;
};
但是,我们知道,所有的动物要么是脊椎动物,要么是无脊椎动物。进而,我们还知道,只有脊椎动物才可能有皮毛,只有无脊椎动物才可能有多于4条的腿。没有一种动物既有毛皮又有超过4条的腿。这样,可以通过把两个互相排斥的字段存储于一个联合中来节省空间:
union secondary_characteristics{
char has_fur;
short num_of_legs_in_excess_of_4;
};
sttuct creature{
char has_backbone;
union secondary_characteristics;
};
联合也可以把同一个数据解释成两种不同的东西。
union bits32_tag{
int whole; /*一个32位的值*/
struct { char c0, c1, c2,c3; } byte; /*4个8位的字节*/
}value;
这个联合允许提取整个32位值(作为int),也可以提取单独的字节字段如value.byte.c0等。
3.2.3 关于枚举
enum 可选标签 {内容……}可选变量定义;
3.3 优先级规则
C语言声明的优先级规则
1. 声明从它的名字开始读取,然后按照优先级顺序依次读取
2. 优先级从高到底依次是:
2.1 声明中北括号括起来的部分
2.2 后缀操作符:括号()表示这是一个函数,而方括号表示这是一个数组。
2.3 前缀操作符:星号*表示“指向…的指针”。
3. 如果const和(或)volatile关键字的后面紧跟类型说明符(如int,long等)它作用于类型说明符。在其他情况下,const和(或)volatile关键字作用于紧邻的指针星号。
char * const *(*next)();
首先,看变量名“next”,并注意到它直接被括号所括住。
所以先把括号里的东西看做是一个整体,得出“next是一个指向…的指针”,然后考虑括号外面的东西,在星号和括号后缀之间作出选择
右边的函数括号优先级较高,所以得出“next是一个函数指针,指向一个返回…的函数”
然后,处理前缀“*”,得出指针所指向的内容
最后,把“char* const”解释为指向字符的常量指针
“next是一个指针,指向一个函数,该函数返回另一个指针,该指针指向一个类型为char的常量指针”
3.4 通过图表分析C语言的声明
char *(* c[10])(int **p);
c是一个数组,它的元素类型是函数指针,其所指向的函数的返回值是一个指向char的指针
3.5 typedef可以成为朋友
void ( *signal (int sig, void (*func)(int) ) )(int)
void ( *signal ( ) )(int)
signal是一个函数,它返回一个函数指针,所指向的函数接受一个int参数并返回void。其中一个恐怖的参数是其本身。
void (*func)(int);
用typedef来“代表”通用部分,从而进行简化。
typedef void (*ptr_to_func)(int);
ptr_to_func signal(int, ptr_to_func);
3.6 typedef int x[10]和#define x int[10]的区别
typedef和宏的区别体现在两个方面:
首先,可以用其他类型说明符对宏类型名进行扩展,但对typedef所定义的类型名却不能这样做。
#define peach int
unsigned peach i;/*没问题*/
typedef int banana;
unsigned banana i; /*错误!非法*/
其次,在连续几个变量的声明中,用typedef定义的类型能够保证声明中所有的变量均为同一种类型,而用#define定义的类型则无法保证。如下所示:
#define int_ptr int *
int_ptr chalk, cheese;
经过宏扩展,第二行变为:
int * chalk, cheese;
3.7 typedef struct foo{… foo;}的含义
typedef struct my_tag { int i; } my_type;
struct my_tag variable_1;
my_type variable_2;/*此处已经不需要struct*/
这个typedef声明引入了my_type这个名字作为“struct my_tag { int i; }”的简写形式。
typedef struct fruit { int weight, price_per_lb;} fruit;
struct veg{ int weight,price_per_lb;} veg;
struct fruit mandarin;/* 使用结构标签”fruit” */
fruit mandarin;/* 使用结构类型”fruit” */
struct veg potato;
如果试图使用veg potato;将是一个错误。
不要为了方便起见对结构使用typedef。
这样做的唯一好处是能使你不必书写“struct”关键字。
typedef应该用在:
数组、结构、指针以及函数的组合类型。
可移植类型。
也可以为后面的强制类型转换提供一个简单的名字。
如果搞不清楚哪个名字是结构标签,就为它取一个以“_tag”结尾的名字。
3.8 理解所有分析过程的代码段
3.9 轻松一下——驱动物理实体的软件