如何解析复杂的C/C++声明






4.73/5 (158投票s)
您是否曾遇到过像int * (* (*fp1) (int) ) [10];
这样的声明,或者类似的、您无法理解的声明?本文将教您如何解析这些复杂的C/C++声明,包括typedef
、const
和函数指针的使用。
目录
引言
您是否曾遇到过像int * (* (*fp1) (int) ) [10];
这样的声明,或者类似的、您无法理解的声明?本文将教您如何解析C/C++声明,从简单的(*请容我在此略带一笔*)到非常复杂的。我们将看到日常生活中遇到的声明示例,然后转向棘手的const
修饰符和typedef
,攻克函数指针,最后学习左右法则,这将使您能够准确地解析任何C/C++声明。我想强调的是,编写像这样的混乱代码并不被认为是好的做法;我只是教您如何理解这些声明。注意:本文最好在最低分辨率为1024x768的情况下查看,以确保注释不会溢出到下一行。
基础知识
让我从一个非常简单的例子开始。考虑声明
int n;
这应该被解释为“声明n
为int
”。
对于指针变量的声明,它将被声明为如下形式
int *p;
这应该被解释为“声明p
为int *
,即指向int
的指针”。我需要在这里做一个小说明——声明指针(或引用)时,最好将*
(或&
)放在变量名前而不是类型后面。这是为了确保在进行如下声明时不会出现失误
int* p,q;
乍一看,p
和q
似乎都被声明为int *
类型,但实际上,只有p
是指针,q
是一个普通的int
。
我们可以有一个指向指针的指针,它可以声明为
char **argv;
原则上,这没有限制,这意味着你可以有一个指向指向指向指向float
的指针的指针,依此类推。
考虑声明
int RollNum[30][4]; int (*p)[4]=RollNum; int *q[5];
在这里,p
被声明为指向包含4个int
的数组的指针,而q
被声明为包含5个指向整数的指针的数组。
我们可以有一个混合了*
和&
的声明,如下所述
int **p1; // p1 is a pointer to a pointer to an int. int *&p2; // p2 is a reference to a pointer to an int. int &*p3; // ERROR: Pointer to a reference is illegal. int &&p4; // ERROR: Reference to a reference is illegal.
const 修饰符
const
关键字用于当你不想修改一个变量(哎呀,这是一个矛盾的说法)时。当你声明一个const
变量时,你需要初始化它,因为你不能在任何其他时间给它赋值。
const int n=5; int const m=10;
上面的两个变量n
和m
都是同一类型——常量整数。这是因为C++标准规定const
关键字可以放在类型前面或变量名前面。我个人更喜欢使用前者,因为它能更清晰地突出const
修饰符。
当处理指针时,const
会有点令人困惑。例如,考虑下面声明中的两个变量p
和q
const int *p; int const *q;
哪个是指向const int
的指针,哪个是指向int
的const
指针?实际上,它们都指向const int
。指向int
的const
指针将声明为
int * const r= &n; // n has been declared as an int
在这里,p
和q
是指向const int
的指针,这意味着你不能改变*p
的值。r
是一个const
指针,这意味着一旦声明如上,像r=&m;
这样的赋值(其中m
是另一个int
)将是非法的,但*r
的值可以改变。
为了结合这两个声明来声明一个指向const int
的const
指针,你应该声明它为
const int * const p=&n // n has been declared as const int
以下声明应该可以消除关于const
如何解释的任何疑虑。请注意,其中一些声明除非在声明时就被赋值,否则将不会编译。为了清晰起见,我省略了它们,而且,添加这些将为每个示例需要额外的两行代码。
char ** p1; // pointer to pointer to char const char **p2; // pointer to pointer to const char char * const * p3; // pointer to const pointer to char const char * const * p4; // pointer to const pointer to const char char ** const p5; // const pointer to pointer to char const char ** const p6; // const pointer to pointer to const char char * const * const p7; // const pointer to const pointer to char const char * const * const p8; // const pointer to const pointer to const char
typedef 的细微之处
typedef
提供了一种方法来克服“*
适用于变量而非类型”的规则。如果你使用一个typedef
,比如
typedef char * PCHAR; PCHAR p,q;
p
和q
都变成了指针。如果未使用typedef
,q
将是char
,这违反直觉。
这里有几个使用typedef
进行的声明,以及解释
typedef char * a; // a is a pointer to a char typedef a b(); // b is a function that returns // a pointer to a char typedef b *c; // c is a pointer to a function // that returns a pointer to a char typedef c d(); // d is a function returning // a pointer to a function // that returns a pointer to a char typedef d *e; // e is a pointer to a function // returning a pointer to a // function that returns a // pointer to a char e var[10]; // var is an array of 10 pointers to // functions returning pointers to // functions returning pointers to chars.
typedef
通常与结构声明一起使用,如下所示。下面的结构声明允许你在创建结构变量时省略struct
关键字,即使在C语言中也是如此,这在C++中是正常的。
typedef struct tagPOINT { int x; int y; }POINT; POINT p; /* Valid C code */
函数指针
函数指针可能是导致声明解释混乱的最大来源。函数指针在旧的DOS时代用于编写TSR(Terminate and Stay Resident)程序;在Win32世界和X-Windows中,它们用于回调函数。函数指针还有很多其他用途:虚函数表、STL中的一些模板,以及Win NT/2K/XP系统服务。让我们来看一个函数指针的简单例子
int (*p)(char);
这声明p
是指向一个函数(该函数接受一个char
参数并返回一个int
)的指针。
指向一个接受两个float
并返回指向指向char
的指针的函数的指针将被声明为
char ** (*p)(float, float);
包含5个指向函数的指针的数组,这些函数接受两个指向const char
的指针并返回一个void
指针,怎么样?
void * (*a[5])(char * const, char * const);
左右法则 [重要]
这是一个简单的规则,可以让你解释任何声明。它如下进行:
从最里面的括号开始阅读声明,向右,然后向左。当你遇到括号时,方向应该反转。一旦括号内的所有内容都被解析,就跳出它。继续直到整个声明都被解析。
对左右法则的一个小小的修改:当你第一次开始阅读声明时,你必须从标识符开始,而不是最里面的括号。
以引言中给出的例子为例
int * (* (*fp1) (int) ) [10];
这可以解释如下:
- 从变量名开始 --------------------------
fp1
- 右边没有东西,只有
)
,所以向左找到*
-------------- 是一个指针 - 跳出括号并遇到 (
int
) --------- 指向一个接受int
作为参数的函数 - 向左,找到
*
---------------------------------------- 并返回一个指针 - 跳出括号,向右,遇到
[10]
-------- 指向一个包含10个 - 向左找到
*
----------------------------------------- 指针 - 再次向左,找到
int
--------------------------------int
。
这是另一个例子
int *( *( *arr[5])())();
- 从变量名开始 ---------------------
arr
- 向右,找到数组下标 --------------------- 是一个包含5个
- 向左,找到
*
----------------------------------- 指针 - 跳出括号,向右找到
()
------ 指向函数 - 向左,遇到
*
----------------------------- 返回指针的函数 - 跳出,向右,找到
()
----------------------- 指向函数 - 向左,找到
*
----------------------------------- 返回指针的函数 - 继续向左,找到
int
----------------------------- 指向int
。
更多示例
以下示例应该能让你清楚
float ( * ( *b()) [] )(); // b is a function that returns a // pointer to an array of pointers // to functions returning floats. void * ( *c) ( char, int (*)()); // c is a pointer to a function that takes // two parameters: // a char and a pointer to a // function that takes no // parameters and returns // an int // and returns a pointer to void. void ** (*d) (int &, char **(*)(char *, char **)); // d is a pointer to a function that takes // two parameters: // a reference to an int and a pointer // to a function that takes two parameters: // a pointer to a char and a pointer // to a pointer to a char // and returns a pointer to a pointer // to a char // and returns a pointer to a pointer to void float ( * ( * e[10]) (int &) ) [5]; // e is an array of 10 pointers to // functions that take a single // reference to an int as an argument // and return pointers to // an array of 5 floats.
推荐阅读
致谢
这篇文章的灵感来自于我阅读Jörgen Sigvardsson发表的一个关于他在邮件中收到一个指针声明的帖子,该声明已在引言中转载。其中一些例子摘自Yashvant Kanetkar的书《Test your C skills》。一些函数指针的例子由我的表弟Madhukar M Rao提供。将混合了*
和&
的例子以及与struct
一起使用的typedef
加入其中的想法是由我的表弟Rajesh Ramachandran提出的。Chris Hills对左右法则以及一些例子的解释方式进行了修改。