我如何理解复杂的函数声明?

Pal*_*Dot 32 c pointers declaration

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

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 **));
Run Code Online (Sandbox Code Playgroud)

pmg*_*pmg 73

正如其他人所指出的那样,cdecl是这项工作的正确工具.

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

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

char (*(*X[3])())[5];
         ^
Run Code Online (Sandbox Code Playgroud)

X是

X[3]
 ^^^
Run Code Online (Sandbox Code Playgroud)

X是 3的数组

(*X[3])
 ^                /* the parenthesis group the sub-expression */
Run Code Online (Sandbox Code Playgroud)

X是一个3 指针的数组

(*X[3])()
       ^^
Run Code Online (Sandbox Code Playgroud)

X是一个3个指针的数组,用于 接受未指定(但固定)数量的参数

(*(*X[3])())
 ^                   /* more grouping parenthesis */
Run Code Online (Sandbox Code Playgroud)

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

(*(*X[3])())[5]
            ^^^
Run Code Online (Sandbox Code Playgroud)

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

char (*(*X[3])())[5];
^^^^                ^
Run Code Online (Sandbox Code Playgroud)

X是一个3个指针的数组,用于接受未指定(但固定)数量的参数并返回指向5个 char 数组的指针.

  • 我希望我能两次投票给你答案. (4认同)

IVl*_*lad 16

由内而外,类似于你将如何求解方程如读出来{3+5*[2+3*(x+6*2)]}=0-你会通过求解里面有什么开始(),然后[],最后{}:

char (*(*x())[])()
         ^
Run Code Online (Sandbox Code Playgroud)

这意味着x什么.

char (*(*x())[])()
          ^^
Run Code Online (Sandbox Code Playgroud)

x是一个功能.

char (*(*x())[])()
        ^
Run Code Online (Sandbox Code Playgroud)

x返回指向某事物指针.

char (*(*x())[])()
       ^    ^^^
Run Code Online (Sandbox Code Playgroud)

x返回指向数组指针.

char (*(*x())[])()
      ^
Run Code Online (Sandbox Code Playgroud)

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

char (*(*x())[])()
     ^         ^^^
Run Code Online (Sandbox Code Playgroud)

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

char (*(*x())[])()
^^^^
Run Code Online (Sandbox Code Playgroud)

意味着由x点返回的数组指针指向一个指向返回char的函数的函数指针数组.

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

如果这仍然让您感到困惑(并且可能应该这样),请尝试在一张纸上或您喜欢的文本编辑器中执行相同的操作.通过观察它无法知道它意味着什么.


unw*_*ind 13

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

cdecl> explain char (*(*f())[])();
declare f as function returning pointer to array of pointer to function returning char
Run Code Online (Sandbox Code Playgroud)

我四处寻找该工具的官方主页,但找不到一个看似真实的主页.在Linux中,您通常可以期望您选择的分发包含该工具,因此我只是安装它以生成上述示例.


Man*_*yNS 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个char数组的指针.

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

例如

void f1(int a, float b)
{ //do something with these numbers
};
Run Code Online (Sandbox Code Playgroud)

顺便说一句,复杂的声明作为最后一个经常看不到.这是我为此目的而做的一个例子.

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;
}
Run Code Online (Sandbox Code Playgroud)


Dal*_*und 5

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

指向指针的指针有什么用途?

当您有某种类型 T 的数组时,往往会出现指向指针的指针,而 T 本身是指向其他内容的指针。例如,

  • C语言中的字符串是什么?通常,它是一个char *.
  • 您时不时想要一个字符串数组吗?当然。
  • 你会如何宣布一个? char *x[10]:x是一个由 10 个指针组成的数组char,也称为 10 个字符串。

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

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

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

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

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

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

  • vecif的类型是T什么char *char **vec

当您有一个函数需要修改 T 类型的参数(本身是一个指针)时,指向指针的指针也会出现。

  • 查看声明strtollong 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;
Run Code Online (Sandbox Code Playgroud)

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

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

struct node {
    struct node *next;
    struct node **prevp;
    /* ... */
} *head;
Run Code Online (Sandbox Code Playgroud)

在每个节点中,prevp不是指向前一个节点,而是指向前一个节点的next指针。那么第一个节点呢?其prevp点为head. 如果您绘制出这样的列表(并且您必须绘制出它才能理解其工作原理),您将看到您可以删除第一个元素或在第一个元素之前插入一个新节点,而无需按head名称显式引用。