#pragma twice

KAB-studio > プログラミング > #pragma twice > 238 Version 13.02 アルゴリズム関数の基本

#pragma twice 238 Version 13.02 アルゴリズム関数の基本

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

 Version 13.02
アルゴリズム関数の基本

これからアルゴリズムの関数を作っていくわけだけど、その前にまず基本
的なことから説明します
基本的なこと?
アルゴリズム関数はこうなってる方がいい、っていうのをまず最初に上げ
ておこうと思って。大まかに上げると、アルゴリズム関数は次のような性質
を持っているべきです

・関数になっている。
・関数の中で閉じている。
・入力と出力、エラーが明確になっている。

んー、最初の〈関数になっている〉、って当たり前じゃない
まぁそうなんだけど一応。とりあえず、クラスひとつにつき1アルゴリズ
ム、っていうのはあまりないから、ってこと
クラスひとつにつきいっぱいアルゴリズムがある、っていうのはOK?
それはOK、というかアルゴリズムの関数が、ただクラスの中に入ってる
ってだけだから。アルゴリズム関数とクラスが密接に関係してなければ
OK
うーん、いまいちよくわからない……
その辺はもう少し先かな。次の〈関数の中で閉じている〉は、関数の外の
変数と関わらない、ってこと
外の変数?
具体的に言うとグローバル変数のこと。アルゴリズム関数がクラスのメン
バ関数の時には、メンバ変数も当てはまるかな
ってことは、引数で渡される変数以外、ってことだよね
そういうこと。これはなぜかというと、関数を呼ぶ度に結果が変わる可能
性があるから
どゆこと?
次の項目とかぶるけど、アルゴリズムは〈Aを入力したらBが返る〉って
いうのが明確に決まっていて、100回やったら100回同じ結果が返って
くる必要があります
うんうん
でも、外の変数にアクセスしてると、その変数の状態で結果が変わる可能
性が出てきます
んー、その外の変数も〈入力〉なんじゃないの?
だったらちゃんと引数として入力してもらうべき、ってことだね
そか、入力と出力、があるわけだから、それ以外の要素は関係しないよう
にしなきゃいけないわけだ
同じ関係で、外的要因が関係してくるものはアルゴリズムに入れないよう
にします
外的要因?
たとえばファイル操作とか
あー、ファイルがあったりなかったりで変化しちゃうね
もちろんそういった情報も入力に加えれば問題なし
でもどっちにしろ、ファイル操作そのものは中に入らなそうだね
だね。最後の〈入力と出力、エラーが明確になっている〉は一番大事
さっきの〈Aを入力したらBが返る〉ってのだね
何度呼んでも同じ結果が返る、っていうのが重要
エラーっていうのは?
エラーというよりは〈範囲〉かな
範囲?
ここからは例を見ながらの方がいいかな。たとえば、次のような関数を作
るとします

・入力:整数1 
・出力:整数1 * 10 

つまり10倍して返す、ってことね
これを、単純に関数化してみます

int TenTimes( int p_i )
{
    return p_i * 10;
}

簡単ねー
でも、実はこれだけでは不十分です
え?
 int に入る一番大きな数は?
えっと…… 2147483647 でーす
ってことは?
ってことはぁ……あ、戻り値も 2147483647 までだから、引数はその
10分の1の 214748364 までしかダメってこと?
正の整数に限れば、そういうこと

void UseTenTimes_Overflow()
{
    int iRes;
    iRes = TenTimes( 214748365 );
    TRACE( "%d\n", iRes );
    // -2147483646
}

げ、マイナスになっちゃった
これは Version 6.14 ( No.114 ) でやった【オーバーフロー】が起きた
から
範囲を超えちゃったのね
もちろん負の方もあるんだけどそれは説明の関係で置いておいて、じゃあ
この場合、関数を作る側はどうすればいいのかというと、次のような選択肢
があります

1:コメントに 214748364 までしか受け取れないことを書いておく。
2:214748364 より大きな数が渡されたら 1 を返す。
3:214748364 より大きな数が渡されたら assert() する。

