VC++5.0
による巡回セールス問題プログラミング
1998.9.17
ワタベ
本報告は,Microsoft Visual C++ 5.0(以下VC)を用いたWindowsアプリケーションの作成方法を述べたものである.とは言っても,すべての機能の解説をするのが目的ではなく,具体的な問題(巡回セールスマン問題)を通して,VCの使い方の概要を掴むのが目的である.ただし,筆者自身VCについては初心者であるため試行錯誤的にプログラミングを行っている.たぶん,もっとエレガントな開発の仕方があるだろうと思う.しかし,何とか作ったプログラムでも後から見るとどうやって作ったがわからなくなることが多く,特にVCの場合はなおさらである.そこで,一通りのプログラミングの記録を取っておけば,後でまた違うプログラムを作ろうとするときの参考になるのではないか,という思いでこの記録を残している.いうなれば,自分自身に対するドキュメントである.
VC++5.0
での作業の流れVC++5.0
でのプログラミングは,大きくは以下の2つの作業から構成されると思われる.スケルトンの生成は,アプリケーションの雛形を作るものであり,質問形式で答えていくことで簡単に行える.しかし,できあがったスケルトンはアプリケーションソフトとしての最低限の機能は備えているものの,あたりまえであるが,実際には何の役にも立たない.やはり,次のコーディングを行って初めて役に立つアプリケーションを作成できる.ところが,このコーディング作業が非常にわかりづらく,プログラミング初心者にとっては大きな壁となっている.
それでは,以下でスケルトンの生成とコーディングに分けて,その作業例を述べていく.
[スケルトン(雛形)の生成]
[コーディング]
次に,実際にコーディングを行いアプリケーションを作ってみる.
ここで作成するアプリケーションは,MDI(マルチドキュメントインターフェース)型のもので,
TSP(巡回セールスマン問題)のデータを設定し問題を解くものである.その機能は次のようになる.
以下,順にプログラムを作成する.
<ステップ1>
ドキュメントクラスとビュークラスに都市数と都市の
x-y座標を表す変数を定義し,両クラス間で連動させる.都市の数:
m_Num都市の
x-y座標:m_PosX[], m_PosY[]なるメンバー変数をビュークラスとドキュメントクラスに作成する.ビュークラスはドキュメントウィンドウへの表示を担当し,ドキュメントクラスはファイルとの読み書きを担当する.画面に表示されたデータをファイルに書き込んだり,逆にファイルから読み込んだデータを画面に表示できるように,この2つのクラスのデータは互いに連動していなければならない.
それでは,具体的にコードを記述する.
まず,ドキュメントクラスにメンバ変数を宣言する.
TestDoc.hを開き,class CTestDocの中に
//
アトリビュートpublic:
int m_Num;
int m_PosX[10000];
int m_PosY[10000];
を記述する(上2行はあらかじめ記述されている).
同様に,ビュークラスにメンバー変数を定義する.TestView.hを開きclass CTestViewの中に以下のようなコードを追加する(上3行はあらかじめ記述されている.)
//
アトリビュートpublic:
CTestDoc* GetDocument();
int m_Num;
int m_PosX[10000];
int m_PosY[10000];
つぎに,データの初期化を行う.初期化は,都市数をゼロにセットすることで行える.
ドキュメントクラスのデータの初期化は,OnNewDocument()メンバー関数で行われる.TestDoc.cppを開き,OnNewDocument()関数を探し,その中に以下のコードを追加する(上2行はあらかじめ記述されている).
// TODO:
この位置に再初期化処理を追加してください。// (SDI
ドキュメントはこのドキュメントを再利用します。)m_Num = 0;
OnNewDoument()
関数は,プログラムを起動したときや,「新規作成」メニューなどで新しいドキュメントを作成したときに呼び出される.ビュークラスのデータの初期化は,
OnInitialUpdate()関数で行われる.ドキュメントクラスと違って,OnInitialUpdate()関数はスケルトンでは定義されていない.そこで,新たにこの関数を定義する必要があるが,ここでは,Class Wizardを使って新たな関数を定義することにする.(もちろん,TestView.cppを直接開いてコードを記述しても良いが,かなり複雑になるので,Class Wizardを使って作成するのが便利である.)Class Wizard
を起動し,「メッセージマップ」タブを選択した状態で,以下のように各項目を選択する.クラス名:
CTestViewオブジェクト
ID:CTestViewメッセージ:
OnInitialUpdateそして,「関数の追加」ボタンをクリックする.(すると,
.cppファイルに関数本体の外枠,.hファイルにプロトタイプ宣言が追加されている.)ついで,「コード編集」ボタンをクリックすると,OnInitialUpdate()関数定義部が表示されるので,以下のコードを追加する(実際に記述するのは太線部のみ).
void CTestView::OnInitialUpdate()
{
CView::OnInitialUpdate();
// TODO:
この位置に固有の処理を追加するか、または基本クラスを呼び出してください
// ドキュメントのポインタを取得する
CTestDoc* pDoc = GetDocument();
// ビュークラスのデータメンバを
// 対応するドキュメントクラスの値で更新する
m_Num = pDoc->m_Num;
int i;
for (i=0; i<m_Num; i++)
{
m_PosX[i] = pDoc->m_PosX[i];
m_PosY[i] = pDoc->m_PosY[i];
}
}
この関数では,ドキュメントのポインタを取得し,そのポインタを介してドキュメントクラスのデータの値をビュークラスのデータに代入している.ドキュメントクラスでの初期化は都市数をゼロに設定しているだけであったが,ビュークラスの初期化では,都市数のみではなく,各都市の座標値もコピーしている.これは,ドキュメントクラスでデータをファイルからロードしたときのことを考慮しているためである.
<ステップ2>
画面表示用の機能を作成する.
画面表示の必要が生じると,ビュークラスの
OnDraw()関数が呼び出される.そこで,何かを画面に表示するには,OnDraw()関数に表示のためのコードを記述すればよい.ここでは,「都市数(文字列)」と「各都市位置に円」を表示するものとする.ファイルTestView.cppを開いて,OnDraw()関数の中に以下のようなコードを記述する(実際の記述は太字のみ).
void CTestView::OnDraw(CDC* pDC)
{
CTestDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
// TODO: この場所にネイティブ データ用の描画コードを追加します。
RECT rect;
int i;
for (i=0; i<m_Num; i++)
{
rect.left = m_PosX[i] - 2;
rect.right = m_PosX[i] + 2;
rect.top = m_PosY[i] - 2;
rect.bottom = m_PosY[i] + 2;
pDC->Ellipse(&rect);
}
char buf[100];
sprintf(buf, "
都市数:%d", m_Num);pDC->TextOut(0,0,buf);
}
このコードの前半では,都市位置を中心とする縦横4の正方形を定義し
(rect),その正方形に内接する楕円,すなわち,円をpDCに表示している.pDCはOnDraw()関数の引数で,表示用のウィンドウに対応している.後半部分では,都市数の値を組み込んだ文字列を定義し,その文字列を表示している.この段階でプログラムを実行してみると,
都市数:0
の表示のみなされる.まだ,都市を定義していないので,都市位置を表す円は表示されない.
<ステップ3>
ウィンドウ内でマウスをクリックすると円を表示する機能を追加する.
ここでの処理は,都市を定義することである.都市を定義するのにマウスを用い,適当な位置でクリックするとその位置座標が変数に代入され,また,都市数を一つ増やす.そして,更新された変数の値を使って画面表示を行う.ステップ2で作成したように画面表示の関数
OnDraw()はすでにできているので,各変数の値を更新したらOnDraw()関数を呼び出せば,画面に表示される.さて,
Windowsでは何かのイベントが発生すると,そのイベントに対応したメッセージがアプリケーションに送られてくる仕組みになっている.たとえば,マウスの左ボタンが押されたというイベントが発生すると,それに対応したメッセージ(WM_LBUTTONDOWN)がアプリケーションに送られる.具体的には,
Class Wizardを起動し,「メッセージマップ」タブを選択した状態で,以下のように各項目を選択する.クラス名:
CTestViewオブジェクト
ID:CTestViewメッセージ:
WM_LBUTTONDOWNそして,「関数の追加」ボタンをクリックする.すると,
OnLButtonDown()関数が追加され,「コード編集」ボタンをクリックすると,OnLButtonDown()関数定義部が表示されるので,以下のコードを追加する(実際に記述するのは太線部のみ).
void CTestView::OnLButtonDown(UINT nFlags, CPoint point)
{
// TODO:
この位置にメッセージ ハンドラ用のコードを追加するかまたはデフォルトの処理を呼び出してください// マウスがクリックされた点のx-y座標を配列に格納し,
// 点の個数を +1 する
m_PosX[m_Num] = point.x;
m_PosY[m_Num] = point.y;
m_Num++;
// OnDraw()関数呼び出しを発生させる
Invalidate();
// 文書のポインタを取得する
CTestDoc* pDoc = GetDocument();
// 文書クラスのデータメンバをビュークラスの
// データメンバの新しい値で更新する
pDoc->m_PosX[pDoc->m_Num] = m_PosX[pDoc->m_Num];
pDoc->m_PosY[pDoc->m_Num] = m_PosY[pDoc->m_Num];
pDoc->m_Num = m_Num;
// 文書が修正されたことを通知する
pDoc->SetModifiedFlag(TRUE);
CView::OnLButtonDown(nFlags, point);
}
この関数では,マウスの座標を都市位置変数に格納し,都市数変数を+1している.(マウスの座標はこの関数の引数pointに代入されている.)そして,Invalidate()関数を呼び出すことにより画面表示メッセージを発生させ,結果としてOnDraw()関数を実行している.ここまでで,画面表示は行われるが,マウスクリックにより更新されたのはビュークラスのデータのみなので,以下で,ドキュメントクラスのデータを更新している.また,SetModifiedFlag(TRUE)関数でドキュメントが更新されたことを通知している.こうすることで,データが更新されたにもかかわらず,データを保存せずにアプリケーションを終了しようとすると,確認のダイアログボックスが表示されるようになる.
ここまでの状態で,プログラムを実行すると,マウスクリックにより次々に都市位置が小さな円で表示されるようになり,また,都市数の値がカウントアップされるようになる.また,保存せずに終了しようとすると,保存を促すダイアログボックスがでてくる.(ただし,保存用のコードはまだ記述していないので,保存の操作は行えるが,実際には何も保存されない.)
<ステップ4>
ファイルへのセーブとロード機能を追加する.
スケルトンで既に,ファイルセーブ・ロードのメニューは存在する.そのメニューを選択するとセーブ・ロード作業は実行できるが,実際には何のデータも読み書きされない.これは当然のことで,まだどのデータを読み書きするのか何も書いていないからである.
ファイルへの読み書きは,ドキュメントクラスで行う.TestDoc.cppを開き,Serialize()関数を探す.そして,以下のようなコードを記述する.
void CTestDoc::Serialize(CArchive& ar)
{
int i;
if (ar.IsStoring())
{
// TODO:
この位置に保存用のコードを追加してください。ar << m_Num;
for (i=0; i<m_Num; i++)
{
ar << m_PosX[i];
ar << m_PosY[i];
}
}
else
{
// TODO:
この位置に読み込み用のコードを追加してください。ar >> m_Num;
for (i=0; i<m_Num; i++)
{
ar >> m_PosX[i];
ar >> m_PosY[i];
}
}
}
Serialize()
関数は,メニューの「開く」,「上書き保存」,「名前を付けて保存」などを選択すると自動的に実行される.ここで,追加したコードは,都市数変数,都市位置座標配列の値をファイル(ar)に保存(上),読み込み(下)するものである.
<ステップ5>
新たなメニューを追加する.
都市の巡回経路の初期値(入力された順番に都市を結ぶ)の表示を「
Line」メニューを選択することで行わせたい.そこで,アプリケーションに新たなメニューを追加し,そのメニューが選択されたときの動作を記述する.MDI
アプリケーションに新たなメニューを追加するには,インターフェイス部分は「Resource View」でグラフィカルに設計できる. まず,「Resource View」タブをクリックし,「Testリソース」をダブルクリックし,「Menu」をダブルクリックし,「IDR_TESTTYPE」をダブルクリックする.すると,現在のメニューが表示される.新たなメニュー項目を追加するには,既存のメニューの横に並んでいる四角い枠をダブルクリックする.すると「メニューアイテムプロパティ」ダイアログボックスが出てくるので,その「キャプション」欄にそのメニューに付ける名前を入力し(ここでは,「TSP」とする),ダイアログボックスを閉じる.さらに,今追加したメニューをクリックすると,その下に四角い枠が出てくるので,それをダブルクリックし,「ID」と「キャプション」を入力する(ここでは,ID:ID_TSP_LINE,キャプション:&Line).キャプションはそのメニューの名前であり,IDは,Class WizardのオブジェクトIDに対応している.そのオブジェクトに関連づけて,プログラムの中の変数や関数を定義することになる.さて,次に今定義したメニュー
Line(ID:ID_TSP_LINE)に関連づけて,そのメニューが選択されたときの動作を記述する.具体的にはLineメニューが選択されたときに実行する関数を定義する.Class Wizardを起動し,「メッセージマップ」タブを選択した状態で,以下のように各項目を選択する.クラス名:
CTestViewオブジェクト
ID:ID_TSP_LINEメッセージ:
COMMANDCOMMANDは,メニューが選択されたときに発せられるメッセージである.次に,「関数の追加」ボタンをクリックすると,「メンバ関数の追加」ダイアログボックスが表示され,「メンバ関数名」がオブジェクトIDから機械的に生成されている(ここでは,OnTspLine).そこで,「OK」ボタンをクリックすると,OnTspLine()関数の雛形が生成される.「コード編集」ボタンをクリックすると,TestView.cppが開き,OnTspLine ()関数定義部が表示されるので,以下のコードを追加する(実際に記述するのは太線部のみ).
void CTestView::OnTspLine()
{
// TODO:
この位置にコマンド ハンドラ用のコードを追加してくださいint i;
for (i=0; i<m_Num; i++)
{
m_Line[i] = i;
}
m_Line[m_Num] = 0;
m_LineNum = m_Num + 1;
// OnDraw()関数呼び出しを発生させる
Invalidate();
}
この関数は,
m_Line[]配列に都市の順序を入力された順番に格納し,さらに,最後に最初の都市へ戻るために,最後の都市としてゼロ番の都市を追加している.ここで,まだ宣言していない以下の変数が使われている.
m_Line[]
m_LineNum;
これらの変数は,とりあえず,ビュークラスでのみ使用するので,(ファイルへの読み書きは行わないのでドキュメントクラスでは必要ない),ビュークラスのヘッダーファイル(
TestView.h)で型宣言を行う.TestView.hを開いて,ステップ1で記述した ,int m_PosY[10000];
の下に,以下のコードを追加する.
int m_LineNum;
int m_Line[10000];
さらに,OnTspLine()関数の最後では,Invalidate()関数を呼び出して再描画(OnDraw()関数の起動)をしている.しかし,OnDraw()関数には,まだ,都市間を結ぶ直線を描画するコードを記述していないので,以下のコードをOnDraw()関数に追加する必要がある.
// 巡回ツアー(直線)の表示
pDC->MoveTo(m_PosX[0], m_PosY[0]);
for (i=0; i<m_LineNum; i++)
{
pDC->LineTo(m_PosX[m_Line[i]], m_PosY[m_Line[i]]);
}
また,m_LineNum変数の初期値を0にセットしておく必要があるので,OnInitialUpdate()関数に以下のコードを追加する.
// 直線数を初期化する
m_LineNum = 0;
<ステップ6>
メニューからダイアログボックスを起動する.
TSPを解かせるために色々なパラメータを設定する必要があるが,ここでは,メニューからダイアログボックスを起動し,そこでパラメータ値を設定するものとする.
まず,ダイアログボックスを定義する.「Resource View」で「Dialog」を選択し,マウスの右ボタンメニューで「Dialogの挿入」を選択する.すると,ダイアログボックスの設計ウィンドウが表示されるので,そのウィンドウをクリックして右ボタンメニューでプロパティを選択し,「ID」と「キャプション」を設定する.ここでは,
ID
:IDD_TSP_DIALOGキャプション:パラメータの設定
とする.次に,ダイアログに以下の項目を追加する.各項目はスタティックテキストとエディットボックスの対で設定する.具体的には,以下を必要回繰り返すことになる.
設定するキャプションと
IDは以下のようにする.個体数:
最大世代数:
IDC_MAXGENエリート数:
IDC_NELITE逆位率:
IDC_RREV最大逆位長:
IDC_MAXLREV最大逆位回数:
IDC_MAXNREV以上の設定が完了したら,
Class Wizardを起動する.(設計中のダイアログをダブルクリックしてもClass Wizardは起動する.)すると,クラスの追加ダイアログが表示されるので,「新規クラスの作成」を選択する.「クラスの新規作成」ダイアログが表示されたら,クラス名,基本クラス,ダイアログIDを設定する.(ここでは,クラス名をCTspDialogに設定する.するとファイル名が自動的にTspDialog.cppに設定される.基本クラスはCDialogにダイアログIDはIDD_TSP_DIALOGになっているのでそのまま利用する.)次に,
Class Wizardの「メンバ変数」タブで各エディットボックスに付けたIDを選択して「変数の追加」を行う.ここでは以下のようにメンバ変数を定義する.IDC_PSIZE : int : m_PSize
IDC_MAXGEN : long : m_MaxGen
IDC_NELITE : int : m_NElite
IDC_RREV : double : m_RRev
IDC_MAXLREV : int : m_MaxLRev
IDC_MAXNREV : int : m_MaxNRev
以上で,パラメータ設定用のダイアログボックスの設定がとりあえずできたので,次にメニューを追加して,そのメニューでダイアログボックスを起動するための設定を行う.
「
Resource View」で「Menu」の「IDR_TESTTYPE」を選択し,前に追加したTSPメニューの項目に「パラメータ」を追加する.キャプションをパラメータとし,IDをパラメータ設定用ダイアログボックスに付けたID(IDD_TSP_DIALOG)に設定する.さて,ダイアログボックスは新規クラス(
CTspDialog)として定義されている.メニューはビュークラスにある.したがって,メニューからダイアログボックスを起動するには,ビュークラスからCTspDialogクラスを呼び出さなくてはならない.つまり,ビュークラスでCTspDialogクラスのオブジェクト(変数)を定義し,そのオブジェクト経由でダイアログボックスの表示を行うことになる.具体的には,
TspView.hに以下のコードを追加する.まず,先頭部分に次のコードを記述する.
#include "TspDialog.h"
そして,アトリビュートの部分に,前に記述したコードの下に,以下のコードを記述する(追加部分は太字の1行のみ).
int m_Line[10000];
CTspDialog m_dlg;
ついで,Class Wizardを起動し,「メッセージマップ」タブを選択して,以下のように各項目を設定して,「関数の追加」を行う.
クラス名:CTestView
オブジェクト
ID:IDD_TSP_DIALOGメッセージ:
COMMANDすなわち,「
TSP」−「パラメータ」メニューが選択されたときに実行する関数を定義しようとしているのである.「コード編集」ボタンを押したときに出てくる位置に以下のコードを追加する(実際の追加は太字部分1行のみ).
void CTestView::OnTspDialog()
{
// TODO:
この位置にコマンド ハンドラ用のコードを追加してくださいm_dlg.DoModal();
}
以上で,メニューからダイアログボックスを起動できるようになった.エディットボックスに数値を入力することもできる.しかし,エディットボックスに数値を入力しただけでは各エディットボックスに対応するメンバ変数の値は書き変わっていない.エディットボックスに入力された値でメンバ変数の値を更新するには,
UpdateData(TRUE)関数を呼び出す必要がある.それでは,いつどこで呼び出すべきか.作成したダイアログボックスには,初めから[OK]ボタンと[キャンセル]ボタンが付いている.そこで,この[OK]ボタンにメンバ変数を更新するためのコードを割り付けることにする.ついでに[OK]ボタンを押すと,各メンバ変数の値を表示するようにする.実際の作業は,
Class Wizardを起動し「メッセージマップ」タブを選択して,各項目を以下のように設定し,「関数の追加」ボタンを押す.クラス名:
CTspDialogオブジェクト
ID:IDOKメッセージ:
BN_CLICKEDそして,「コード編集」ボタンで,
TspDialog.cppファイルのOnOK()関数部を開き以下のようなコードを追加する.
void CTspDialog::OnOK()
{
// TODO:
この位置にその他の検証用のコードを追加してくださいUpdateData(TRUE);
char buf[200];
sprintf(buf, "
個体数:%d 最大世代数:%d エリート数:%d\n逆位率:%lf 最大逆位長:%d 最大逆位回数:%d", m_PSize, m_MaxGen, m_NElite, m_RRev, m_MaxLRev, m_MaxNRev);
MessageBox(buf);
CDialog::OnOK();
}
UpdateData(TRUE)
関数で,エディットボックス内に入力された値でメンバ変数の値を更新し,後の3行で各メンバ変数の値をメッセージボックスを使って表示している.現状では,パラメータの設定ダイアログボックスの各エディットボックスの初期値はすべて0になっているが,適当な初期値が設定されている方が便利である.そこで,各変数の初期値を設定することにする.初期値の設定はダイアログボックスクラスの初期値設定関数(
OnInitDialog())ではなく,ビュークラスの初期値設定関数(OnInitialUpdate())で行うことにする.なぜならば,もし,ダイアログボックスクラスの初期値設定関数で初期値を設定すると「パラメータの設定」ダイアログボックスが開くたびに初期化が行われることになり,これでは,パラメータの値を変更しても次にダイアログボックスを開いたときにはその値がまた初期値に戻ってしまうので具合が悪いからである.それでは,ビュークラスのファイル(
TestView.cpp)を開いて,OnInitialUpdate()関数に以下のコードを追加する.
// GAのパラメータの初期値を設定する
m_dlg.m_PSize = 50;
m_dlg.m_MaxGen = 1000;
m_dlg.m_NElite = 1;
m_dlg.m_RRev = 0.5;
m_dlg.m_MaxLRev = 5;
m_dlg.m_MaxNRev = 3;
<ステップ7>
TSP
を解き,結果をドキュメントウィンドウに表示する.以上で,都市の設定,GAのパラメータの設定(ここでは,TSPを解くための手法としてGA:遺伝的アルゴリズムを想定している)が終わったので,最後にそれらのデータを使ってTSPを解き,結果を表示する.TSPを解くプログラムはインターフェイスから独立させた方が便利なので,TSPを解くクラス(SolveTSP)を作成し,ビュークラスからそれを呼び出して計算させ,結果をもらって表示するものとする.
TSP
を解くのに必要なデータは以下の通りである.都市の数:
Num都市の
x-y座標:PosX[], PosY[]GA
のパラメータ:個体数:
最大世代数:
MaxGenエリート数:
NElite逆位率:
RRev最大逆位長:
MaxLRev最大逆位数:
MaxNRev都市の巡回順序:
Line[]これらのうち
Line[]は計算結果として得られる値である.それでは,以下
SolveTSPクラスの作成とビュークラスからのSolveTSPクラスの呼び出しをコーディングしていく.1.「
Resource View」で「Dialog」の下に「IDD_SOLVE_TSP」を作成する.2.
Class Wizardで「クラスの追加」を行い,SolveTSPクラスを作成する.クラス名:
(基本クラスを
CDialogとしたが,何にするのが良いのかはよくわからない.とりあえず,メッセージボックスなどが使えそうなので,そうした.)3.
SolveTSP.hファイルに以下のコードを追加する.
class SolveTSP : public CDialog
{
//
コンストラクションpublic:
SolveTSP(CWnd* pParent = NULL); //
標準のコンストラクタ
//
メンバ変数int Num;
int PosX[10000], PosY[10000];
int PSize;
long MaxGen;
int NElite;
double RRev;
int MaxLRev;
int MaxNRev;
int Line[10000];
4.「Resource View」でビュークラスのメニューの「TSP」に「実行」メニューを追加する.
5.TestView.hファイルに以下のコードを追加する.
先頭部に,
#include "SolveTSP.h"
アトリビュート部に,
SolveTSP m_tsp;
クラス名:
CTestViewオブジェクト
ID:ID_SOLVE_TSPメッセージ:
COMMANDに「関数を追加」する.
TestView.cpp
ファイルを開いて以下の関数を追加する.
void CTestView::OnSolveTsp()
{
// TODO:
この位置にコマンド ハンドラ用のコードを追加してくださいm_tsp.Exec(m_Num,
m_PosX,
m_PosY,
m_dlg.m_PSize,
m_dlg.m_MaxGen,
m_dlg.m_NElite,
m_dlg.m_RRev,
m_dlg.m_MaxLRev,
m_dlg.m_MaxNRev,
m_Line);
// OnDraw()関数呼び出しを発生させる
Invalidate();
}
すなわち,ビュークラスでメニュー「
TSP」−「実行」を選択すると,SolveTSPクラスのExec()関数が呼び出され,Exec()関数での計算結果(m_Line)がドキュメントウィンドウに表示される.
int Line[10000];
// メンバ関数
void Exec(int, int [], int [], int, long, int, double, int, int, int []);
SolveTSP.cpp
に以下の関数を追加する.
void SolveTSP::Exec(int Num, int PosX[], int PosY[],
int PSize, long MaxGen, int NElite,
double RRev, int MaxLRev, int MaxNRev,
int Line[])
{
char buf[200];
sprintf(buf, "Num=%d, PosX[0]=%d, PosY[0]=%d",
Num, PosX[0], PosY[0]);
MessageBox(buf);
sprintf(buf, "PSize=%d, MaxGen=%d, NElite=%d",
PSize, MaxGen, NElite);
MessageBox(buf);
sprintf(buf, "RRev=%f, MaxLRev=%d, MaxNRev=%d",
RRev, MaxLRev, MaxNRev);
MessageBox(buf);
// Line[1]
とLine[2]を交換するint dummy;
dummy = Line[1]; Line[1] = Line[2]; Line[2] = dummy;
sprintf(buf, "Line[0]=%d, Line[1]=%d, Line[2]=%d",
Line[0], Line[1], Line[2]);
MessageBox(buf);
}
ただし,このコードはExec()関数に正しく引数の値が渡っているかとビュークラスに結果が正しく返っているかを見るためのものである.Line[1]とLine[2]を交換している(都市1と都市2の訪問順序を逆にする)ので,「TSP」−「実行」メニューを選択した後,ドキュメントウィンドウの線のつなぎ方が変わるはずである.
実際にはここにTSPを解くためのコードを記述する必要があるが,ここでは省略する.(C++の文法に則って,SolveTSPクラスにコードを記述すればよい.)