11

私はCの初心者で、qsort関数に必要な比較関数を理解しようとしています。

パート1:構文

簡単な推奨される使用法は次のとおりです(結果を出力するためにいくつかのmain()コードも含めました):

#include <stdio.h>
#include <stdlib.h>

int values[] = { 40, 10, 100, 90, 20, 25, 12, 13, 10, 40 };

int compare(const void *a, const void *b)
{
    const int *ia = (const int *)a; // casting pointer types 
    const int *ib = (const int *)b;
    return *ia  - *ib; 
}

int main()
{
    int n;
    for (n=0; n<10; n++)
    {
        printf("%d ",values[n]);
    }
    printf("\n");
    qsort(values, 10, sizeof(int), compare);
    for (n=0; n<10; n++)
    {
        printf("%d ",values[n]);
    }
    printf("\n");
    system("pause");
    return 0;
}

比較関数に余分なものがすべて必要な理由がわからないので、次のように簡略化しました。

int compare (int *a, int *b)
    {
        return *a-*b; 
    }

これは引き続き機能し、同じ結果を生成します。誰かが私が削除したものと、なぜそれがまだ機能するのかを私に説明できますか?

パート2:なぜポインター?

さらに、本当にポインタを使用する必要がありますか?「a」と「b」を直接比較できないのはなぜですか(これは機能しません)。

int compare (int a, int b)
        {
            return a-b; 
        }

何らかの理由で、多次元配列を使用すると、ポインターを使用せずに回避することができ、何らかの理由で機能しました。何が起こっている?(各サブ配列の2番目の項目で多次元配列をソートするサンプルコード):

#include <stdio.h>
#include <stdlib.h>

int values[7][3] = { {40,55}, {10,52}, {100,8}, {90,90}, {20,91}, {25,24} };

int compare(int a[2], int b[2])
{
    return a[1] - b[1];
}

int main()
{
    int n;
    for (n=0; n<6; n++)
    {
        printf("%d,",values[n][0]);
        printf("%d ",values[n][1]);
    }
    printf("\n");
    qsort(values, 6, sizeof(int)*3, compare);
    for (n=0; n<6; n++)
    {
        printf("%d,",values[n][0]);
        printf("%d ",values[n][1]);
    }
    printf("\n");
    system("pause");
    return 0;
}

とにかくそれが私の最終目標であるため、多次元配列の並べ替えが機能していることを本当に嬉しく思いますが、どうやってそれを機能させることができたのかわかりません(運が悪いとコードを切り刻む以外)ので、説明が大好きです私が提供した例のいくつかが機能する理由と、機能しない理由について説明します。

4

4 に答える 4

12

これは引き続き機能し、同じ結果を生成します。誰かが私が削除したものと、なぜそれがまだ機能するのかを私に説明できますか?

Cで未定義動作を呼び出しています。C996.3.2.3ポインタ/8を参照してください。

あるタイプの関数へのポインターは、別のタイプの関数へのポインターに変換され、また元に戻される場合があります。結果は元のポインタと同じになります。変換されたポインターを使用して、指定された型と互換性のない型の関数を呼び出す場合、動作は未定義です。

C ++では、このプログラムは完全に形式が正しくありません:http: //ideone.com/9zRYSj

compare関数は1対のポインターを予期しているため、それでも「機能する可能性があります」。また、特定のプラットフォームsizeof(void*)では、と同じであるため、実際には型の関数へのポインタを含むsizeof(int*)型の関数ポインタを呼び出すことは、この特定の時点で特定のプラットフォームでキャストされるポインタ型と実質的に同じです。int(void *, void *)int(int *, int *)

さらに、本当にポインタを使用する必要がありますか?「a」と「b」を直接比較できないのはなぜですか(これは機能しません)。

qsort任意の2つのタイプの一般的な比較関数を使用するため。だけではありませんint。したがって、ポインタが逆参照されるタイプはわかりません。

何らかの理由で、多次元配列を使用すると、ポインターを使用せずに回避することができ、何らかの理由で機能しました。何が起こっている!

これは、次のプロトタイプが同じであるためです。

  1. int foo(int *a, int *b);
  2. int foo(int a[], int b[])

つまり、配列は関数に渡されるときにポインタに減衰します。行ったように、配列の長さを明示的に指定します。

int foo(int a[2], int b[2])

コンパイラが作成sizeofし、他のコンパイル時ビットがアイテムを2要素配列として扱うようにします。ただし、関数は、マシンレベルに到達したときに、ポインターのペアを受け入れます。

これらのいずれの場合でも、のペアをとらない比較関数を渡すとvoid *、未定義の動作が発生します。「未定義動作」の有効な結果の1つは、「動作しているように見える」です。もう1つの有効な結果は、「火曜日に動作する」または「ハードディスクをフォーマットする」です。この動作に依存しないでください。

于 2012-12-27T18:46:35.563 に答える
2

比較関数に余分なものがすべて必要な理由がわからないので、これに簡略化しました

const修飾子を使用するかどうかはあなた次第です。コンパレータの値は変更しないでください。しかし、を捨ててconst、コンパイラーとの約束を破ることは可能です。


qsortは、パラメーターとして2つを取る関数ポインターを想定しているconst void *ため、コンパレーター関数にポインターが渡されます。

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

したがって、渡すaと、をポインタとしてb解釈することになりますが、これは明らかに間違っています。


多次元配列のポインターを渡さなくても機能します。配列を渡すと、ポインターに減衰するためです。したがって、次のコンパレータは問題ありません。

int compare (int a[2], int b[2])
{
    return a[1] - b[1];
}
于 2012-12-27T18:43:27.517 に答える
0

答えは非常に簡単です。qsorthttp : //pubs.opengroup.org/onlinepubs/009695399/functions/qsort.htmlのマニュアルから 、関数ポインターのプロトタイプは次のとおりです。

int comp(const void *a, const void *b)

このプロトタイプに関連付けられているtypedefは、この方法で宣言できます

typedef int (*comp_qsort_funct_t) ( const void *, const void * )

comp_qsort_funct_t関数ポインタのタイプはどこにありますか

qsortのcompare関数のプロトタイプは、const変数を使用しています。これは、データが変更されないため、これは優れたプラクティス/設計であるためです。

別のプロトタイプを使用すると、コンパイラ/プラットフォームによっては予期しない結果が生じる可能性があります。または、コンパイルに失敗します。例はコメントで提供されています:http: //ideone.com/9zRYSj

したがって、別のプロトタイプを使用しないでください。

于 2012-12-27T18:42:11.227 に答える
0

元の質問をしたDlinetの観察を確認したいと思います。

Cコンパイラは、constがなくても、またvoid*の代わりにint*を使用していても、実際にすべての形式の比較関数を受け入れます。ただし、私のC ++コンパイラはバリエーションを拒否し、constvoid*形式のみを受け入れます。

Cコンパイラは、ポインタをまったく使用しないint形式も受け入れます。確認したところ、compare関数のint形式は、配列のintではなく、ポインター値を受け取っていることがわかりました。その結果、配列は並べ替えられませんが、同じ順序のままになります。そして、私はこれがすべてあなたが期待するものだと思います。

したがって、関数定義でvoid*の代わりにint*を使用でき、関数内でキャストする必要がないようです。

これが優れたプログラミング手法であるかどうかは議論の余地があり、一部のプログラマーは「はい」と言い、一部のプログラマーは「いいえ」と言います。私には、優れたプログラミングであろうとなかろうと、混乱を減らすことは、void*の代わりにint*を使用することを支持するポイントになるでしょう。しかし、C++コンパイラではそれができません。

于 2015-02-08T01:37:24.497 に答える