65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.73/5 (158投票s)

2004 年 5 月 14 日

CPOL

7分钟阅读

viewsIcon

625066

您是否曾遇到过像int * (* (*fp1) (int) ) [10];这样的声明,或者类似的、您无法理解的声明?本文将教您如何解析这些复杂的C/C++声明,包括typedefconst和函数指针的使用。

目录

引言

您是否曾遇到过像int * (* (*fp1) (int) ) [10];这样的声明,或者类似的、您无法理解的声明?本文将教您如何解析C/C++声明,从简单的(*请容我在此略带一笔*)到非常复杂的。我们将看到日常生活中遇到的声明示例,然后转向棘手的const修饰符和typedef,攻克函数指针,最后学习左右法则,这将使您能够准确地解析任何C/C++声明。我想强调的是,编写像这样的混乱代码并不被认为是好的做法;我只是教您如何理解这些声明。注意:本文最好在最低分辨率为1024x768的情况下查看,以确保注释不会溢出到下一行。

[返回目录]

基础知识

让我从一个非常简单的例子开始。考虑声明

int n;

这应该被解释为“声明nint”。

对于指针变量的声明,它将被声明为如下形式

int *p;

这应该被解释为“声明pint *,即指向int的指针”。我需要在这里做一个小说明——声明指针(或引用)时,最好将*(或&)放在变量名前而不是类型后面。这是为了确保在进行如下声明时不会出现失误

int* p,q;

乍一看,pq似乎都被声明为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;

上面的两个变量nm都是同一类型——常量整数。这是因为C++标准规定const关键字可以放在类型前面或变量名前面。我个人更喜欢使用前者,因为它能更清晰地突出const修饰符。

当处理指针时,const会有点令人困惑。例如,考虑下面声明中的两个变量pq

const int *p;
int const *q;

哪个是指向const int的指针,哪个是指向intconst指针?实际上,它们都指向const int。指向intconst指针将声明为

int * const r= &n; // n has been declared as an int

在这里,pq是指向const int的指针,这意味着你不能改变*p的值。r是一个const指针,这意味着一旦声明如上,像r=&m;这样的赋值(其中m是另一个int)将是非法的,但*r的值可以改变。

为了结合这两个声明来声明一个指向const intconst指针,你应该声明它为

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;

pq都变成了指针。如果未使用typedefq将是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];

这可以解释如下:

  1. 从变量名开始 -------------------------- fp1
  2. 右边没有东西,只有),所以向左找到* -------------- 是一个指针
  3. 跳出括号并遇到 (int) --------- 指向一个接受int作为参数的函数
  4. 向左,找到* ---------------------------------------- 并返回一个指针
  5. 跳出括号,向右,遇到[10] -------- 指向一个包含10个
  6. 向左找到* ----------------------------------------- 指针
  7. 再次向左,找到int -------------------------------- int

这是另一个例子

int *( *( *arr[5])())();
  1. 从变量名开始 --------------------- arr
  2. 向右,找到数组下标 --------------------- 是一个包含5个
  3. 向左,找到* ----------------------------------- 指针
  4. 跳出括号,向右找到() ------ 指向函数
  5. 向左,遇到* ----------------------------- 返回指针的函数
  6. 跳出,向右,找到() ----------------------- 指向函数
  7. 向左,找到* ----------------------------------- 返回指针的函数
  8. 继续向左,找到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.

[返回目录]

推荐阅读

  • 指针入门,作者Nitron。
  • cdecl是一个优秀的实用程序,它可以解释变量声明并执行更多功能。你可以从这里下载cdecl的Windows版本。

[返回目录]

致谢

这篇文章的灵感来自于我阅读Jörgen Sigvardsson发表的一个关于他在邮件中收到一个指针声明的帖子,该声明已在引言中转载。其中一些例子摘自Yashvant Kanetkar的书《Test your C skills》。一些函数指针的例子由我的表弟Madhukar M Rao提供。将混合了*&的例子以及与struct一起使用的typedef加入其中的想法是由我的表弟Rajesh Ramachandran提出的。Chris Hills对左右法则以及一些例子的解释方式进行了修改。

[返回目录]

© . All rights reserved.