C言語関係掲示板

過去ログ

No.147.カラムごとのエラーチェック


No.929

関数ポインタでエラーチェック
投稿者---masa(2002/01/25 16:52:54)


ファイルからCSV形式のデータを読み込み、各カラムに分解して関数ポインタを必ず使用してデータのカラムチェックを行います。目的としては、誰のどのカラムでエラーが発生したかを確認することです。
・ファイル形式
 名前(全角10文字まで)、学籍番号(5桁)、生年月日(8桁)、年齢(2桁)、性別(1:男 2:女)
・ファイル内容
 太郎,00001,19760912,24,1
花子,00002,19760915,25,2
・チェック内容
名前:桁数チェック
学籍番号:数字かどうかのチェック
     桁数チェック
生年月日:数字チェック
     桁数チェック
     日付の妥当性チェック
年齢:数字チェック
   桁数チェック(2桁まで)
性別:数字チェック
   桁数チェック(1桁まで)

上記のチェックを関数ポインタを用いて、誰のどのカラムでエラーが起きたかを
調べたいのですが、うまくできません。
わかる方サンプルソースなどがあるととても助かります。



No.942

Re:関数ポインタでエラーチェック
投稿者---ともじ(2002/01/26 11:16:19)


masaさん、こんにちは。

このご質問、丁度No.928の返信を参考にすればできますね。

ファイルオープン
ファイル終了まで
 一行単位で読み込み
 No.928のGetData関数を参考に、カラムに分解(全部char型配列)
 カラムごとにチェック用の関数を用意し、チェック
 チェックエラーがあればエラー情報を出力
ファイルクローズ

なお、カラムチェックは
 桁数チェック:strlen関数で文字列長さを取得してチェック
 数字チェック:atoi関数で0が返却されればエラー
 日付の妥当性チェック:strncpy関数で4,2,2文字づつ年月日を分解し、
            それぞれ妥当性をチェック
でいいと思います。


No.945

Re:関数ポインタでエラーチェック
投稿者---Cプログラマー(初心者)(2002/01/26 15:33:23)


返信ありがとうございます。
strtok関数を用いてカラム毎に分解することはできました。
しかし、関数ポインタを用いる部分が具体的にどうコーディングすれば
いいのかいまいちわかりません。
要するに呼び方がわからないのです。




No.948

Re:関数ポインタでエラーチェック
投稿者---B.Smith(2002/01/26 16:25:46)


No.935を少しだけ改造してみました。処理の大部分をカットしていますが、関数ポインタを使用する例としては参考になると思います。
サンプル.
#define     MAX_FIELD       4   /* データ項目数 */

/* 関数のポインタの型を定義します */
typedef int     (*CHECKFUNC)(char *);

/* チェック用関数プロトタイプ                   */
/* 配列にセットする前に宣言しなければなりません */
int     Check_Gakuseki(char *);
int     Check_Seinengappi(char *);
int     Check_Nenrei(char *);
int     Check_Seibetsu(char *);

/* 関数ポインタを配列にセット */
CHECKFUNC   ChkFunc[] = {
    Check_Gakuseki,     /* 学籍番号 */
    Check_Seinengappi,  /* 生年月日 */
    Check_Nenrei,       /* 年齢 */
    Check_Seibetsu      /* 性別 */
};

void    GetData(char *Record,DATA *Out)
{
    int             Idx;
    char            *tk;
    static char     *token = ",\r\n";

    Idx = 0;    /* チェック項目切り替え */

    tk = strtok(Record,token);
    while(tk){
        /* 項目毎のチェック */
        if (!ChkFunc[Idx](tk)){
            (エラー時の処理)
        }

        tk = strtok(NULL,token);    /* 次の項目切り出し */
        Idx++;                      /* 次のチェック項目 */
    }

    /* チェック項目がデータ項目数よりも小さい場合、 */
    /* 読み込んだデータ内にデータ項目が足りないとい */
    /* うこと                                       */
    if (Idx < MAX_FIELD - 1)
        (エラー時の処理)
}

/** チェック関数 **/
/** 1を返した時、正常終了。0はエラー **/
int     Check_Gakuseki(char *String)
{…}

