C语言的声明比较复杂,到现在还有一些声明搞得不是很清楚,正好有点时间,把C的声明复习一下,记一下自己的复习笔记吧。
1, 解读C声明的方法
在《C专家编程中》介绍了一种方法用来分析C语言的声明,还是很有效果,不过脑子不好使,老爱忘记,现在把这段秘诀贴在这里:
--------------------------------------------------------------------------
A: 从变量名字开始读声明,然后按优先级读.
B: 优先级从高到低是:
B.1: 声明中()里的部分 #这里是指和变量名在一起的括号里面的部分
B.2: 后缀操作符:
圆括号()标识一个函数原型,
括号[]标识一个数组,
B.3: 前缀操作符:星号*表示“指向...的指针”.
C: 如果const和(或)volatile关键字的后面紧跟类型说明符(如int,long等), 那么它作用于 类型说明符。在其他情况下,const和(或)volatile关键字作用于它左边紧邻的指针星号。
--------------------------------------------------------------------------
总结一下以上规则就是,找到变量名-->读和变量名在一切的括号里面的东西->读后缀->读前缀。
最好的方法就是举例来说明这些规则的使用方法。
例1:
1, 解读C声明的方法
在《C专家编程中》介绍了一种方法用来分析C语言的声明,还是很有效果,不过脑子不好使,老爱忘记,现在把这段秘诀贴在这里:
--------------------------------------------------------------------------
A: 从变量名字开始读声明,然后按优先级读.
B: 优先级从高到低是:
B.1: 声明中()里的部分 #这里是指和变量名在一起的括号里面的部分
B.2: 后缀操作符:
圆括号()标识一个函数原型,
括号[]标识一个数组,
B.3: 前缀操作符:星号*表示“指向...的指针”.
C: 如果const和(或)volatile关键字的后面紧跟类型说明符(如int,long等), 那么它作用于 类型说明符。在其他情况下,const和(或)volatile关键字作用于它左边紧邻的指针星号。
--------------------------------------------------------------------------
总结一下以上规则就是,找到变量名-->读和变量名在一切的括号里面的东西->读后缀->读前缀。
最好的方法就是举例来说明这些规则的使用方法。
例1:
按优先级步骤来:
1) 找变量名 : next
2) 找括号里面的东西: *next
说明next是一个指向...的指针
3) 看后缀: (int a, int b)
说明(*next)是一个函数,和2)连起来就是: next是一个指向有两个参数的函数的指针
4) 看前缀: char * const *
说明是一个指向一个字符的指针的常量指针。
连起来:next是一个指向有两个参数的函数的指针,其返回值是一个指向一个字符的指针的常量指针。
例2:
char *(*c[10])(int **p)
解析步骤:
1) 找变量名: c
2) 找和变量名在一起的小括号里面的内容: (*c[10])
读后缀:c是一个数组;读前缀:c是一个指向...的指针;
连起来:c是一个有10个元素的数组, 每个元素都是指向...的指针。
3) 读后缀: (int **p)
c是一个有10个元素的数组,每个元素都是指向函数的指针,函数的参数是int指针的指针类型。
4) 读前缀: char *
返回值是字符指针
c是一个有10个元素的数组,该数组的每个元素是指向函数的指针,该函数的参数是int **,返回值是字符指针。
例3:
void (*signal(int sig, void(*func)(int)))(int);
解析步骤:
1) 变量名: signal
2) 括号里面的: signal是一个函数,返回值是一个指针
注意,这里不要把signal当成是一个函数指针了,要首先看变量名后面的括号。
3) 找后缀:
signal返回的函数指针,指向的函数参数是int,没有返回值
例4:
char (*j)[20];
1) 找变量名: j
2) 括号里面的: j是一个指向...的指针
2) 找后缀:
j是一个指向数组的指针,该数组有20个元素。
3) 读前缀
每个元素是char类型。
j是一个指向数组的指针,该数组内有20个char元素。
例5:
char *p[20];
1) 读变量名 : p
2) 读后缀 : p是一个有20个元素的数组
3) 读前缀 : p是一个有20个元素的数组,每个元素都是字符指针。
小结: 1) 请注意例4和例5的区别。
2) 数组前面的修饰符如char *, char 等,修饰的是数组中每个元素。
比如: char *(*a(int i))[10]
表示a是一个函数,返回值是一个指针,该指针指向一个有20个元素的数组,每个数组元素是一个字符指针。
2, 关于typedef
tpyedef是一种声明方式,它为一种类型引入新的名字,而不是为变量分配空间。注意:它没有引入新的类型,而是为现有类型取个别名。
一般来说,typedef用于简洁的表示复杂的东西。
2.1 定义方法
char *pa;
typedef char * string; //
string sa, sb; //声明了两个char* 的变量。
typedef的定义新别名的方法:
1) 在定义变量的前面加typedef 如:
typedef char * pa;
2) 把变量名,换成你想要使用的别名就可以了:
typedef char * string;
3) 使用别名定义其他变量。
例1:
int chars[81];
1) typedef int chars[81]; //第一步
2) typedef int Line[81]; // 把变量名换成别名
现在你可以用Line来表示有81个字符的数组了。
3) Line string1, string2;
2.2 typedef 和 #define 的区别
根据<<c expert>>,它们的区别主要表现在两个方面:
1) 可以用其他类新说明符对宏类型名进行扩展,但对typedef所定义的类型名却不能。
#define peach int;
unsigned peach i; /* ok */
typedef int ban;
unsigned ban i; /* errno */
2) 在连学的几个变量的声明中,用typedef定义的类型能够保证声明中所有的变量均为同一类型,而用
#define定义的类型无法保证。
#define int_p int *;
int_p pt1, pt2;
扩展后的形式为:
int *pt1, pt2; //这两个变量是一同的类型
用typedef可以避免这个问题:
typedef char * p_str;
p_str ps1, ps2; //ok, 两个变量都是char *
2.3 typedef的用途
用途一:
如上面所示,为类型取一个别名。
用途二:
和struct配合使用,可以少写几个字,但C专家的作者不推荐这样做,因为struct关键字,可以为你提供一些信息。
struct point {
int x;
int y;
};
typedef struct point point;
这样就可以用point和struct point来定义变量。 但看起来容易混淆,所以建议还是加上struct好些,不要偷懒使用tpyedef。
用途三:
用typedef来定义与平台无关的类型。
比如定义一个叫 REAL 的浮点类型,在目标平台一上,让它表示最高精度的类型为:
typedef long double REAL;
在不支持 long double 的平台二上,改为:
typedef double REAL;
在连 double 都不支持的平台三上,改为:
typedef float REAL;
也就是说,当跨平台时,只要改下 typedef 本身就行,不用对其他源码做任何修改。
标准库就广泛使用了这个技巧,比如size_t。
另外,因为typedef是定义了一种类型的新别名,不是简单的字符串替换,所以它比宏来得稳健(虽然用宏>有时也可以完成以上的用途)。
用途四:
为复杂的声明定义一个新的简单的别名。方法是:在原来的声明里逐步用别名替换一部分复杂声明,如此循环,把带变量名的部分留到最后替换,得到的就是原声明的最简化版。举例:
1. 原声明:int *(*a[5])(int, char*);
变量名为a,直接用一个新别名pFun替换a就可以了:
typedef int *(*pFun)(int, char*);
原声明的最简化版:
pFun a[5];