如何理解复杂的函数声明?(How do I understand complicated funct

2019-07-18 14:34发布

如何理解下面复杂的声明?

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 **));

Answer 1:

正如其他人所指出的那样,CDECL是这份工作的合适工具。

如果您想了解那种声明,而不从CDECL的帮助下,尝试从一些内幕,并从右至左读

以从列表中选择一个随机例如char (*(*X[3])())[5];
开始在X,这是所声明/定义的标识符(和最内层标识符):

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

X是

X[3]
 ^^^

3的阵列

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

指针 ,以

(*X[3])()
       ^^

接受的参数未指定的(但固定的)数

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

和返回指针

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

到5的阵列

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

字符 。



Answer 2:

由内而外,类似于你将如何求解方程如读出来{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点的函数数组的指针即指向返回一个字符的功能。

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

如果这仍扑朔迷离你(它可能应该),尝试做在一张纸上或你喜欢的文本编辑器同样的事情。 有没有知道什么它只是看它的意思的方式。



Answer 3:

听起来像是为CDECL工具作业:

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

我环顾四周,该工具的官方网站,但无法找到一个看起来很真诚。 在Linux中,你通常可以期望你的选择,包括工具的分布,所以我只是为了产生上述样品安装它。



Answer 4:

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

例如,对于这个功能,它会回报你:

char (*(*f())[])(); - 声明˚F作为函数返回指针指向函数返回炭的阵列

void (*f)(int,void (*)()); - 函数指针的F原型。 f是一个函数,它有两个参数,第一个为int,而第二个是用于返回空隙的功能的功能的指针。

char far *far *ptr; - PTR是远指针到远指针(其指向一些字符/字节)。

char (*(*X[3])())[5]; - X是3个指针的数组起作用接受的参数的数目undeterminate和返回指针到5字符数组。

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

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;
}


Answer 5:

看来,你的实际问题是这样的:

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

指针的指针倾向于显示,当你有一些类型T的数组,而T本身是一个指向别的东西。 例如,

  • 什么是在C字符串? 通常情况下,这是一个char *
  • 你想一个字符串数组不时? 当然。
  • 你会如何申报呢? char *x[10] x是10个指针的数组,以char ,又名10串。

在这一点上,你可能想知道char **用武之地它进入图片从指针算术和阵列之间C.数组名称,非常密切的关系x是(几乎)总是被转换为一个指向它的第一元件。

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

在C中,阵列E1[E2]被定义为等同于*(E1 + E2) 通常情况下, E1是数组名,让我们说x ,它会自动转换为char ** ,和E2是一些指标,比如说3(这条规则也解释了为什么3[x]x[3]是一样的。 )

指针的指针也显示,当你需要一些类型的动态分配的数组T ,这本身就是一个指针 。 首先,让我们假装我们不知道是什么类型T。

  • 如果我们要T的动态分配的载体,我们需要什么类型的? T *vec
  • 为什么? 因为我们可以在C执行指针算术,任何T *可以作为连续序列的碱基T的在存储器中。
  • 我们如何分配此向量,说的n元素? vec = malloc(n * sizeof(T)) ;

这个故事是真实的绝对任何类型的T ,所以它是真正char *

  • 什么类型vec如果Tchar *char **vec

指针的指针也显示,当你有需要修改类型T,本身就是一个指针参数的函数

  • 看看声明strtollong strtol(char *s, char **endp, int b)
  • 这是什么一回事呢? strtol从基部的字符串转换b为整数。 它想告诉你多远它得到的字符串。 这或许可以返回同时含有一个struct longchar * ,但是这不是多么声明。
  • 相反,它返回它通过传递在它返回之前修改字符串的地址,第二个结果。
  • 什么又是一个字符串? 噢, 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的名字。



Answer 6:

X:函数返回指针数组指针的[]将函数返回字符” - 哈?

你有一个函数

该函数返回一个指针。

该指针指向数组。

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

这些函数返回的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不是一个整数,而是一个char * ? 那么,它仍然是相同的,我们有一个指针传递给。 的指针的指针:

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

你这样称呼它

char *x;
function(&x); 


Answer 7:

Remo.D的答案阅读功能,是一个很好的建议。 这里有一些答案给了别人。

一个用例为指针的指针是当你想将它传递给将修改指针的函数。 例如:

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 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_typearray_of_ptrs_to_fun可能是name_generator_list 。 所以,你可以折叠它几行,并只定义这两个类型定义 - 这很可能会是有用的,任何情况下,在其他地方。



Answer 8:

char far *far *ptr;

这是一个过时的微软形式,可以追溯到MS-DOS和非常早期的Windows天。 简短的版本是,这是一个远指针远指针为char,其中远指针可以在内存中指向任何地方,而不是近指针,它只能在64K数据段指向任何地方。 你真的不希望了解微软内存模型细节周围的完全脑死亡英特尔80x86的工作分段存储器架构。

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

这声明pfun作为一个指向接受一个int和浮子的过程的类型定义。 你通常会在函数声明或原型,即使用。

float foo_meister(pfun rabbitfun)
{
  rabbitfun(69, 2.47);
}


Answer 9:

我们必须给所有指针声明语句评估从左至右,从那里指针名或声明的名字在声明中宣布开始。

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

先从指针名字或函数的名称,然后在parenthersis最右边的字符,然后由最左边的字符follwed。

例:

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是返回指针阵列的指针[]起作用返回炭的功能。



Answer 10:

忘掉1和2 - 这仅仅是理论上的。

3:这是在程序入口函数使用int main(int argc, char** argv) 。 您可以通过使用访问字符串列表char** 。 的argv [0] =第一串,的argv [1] =第二串,...



Answer 11:

指针传递作为参数的函数让该函数改变变量的内容指出,它可以是用于通过比函数的返回值其他方式返回信息是有用的。 例如,可能已被使用的返回值来指示错误/成功,或者你可能想返回多个值。 在调用代码本的语法是FOO(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()改变了自己的指针,使它们指向不同的地方,它可以做到这一点,因为它有指向他们。



文章来源: How do I understand complicated function declarations?