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の助けを借りずにその種の宣言を理解したい場合は、右から左に裏返しに読んでみてください

リストからランダムな例を1つ取り上げます 。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は、指定されていない(ただし固定された)数の引数を受け入れ、 5の配列へのポインターを返す関数への3つのポインターの配列です。

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

Xは、指定されていない(ただし固定された)数の引数を受け入れ、5文字の配列へのポインターを返す関数への3つのポインターの配列です

于 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())[])()
^^^^

によって返される配列ポインタが、 charxを返す関数を指す関数ポインタの配列を指すことを意味します。

しかし、ええ、cdeclを使用してください。私は自分で答えを確認するためにそれを使用しました:)。

それでも混乱する場合は(おそらくそうする必要があります)、同じことを1枚の紙またはお気に入りのテキストエディタで実行してみてください。それを見ただけでは、それが何を意味するのかを知る方法はありません。

于 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は2つのパラメーターを受け取る関数で、最初のパラメーターはintで、2番目のパラメーターはvoidを返す関数の関数ポインターです。

char far *far *ptr;--ptrは、farポインタ(文字/バイトを指す)へのfarポインタです。

char (*(*X[3])())[5];-Xは、不確定な数の引数を受け入れ、5文字の配列へのポインターを返す関数への3つのポインターの配列です。

typedef void (*pfun)(int,float);-関数ポインタpfunを宣言します。pfunは、2つのパラメーターを受け取る関数です。最初のパラメーターはintで、2番目のパラメーターは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自体が他の何かへのポインターである場合、ポインターへのポインターが表示される傾向があります。例えば、

  • Cの文字列とは何ですか?通常、これはchar *です。
  • 時々文字列の配列が必要ですか?もちろん。
  • どのように宣言しますか? char *x[10]:は、、別名10文字列xへの10個のポインタの配列です。char

この時点で、どこchar **に入るのか疑問に思われるかもしれません。これは、Cのポインタ演算と配列の非常に密接な関係から画像に入ります。配列名xは(ほとんど)常に最初の要素へのポインタに変換されます。

  • 最初の要素は何ですか?A。char *_
  • 最初の要素へのポインタは何ですか?A。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 *

  • vecifのタイプは何Tですかchar *char **vec

ポインターへのポインターは、T型の引数、それ自体がポインターを変更する必要がある関数がある場合にも表示されます。

  • の宣言を見てくださいstrtollong strtol(char *s, char **endp, int b)
  • これはどういうことですか?文字列をベースから整数にstrtol変換します。b文字列のどこまで進んだかを伝えたいのです。longおそらくaとaの両方を含む構造体を返す可能性がchar *ありますが、それは宣言された方法ではありません。
  • 代わりに、返す前に変更した文字列 のアドレスを渡すことにより、2番目の結果を返します。
  • もう一度文字列は何ですか?そうそう、char *
  • では、文字列のアドレスは何ですか? char **

このパスを十分に長く歩き回ると、T ***型に遭遇する可能性もありますが、ほとんどの場合、コードを再構築してそれらを回避することができます。

最後に、ポインタへのポインタは、リンクリストの特定のトリッキーな実装に表示されます。Cの二重リンクリストの標準宣言について考えてみます。

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

ここでは挿入/削除機能を再現しませんが、これは問題なく機能しますが、少し問題があります。リストの先頭を参照せずに、任意のノードをリストから削除できます(またはその前に新しいノードを挿入できます)。まあ、まったくノードではありません。これはリストの最初の要素には当てはまりません。ここprevでnullになります。これは、概念としてのリストよりもノード自体をより多く操作するある種のCコードでは適度に煩わしい場合があります。これは、低レベルのシステムコードでかなり一般的に発生します。

nodeこのように書き直すとどうなりますか?

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

各ノードprevpで、前のノードではなく、前のノードのnextポインタを指します。最初のノードはどうですか?でprevpポイントheadです。このようなリストを描画する場合(そして、これがどのように機能するかを理解するために描画する必要あります)、名前で明示的に参照headしなくても、最初の要素を削除するか、最初の要素の前に新しいノードを挿入できることがわかります。

于 2010-04-19T01:32:04.620 に答える
2

関数を読み取るためのRemo.Dの答えは良い提案です。ここに他の人へのいくつかの答えがあります。

ポインターへのポインターのユースケースの1つは、ポインターを変更する関数にポインターを渡したい場合です。例えば:

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;

または、「関数がcharを返す関数へのポインタのarray []へのポインタを返す関数」の最初の例では、次のようにします。

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。したがって、数行折りたたんで、これら2つのtypedefのみを定義することができます。これらはおそらく他の場所で役立つでしょう。

于 2010-04-18T18:19:55.740 に答える
2

x:関数がcharを返す関数へのポインタのarray[]へのポインタを返す関数"-ハァッ?

あなたには機能があります

その関数はポインタを返します。

そのポインタは配列を指しています。

その配列は、関数ポインター(または関数へのポインター)の配列です。

これらの関数はchar*を返します。

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

1つは、引数を介して戻り値を容易にすることです。

あなたが持っているとしましょう

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

あなたはそれを次のように呼びます

int x;
function(&x);

ご覧のとおり、function変更できるようにするxには、xへのポインターを渡す必要があります。

intではxなく、?char *まあ、それでも同じです、私たちはそれにポインタを渡さなければなりません。ポインタへのポインタ:

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

あなたはそれを次のように呼びます

char *x;
function(&x); 
于 2010-04-18T18:27:21.850 に答える
1
char far *far *ptr;

これは廃止されたMicrosoftフォームであり、MS-DOSおよび非常に初期のWindows時代にまでさかのぼります。SHORTバージョンでは、これはcharへのfarポインターへのfarポインターであり、64Kデータセグメント内の任意の場所のみを指すことができるnearポインターとは対照的に、farポインターはメモリ内の任意の場所を指すことができます。完全に頭がおかしいIntel80x86セグメント化メモリアーキテクチャを回避するためのMicrosoftメモリモデルの詳細を知りたくありません。

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

これは、intとfloatをとるプロシージャへのポインタのtypedefとしてpfunを宣言します。通常、これは関数宣言またはプロトタイプで使用します。

float foo_meister(pfun rabbitfun)
{
  rabbitfun(69, 2.47);
}
于 2009-09-19T16:24:02.097 に答える
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() *

内側の括弧文字を完成させたので、この後ろの1つのレベルに戻る必要があります。

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

   ------

これは現在の括弧の右側にあるため、[]を取ります。

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

f() * []

右側に文字がないので、*を取ります。

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

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

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

次に、外側の開き括弧と閉じ括弧を評価します。これは関数を示しています。

f() * [] * ()

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

これで、ステートメントの最後にデータ型を追加できます。

f() * [] * () char.

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

最終的な答え:

    f() * [] * () char.

fは、charを返す関数へのポインタのarray[]へのポインタを返す関数です。

于 2015-04-24T05:06:58.747 に答える
0

1と2については忘れてください-これは単なる理論上のものです。

3:これはプログラム入力機能で使用されますint main(int argc, char** argv)。を使用して文字列のリストにアクセスできますchar**。argv [0] =最初の文字列、argv [1] = 2番目の文字列、..。

于 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()は、charsへのポインタとして渡された場合のように、str1、str2、およびstr3に書き込みを行わないことに注意してください。むしろ、point_me_to_the_strings()はポインター自体を変更して、ポインターを別の場所にポイントさせます。これは、ポインターへのポインターがあるため、これを行うことができます。

于 2010-04-19T00:37:53.207 に答える