35

我如何理解以下复杂的声明?

char (*(*f())[])();

char (*(*X[3])())[5];

void (*f)(int,void (*)()); 

char far *far *ptr;

typedef void (*pfun)(int,float);

int **(*f)(int**,int**(*)(int **,int **));
4

11 回答 11

75

正如其他人所指出的,cdecl 是完成这项工作的正确工具。

如果您想在没有 cdecl 帮助的情况下理解这种声明,请尝试从内到外从右到左阅读

从您的列表 Start at X 中随机抽取一个示例,它是声明/定义的标识符(以及最里面的标识符):char (*(*X[3])())[5];

char (*(*X[3])())[5];
         ^

X 是

X[3]
 ^^^

X 是 一个 3 的数组

(*X[3])
 ^                /* the parenthesis group the sub-expression */

X 是一个由 3 个指针组成的数组

(*X[3])()
       ^^

X 是一个包含 3 个指针的数组,指向 函数接受未指定(但固定)数量的参数

(*(*X[3])())
 ^                   /* more grouping parenthesis */

X 是一个由 3 个指针组成的数组,指向函数接受未指定(但固定)数量的参数 并返回一个指针

(*(*X[3])())[5]
            ^^^

X 是一个由 3 个指向函数的指针组成的数组,它接受未指定(但固定)数量的参数并返回一个指向 5 个数组的指针

char (*(*X[3])())[5];
^^^^                ^

X 是一个由 3 个指向函数的指针组成的数组,它接受未指定(但固定)数量的参数并返回一个指向 5 个 char数组的指针。

于 2009-09-19T16:28:07.923 回答
16

从里面读出来,类似于你如何解方程,比如{3+5*[2+3*(x+6*2)]}=0- 你首先解决里面的内容()然后[]最后{}

char (*(*x())[])()
         ^

这意味着那x东西

char (*(*x())[])()
          ^^

x是一个函数

char (*(*x())[])()
        ^

x返回指向某事的指针

char (*(*x())[])()
       ^    ^^^

x返回一个指向数组的指针。

char (*(*x())[])()
      ^

x返回指向指针数组的指针

char (*(*x())[])()
     ^         ^^^

x返回指向函数指针数组的指针

char (*(*x())[])()
^^^^

意味着返回的数组指针指向x一个函数指针数组,这些函数指针指向返回一个char的函数。

但是,是的,使用cdecl。我自己用它来检查我的答案:)。

如果这仍然让您感到困惑(并且可能应该),请尝试在一张纸或您最喜欢的文本编辑器中做同样的事情。仅仅看一眼就无法知道它的含义。

于 2010-04-18T18:27:22.090 回答
13

听起来像是 cdecl 工具的工作:

cdecl> explain char (*(*f())[])();
declare f as function returning pointer to array of pointer to function returning char

我四处寻找该工具的官方主页,但找不到看起来是真的。在 Linux 中,您通常可以期望您选择的发行版包含该工具,因此我只是安装了它以生成上述示例。

于 2009-09-19T16:11:28.777 回答
5

您应该使用cdecl工具。它应该在大多数 Linux 发行版上都可用。

例如对于这个函数,它会返回给你:

char (*(*f())[])();- 将 f 声明为函数返回指针,该指针指向返回 char 的函数的指针数组

void (*f)(int,void (*)()); - 函数指针 f 的原型。f 是一个接受两个参数的函数,第一个是 int,第二个是返回 void 的函数的函数指针。

char far *far *ptr;- ptr 是指向远指针(指向某个字符/字节)的远指针。

char (*(*X[3])())[5];- X 是一个由 3 个指向函数的指针组成的数组,它接受不确定数量的参数并返回一个指向 5 个字符数组的指针。

typedef void (*pfun)(int,float);- 声明函数指针 pfun。pfun 是一个有两个参数的函数,第一个是 int,第二个是 float 类型。该函数没有返回值;

例如

void f1(int a, float b)
{ //do something with these numbers
};

顺便说一句,最后一个复杂的声明并不常见。这是我为此目的而编造的一个例子。

int **(*f)(int**,int**(*)(int **,int **));

typedef int**(*fptr)(int **,int **);

int** f0(int **a0, int **a1)
{
    printf("Complicated declarations and meaningless example!\n");
    return a0;
}

int ** f1(int ** a2, fptr afptr)
{
    return afptr(a2, 0);
}

int main()
{
    int a3 = 5;
    int * pa3 = &a3;
    f = f1;
    f(&pa3, f0);

    return 0;
}
于 2009-09-19T16:21:58.707 回答
5

看来您的实际问题是这样的:

指向指针的指针的用例是什么?

