6

次の宣言があります。

void qsort(void *lineptr[], int left, int right, int (*comp)(void *, void *));
int numcmp(char *, char *);
int strcmp(char *s, char *t);

次に、プログラムのどこかに次の呼び出しがあります。

  qsort((void**) lineptr, 0, nlines-1, 
                    (int (*)(void*,void*))(numeric ? numcmp : strcmp));

(最初の 3 つの引数と は無視してくださいnumeric)。

これは何ですか?

(int (*)(void*,void*))(numeric ? numcmp : strcmp)

4番目の引数として「2つのポインターを取得して返すqsort関数へのポインター」を期待していることは理解していますが、上記の内容はどのようにそれを満たしていますか? 2 つの括弧で構成されているため、ある種のキャストのように思えますが、それは非常に奇妙なキャストです。関数を取り、この関数を「2つのポインターを取得して返す関数へのポインター」にするためです。これは無意味です。(ここでは、変数の前の括弧内の型が変数をその型に昇格させるという 規則に従いました)。voidintvoidint
type

だから私はそれを間違っていると思う. 多分誰かがこれを読む方法を教えてくれる. 順序は何ですか?

4

9 に答える 9

5

あなたはここでトリックを逃しました - その部分

(numeric ? numcmp : strcmp)

三項演算子を使用して、qsort 内で呼び出される関数を選択しています。データが数値の場合は、numcmp を使用します。そうでない場合は、strcmp を使用します。より読みやすい実装は次のようになります。

int (*comparison_function)(void*,void*) = 
    (int (*)(void*,void*))(numeric ? numcmp : strcmp);
qsort((void**) lineptr, 0, nlines-1, comparison_function);
于 2009-04-16T15:09:10.810 に答える
5

ここで起こっていることは、まさにキャストです。3 進数を一瞬無視して、numcmp が常に使用されるふりをしましょう。この質問の目的のために、関数はCで関数ポインターとして機能できます。したがって、数値の型を見ると、実際には

(int (*)(int*,int*))

これを qsort で適切に使用するには、void パラメータが必要です。ここでの型は、パラメーターと戻り値の型に関してすべて同じサイズであるため、他の型に置き換えることができます。必要なのは、コンパイラを満足させるためのキャストだけです。

(int (*)(void*,void*))(numcmp )
于 2009-04-16T15:09:21.497 に答える
5

他の人が指摘しているように、

(int (*)(void*,void*))(numeric ? numcmp : strcmp)

次に、以下は型キャストです

(int (*)(void*,void*))

そして式は

(numeric ? numcmp : strcmp)

C の宣言は非常に読みにくい場合がありますが、学習することは可能です。その方法は、内側から始めて、右に 1 歩進み、次に左に 1 歩進み、右、左、右、左、というように外に出て、終了するまで続けます。括弧内のすべてが評価される前に、括弧の外側を横切らないでください。たとえば、上記の型キャストの場合、(*)これがポインターであることを示します。括弧の中にあるのはポインタだけだったので、その外側の右側に評価します。(void*,void*)は、2 つのポインター引数を持つ関数へのポインターであることを示します。最後intに、関数の戻り値の型を示します。外側の括弧は、これを型キャストにします。更新: 2 つの詳細な記事: The Clockwise/Spiral RuleReading C Declarations: A Guide for the Mystified.

ただし、良いニュースは、上記は知っておくと非常に便利ですが、チートする非常に簡単な方法があるということです。cdeclプログラムは、C から英語の記述に、またはその逆に変換できます。

cdecl> explain (int (*)(void*,void*))
cast unknown_name into pointer to function (pointer to void, pointer to void) returning int
cdecl> declare my_var as array 5 of pointer to int
int *my_var[5]
cdecl>

演習: はどのような変数iですか?

int *(*(*i)[])(int *)

マシンに cdecl がインストールされていない場合は、 rot13で回答してください (ただし、インストールする必要があります)。

pqrpy> rkcynva vag *(*(*v)[])(vag *)
qrpyner v nf cbvagre gb neenl bs cbvagre gb shapgvba (cbvagre gb vag) ergheavat cbvagre gb vag
pqrpy>
于 2009-04-16T16:54:54.303 に答える
4

関数ポインタをキャストせずにそれを行うことができます。方法は次のとおりです。私の経験では、ほとんどの場所で、キャストを使用している場合、それは間違っています。

