web-dev-qa-db-ja.com

Cで定数関数ポインタの配列をどのように宣言しますか?

次のような関数へのポインタの配列を宣言する必要があります。

extern void function1(void);
extern void function2(void);
...

void (*MESSAGE_HANDLERS[])(void) = {
   function1,
   function2,
   ...
};

ただし、配列を定数として宣言する必要があります。配列内のデータとデータへのポインターの両方です。残念ながら、constキーを配置する場所(単語)を思い出せません。

実際のポインタ(この場合はMESSAGE_HANDLERS)は、配列として宣言されているため、すでに定数であると想定しています。一方、配列内の関数ポインターは、示されているように宣言されている場合、実行時に変更できませんでしたか?

23
Judge Maygarden

そのようなタイプを構築する方法を覚えておくテクニックがあります。まず、ポインタの名前から始めて、右から左に読んでみてください。

助けなしでそのようなものを宣言する方法は?

配列

T t[5];

5Tの配列です。 Tを関数型にするには、戻り値の型を左側に、パラメーターを右側に記述します。

void t[5](void);

would be voidを返し、パラメータをとらない5つの関数の配列。しかし、関数自体を配列に詰め込むことはできません!それらはオブジェクトではありません。それらへのポインタのみが可能です。

どうですか

void * t[5](void);

戻り値の型をvoidへのポインタに変更するだけなので、それはまだ間違っています。括弧を使用する必要があります。

void (*t[5])(void);

これは実際に機能します。 tは、voidを返し、パラメーターを受け取らない関数への5つのポインターの配列です

すごい!アラスへのポインタの配列はどうですか?それは非常に似ています。要素タイプが左側に表示され、寸法が右側に表示されます。繰り返しますが、括弧が必要なのは、そうしないと、配列が整数ポインターの多次元配列になるためです。

int (*t[5])[3];

それでおしまい! intの配列への5つのポインタの配列

関数はどうですか?

私たちが今学んだことは、関数についても真実です。パラメータをとらずにvoidを返す別の関数へのポインタを返すintをとる関数を宣言しましょう。

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

上記と同じ理由で、もう一度括弧が必要です。これで、それを呼び出して、返された関数を再度呼び出すことができます。

f(10)();

関数へのポインタを返す関数への別のポインタを返す

これはどうですか?

f(10)(true)(3.4);

?言い換えると、intを取り、boolを取り、boolを取り、doubleを取り、voidを返す関数へのポインタを返す関数はどのようになりますか?答えは、それらをネストするだけです。

void (*(*f(int))(bool))(double);

あなたはそうすることができます。実際、関数へのポインタと同じように、配列へのポインタを返すこともできます。

int (*(*f(int))(bool))[3];

これはintをとる関数が3intの配列へのポインタを返すboolをとる関数です。

Constとは何の関係がありますか?

上記で基本型からより複雑な型を構築する方法を説明したので、それらがどこに属しているかがわかっている場所にconstを配置できます。考えてみてください:

T c * c * c ... * c name;

Tは、最後に指す基本的なタイプです。 cは、constまたはnotconstのいずれかを表します。例えば

int const * const * name;

名前の型を宣言します定数intへの定数ポインタへのポインタnameは変更できますが、*nameは変更できません。

int const * const

どちらも**nameではありません。

int const

これを上記の関数ポインタに適用してみましょう。

void (* const t[5])(void);

これは実際には、定数ポインタを含むように配列を宣言します。したがって、配列を作成(および初期化)した後、pointersはconstになります。これは、constがスターの後に表示されるためです。この場合、星の前にconstを置くことはできないことに注意してください。これは、定数関数へのポインタがないであるためです。関数は、意味がないため、単純にconstにすることはできません。したがって、以下は無効です。

void (const * t[5])(void);

結論

関数と配列を宣言するC++とCの方法は、実際には少し混乱します。最初に頭を悩ませる必要がありますが、それを理解していれば、それを使用して非常にコンパクトな関数宣言を書くことができます。

このような状況では、typedefを実行して関数のシグネチャに名前を付けます。これにより、はるかに簡単になります。

typedef void MESSAGE_HANDLER(void);

それが整っていると、それはちょうどあるはずです:

MESSAGE_HANDLER * const handlers[] = { function1, function2 };

配列定数の実際の内容を取得します。

[〜#〜] edit [〜#〜]typedefからポインタ部分を削除しました。これは本当に優れています(ライブで学習)。

15
unwind

cdeclのコメント:

cdecl> explain void (* const foo[])(void)
declare foo as array of const pointer to function (void) returning void

それはあなたが必要なものですか?

15
qrdl

Visual Studio 2008を使用すると、次のことがわかります。

void (* const MESSAGE_HANDLERS[])(void) = {
   NULL,
   NULL
};

int main ()
{
    /* Gives error 
        '=' : left operand must be l-value
    */
    MESSAGE_HANDLERS = NULL;

    /* Gives error 
        l-value specifies const object
    */
    MESSAGE_HANDLERS[0] = NULL;
}
1
David Norman

これが「C」で機能するかどうかはわかりません。 'C++'で動作します:

  • まず、MESSAGE_HANDLERSをタイプとして定義します。

    typedef void (*MESSAGE_HANDLER)();

  • 次に、型定義を使用して、配列を定数として宣言します。

    MESSAGE_HANDLER const handlers[] = {function1, function2};

秘訣はtypedefにあります。「C」で同じ意味論を実行できる場合は、それも機能するはずです。

1
Curro