你有一个类型为 T 的数组时,往往会出现指向指针的指针,而 T 本身是指向其他东西的指针。例如,

  • C中的字符串是什么?通常,它是一个char *.
  • 你想要一个字符串数组不时?当然。
  • 你会如何申报? char *x[10]:x是一个包含 10 个指针的数组char,也就是 10 个字符串。

在这一点上,您可能想知道char **从哪里来。它从 C 中指针算术和数组之间非常密切的关系进入图片。数组名称x(几乎)总是转换为指向它的第一个元素的指针。

  • 第一个元素是什么?一个char *
  • 指向第一个元素的指针是什么?一个char **

在 C 中,数组E1[E2]被定义为等价于*(E1 + E2). 通常,E1是数组名称,比如说x,它会自动转换为 a char **,并且E2是某个索引,比如说 3。(这个规则也解释了为什么3[x]x[3]是同一件事。)

您想要动态分配的某种类型的数组(T它本身就是一个指针)时,也会出现指向指针的指针。首先,让我们假设我们不知道 T 是什么类型。

  • 如果我们想要一个动态分配的 T 向量,我们需要什么类型?T *vec.
  • 为什么?因为我们可以在 C 中执行指针运算,所以 anyT *可以作为T内存中连续序列的基础。
  • 我们如何分配这个向量,比如说n元素? vec = malloc(n * sizeof(T));

这个故事绝对适用于任何类型T,因此它适用于char *.

  • vecif Tis的类型是char *什么? char **vec.

当你有一个函数需要修改类型为 T 的参数时,指针的指针也会出现,它本身就是一个指针。

  • 查看 : 的strtol声明long strtol(char *s, char **endp, int b)
  • 这是怎么回事?strtol将字符串从基数转换b为整数。它想告诉你它在字符串中的距离。它可能会返回一个包含 along和 a的结构char *,但这不是它的声明方式。
  • 相反,它通过传入它在返回之前修改的字符串 的地址来返回它的第二个结果。
  • 又是什么字符串?哦,是的,char *
  • 那么什么是字符串的地址呢? char **.

如果你在这条路上徘徊足够长的时间,你也可能遇到T ***类型,尽管你几乎总是可以重组代码来避免它们。

最后,指向指针的指针出现在某些棘手的链表实现中。考虑 C 中双向链表的标准声明。

struct node {
    struct node *next;
    struct node *prev;
    /* ... */
} *head;

这工作正常,虽然我不会在这里重现插入/删除功能,但它有一个小问题。任何节点都可以从列表中删除(或在其之前插入一个新节点),而无需引用列表的头部。好吧,不是任何节点。这不适用于列表的第一个元素,其中prev将为空。在某些类型的 C 代码中,这可能会有点烦人,在这些代码中,您更多地使用节点本身而不是列表作为概念。这在低级系统代码中相当常见。

如果我们这样重写会node怎样:

struct node {
    struct node *next;
    struct node **prevp;
    /* ... */
} *head;

在每个节点中,prevp指向的不是前一个节点,而是前一个节点的next指针。第一个节点呢?它的prevp点在head。如果你画出这样的列表(你必须画出来才能理解它是如何工作的),你会看到你可以删除第一个元素或在第一个元素之前插入一个新节点,而无需head通过名称显式引用。

于 2010-04-19T01:32:04.620 回答
2

x:函数返回指向数组 [] 的指针的函数返回 char 的指针” - 嗯?

你有一个功能

该函数返回一个指针。

该指针指向一个数组。

该数组是一个函数指针数组(或指向函数的指针)

这些函数返回 char*。

 what's the use case for a pointer to a pointer?

一种是通过参数促进返回值。

假设你有

int function(int *p)
  *p = 123;
   return 0; //success !
}

你这样称呼它

int x;
function(&x);

正如你所看到的,为了function能够修改我们的x我们必须传递一个指向我们的 x 的指针。

如果x不是 int 而是 achar *怎么办?好吧,它仍然是一样的,我们必须传递一个指向它的指针。指向指针的指针:

int function(char **p)
  *p = "Hello";
   return 0; //success !
}

你这样称呼它

char *x;
function(&x); 
于 2010-04-18T18:27:21.850 回答
2

Remo.D's answer for reading functions 是一个很好的建议。以下是其他人的一些答案。

指向指针的指针的一个用例是当您希望将其传递给将修改指针的函数时。例如:

void foo(char **str, int len)
{
   *str = malloc(len);
}

此外,这可能是一个字符串数组:

void bar(char **strarray, int num)
{
   int i;
   for (i = 0; i < num; i++)
     printf("%s\n", strarray[i]);
}

通常,不应使用如此复杂的声明,尽管有时您确实需要对于函数指针等非常复杂的类型。在这些情况下,将 typedef 用于中间类型的可读性更高;例如:

