#pragma twice

KAB-studio > プログラミング > #pragma twice > 253 Version 13.17 ランタイムでクイックソート

#pragma twice 253 Version 13.17 ランタイムでクイックソート

前のページへ 表紙・目次へ 次のページへ

 Version 13.17
ランタイムでクイックソート

前回は、クイックソートする関数を作ってみました
再帰呼び出ししててちょっと複雑だったかも……
さて、実はあの関数は必要ありません
ええっ?
クイックソートは、専用の関数がランタイムに用意されてるからです
え”、なに無意味?
アルゴリズムの勉強だから無意味じゃないんだけど……
でもなんか無駄~
でも、仕組みがわからなきゃ安定ソートじゃないとかの理由がわからない
でしょ
う”
だから、実際に作ってみることが必要ってこと
うー
さて、ではこれからランタイムのクイックソート用関数 qsort() を使っ
てみます
 qsort() 、そのままね
ちなみにクイックソート以外のソート関数は用意されてません
げ、じゃあ安定ソートできないじゃん
なんだよね。だからこれもあまり使う機会ないかも。まぁそれは置いとい
て、まずは使ってみましょう

// 比較用関数。 int で比較します。
// 左の方が小さければマイナス、
// 同じであれば 0 、
// 左の方が大きければプラスを返します。
int CompareTo( const void *p_pvL, const void *p_pvR )
{
    return *( (int *)p_pvL ) - *( (int *)p_pvR );
}

void Use_qsort()
{
    int iAry[5];
    iAry[0] = 4;
    iAry[1] = 9;
    iAry[2] = 3;
    iAry[3] = 5;
    iAry[4] = 8;

    // ソートします。
    qsort( iAry, 5, sizeof( int ), CompareTo );

    for( int iF1 = 0; iF1 < 5; ++iF1 )
    {
        TRACE( "%d ", iAry[iF1] );
    }
    TRACE( "\n" );
    // 3 4 5 8 9 
}

うわ、みじか! ん、でも比較用関数は必要なんだ
そう、 Version 13.09 ( No.245 ) のときみたいに。でも微妙に違うで
しょ
……引数が void ポインタで、戻り値が int ……
 qsort() を使う上で、色々なルールがあるから、まずそのあたりをひと
つずつ押さえていきます。まずは呼び出している箇所から

    // ソートします。
    qsort( iAry, 5, sizeof( int ), CompareTo );

第1引数は配列、第2引数はその配列の要素数、第3引数はその要素のサ
イズ、第4引数は比較用関数へのポインタ
要素数とサイズ、両方渡すんだ
これは qsort() の引数を見た方が早いかな

void qsort
    ( void *base
    , size_t num
    , size_t width
    , int (__cdecl *compare)(const void *elem1, const void *elem2) 
    );

第1引数が void ポインタだから、各要素のサイズがわからないと〈次の
要素〉が見られないから
そか、今までのソートって普通の型の配列だったから、要素のサイズって
わかってたけど、 qsort() は void のポインタだから……って、なんで 
void ポインタなの?
そうじゃなきゃいろんな型が使えないでしょ
あ、 CObArray と同じね
そういうこと。 void ポインタは〈汎用型〉として使えるから便利なんだ
よね。その分、危険なんだけど
だよねー、型間違えたらエラーだし、コンパイルしたときにはわからない
し……
さて次に、比較用関数について
なんかややこしい引数だね

    , int (__cdecl *compare)(const void *elem1, const void *elem2) 

この引数は、【関数ポインタ】を引数として受け取るときの書き方
かんすうぽいんた? そういえばアルゴリズムの最初の時に言ってたね
そう、この方法を使うと関数をとっかえひっかえできるから、アルゴリズ
ムには便利なんです
確かに、引数として渡せると便利ね……
じゃあ、この関数ポインタの見方について

    戻り値 (呼出方 *変数)(引数) 

という書式になっています

そうだね、普通に変数として使ってみようか

void FunctionPointer()
{
    // 関数ポインタ。
    int (__cdecl *fp)(const void *elem1, const void *elem2);
    fp = CompareTo;

    int i50 = 50;
    int i100 = 100;
    // 関数ポインタ経由で呼び出し。
    TRACE( "%d\n", fp( &i50, &i100 ) );
    TRACE( "%d\n", fp( &i100, &i100 ) );
    TRACE( "%d\n", fp( &i100, &i50 ) );
    // -50
    // 0
    // 50
}