于 2009-04-16T15:17:13.947 に答える
3

の標準定義には次のものが含まれていることに注意してくださいqsort()const

void qsort(void *base, size_t nmemb, size_t size,
           int (*compar)(const void *, const void *));

文字列コンパレータには、' char **' 値ではなく、 2 つの ' ' 値が与えられることに注意してくださいchar *

呼び出しコードでキャストが不要になるように、コンパレーターを作成します。

#include <stdlib.h>    /* qsort() */
#include <string.h>    /* strcmp() */

int num_cmp(const void *v1, const void *v2)
{
    int i1 = *(const int *)v1;
    int i2 = *(const int *)v2;
    if (i1 < i2)
        return -1;
    else if (i1 > i2)
        return +1;
    else
        return 0;
}

int str_cmp(const void *v1, const void *v2)
{
    const char *s1 = *(const char **)v1;
    const char *s2 = *(const char **)v2;
    return(strcmp(s1, s2));
}

関数を使用してコードにキャストを書くように強制するのは見苦しいです。しないでください。

私が書いた 2 つの関数は、標準で必要な関数プロトタイプと一致しますqsort()。括弧が続かない関数の名前は、関数へのポインターと同等です。

古いコード、または古いコンパイラで育てられた人によって書かれたコードでは、関数へのポインタが次の表記法を使用して使用されていることがわかります。

result = (*pointer_to_function)(arg1, arg2, ...);

現代文では、次のように書かれています。

result = pointer_to_function(arg1, arg2, ...);

個人的には、明示的な逆参照の方が明確だと思いますが、誰もが同意するわけではありません。

于 2009-04-16T16:02:47.363 に答える
3

そのコード スニペットを書いた人は、巧妙になりすぎていました。頭の中で、彼はおそらく、巧妙な「ワンライナー」を作成することで、自分が優れたプログラマーになっていると考えています。実際には、彼が作成しているコードは読みにくく、長期にわたって作業するのは不快であり、Harper Shelby のコードに似た、より明白な形式に書き直す必要があります。

Brian Kernighan の格言を思い出してください。

デバッグは、最初にコードを書くよりも 2 倍大変です。したがって、コードをできるだけ賢く書いたとしても、定義上、それをデバッグするほど賢くはありません。


私は厳しいリアルタイムの締め切りで多くのパフォーマンスクリティカルなコーディングを行っています...そして、密集したワンライナーが適切な場所をまだ見ていません。

asm のコンパイルとチェックをいじって、ワンライナーの方がコンパイル済みの asm 実装が優れているかどうかを確認しましたが、ワンライナーに価値があるとは思いませんでした。

于 2009-04-16T16:56:49.137 に答える
1

私はおそらく次のように読むでしょう:

typedef int (*PFNCMP)(void *, void *);

PFNCMP comparison_function;

if (numeric)
{
    comparison_function =  numcmp;
}
else
{
    comparison_function = strcmp;
}

qsort((void**) lineptr, 0, nlines-1, comparison_function);

質問の例には、明示的なケースがあります。

于 2009-04-16T15:56:16.350 に答える
0

numcmpとは両方とも、2つをパラメーターとして受け取り、。を返すstrcmp関数へのポインターです。このルーチンは、2つをパラメーターとして受け取り、を返す関数へのポインターを想定しています。したがって、キャスト。ジェネリックポインタとして機能するため、これは安全です。さて、宣言を読んでみましょう:あなたの宣言を見てみましょう:char*intqsortvoid*intvoid* strcmp

 int strcmp(char *, char *);

コンパイラstrcmpは実際にそれをそのまま読み取ります。

 int (strcmp)(char *, char *)

char *2つの引数を取る関数(ほとんどの場合、関数へのポインターに減衰します) 。strcmpしたがって、ポインタのタイプは次のとおりです。

 int (*)(char *, char *)

したがって、互換性を持たせるために別の関数をキャストする必要がある場合は、キャスト先のstrcmpとして上記を使用します。

同様に、qsortのコンパレータ引数は2void *秒かかるため、奇数キャストになります。

于 2009-04-16T15:13:55.057 に答える
0

あなたの論理は正しいと思います。実際、メソッド シグネチャで必要な型である「2 つの void ポインターを取得して int を返す関数へのポインター」にキャストしています。

于 2009-04-16T15:07:59.517 に答える