typedef void foofun(char**, int);
foofun *foofunptr;

或者,对于“函数返回指向数组 [] 的指向函数返回字符的指针的指针”的第一个示例,您可以这样做:

typedef char fun_returning_char();
typedef fun_returning_char *ptr_to_fun;
typedef ptr_to_fun array_of_ptrs_to_fun[];
typedef array_of_ptrs_to_fun *ptr_to_array;
ptr_to_array myfun() { /*...*/ }

在实践中,如果你写的是理智的东西,其中许多东西都会有自己有意义的名字。例如,这些可能是返回名称(某种)的函数,所以fun_returning_char可能是name_generator_type,也array_of_ptrs_to_fun可能是name_generator_list。所以你可以把它折叠几行,然后只定义这两个 typedef——在任何情况下它们都可能在其他地方有用。

于 2010-04-18T18:19:55.740 回答
1

我们必须从左到右评估所有指针声明语句,从在语句中声明指针名称或声明名称的位置开始。

在评估声明时,我们必须从最里面的括号开始。

从指针名称或函数名称开始,然后是括号中最右边的字符,然后是最左边的字符。

例子:

char (*(*f())[])();
         ^

char (*(*f())[])();
         ^^^
In here f is a function name, so we have to start from that.

F()

char (*(*f())[])();
            ^
Here there are no declarations on the righthand side of the current
parenthesis, we do have to move to the lefthand side and take *:

char (*(*f())[])();
      ^
f() *

我们已经完成了内括号字符,现在我们要回到这后面的一层:

char (*(*f())[])();

   ------

现在取 [],因为它在当前括号的右侧。

char (*(*f())[])();
             ^^

f() * []

现在取 *,因为右侧没有字符。

char (*(*f())[])();
               ^

char (*(*f())[])();
      ^
f() * [] *

char (*(*f())[])();

接下来评估外部开闭括号,它表示一个函数。

f() * [] * ()

char (*(*f())[])();

现在我们可以在语句末尾添加数据类型。

f() * [] * () char.

char (*(*f())[])();

最终答案:

    f() * [] * () char.

f 是一个函数,它返回指向返回 char 的函数的指针数组 [] 的指针。

于 2015-04-24T05:06:58.747 回答
1
char far *far *ptr;

这是一种过时的 Microsoft 形式,可以追溯到 MS-DOS 和早期的 Windows 时代。SHORT 版本是一个指向 char 的远指针的远指针,远指针可以指向内存中的任何位置,而近指针只能指向 64K 数据段中的任何位置。您真的不想知道有关 Microsoft 内存模型的详细信息,以解决完全脑死亡的英特尔 80x86 分段内存架构。

typedef void (*pfun)(int,float);

这将 pfun 声明为指向采用 int 和 float 的过程的指针的 typedef。您通常会在函数声明或原型中使用它,即。

float foo_meister(pfun rabbitfun)
{
  rabbitfun(69, 2.47);
}
于 2009-09-19T16:24:02.097 回答
0

忘记 1 和 2 - 这只是理论上的。

3:这个用于程序入口功能int main(int argc, char** argv)。您可以使用char**. argv[0] = 第一个字符串,argv[1] = 第二个字符串,...

于 2010-04-18T18:14:05.360 回答
0

将指针作为参数传递给函数可以让该函数更改指向的变量的内容,这对于通过函数返回值以外的方式返回信息很有用。例如,返回值可能已经用于指示错误/成功,或者您可能希望返回多个值。调用代码中的语法是 foo(&var),它获取 var 的地址,即指向 var 的指针。

因此,如果您希望函数更改其内容的变量本身是一个指针(例如,一个字符串),则该参数将被声明为指向指针的指针

#include <stdio.h>

char *some_defined_string = "Hello, " ; 
char *alloc_string() { return "World" ; } //pretend that it's dynamically allocated

int point_me_to_the_strings(char **str1, char **str2, char **str3)
{
    *str1 = some_defined_string ;
    *str2 = alloc_string() ;
    *str3 = "!!" ;

    if (str2 != 0) {
        return 0 ; //successful
    } else {
        return -1 ; //error
    }
}

main()
{
    char *s1 ; //uninitialized
    char *s2 ;
    char *s3 ;

    int success = point_me_to_the_strings(&s1, &s2, &s3) ;

    printf("%s%s%s", s1, s2, s3) ;
}

请注意,main() 不会为字符串分配任何存储空间,因此 point_me_to_the_strings() 不会像将它们作为指向字符的指针传递那样写入 str1、str2 和 str3。相反,point_me_to_the_strings() 改变了指针本身,使它们指向不同的地方,它可以这样做,因为它有指向它们的指针。

于 2010-04-19T00:37:53.207 回答