C/C++ 関数ポインタ
関数ポインタ
関数のポインタ渡し。C++ で構造体のソート方法をあれこれ考察していが、qsort STL std:sort コンテナ ベクトル … テンプレート 〜 演算子のオーバーロード クラスと、嫌と言うほど「芋ずる式」に学習を強要される。(さすが、C++ !)
… で、一巡しソートに戻っても、qsort std:sort は、安定性の無い並べ替え手段であることが、ほぼ明確となる。(並べ替え手段が速度優先で、同値の並びを維持できない)
そこで単純な並べ替え関数を作成。「並べ替えの基準は qsort 同様に比較関数を別途準備して…」などと思ったら、比較関数の呼出を汎用化するには、「関数のポインタ渡し」の知識を要求される。
はっきり言って、へろへろ ... 。
関数ポインタの最小モデル
minimum model 3::関数ポインタを配列で呼び出す
minimum model 2 の関数の変な呼出最小モデル。sumple 3
void fn_a() { printf("fn_a \n"); } void fn_b() { printf("fn_b \n"); }
void main() { typedef void (*fn_p)(void); fn_p ap[2]; ap[0]=fn_a; ap[1]=fn_b; // 表示(へんてこな関数呼出し) ap[0](); // 注:ap[0]; では関数は呼び出し不可 ap[1](); }※データ配列の後ろに引数が続く意味不明な記述だが、しっかり動く。
表示サンプル
fn_a fn_b 続行するには何かキーを押してください . . .※変な呼出をしても、結果はしっかり期待値を出す。
▲上へ [ 編集 ]
minimum model 2::構造体・関数ポインタの配列化
minimum model 1 の fn_a fn_b を配列で表す。sumple 2
void main() { struct data dat; dat.id = 10; dat.no = 20; typedef int (*fn_p)(int,int); // 関数ポインタ型 fn_p fn_p ap[1]; // fn_p 型の配列 ap[] を宣言 ap[0] = fn_a; // ap[0] へ関数ポインタ fn_a アドレス代入 ap[1] = fn_b; // ap[1] へ関数ポインタ fn_b アドレス代入 cout << "main -> "; cout << f_main(dat, ap[0]) << endl; cout << "main -> "; cout << f_main(dat, ap[1]) << endl; }※これでも全く同じに動く。呼出関数を整数で管理が可能である。
※全くもって、何でも有りである。これがポインタ利用のメリットである。
※どんどん、可読性が落ちていく。
▲上へ [ 編集 ]
minimum model 1::構造体・関数ポインタ
構造体を処理する場合の関数ポインタ最小モデルのサンプルsumple 1
#include <iostream> using namespace std;
struct data { int id; int no; };
// main から直接呼ばれる f_main() 関数 int f_main( struct data dat, // 対象データ受取り int (*pf)(int, int) ) { // 関数ポインタ定義・受取 return pf(dat.id, dat.no); // pf は、fn_a, fn_b の別名 }
// f_main(fn_?) で呼ばれる関数2種 int fn_a(int a, int b) { cout << "call fn_a : return id = " ; return a; } int fn_b(int a, int b) { cout << "call fn_b : return no = " ; return b; }
void main() { struct data dat; dat.id = 10; dat.no = 20; cout << "main -> "; cout << f_main(dat, fn_a) << endl; cout << "main -> "; cout << f_main(dat, fn_b) << endl; }※fn_a なら dat.id を、fn_b なら dat.no を返す。単にそれだけ。
表示サンプル
main -> call f_main -> call fn_a : return id = 10 main -> call f_main -> call fn_b : return no = 20 続行するには何かキーを押してください . . .※呼出関数へ更に呼び出す関数を指示できる。
▲上へ [ 編集 ]
関数のアドレスで、関数を呼び出す
例えば、qsort 同様汎用的ソート関数を作成する場合、swap の大小判断を外部に別途定義する必要が発生する。インラインで判断するプログラムとすれば、汎用性皆無となるためである。
構造体の各メンバデータで任意にソートしたい場合、案外これが厄介でまたしても頭を抱えることとなる。ちなみに既存の qsort std::sort でも出来ないことは無いが、判断用の関数で 並べ替えメンバの連結と大小判断まで定義が必要(並べ替え以前の並び順維持不能なため)となる。
※この方が、実行速度は速い…とおもわれる…
できれば、単純な判断関数で、複数回ソート掛けられれば、あらゆるシーンに応用が利くのは言うまでも無いが、これを行うには、汎用的ソート関数から、大小判断用関数をとっかえ、ひっかえ呼び出す必要があるのである。これを実現できるのが、関数のアドレス渡し(関数ポインタ)と言うことになる。
C++ は、非常に柔軟性が高い言語である。VB も何でもありとよく言われるが、C++ の言語拡張も別の意味で何でもあり状態。しかし、使い方を広範囲に掌握してなければ、まともなプログラムが作れない言語と言っても良いと思われる。
難解(恐るべし)なり、C++ ... ソートだけでほぼ3ヶ月費やしている ... もう (-_-;) 疲れた〜
sample 5::関数ポインタを利用したソート
C/C++ 構造体とソート へ転記し「関数ポインタ」部分のみに要約。構造体配列のソートを、ソート関数を用意しておこなうテスト。
動作テストプログラム sample 5
※qsort で使ったソース使い回し。(内容:関数ポインタ要約実施)
// 関数プロトタイプ int no_comp(const void*, const void *); int age_comp(const void*, const void *); void sort(struct student*, int, int(*)(const void*, const void*)); void swap(struct student*, struct student*);
// 構造体定義 struct student { int no; int age; string name; }; /**************** main *****************/ void main() { int max_no = 9; struct student seito[] = { { 3, 14, "李 小狼" }, { 4, 13, "李 苺鈴" }, { 1, 14, "木之本 桜" }, { 2, 13, "大道寺 知世" } }; // --- sort --- sort(seito, max_no, no_comp); sort(seito, max_no, age_comp); // --- 表示 --- out_dsp(seito, max_no); }
/**************** 関数・サブルーチン *****************/ // sort関数(function) void sort(struct student* p, int n, int (*p_cp)(const void* x, const void* y) ) { struct student temp; // swap 用一時変数 int k = n-1; while(k>=0) { int i=1, j=-1; for(; i<=k; i++) { if ( p_cp( p+i-1, p+i ) > 0 ) { // ここが関数ポインタの要 j = i-1; temp = *(p+i); // 値の入替え(swap) *(p+i) = *(p+j); *(p+j) = temp; } } k = j; } } // sort 比較関数(sub) 2種 int no_comp(const void* x, const void * y) { // student->no の比較 struct student* a = (struct student *) x; struct student* b = (struct student *) y; if (a->no < b->no) return -1; else if (a->no > b->no) return 1; else return 0; } int age_comp(const void* x, const void * y) { // student->age を比較 struct student* a = (struct student *) x; struct student* b = (struct student *) y; if (a->age < b->age) return -1; else if (a->age > b->age) return 1; else return 0; }※qsort や std::sort より、汎用性・使い回しで安定ソートベースが使いやすい。
...今回 Turbo C++ で同じコードをコンパイルしたところ出来ない。・・・ 何故? ・・・
対処
・構造体配列へ初期値代入が出来ない -> 直代入で回避
・なんとか実行できるがメモリ処理の実行時内部エラーが出る -> 原因不明
※コンパイラにより C++ の実装の違いがあるため?...か、単に Turbo C++ の使い方を間違っているのが?
※簡単なプログラム(Hello World どか)は何ら問題無い。
▲上へ [ 編集 ]
sample 4::関数ポインタを利用した引数渡し
sample 4
#include <iostream> using namespace std;
struct data { int id; int no; };
int function( int (*pf)(const void* pa, const void* pb) ) { int ret; struct data dat; dat.id = 10; dat.no = 20; ret = pf(&dat.id, &dat.no); return ret; } int fn_a(const void* pa, const void* pb) { int a = *(int*)pa; int b = *(int*)pb; int ret = a; return ret; } int fn_b(const void* pa, const void* pb) { int a = *(int*)pa; int b = *(int*)pb; int ret = b; return ret; }
void main() { cout << "fn_a : id = " << function(fn_a) << endl; cout << "fn_b : no = " << function(fn_b) << endl; }※難解になってきた... function へ渡すのは関数のポインタのみでよいようである。但し、前準備がしっかりして無いとまともに動き出してくれない。(qsort の「比較関数」呼出と同じように動くようにしてみた。※正誤は無保証)
表示サンプル
fn_a : id = 10 fn_b : no = 20 続行するには何かキーを押してください . . .※なんとか、期待値が帰ってきた。
▲上へ [ 編集 ]
sample 3::同名関数の関数ポインタ
オーバーロードされた同名関数をポインタで渡す場合も、うまく出来ている物で問題なく動く。sample 3
#include <iostream> using namespace std; void fn_a(void*); // fn_a 関数プロトタイプ(1) void fn_a(); // fn_a 関数プロトタイプ(2) int function(void (*)(void)); // この時点で(2)対応決定
struct data { int id; int no; };
void main() { // ----- sample 1 ----- struct data dat; dat.id = 10; dat.no = 20; void (*p_fn)(void *); // (1)対応の関数ポインタ宣言 p_fn = fn_a; // (1)対応関数 fn_a アドレス代入 p_fn( &(dat.id) ); // ----- sample 2 ----- function(fn_a); // (2)対応関数 fn_a アドレス渡 }
void fn_a( void* p ) { cout << "1:fn_b : id = " << *(int*)p << endl; } int function( void (*pf)(void) ) { // (2)対応関数ポインタ宣言含む pf(); return 0; } void fn_a() { struct data dat; dat.id = 30; dat.no = 40; cout << "2:fn_a : id = " << dat.id << endl; }※「これは〜?」と思ったが、fn_a(void*) fn_a() のオーバーロードに関わらず fn_a から取得される関数のアドレスはしっかり区別されており期待値が返る。(当たり前と言えばそれまで...)
表示例
1:fn_a : id = 10 2:fn_a : id = 30 続行するには何かキーを押してください . . .※オーバーロードした fn_a を、きっちり分けて処理されている。
▲上へ [ 編集 ]
sample 2::関数ポインタを渡して処理を切り替える
sample 1 の発展形のようなもの。main() から、1.関数アドレスを持たせた、function(void(*)(void)) を実行。
int function() 関数の 2. 3. で、呼び出す関数を外部指示に従い切り替え 4. で実行となる。
ここで重要なのは、2. と 3. になる。
sample 2
#include <iostream> using namespace std; int function(void (*)(void)); void fn_a(); void fn_b();
struct data { int id; int no; };
void main() { function(fn_a); // 1.fn_a のアドレス渡 function(fn_b); // 1.fn_b のアドレス渡 }
int function( void (*pf)(void) ) { // 2.関数ポインタの宣言を含む pf(); // 3.渡された関数ポインタを実行 return 0; } void fn_a() { // 4.fn_a 実行 struct data dat; dat.id = 30; dat.no = 40; cout << "2-1:fn_a : id = " << dat.id << endl; } void fn_b() { // 4.fn_b 実行 struct data dat; dat.id = 30; dat.no = 40; cout << "2-2:fn_b : no = " << dat.no << endl; }※例えば、function() 内のループカウンタをインデックスに使う場合で、且つ、処理対象とする、配列化された構造体の構造体メンバを任意に切り替えたい場合、大変有効。(要するに qsort の比較関数切替と同じ事が出来る。)
▲上へ [ 編集 ]
sample 1::最も簡単な関数ポインタの例
最も簡単?…構造体、引数に void 使ってるなどあるので、ちと複雑?かも…(個人的メモなので...)要するに、1. 2. 3. の手順に従えば、関数ポインタを使った呼出が可能である。
これで何が出来るのか?--->閉じた関数へ関数ポインタを渡すことで、任意の関数実行の切替を外部から指示できる。
※sample 2 参照
sample 1
#include <iostream> using namespace std; void fn_a(void*); void fn_b(void*);
struct data { int id; int no; };
void main() { struct data dat; dat.id = 10; dat.no = 20; void (*p_fn)(void *); // 1.関数ポインタの宣言 p_fn = fn_a; // 2.関数ポインタ p_fn 関数address代入 p_fn( &(dat.id) ); // 3.fn_a() 呼出 p_fn = fn_b; // 2.関数ポインタ p_fn 関数address代入 p_fn( &(dat.no) ); // 3.fn_b() 呼出 }
void fn_a( void* p ) { cout << "1-1:fn_b : id = " << *(int*)p << endl; // 4.void はキャスト } void fn_b( void* p ) { cout << "1-2:fn_b : no = " << *(int*)p << endl; // 4.void はキャスト }※単に呼出のみなら引数なしでも良い。4.は 型指定無し void で渡した引数は利用時にキャストしなければ鳴らないというだけのことで、関数ポインタと直接関係は無い。
▲上へ [ 編集 ]
リンク
内部リンク
- C/C++ C++/CLI C# 関連
- VC++ 2005 Express のインストール
- C/C++ の簡単なプログラム例
- 変数・定数
- プログラムの分割/ダイナミックリンクライブラリ など
- その他
- C/C++ その他::書式文字/ESC code など
- VB2005リファレンス(覚え書き)
- SQL文:SQLステートメント
- VBA(VisualBasic for Applications)
外部リンク
- 現在ありません
▲上へ [ 編集 ]
2008年07月13日(日) 20:31:54 Modified by cafeboy1