んと、つまり fp って変数を作ってるってこと?
そういうこと。 CompareTo() の関数のアドレスを入れる変数 fp を作っ
て、その中に関数のアドレスを入れると、 fp が CompareTo() の代わりに
なるんです
ほほう、これを使えば、どの関数を呼ぶか、っていうのを引数で変えられ
るわけね
そういうこと。関数ポインタを直に扱うのは危険だからあまり使いすぎな
い方がいいけど、こういう方法もあるってこと
確かに便利は便利ね
ちなみに、火美ちゃんはこれまでになんども関数ポインタを使ったことが
あるんです
え”? 全然憶えないんだけど
ウィンドウプロシージャやダイアログプロシージャ
あ……あ! そっか、あれって関数ポインタを渡してたんだ!
 Version 8.07 ( No.149 ) の

    int iRet
        = DialogBox
            ( p_hInstance
            , MAKEINTRESOURCE( IDD_MAIN )
            , NULL
            , DialogProc
            );

は、 DialogProc っていう関数ポインタを引数として渡していたんです
そうするとメッセージが来たら DialogProc() 呼んで渡す、ってできるも
んね。なるほどねー
 qsort() もこのウィンドウプロシージャも、関数の中から呼ぶ関数のポ
インタを渡すしてるでしょ
ややこしい……
関数ポインタの主な使い道がこれかな
他に使い道あるの?
配列に入れて立て続けに呼び出すとか
う”、なんか危険な香りが……
まぁね、実は C++ では【ポリモーフィズム】っていうのがあって、同じ
ような機能をもっと安全に使う方法があるから、最近はあまり使わないか

ポリモーフィズム、よく聞くけど……
ま、説明は今度ね。さて、話を戻して、今度は比較用関数そのものを見て
みます

// 比較用関数。 int で比較します。
// 左の方が小さければマイナス、
// 同じであれば 0 、
// 左の方が大きければプラスを返します。
int CompareTo( const void *p_pvL, const void *p_pvR )
{
    return *( (int *)p_pvL ) - *( (int *)p_pvR );
}

今までと戻り値と引数の型が違う!
引数の void ポインタは説明したでしょ
 Version 13.13 ( No.249 ) の?
そう、それと同じ。 void ポインタで受ければどんな型にでも使えるか

ん? それって qsort() の第1引数と同じ理由?
同じ理由。 qsort() っていうひとつ関数でどんな型でも扱えるようにす
るには、 void ポインタが一番簡単だからね
でも危険、と
そういうこと
戻り値が int なのはなんで?
比較方法がそうだから。〈より大きい〉と〈より小さい〉が必要だから
だから3つに分けてるわけね。でも…… if 使ってないね

    return *( (int *)p_pvL ) - *( (int *)p_pvR );

こんなんでいいの?
〈プラス〉と〈マイナス〉って、正確な数は決めてないから
あ、そういうえば…… 1 も 100 も同じ?
そういうこと。だから単純に引き算すればいいだけ
そうすれば、左が大きければプラス、小さければマイナス、同じなら 0 
ってなるわけね、なるほど
ちょっとわかりにくいから、丁寧に書いてもいいかもね。ではまとめ。
qsort() はこんなに簡単に使えます
ホント、簡単ね……
なので、クイックソートをする場合には qsort() を使いましょう
はーい
さらに言えば、安定ソートをしない場合には qsort() を使いましょう
最速だもんね
そういうこと。あるものをできる限り使うのが一番だからね

/*
    Preview Next Story!
*/
やっぱランタイムがあると簡単ねー
さて、次回はマージソートの説明をします
ランタイムある?
ありません
というわけで次回
< Version 13.18 一番便利・マージソート >
につづく!
ランタイムないといやー!
でも、僕が関数の例を作ってあげてるんだけど……
あ、そっか
 
del.icio.us 登録する
Yahoo!ブックマーク 詳細を表示 users
livedoorクリップ 詳細を表示 livedoorクリップ ブックマーク数
はてなブックマーク 詳細を表示 はてなブックマーク ブックマーク数
RSSに登録
del.icio.us 登録する
Yahoo!ブックマーク 詳細を表示 users
livedoorクリップ 詳細を表示 livedoorクリップ ブックマーク数
はてなブックマーク 詳細を表示 はてなブックマーク ブックマーク数
 
このページは、Visual C++ 6.0を用いた C++ 言語プログラミングの解説を行う#pragma twiceの一コンテンツです。
詳しい説明は#pragma twiceのトップページをご覧ください。