为何不精通C? 03 深入剖析声明

对于复杂的C函数声明,或者被typedef别名后的声明,很多人往往一头雾水。本文主要解析下C语言中声明过程所遵循的原则。

声明

引用《C专家编程》的第三章内容,说明下声明的优先级规则:

  1. 声明从它的名字开始读,然后按照优先级顺序依次读取。
  2. 优先级从高到底依次为:

    • 括号包围的地方
    • 后缀操作符:

      • 括号()表示是一个函数
      • 方括号[]表示是一个数组
    • 前缀操作符:星号*表示类型是 指向....的指针

  3. 若const/volatile关键字的后面紧跟类型说明符(int,float),那么其作用于类型,在其他情况下,作用于其左边紧邻的指针星号。

不过,我觉得这个规则的不够通俗,看了《C++Annotation》中关于const的那一章节,也详细解释了下这个规则,高效,庖丁解牛般分析:

// 例子char* const *(*next)();
  1. 从变量名开始
  2. 往右看,直到声明结束或碰到')'
  3. 回到上一次开始的地方,往左看,直到直到碰到‘(’或到声明开始的地方。
  4. 若碰到(, 从2开始,整个被()包含的地方为一个成分,根据语境解释其含义
  5. 到开头结束

通俗的乒乓解释,就是从变量名开始,碰到)或结束往左读,碰到(往右读。

遇到*()[]解释之

其中 解释是函数的话,需解释其形参表返回值

解释为数组的话,需解释数组元素是什么

解释为指针时,需解释其指向什么类型

举例:

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是个别名好朋友
原文链接: https://www.cnblogs.com/IntellX/archive/2013/05/17/3083701.html

欢迎关注

微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍

原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/88950

非原创文章文中已经注明原地址,如有侵权,联系删除

关注公众号【高性能架构探索】,第一时间获取最新文章

转载文章受原作者版权保护。转载请注明原作者出处!

(0)
上一篇 2023年2月9日 下午11:52
下一篇 2023年2月9日 下午11:53

相关推荐