Version 14.28
再帰呼び出しでフォルダを潜る
「今回は前回の続き、ファイルの検索です」
『結構長いから途中で止まっちゃったんだよね……えっと、次は……』
else if( _stricmp
( stWin32FindData.cFileName
, p_pchFileName
) == 0 )
{
// ファイル名が一致しました。
++iNum;
}
『ファイルのチェックだよね。これは前回と同じ、と』
「あ、でも前回 _stricmp() に触れなかったよね」
『そういえばそうだよね、ってゆーか _ 付いてるじゃん、全部小文字だか
らこれってランタイムだよね。 _ 付きって使っていいんだっけ?』
「 _ が付いているランタイムは Visual C++ にしか入ってないランタイム。
だから、ちょっと汎用性ないんだよね」
『でもさ、ファイル名の一致チェックなんて簡単にできるんじゃないの?』
「ファイル名って大文字と小文字を区別しないでしょ」
『……だから?』
「だから。実は大文字小文字を区別しないランタイムってないんです」
『ええー!? なんか不便!』
「なんだけどね。 CString::CompareNoCase() っていうメンバ関数もあるか
らそっちでもいいけど」
『 CString 使うってこと? なんかそのためだけに CString 使うっていう
のも……』
「それに、 CString::CompareNoCase() の中で _stricmp() 使っているか
ら」
『同じじゃん!』
「もし _stricmp() を使わずに自作する場合には」
・大文字→小文字変換関数を作る。
・ランタイムの strcmp() で比較する。
「っていうのがいいかな」
『あー、大文字小文字を統一しちゃうわけね、先に……』
「さて、ここまでがファイル検索部分。ここまでで、指定されたフォルダの
中身は検索できました」
『ってことは、次はフォルダの中に潜るわけね』
「そういうこと。それがこの部分」
// フォルダに対して再帰呼び出しをします。
for( int iF1 = 0; iF1 < cFolderStrAry.GetSize(); ++iF1 )
{
if (
( cFolderStrAry.GetAt( iF1 ) == "." ) ||
( cFolderStrAry.GetAt( iF1 ) == ".." )
)
{
// . と .. はスキップします。
continue;
}
// フォルダを連結します。
CString cPathStr = p_pchFolderPath;
if (
( cPathStr.GetAt
( cPathStr.GetLength() - 1 ) != '\\' ) ||
(
( cPathStr.GetAt
( cPathStr.GetLength() - 1 ) == '\\' ) &&
( _mbsbtype
( (const unsigned char *)(LPCTSTR)cPathStr
, cPathStr.GetLength() - 1
) == 2 )
)
)
{
// 最後に \ がなければくっつけます。
cPathStr += "\\";
}
cPathStr += cFolderStrAry.GetAt( iF1 );
// 再帰呼び出しします。
iNum += CountMatchFile( cPathStr, p_pchFileName );
}
「 cFolderStrAry にはみつかったフォルダを入れてあるので、その数だけ
ループして処理します」
『おー』
「次に、【.】と【..】を省きます」
if (
( cFolderStrAry.GetAt( iF1 ) == "." ) ||
( cFolderStrAry.GetAt( iF1 ) == ".." )
)
{
// . と .. はスキップします。
continue;
}
『な、何これ……』
「火美ちゃんは DOS コマンドの DIR って使ったことある?」
『時々あるけど……ファイル一覧出すヤツでしょ?』
「こんな感じに出るでしょ」
2004/10/31 02:22 <DIR> .
2004/10/31 02:22 <DIR> ..
2005/02/07 15:04 <DIR> Pragma_twice
1999/03/26 14:11 2,673 Plot.txt
『あ! そういえばこの時に【.】と【..】って出る! 確か、【.】って
今表示してるフォルダで、【..】はひとつ上のフォルダのことだよね』
「そう。実はこのふたつのフォルダも、 FindFirstFile() を使った検索で
取得されるんです」
『げ!』
「ま、 DIR コマンドと同じってことだね」
『でもこれをフォルダにして検索しちゃったら……永久ループ?』
「になっちゃうので、これをスキップするのを忘れないようにしてくださ
い」
『うん……これ結構怖いね……』
「まぁエラーになって落ちるだけだけど」
『? ずっと動きっぱなしじゃないの?』
「うん、その理由は最後に。その前に、フォルダの連結をします」
『フォルダの連結?』
「引数の p_pchFolderPath には、検索対象のフォルダが渡されているで
しょ。たとえば "C:\\WinNT" とか」
『うんうん』
「このフォルダのフルパスに、次に検索するフォルダ、たとえば "system"
をくっつけて "C:\\WinNT\system" にします。それがこれ」
// フォルダを連結します。
CString cPathStr = p_pchFolderPath;
if (
( cPathStr.GetAt
( cPathStr.GetLength() - 1 ) != '\\' ) ||
(
( cPathStr.GetAt
( cPathStr.GetLength() - 1 ) == '\\' ) &&
( _mbsbtype
( (const unsigned char *)(LPCTSTR)cPathStr
, cPathStr.GetLength() - 1
) == 2 )
)
)
{
// 最後に \ がなければくっつけます。
cPathStr += "\\";
}
cPathStr += cFolderStrAry.GetAt( iF1 );
『……あれ? なんか前回と違うような……』
「ごめんなさい、前回のプログラム、間違えてました」
『げ』
「これから説明する日本語処理のところが……」
『だめねー』
「……日本語処理がなければ簡単なんだけどね。まず」
CString cPathStr = p_pchFolderPath;
「で検索したフォルダ "C:\\WinNT" を入れて」
cPathStr += "\\";
「で \ を最後にくっつけて "C:\\WinNT\\" にして」
cPathStr += cFolderStrAry.GetAt( iF1 );
「でその後ろにフォルダ "system" をくっつけて」
『 "C:\\WinNT\system" にするわけねー。でも、なんかその \ をくっつけ
るとこですごく大変なことしてるんだけど……』
「二重に if してるからね。まず」
if( cPathStr.GetAt( cPathStr.GetLength() - 1 ) != '\\' )
「これは検索したフォルダの一番後ろに \ が付いてないか。もし付いてる
ところに \ を付けると \ が二重に付いちゃうから」
『そっか、後ろに付いてないときだけ \ を追加するわけね』
「でも、このチェックだけじゃダメなんです。 Version 11.04 ( No.204 )
を思い出して」
『んー、文字コード……あ!! 【ソ】って、 \ が混ざってるんだ!』
「【ソ】みたいに \ がトレイルバイトにある文字が一番最後にあると、 \
がくっついているって勘違いしちゃうんです。だから」
if( cPathStr.GetAt( cPathStr.GetLength() - 1 ) == '\\' )
「の時、つまり最後に \ のコードがある場合、」
if( _mbsbtype
( (const unsigned char *)(LPCTSTR)cPathStr
, cPathStr.GetLength() - 1
) == 2 )
「で、トレイルバイトなら \ とみなさずくっつける、ってしてます」
『そんなめんどくさいチェックが必要なんだね……ん? なんか変なキャス
トしてるけどこれって?』
「まず、 LPCTSTR にキャストしてるのは cPathStr の文字列ポインタを取
り出すため。 Version 5.22 ( No.087 ) 、 Version 7.09 ( No.129 ) 、
Version 11.20 ( No.220 ) を参照」
『あー、 CString クラスの中に文字列が入ってるから、それを取り出すた
めの operator LPCTSTR を呼ぶってゆーことね』
「そういうこと。次に (const unsigned char *) にキャストしてるのは、
_mbsbtype() の引数がそうだから。これは Version 11.05 ( No.205 ) を参
照」
『比較するときは unsigned でないとまずい、って話ね』
「というわけで、これで cPathStr に次に検索するフォルダのフルパスが入
りました。そこで、このパスで自分自身を呼び出します」
// 再帰呼び出しします。
iNum += CountMatchFile( cPathStr, p_pchFileName );
『あ、再帰呼び出し! Version 13.16 ( No.252 ) のクイックソートの時
にやったね』
「こうやって、再帰呼び出しすれば、ひとつ深いフォルダの中を同じように
検索できるわけです」
『はー、なるほどねー』
「で、先ほどの答。もし無限ループになっていた場合、この再帰呼び出しが
永遠に行われます」
『うん、行われるね』
「さて、再帰呼び出しの復習。この関数の中のローカル変数、たとえば」
int iNum = 0;
「とかは、再帰呼び出しするとどうなる?」
『んー、確か新しく作られるんだよね。関数の中の変数って呼ばれて使われ
るときに作られて、それは同じ関数呼ぶんでも同じ、なんだよね』
「そういうこと。ってことは、再帰呼び出しで CountMatchFile() が 100
段呼ばれたら、 iNum は」
『 100 個作られる……再帰呼び出しで無限ループすると、変数が無限に作
られる……メモリ足りない!』
「というエラーになるわけです。正確には【スタックオーバーフロー】って
いうエラー」
『だから永久に動き続けるわけじゃなくて、途中でエラー出て止まっちゃう
わけね』
「そういうこと」
/*
Preview Next Story!
*/
『ん、これで一応ファイル検索はできてるわけね』
「でも検索中はなにもできない」
『というわけでマルチスレッドの出番!』
「そういうこと。というわけで次回」
< Version 14.29 別スレッドで検索! >
『につづく!』
「いよいよマルチスレッドを使った実践的な内容に!」
『つか今度は間違えないでよね』
「う”」