本章节所说的声明不同于函数声明,它指所有类型的变量表达式的声明和定义。而本章节的主要内容是指针的各种声明的解读和理解。
C语言设计哲学要求对象的声明形式与它的使用形式尽可能相似。(例如,int p[3];表示一个int型指针的数组,而用p[i]表示指针所指向的int型数据。)这种方式的优点是操作符的优先级在“声明”和“使用”时是一样的。但,由于C语言的操作符的优先级过于复杂,导致程序员需要记住特殊的规则才能理解声明。
在说明如何解读声明之前,先说下一般声明所包含的各个元素。首先是类型说明符修饰,一般由类型说明符(void、char、short、int、long、signed、unsigned、float、double、struct、enum、union)+存储类型(extern、static、register)+类型限定符(const、volatile)组成(并非所有组合都合法)。然后出现的是指针修饰符(可有可无,也可以是多个表示多维指针)。后面紧跟的是声明器(所谓的声明器是指标识符以及与它组合在一起的任何指针、函数括号、数组下标等)。如果是函数则括号里的是函数的形参类型声明。如果有初始值,则后面紧跟着 = 初始值。如果同时声明多个变量表达式,则用逗号(,)隔开。最后,声明用分号(;)结束。
2.1 声明的规则
声明只是类型的一种声明,它必须要符合一定的规则。比如说:
1.类型说明符有且只能出现一个。(不包括函数的形参类型)
例如:int *float*p; 非法
2.函数的返回值不能是一个函数。
例如:int pfoo()(); 非法
3.函数的返回值不能是一个数组。(参见函数与数组章节)
例如:int pfoo()[]; 非法
4.数组里不能有函数。(因为数组元素的大小要相同,而不同函数的代码长短不一)
例如:int pfoo[]();
5.其他
2.2 声明的优先级
正如前文所述程序员必须记住一些特殊的规则才能理解声明,这些特殊的规则称为理解C语言声明的优先级规则。C语言声明的优先级规则如下:
A.声明从它的名字开始读,然后按照优先级顺序依次读取。
B.优先级从高到低依次是:
B1 声明中被括号括起来的部分
B2 后缀操作符:
圆括号()表示这是一个函数
方括号[]表示这是一个数组
B3 前缀操作符:
星号*表示 "指向……的指针"
C 如果const、volatile关键字的后面紧跟类型说明符(如int,long等),那么它作用于类型说明符。
在其他情况下,const、volatile关键字作用于它紧邻的指针星号。
2.3 实例分析
- 普通类型
int f; // int型变量
(int) f; // 将f强制转换成int型
int *f; // int型指针
int **f; // int型二维指针
(int*)*f; // 将f所指向的地址的值强制转换成int型指针
int *f,g; // f是int型指针,g是int型变量
int* f,g; // f是int型指针,g是int型变量
(int*) f,g; // 错误定义,但是逗号表达式。(int*)f表示将f强制转换为int型指针,g是前面定义的变量,整个表达式的值是g
(int*)f; // 将f强制转换为int型指针
注
:
1.int* ...和int *....的意思一样
2.(int*)是强制类型转换
3.(int)强制类型转换
- 函数
int f(); // f是个函数,返回值是int型
int *f(); // f是个函数,返回值是int型指针
(int*) f(); // 将f函数的返回值强制转换成int型指针
int (*f)(); // f是个指针,指向一个函数,该函数的返回值是int型,无参数
(int) (*f)(); // 将函数指针f所指向的函数的返回值强制转换成int型
int *(*f)(); // f是个指针,指向一个函数,该函数的返回值是int型指针,无参数
(int *)(*f)(); // 将函数指针f所指向的函数的返回值强制转换成int型指针
注
:
1.将标识符用()括起来的括号内所有的语句表示对标识符的修饰
2.标识符左边的圆括号()表示强制类型转换
3.标识符右边的圆括号()表示函数,括号里表示参数
- 数组
int f[N]; // f是int型数组
int *f[N]; // f是数组,数组中的每个元素都是int型指针
int f()[]; // f是函数,函数的返回值是int型数组。非法
int f[N](); // f是数组,数组中的每个元素是返回值为int型的无参函数。非法
int (*f[N])(); // f是数组,数组中的每个元素是个指针,指针指向返回值为int型的无参函数。
int *(*f[N])(); // f是数组,数组中的每个元素是个指针,指针指向返回值为int型指针的无参函数。
int *(*f[N])(),p; // p仍然是int型变量。
int (*f)(int , float); // f是个指针,指向返回值是int型,两个参数分别是int和float型的函数
int *(*f[N])(int , float); // f是个数组,数组中的每个元素都是指针,指针指向返回值是int型指针,参数分别是int和float型的函数
注
:
1.逗号表达式后面的表达式只继承前面的类型说明符。
2.匹配规则:
第一步,寻找标识符及将标识符括起来的括号。
(*f[N]),根据优先级规则,[]>*,f[N]则表示其是个数组元素为N的数组,
*f[N]则表示数组中的每个元素都是指针。
第二步,根据优先级规则,先看右边,再看左边。
由(int, float)中的()知,该指针指向一个函数,该函数的两个参数分别是int和float,
再由int *知该函数的返回值是int型指针。
3.char *const *(*nest)();
// nest是个指针,指针指向返回值是char *const *的无参函数。
// 该函数的返回值是个指针,该指针指向一个只读的指向char的指针。
注:返回值类型的解析参见指针与类型限定符一章。
2.4 typedef简化声明
typedef是为一种类型引入新的名字,而不会为该类型变量分配空间,它类似于函数的声明。它和普通的声明不同的地方在于,普通的声明表示“这个名字是一个指定类型的变量”,而typedef则是宣称“这个名字是指定类型的同义词”。
Unix系统信号机制中提供了一个名为signal的信号函数,其函数声明如下:
void (*signal(int signo, void (*func) (int)))(int);
分析如下:
首先,从左到右寻找到标识符signal,根据优先级规则,知道signal是个函数。
该函数有两个参数。
由( *signal() )知signal函数的返回值是个指针,
再由void (*signal())(int);知该指针是个指向B函数的指针,而B函数的返回值是void型,参数是int型。
接着,再看看signal函数的参数。
它第一个参数是int型的变量,第二个参数是void (*func) (int)。
由(*func )知道它是个指针,由(*func )(int)知道这个指针指向某个函数且函数的参数是int型的变量,
由void (*func) (int)知该函数的返回值是void型。
综上,signal函数是:一个函数,该函数的返回值是一个指向返回值为void型、参数为int型的函数的函数指针。
该函数的第一个参数是int型,该函数的第二个参数是一个指向返回值为void型、参数为int型的函数的函数指针。
为了巩固前面关于声明部分的知识,现在简要的讨论下:
void *signal(int signo, void (*func) (int)) (int);
分析如下:
由signal(…)知道signal是个函数,再由signal(…)(int),知道该函数的返回值是个函数。
由于函数的返回值不能是函数,所以这个声明是非法的。
现在我们可以用typedef来简化声明了,关于这个函数,通常有以下两种简化声明的形式。
方式一:
typedef void(*ptr_to_func)(int);
说明:声明ptr_to_func是一个指针,该指针指向一个返回值为void型、参数为int型的函数。
ptr_to_func signal(int , ptr_to_func);
说明:声明函数signal,该函数有两个参数。
第一个参数是int型的,第二个函数是ptr_to_func型的,返回值是ptr_to_func型的。
所以,signal函数就是:一个函数,该函数的返回值是一个指向返回值为void型、参数为int型的函数的函数指针。
该函数的第一个参数是int型,该函数的第二个参数是一个指向返回值为void型,参数为int型的函数的函数指针。
方式二:
typedef void ptr_to_func(int);
说明:ptr_to_func是一个函数,该函数的参数是int型,返回值是void型。
ptr_to_func* signal(int , ptr_to_func*);
说明:声明函数signal,该函数有两个参数。
第一个参数是int型的,第二个函数是指向ptr_to_func的指针,返回值也是指向ptr_to_func的指针。
所以,signal函数就是:一个函数,该函数的返回值是一个指向返回值为void型、参数为int型的函数的函数指针。
该函数的第一个参数是int型,该函数的第二个参数是一个指向返回值为void型,参数为int型的函数的函数指针。
typedef和 #define不一样。typedef是类型声明,表示某种变量是什么类型的,我们可以用这个变量名参照其他变量声明规则重新声明新的类型变量。而#define只是宏定义,会进行宏替换,它会在任何出现该宏替换的地方进行替换。
例如:
typedef int* AA; AA b,c; 相当于 int *b,*c;
而,
#define AA int*; AA b,c; 则相当于int *b,c;