int     Check_Seinengappi(char *String)
{…}

int     Check_Nenrei(char *String)
{…}

int     Check_Seibetsu(char *String)
{…}

ここでは、ポインタを配列にし、切り出した項目毎にチェックする関数を切り替えていきます。
関数ポインタの実体を定義する時、
int     (*ChkFunc)(char *);

とします。変数名部分がポインタ名になります。しかし、この方法では、多用した場合にソースが見辛くなりますので、typedefにより型として定義してしまいます。
typedef int     (*CHECKFUNC)(char *);

例えば関数ポインタpFuncを定義し、そこに関数Check_Gakusekiのポインタを代入したい場合、
    CHECKFUNC    pFunc;

    pFunc = Check_Gakuseki;

と記述できます。

ポインタによって呼び出す際は、普通の関数呼び出しと同じような記述をします。
    int    Ret;

    Ret = pFunc(引数);

ポインタが配列の場合は、インデックスを用います。
(上記サンプルより)
        if (!ChkFunc[Idx](tk))




No.950

Re:関数ポインタでエラーチェック
投稿者---Cプログラマー(初心者)(2002/01/26 20:08:47)


RESありがとうございます。
おかげさまでどうやればうまくいくか理解できました。
つまり取り出したカラム毎にそこで行うべきチェックをすべて行い、次の
カラムを取り出しまたチェックをすべて行うということですね。
ただ疑問点があります。
,泙坤妊螢潺燭任垢なぜ\r\nがカンマのあとにに必要なのでしょうか。
なくても正常に動きます。参考書でもただ単にカンマで区切っていました。
△修譴肇瀬屮襯櫂ぅ鵐燭いまいち理解できません。
教えていただくと助かります


No.951

Re:関数ポインタでエラーチェック
投稿者---B.Smith(2002/01/26 23:08:39)


関数fgets等で改行文字単位に(行単位)に読み込む場合は、関数strtokに指定するセパレータに改行文字は必要ありません。基本的に私はメモリにすべてロードした状態でデータ処理を行うクセがありますので、セパレータの指定に改行文字を入れてしまいました(No.906参照)。
不要な部分は、ご自身の判断で削除してください(多くの説明は要らないと思います)。

◆屮櫂ぅ鵐燭離櫂ぅ鵐拭廚亡悗靴討蓮こちらで分かりやすく紹介していますので、まずご覧になってみてください。

「ポインタのポインタ」は難しそうに見えますが、「ポインタ」さえ理解出来ていれば、考え方は非常に簡単です。
ポインタを単なるlongの整数値として見てください(実際に、ポインタはlongの値として扱うことができます)。
この変数ptrにバッファBufのアドレスを代入します。
    char    Buf[10];
    long    ptr;

    ptr = (long )Buf;

変数ptrは、見てお分かりの通り、単なる整数を格納するlong型の変数です。この変数自体のアドレスは以下のように表せますよね?
    long    *lptr;

    lptr = &ptr;

また、変数ptrが配列であったならば、その先頭アドレスをlptrに代入するには、以下のようになると思います。
    long    ptr[100];
    long    *lptr;

    lptr = ptr;

単なるlong型の変数、または配列ならば、扱いは容易だと思います。
このptrのアドレスを格納するポインタlptrが「ポインタのポインタ」ということになります。アドレスをlong型の数値として考えれば、単なるlong型のポインタや、long配列のポインタとして見ることができます。ただし、lptrの指す領域に入っている数値、つまりptrに格納されている数値は別領域を指すアドレスです。

例えば上記の配列ptrの各要素に、文字列バッファのアドレスがlong値として格納されているとします。そのバッファの内容をすべて表示したい場合、配列をインデックスにより表示させたいのであれば、以下のようになります。
    long    ptr[100]; ←すでに複数のバッファのアドレスを格納してあるとします
    int     Idx;

    for(Idx = 0;Idx < sizeof(ptr )/sizeof(long );Idx++){
        printf("%s\n",(char *)ptr[Idx]);
    }