……1って、何もしないのと同じじゃない
そう。でも、実はこれってすごく重要なんです
どゆこと?
さっき、アルゴリズム関数は入力と出力がはっきりしてないといけない、
って言ったよね
うん
でも、それは結局、ソースコードを読んで調べないといけないんです
でもそれって大変だよね、ランタイムなんて読み切れないもん
そうなるとコメントやドキュメントを信じるしかなくなるでしょ。だから
1はちゃんと機能するんです。〈この関数は引数を10倍して返します〉と
〈214748364 までしか受け取れません〉っていうのは同じ、ってこと
ん……関数がどう動作するかは、ドキュメントを信じてその通りに操作す
るしかないわけね
そういうこと。プログラムは関数やクラスの集まり。その実装までは追い
切れないなら、〈信用〉だけで組むしかないわけ
……それって怖くない?
怖いよ。だから、できる限りプログラムの側で〈信用〉を〈保証〉する必
要があります。その方法のひとつが2
 1 を返す……なんで 1 ?
 1 の10分の1は 0.1 、でもこれは引数で渡せないから
 int だから……そか、 1 は絶対に返らない数なんだ
こういう形でエラーを返すのが、2の方法。この関数を呼び出す側では、
このエラーをチェックすることで関数がちゃんと動作したのか確認すること
ができます
でもチェックは必要なのね
そうなるね。今回のエラーは、引数でちゃんと 214748364 以下ってこと
をチェックすれば問題にはならないから、そういう意味ではこの方法はあま
り意味はないかな

void UseTenTimes_WithCheck()
{
    int iInput = 214748365;
    int iRes;

    // iInput の中身をチェックします。
    // 注! 実際には最小値もチェックする必要あります。
    if( iInput <= 214748364 )
    {
        iRes = TenTimes( iInput );
        TRACE( "%d\n", iRes );
        // 2147483640
    }
    else
    {
        TRACE( "%s:%d\n", "エラー!", iInput );
    }
}

呼ぶ前にチェックする、と
でも、この場合はチェックができるっていうだけで、複雑な処理をする関
数の場合には事前のチェックは不可能だから
エラーを返すしかないわけね
ここで重要なのは、どの線で切り分けるか。たとえば、2 assert() を使
う場合を考えてみます

#include <assert.h>

int TenTimes_WithAssert( int p_i )
{
    assert( p_i <= 214748364 );
    return p_i * 10;
}

 assert() は Version 6.17 ( No.117 ) でやった……あれ? 大文字小
文字違う
それは MFC の ASSERT マクロ。こっちの assert() はランタイムの。で
も同じ機能だからどっちでもいいよ
ってことは、中が 0 だとダイアログ出るんだね
そう。使用例はこんな感じ

void UseTenTimes_WithAssert()
{
    int iInput = 214748365;
    int iRes;

    iRes = TenTimes_WithAssert( iInput );
    TRACE( "%d\n", iRes );
}

出た出た、 Assertion failed! って
つまり、この場合には必ず呼ぶ前にチェックが必要、ってこと
必ず?
必ず。 ASSERT の説明の時にしたけど、 assert() はエラーチェックじゃ
ないから。 assert() はプログラム上絶対に通らない経路、通らない値の時
にひっかかるようにするためのもの
ってことは、 TenTimes_WithAssert() に渡されるのは絶対に assert() 
にひっかからない数じゃなきゃいけない、と
そういうこと
んー、で、どっちがいいの?
というより組み合わせるのが普通かな。さっき言ったように、複雑な処理
の場合にはすべて事前にチェックさせるのは不可能、だけど簡単なチェック
なら呼ぶ前にさせる形の方がいいから
そう? 全部エラーの方が楽だけど
そうでもないよ。エラーが返ってくる場合、エラーのチェックは必要にな
るし
そか、労力は同じか
それに、呼ぶ前のチェックは、複数の関数を呼ぶときにまとめてできたり
するから
同じ引数を使い回す時、ってこと?
そういうこと。ま、明確なルールは決まってないから、微妙なところだけ
どね

/*
    Preview Next Story!
*/
なんかちょっと微妙……
ルール決まってないからね。でも、次回はもう少し絞り込んでみるから
絞り込む?
ルールが決まってるところはしっかり決めた方がわかりやすいでしょ
ってゆーかプログラム組めないと思う
というわけで次回
< Version 13.03 コントラクトという考え方 >
につづく!
ま、それでも組まなきゃいけないときはあるんだけどね
え”
 
del.icio.us 登録する
Yahoo!ブックマーク 詳細を表示 users
livedoorクリップ 詳細を表示 livedoorクリップ ブックマーク数
はてなブックマーク 詳細を表示 はてなブックマーク ブックマーク数
RSSに登録
del.icio.us 登録する
Yahoo!ブックマーク 詳細を表示 users
livedoorクリップ 詳細を表示 livedoorクリップ ブックマーク数
はてなブックマーク 詳細を表示 はてなブックマーク ブックマーク数
 
このページは、Visual C++ 6.0を用いた C++ 言語プログラミングの解説を行う#pragma twiceの一コンテンツです。
詳しい説明は#pragma twiceのトップページをご覧ください。