「C++Builder3パワフルテクニック大全集」は、インプレス社から1998年に発売された書籍です。C++Builderを使うにあたっての便利なテクニックがたくさん紹介されていて、他の本と比べると高めの値段設定ですが、それだけの価値はあると思います。しかし、この本はなぜかバグが多く、結構困ることもあります。(他の本よりよく見てるから気づきやすいだけかもしれませんが。)
私が出会った最大の問題は、文字列の検索と置換に関するテクニックのところで、原著のままソースをコピーしたためか日本語(2バイト文字)に対応しておらず、それ以前に条件によっては無限ループに陥るという困ったものでした。今はもちろん直してありますが、拙作の"Word Bank"でも正式公開前のバージョンに書籍のソースをほぼそのまま利用したため、無限ループになってしまいました。
気づいてから数時間で問題を修正し、バージョンアップをしました。ついでにこの本の編集部のほうにメールで報告し、WWWページでの公開許可にもOKの返事を出したにもかかわらず、なぜか2ヶ月以上経った今もWWWページ上の正誤リストに反映されていません。せっかくなので、このページで公開します。
なお、以下の文章は99/08/06に私が編集部当てに送信したメールのほぼ全てをそのまま引っ張ってきたものです。
確認した書籍は、98年7月11日発行の初版です。
3.2 ワードプロセッサに検索と置換の機能を加えるには(P.135)
のプログラムをそのとおりに作成し、実行すると次のような現象が発生します。
また、付属CD−ROMに入っているこの章のプログラム(SearchReplace.exe)でも同じです。
確認した現象は次のとおりです。(置換と検索、両方に別のバグがあります)
1. 置換により、全て置換しようとすると同じ個所に対して無限に置換を行ってしまう。
この結果、プログラムが制御不能になり、他のプログラムやOSに影響を与えることがある。
例 :
RichEditの内容 : "Testを行います。Testです。"
この状態で置換ダイアログから"Test"を"First Test"に「全て置換」ボタンにより
置換する。(オプションは任意)
期待される結果 : "First Testを行います。First Testです。"
しかし、全て置換を行うと、"First First First First First First First ....."
のように同じ個所に対して繰り返し置換を繰り返してしまい、
いつまでたってもとまりません。
このバグは、置換後に置換前の文字があった開始位置の1バイト次の文字から
再び置換しようとしてしまうので、この例のように置換後の文字列の
2バイト目以降に置換前の文字列全体を含む場合は再帰的に置換を行ってしまい、
いつまでたっても終わらなくなることが原因です。
これを修正したコードは次のとおりです。
C++Builder4(Update Pack1)により動作は確認しています。
変更箇所の前にはコメントが入っています。
修正対象のコード : 書籍P.138の手順9のコードの一部
if (ReplaceDialog1->Options.Contains (frReplaceAll))
{
TSearchTypes options;
if (ReplaceDialog1->Options.Contains (frMatchCase))
options << stMatchCase;
if (ReplaceDialog1->Options.Contains (frWholeWord))
options << stWholeWord;
// 次の文は変更されています
int position = RichEdit1->FindText (ReplaceDialog1->FindText, 0,
RichEdit1->Text.Length(), options);
while (position >= 0)
{
RichEdit1->SelStart = position;
RichEdit1->SelLength = ReplaceDialog1->FindText.Length ();
RichEdit1->SelText = ReplaceDialog1->ReplaceText;
// 次の文は追加されています
position += ReplaceDialog1->ReplaceText.Length();
// 次の文は変更されています
position = RichEdit1->FindText(ReplaceDialog1->FindText,
position,
RichEdit1->Text.Length()-position,
options);
}
}
以上です。
2. 2バイト文字を含む場合の検索がうまくできない。
例 :
RichEditの内容 : "検索のテストを行います。正しく検索できるか確認してください。"
この状態で検索ダイアログから"検索"を「次を検索」ボタンにより検索する。
(オプションは「下に検索」)
期待される結果 : 1回目の検索で1つ目の”検索”が見つかり、
2回目の検索で2つ目の”検索”が見つかる。
しかし、実際には1回目は見つかるものの2回目の検索をかけても文字列を見つけることが
できません。
このバグは、2バイト文字に対しての処理がなされていないために起こります。
原著は英語版を対象にしていたので問題ないのですが、
日本語版では日本語を扱うために「次の文字に移る=1バイト足す」
の式が成り立たないことがあることが原因です。
また、上方向の検索はアルゴリズム的に書籍に掲載されていたものは
かなり非効率なのですが、同じアルゴリズムのまま修正しています。
修正対象のコード : 書籍P.137の手順7のコードの一部
// 上下どちらの方向に検索するかを確認する
if (dialog->Options.Contains (frDown))
{
// 下方向の方が簡単である
int start = RichEdit1->SelStart;
// 次のif文内は変更(追加)されています
if (RichEdit1->SelLength != 0){ // これでカーソル位置を一致させることができる
if (ByteType(RichEdit1->Text,start+1)==mbLeadByte){
// 2バイト文字の1バイト目のときは2進める
start += 2;
}else{
// それ以外(1バイト文字のはず)のときは1進める
start += 1;
}
}
// 次の文は変更されています
position = RichEdit1->FindText (dialog->FindText,
start,
RichEdit1->Text.Length() - start,
options) ;
}else if(RichEdit1->SelStart > 0){
// 次の行(コメント)は、書籍では下方向になっていたので修正しました。
// 上方向に検索する場合、前に一致したものを見つけるまで、
// ループしなければならない
int search = -1 ;
do{
position = search ;
// 次の文は追加されています
int lastpos = RichEdit1->SelStart + RichEdit1->SelLength-1;
// 次の文は追加されています
if((lastpos>0)&&(ByteType(RichEdit1->Text,lastpos)==mbLeadByte))
lastpos--;
// 次の文は追加されています
if(ByteType(RichEdit1->Text,search+1)==mbLeadByte){
// 次の文字が2バイト文字の1バイト目のとき
search += 2;
}else{
// それ以外(1バイト文字のはず)のとき
search += 1;
}
// 次の文は変更されています
search = RichEdit1->FindText (dialog->FindText,
search,
lastpos - search,
options) ;
}while (search >= 0) ;
}else{
// バッファーの最初から上方向に検索する
position = -1 ;
}
以上です。