これを、long型のポインタlptrを使用した形にしてみます。
    long    ptr[100]; ←すでに複数のバッファのアドレスを格納してあるとします
    long    *lptr;
    int     Idx;

    lptr = ptr;
    for(Idx = 0;Idx < sizeof(ptr )/sizeof(long );Idx++){
        printf("%s\n",(char *)*lptr); ← *lptrはptrの各要素を表す
        lptr++;
    }

今まで、char *をlongとして見てきました。ここで、longをchar *に戻してみます。
    char    *ptr[100]; ←すでに複数のバッファのアドレスを格納してあるとします
    char    **lptr;
    int     Idx;

    lptr = ptr;
    for(Idx = 0;Idx < sizeof(ptr )/sizeof(char *);Idx++){
        printf("%s\n",*lptr); ← *lptrはptrの各要素を表す
        lptr++;
    }

すべてのアドレスがlongの値として表現できることから、ポインタが何重になっても、ポインタの型が何であっても、上記の考え方は適用できます。



No.955

Re:関数ポインタでエラーチェック
投稿者---Cプログラマー(初心者)(2002/01/27 20:27:33)



RESありがとうございます。
現在のやり方では、たとえば年齢のチェックを一切行わないよう処理することは
できないと思います。つまり今はすべてのカラムに対してチェックを行っていますが途中でチェックしないカラムが存在したときにはどう対応すればいいのでしょうか??


No.957

Re:関数ポインタでエラーチェック
投稿者---お助け探し人(2002/01/28 09:08:19)


おはようございます。もう少し詳しく説明しますと、
項目A、項目B、項目C、項目D、項目Eとあったときに
項目A,C,Eのみチェックしたいのです。
項目B、Dはチェックはしたくないのです。
つまりチェックする項目にたいしてのみ関数を呼びたいのです。
現状では切り出したカラムごとに毎回関数を呼んでいますが
ずばっとチェックする項目が切り出されたときにチェック関数を
呼びたいのです。
できますでしょうか。


No.959

Re:関数ポインタでエラーチェック
投稿者---B.Smith(2002/01/28 12:30:04)


こんにちは。

特定の項目をチェックしない方法を2つご紹介します。

関数ポインタを無効なものに置き換える

配列で指定している関数のポインタを無効なものに置き換えます。例えばNULLです。
/* 関数ポインタを配列にセット */
CHECKFUNC   ChkFunc[] = {
    NULL,      /* Check_Gakuseki,     学籍番号 */
    Check_Seinengappi,  /* 生年月日 */
    Check_Nenrei,       /* 年齢 */
    Check_Seibetsu      /* 性別 */
};

次に、ポインタを使って呼び出しを行っている部分を修正します。
今回はNULLを使用していますので、NULLの場合は関数を呼び出さないようにします。
    tk = strtok(Record,token);
    while(tk){
        /* 項目毎のチェック                     */
        /* ポインタがNULLの場合はチェックしない */
        if (ChkFunc[Idx] && !ChkFunc[Idx](tk)){
            (エラー時の処理)
        }

        tk = strtok(NULL,token);    /* 次の項目切り出し */
        Idx++;                      /* 次のチェック項目 */
    }


何も処理しない関数のポインタを指定する

チェックしない項目の関数ポインタを、常に真を返す関数(ダミー)のポインタに置き換えます。
/* チェックをしない */
int     Ignore(char *String)
{
    return 1;   /* 常に真 */
}

この関数のポインタを、チェックしたくない項目にセットします。
/* 関数ポインタを配列にセット */
CHECKFUNC   ChkFunc[] = {
    Ignore,      /* Check_Gakuseki,     学籍番号 */
    Check_Seinengappi,  /* 生年月日 */
    Check_Nenrei,       /* 年齢 */
    Check_Seibetsu      /* 性別 */
};

これだけです。ループ部分に手を加える必要はありません。

2つの方法をご紹介しました。両方共、簡単な内容だったと思います。特に△牢愎瑤慮討喀个敬分に手を加える必要が無いので、非常に簡単に実装できます。それでは、なぜ,鮠匆陲靴燭というと、実は△廊,茲蠅盻萢効率が悪いのです。
今はお好きな方を選んでください。もう少し慣れてきたら、何故効率の違いが出るのかを、考えてみてください。



