为何不精通C? 03 深入剖析声明
对于复杂的C函数声明,或者被typedef别名后的声明,很多人往往一头雾水。本文主要解析下C语言中声明过程所遵循的原则。
声明
引用《C专家编程》的第三章内容,说明下声明的优先级规则:
- 声明从它的名字开始读,然后按照优先级顺序依次读取。
- 优先级从高到底依次为:
- 括号包围的地方
- 后缀操作符:
- 括号()表示是一个函数
- 方括号[]表示是一个数组
- 前缀操作符:星号*表示类型是 指向....的指针
- 若const/volatile关键字的后面紧跟类型说明符(int,float),那么其作用于类型,在其他情况下,作用于其左边紧邻的指针星号。
不过,我觉得这个规则的不够通俗,看了《C++Annotation》中关于const的那一章节,也详细解释了下这个规则,高效,庖丁解牛般分析:
// 例子
char* const *(*next)();
- 从变量名开始
- 往右看,直到声明结束或碰到 ')'
- 回到上一次开始的地方,往左看,直到直到碰到 ‘(’ 或到声明开始的地方。
- 若碰到(, 从2开始,整个被()包含的地方为一个成分,根据语境解释其含义
- 到开头结束
通俗的乒乓解释,就是从变量名开始,碰到)或结束往左读,碰到(往右读。
遇到*()[]解释之
其中 解释是函数的话,需解释其形参表及返回值
解释为数组的话,需解释数组元素是什么
解释为指针时,需解释其指向什么类型
举例:
1 next 名叫next, 开始往右读,碰到了),折向往左 2 (*next) 遇到* ,解释是一个指针 继续往左读碰到(, 折向往右 3 (*next)() 遇到(),解释指向一个函数,该函数没有形参 到末尾,折向往左
4 *(*next)() 遇到*,解释函数返回一个指针,关于函数的形参表和返回值解释完毕 , 继续往左 5 char* const *(*next)() 先碰到const,再是*, 最后是char, 解释函数的返回值(指针)指向一个char型指针常量,即指针不可赋新值,所指的值可以改变。到开头,结束。
以上的例子是函数的声明举例。
对其他的声明也是一样的,比如之前文章中 int *ap[], 我们也是这样解释的:
- 从 ap 开始往右读,碰到了[], 说明是一个数组
- 然后到末尾,折向向左,碰到*,说明数组的元素是指针,
- 最后碰到int,说明该元素的指针类型为 int*
常规的 const int * p; 也可以这么来:
- 从 p 开始,结尾折向,往左走。
- 先碰到*, 说明是一个指针
- 再碰到 int,const, const修饰的是int, 则说明该指针指向一个const int东西
同理,对于 int* const p: 我们在往左看时,先看到了const,再*, 说明const修饰的是*。
来个复杂的挑战下吧:
char* const* (* const (*(*ip)() ) [] ) [] (*ip) ip是一个指针 (*ip)() ip是一个函数指针,该函数无形参 *(*ip)() 函数返回一个指针 (...)[] 该指针指向一个数组 *const (...)[] 数组元素为 常量指针 (*const (...)[]) [] 该常量指针指向一个数组 char* const* {...}该数组内元素为指向char型常量指针的指针 合起来就是: ip是一个函数指针(无形参), 返回一个指向数组的指针,该数组内元素为常量指针,其指向一个元素为char型常量指针的数组。
不过,一般不会有这种声明来恶心我们的。。我们基本上知道怎么打乒乓就行了。
typedef
剖析完了声明,一般来说还要说下 typedef ,这可是C的一大神器呀。
首先,我们要明确的是,typedef是为类型创建别名,而不是创造新的类型。
讲typedef时,又必须和 #define做下区分, define仅是简单的宏扩展
主要来说,有以下的区别:
- 首先,可以对其他类型说明符采用宏类型名进行扩展,但对typedef所定义则不行
#define INT int typedef int tInt unsigned INT ci; // 正确,类型为unsigned int unsigned tInt tci; // 非法,错误
- 其次,连续变量声明中,typedef能够保证所有定义类型一致,而#define不能保证
#define pD int* typedef int* pT pD a,b; // a类型为 int*, b类型为int pT c,d; // c,d类型均为int*
和typedef做好朋友
void (*signal(int sig, void(*func)(int))) (int);
我们来分析下这个是什么东西:
signal(..) : signal 是一个函数,有复杂的形参表
*signal(...) : 返回值是一个指针
void {*}(int) : 该指针指向一个函数,该函数的形参为int, 这个函数的返回值是void
我们看看signal的形参表: 两个参数,一个参数是 int; 另外个参数 void(*func)(int) 是一个函数指针,该函数有一个int形参,返回void;
对比下,我们可以分析出, signal的返回值和 func的定义一样, 都是 void(*)(int), 但若是采用这种写法的话,不好看懂,这时候,我们的好朋友typedef就出现了:
typedef void(*ptr_to_func)(int); ptr_to_func signal(int, ptr_to_func){.....}
另外,typedef也经常和struct配合使用。
typedef struct my_struct{....} NewName;
这样,NewName 就等同于 struct my_struct , 少打了struct这几个字符
扩展说下 声明和定义的区别:
C语言中,对象有且仅有一个定义,而声明却可以有多个extern 声明。
定义:只能出现在一个地方,确定同时分配内存,它是特殊的声明
声明:只是描述其他地方创建对象的属性。有extern前缀,作用于变量
具体的一些扩展区别,见后续博文,关于指针和数组的阐释
END
要点: 怎么打乒乓?typedef是个别名好朋友