No.961

Re:関数ポインタでエラーチェック
投稿者---お助け探し人(2002/01/28 13:09:36)


RESありがとうございます。
実はignore関数の方は考えていましたが処理効率が悪いと確かに思い、ほかの
やり方を探していました。
,諒を参考にさせていただきます。
疑問点なのですが、
if (ChkFunc[Idx] && !ChkFunc[Idx](tk)){
(エラー時の処理)
}
の部分が省略系が使われているのでがこういうことと同じことでしょうか。
//チェックすべき項目のとき、チェック関数をコール
if (ChkFunc[Idx] != NULL){
//戻り値で判断
ret=ChkFunc[Idx](tk);
if(ret==FALSE){
エラー処理
}
}





No.962

Re:関数ポインタでエラーチェック
投稿者---お助け探し人(2002/01/28 13:28:47)


たびたびすいません。
ちなみに,僚萢は10万件とか処理しても高速に処理されるでしょうか。
パフォーマンスが命らしいんですよ。
結局チェックしようがしまいがカラムの数だけ関数ポインタの配列を用意
しなければならないじゃないですか(NULLも含めて)

>
>
>



No.965

Re:関数ポインタでエラーチェック
投稿者---B.Smith(2002/01/28 15:19:13)


まずは…

>疑問点なのですが、
    if (ChkFunc[Idx] && !ChkFunc[Idx](tk)){
        (エラー時の処理)
    }

>の部分が省略系が使われているのでがこういうことと同じことでしょうか。
    //チェックすべき項目のとき、チェック関数をコール
    if (ChkFunc[Idx] != NULL){
        //戻り値で判断
        ret=ChkFunc[Idx](tk);
        if(ret==FALSE){
            エラー処理
        }
    }

その通りです。説明の必要はありませんね(FALSEを0と仮定します)。

>ちなみに,僚萢は10万件とか処理しても高速に処理されるでしょうか。
>パフォーマンスが命らしいんですよ。

分かりました。速度重視の説明に切り替えます。

>結局チェックしようがしまいがカラムの数だけ関数ポインタの配列を用意
>しなければならないじゃないですか(NULLも含めて)

,鉢△鯣羈咾垢襪函↓,諒が高速です。
コンパイラやオプティマイザに依存する面もありますが、条件分岐だけよりも、関数呼び出しのオーバヘッドの方が大きいため、,諒が高速なのです。

ポインタがNULLであるかをチェックするのは、配列の参照と、NULL値との比較だけで済みます。しかし、関数Ignoreにより必ず条件が真になるようにした場合、関数呼び出しの機構、すなわちCALL命令発行と、RET命令によるリターン、ベースポインタ等のレジスタ値をスタックへ保存、復元といったコードが、チェック項目数分、必ず実行されてしまうので、その分が処理速度の違いに現れてきます。

…とは言うものの、一般的に、ファイル操作の場合には、制御やバッファ操作よりもI/Oの方が非常に大きな処理時間を消費しますので、見て分かるほどの違いを生じません。今回はお好きな方を選んで良いと思いますよ。
しかし、全体の処理時間は、個々の処理時間の累積によるものなので、処理回数が多くなると、多少の違いであっても無視できなくなってくる、ということは忘れないでください。タイムクリティカルな処理を記述しなければならない場合、これは大変重要な意味を持ってきます。

処理速度を劇的に高速化したいのであれば、ファイル内のデータ処理をすべてメモリ上で行うことです。ただし、これにはアルゴリズムを再検討し、現存の処理を変更する覚悟が必要になります(すでに見ているかもしれませんが、No.906を参照してください)。
また、必ずしもメモリがフルに利用できるとは限りません。Windowsのように、物理メモリ以上の大きなメモリを確保できる環境もありますが、スワップを発生させてしまえば、結局はパフォーマンスを低下させる原因になってしまいます。現在使用できるメモリを利用し、極力メモリ上で処理を行うような工夫をする必要があります。


戻る


「初心者のためのポイント学習C言語」 Last modified:2002.03.06
Copyright(c) 2000-2002 TOMOJI All Rights Reserved