システム部のNです。
このブログでは仕事に関係することを全く書いたことがなかったので今回は仕事に関係することを書こうと思います。
今、私は「Microsoft Visual Studio 2017(VS)」を使って、「Visual C++(VC++)(MFC)」で10年ぶりくらいにプログラムを作っております。
ずっとphp+javascriptしかやってなかったのでかなり苦戦中です。
久々にやってみると、かなり環境が変わっていました。
まず「Visual Studio Community 2017」というバージョンが無料で手に入る。
Unicode対応の本格化によりMFCのCStringが、クラステンプレートCStringTになっている。
文字列リテラルを_T()関数で囲わないといけない。
下記の連想配列構文が用意されている。
CString str = _T(“文字列リテラル”);
CStringW wStr = _T(“ワイド文字列”);
CStringA ascii(str);
// ↑asciiとして char* を使いたいときの変換方法
std::map<CString, CString> aAssoc;
aAssoc[str] = CString(wStr);
aAssoc[str + _T(“1”)] = CString(ascii);
aAssoc[str + _T(“2”)] = _T(“文字列”);
for each(pair<CString, CString> p in aAssoc) {
p.first; // $key
p.second; // $value
}
などです。
ちょうどいい機会なので、php,javascriptのプログラマーがVC++へ移行するときにはまりそうなポイントを書こうかと思いました。
■1.まずこれらのキーワードを良く理解する。
値渡し
参照渡し
ポインタ渡し
コピーコンストラクタ(値渡しされるときの初期化処理)
キャスト演算子(暗黙の型変換が行われるのはこれのおかげ)
代入演算子(コピーコンストラクタとは別物)
アクセスバイオレーション
int fnc1_cast(TCHAR* pSTr);
int fnc2_cp(CString inStr);
int fnc3_Ref(CString &inStr);
int fnc4_ptr(CString *pCStr);
CString str = _T(“文字列リテラル”);
// ↑CString strのコピーコンストラクタが呼ばれている
CStringA aStr(str);
// ↑CStringA aStrのコピーコンストラクタ
str = _T(“代入”);
// ↑CString strの代入演算子が呼ばれている
// str = 100;
// ↑×エラー 100は整数型 このような型変換を行う、コピーコンストラクタ、キャスト演算子、代入演算子が用意されていない。
fnc1_cast(str);
// ↑CString strのキャスト演算子が呼ばれる
fnc2_cp(str);
// ↑CString strのコピーコンストラクタが呼ばれる(値渡し)
fnc3_Ref(str);
// ↑参照を渡しているので、何も呼ばれない。strがそのまま渡される
fnc4_ptr(&str);
// ↑アドレス解決演算子でポインタを渡している
■2.これらのキーワードも良く理解する。
ローカルスコープ
関数スコープ
クラススコープ
ファイルスコープ(static,extern宣言) ※この2つは最初は良く理解しなくてもいいかも、staticの使い方が2種類ある。1つはphpと同じ使い方
グローバルスコープ(extern 宣言) ※これは最初は良く理解しなくてもいいかも
// グローバルスコープ
int g;
extern g;
///// 別ファイル /////
// ファイルスコープ
int f;
class cls
{
int fnc(int a);
int m_cls_int;
static int m_cls_static_int;
}
cls::fnc(int a)
{
// 関数スコープ
int f_int;
f_int = m_cls_int; // クラススコープ
f_int = m_cls_static_int; // クラススコープ
f_int = f; // ファイルスコープ
f_int = g; // グローバルスコープ
{
// ローカルスコープ
int local_int = 0;
// ローカルスコープ範囲ここまで
}
// local_int = 0; // エラー(ローカルスコープ)
// 関数スコープ範囲ここまで
}
// f_int = 0; // エラー(関数スコープ)
cls::m_cls_static_int = 10;
// ↑クラススコープ解決演算子により、グローバル領域で、静的メンバにアクセス
// cls::m_cls_int = 10;
// ↑エラー(静的メンバでない場合は、グローバル領域でアクセスできない)
///// 別ファイル /////
// f = 0; // エラー(ファイルスコープ)
■3.文字列 ( C言語の文字列を理解しておくこと )
※ユニコード対応のcppファイルのソースファイルの文字コードはUTF-8で書くが、BOM付きにしないと文字列リテラルが実行後の画面上で文字化けするので注意!!
文字列は基本的に、VC++(MFC)では『CString』を使ったほうが簡単そうです。
(調べると『std::String』を使ったほうがいいという意見もあるようです)
C++だけ扱えばいいのであれば、文字列は文字列クラスを使えば、javascriptの文字列型と同じような物なので楽に扱えます。
しかし、C++の中でC言語の関数を呼び出す場面がどうしても発生します。
例えば、WinAPI(Win32 API)を使うときは、Cの文字列配列を使用する必要がある。
(WinAPIは、C言語で作られている。
MFCはWinAPIをクラスカプセル化していて、ほとんどのWinAPIはMFCのフレームワーク内でC++の関数として呼び出せるようになっていますが、たまにMFCに取り込まれていないWinAPI関数が存在します。
その場合は、MFCを経由せず、WinAPIを直接呼ぶ。
何らかの装置と通信を行うプログラムを作る場合にその装置が提供するドライバが、C言語で作られているということが非常に多いため、ドライバが提供する関数をC言語で呼び出す必要が出てくるなど、c++プログラマはC言語は絶対理解していないといけない)
C の文字列配列とCStringの変換方法(関係性)を以下に示すので暗記してください。
TCHAR str[32+1];
// ↑C言語の文字列は char配列で最後に’\0’をつける。TCHARは、windowsで日本語を格納できるchar型
LPTSTR pStr;
// LPとはlong pointer の略 WinAPIでよく出てくる LPxxx という大文字の型は、全部何かの型ポインタという意味
TCHAR* pStr;
// 上の行と同じ意味
const TCHAR* constStr = _T(“文字列リテラル”);
// 文字列リテラルは const をつける必要あり、(変更不能の意味)
LPCTSTR constStr = _T(“文字列リテラル”);
// 上の行と同じ意味 LP(ポインタ) C(const)TSTR(TCHARの文字列)
CString cStr;
// 文字列クラス 圧倒的に使いやすいので、文字は文字列クラス を使用する
// 標準c++(std)にStringという文字列クラスもあるが、Windows開発ではCStringを使うほうが、解かりやすいと思われる(std::Stringでも別にプログラムは作れる)
CString cStr = _T(“文字列リテラル”);
// ↑昔はそのまま代入可能だったが、_T()関数で囲わないと、CStringに代入出来なくなった
cStr = str;
// ok CStringのコピーコンストラクタのうち、どれか最適なものが呼ばれる
//もしも、コピーコンストラクタが定義されていない物で初期化しようとするとエラーになる
//例:CString cs = 100; // 100は整数型、c++はエラーになる。php,js は、このコピーコンストラクタが用意されているのでOKということ)
//str = cStr;
// × エラー CStringを使いこなせれば、こんなことしなくていいが もし、この行と同じことがしたい場合、 strcpy(str, cStr)これがc言語の基本形だがエラーになる、厳密には _tcscpy_s(str, sizeof(str), cStr) とする。※str[255+1]と宣言した、配列のポインタである(メモリ領域確保済みということ)
TRACE(_T(“cStr=%sn”), cStr);
// TRACE( _T() );
// ↑TRACE文もTRACE(_T()) をつけないと面倒なことが起きるので注意
BOOL testFunc(LPCTSTR pStr);
// ※ LP”C”TSTR の場合 (const char*)
////////////////////////////////////
// 上記のようなtestFunc関数があった場合
testFunc(cStr);
// これでOK CStringオブジェクトをそのまま渡せば良い キャスト演算子が呼ばれている
BOOL testFunc2(LPTSTR pStr);
// ※ LP TSTR の場合 (char*) 文字列リテラルでない場合
// → すなわち、この関数作成者は 文字列格納用の配列のポインタを欲しがっている
// → phpで言えば 参照渡し の引数を使って関数作るのと同じ
testFunc2(cStr); // × エラーになります
TCHAR hairetu[255+1]; // 正解
testFunc(hairetu);
// こう渡す、大体この形の場合、関数の中でこの配列に、中身に何かを入れて戻してくれます
// TCHAR*で、受け取ったら
CString jikkoukekka = hairetu;
// ↑こうしてすぐ文字列クラスに戻すとプログラム作りが楽になります。
TCHAR *fileName = new TCHAR[ s_fileName.GetLength() + 1 ];
// 配列の大きさを動的に定義するには new する。
// 基本newしたら必要なくなったらdeleteする必要あり!MFCに渡す場合は、MFC側でdeleteしてくれる
// まとめると
//LPCTSTR testFunc3( このようにリターン値をLPCTSTR型にする関数は多分作れないはず・・・ 文字列リテラルが関数スコープ内で消滅するため
LPTSTR testFunc3(
LPTCSTR pConstStr
, LPTSTR pStrArray
, TCHAR cChar
, TCHAR * pConstStr // or pStrArray ?
// ↑文法的にOKだが、これだとどっちを渡せばいいのか?関数を呼びだすときに、良くわからない なので LPTCSTR型、LPTSTR型を Windowsが用意している
// , TCHAR[] pStrArray // × 間違い こんな文法はない
// , TCHAR[32] pStrArray // × 間違い こんな文法はない
, CString csStr
, CString & csStrRef
, CString * pCsStrRef
){
LPTCSTR retPconstStr = “ローカル変数なので関数スコープ内のみで有効、すなわち、この関数実行終了とともにメモリ上から消滅する ( もっと厳密に言えば、retPconstStrの名前でメモリ上にアクセスできなくなる”;
//return (LPTSTR)1;
// キャストで無理やり型を合わせる → コンパイルエラー or 実行時にアクセスバイオレーションが発生する。 LPxxx はポインタなので、実体はメモリ上のアドレスです。プログラムが起動されたときに、OSがプログラムが使用していいメモリのアドレスの範囲を決めます。そのOS許可範囲内ではない 1 というアドレスにアクセスしているため
return csStr1;
// 多分これもエラーになる。この関数は、コンパイルしてないので動かないと思います
}
////////////////////////////////////
// 上記のような testFunc3 関数は
TCHAR * pConstStr = _(“文字列リテラル”);
LPTCSTR pConstStr = _(“文字列リテラル”);
INT len = 16;
TCHAR pStrArray[ 16 + 1 ]; // ○ 静的宣言
//TCHAR pStrArray[ len + 1 ];
// × 間違い 文法エラー 配列に限らず、宣言は静的にしか出来ない
LPTSTR pStrArray = new TCHAR[ len + 1];
// ○ newすれば OK 動的 配列 定義
TCHAR *pStrArray2 = new TCHAR[ len + 1];
// ○ newすれば OK 動的 配列 定義
TCHAR cChar = ‘c’;
// 1文字しか入らない、1文字は” 文字列は “”
CString csStr = pConstStr;
// 右辺から左辺、値渡し CStringのコピーコンストラクタが呼ばれている
CString csStrRef = pConstStr;
// 右辺から左辺、値渡し CStringのコピーコンストラクタが呼ばれている
CString * pCsStrRef = & csStr;
// 実体のポインタ(先頭アドレス)を渡す
// 右辺から左辺、参照(ポインタ)渡し 厳密に言うならばポインタ型のコピーコンストラクタが呼ばれている
testFunc3(
pConstStr
, pStrArray
, cChar
, pConstStr
// , pStrArray // × 間違い こんな文法はない!!
// , pStrArray // × 間違い こんな文法はない!!
, csStr
, csStrRef
, pCsStrRef
);
// こう呼び出す
delete pStrArray ;
// new したら 必要なくなったら delete しないといけない 、 面倒な場合、メンバにしといて、デストラクタでやる
delete pStrArray2;
// new したら 必要なくなったら delete しないといけない 、 面倒な場合、メンバにしといて、デストラクタでやる
■4.連想配列
phpやjavascriptに慣れると、文字列の連想配列がないとプログラムを作る気が起きません。
c++やc言語に連想配列はありませんが『std::map(std は標準c++ライブラリ)』を使えばphpの連想配列と同等なものが使えます。
以下のようにする↓
.h
public:
std::map<CString, CString> m_aIniData; // 宣言
// map<CString, CString> m_aIniData; // using namespace std してるので こうしても良い
// クラスのメンバ変数は m_ を先頭につけるのが MFC スタイルです
// C++ の場合、クラススコープにより、自分のクラスの中のメンバに対して
// $this->m_aIniData このようにいちいちthis->ポインタを使う必要はないです。
// そのため m_ をつけて メンバであることを解かりやすくしています
.cpp
// 連想配列の初期化 keyを決めて、空文字で初期化
// pair で取り出す際 [first] = second; となります
m_aIniData[ _T( “Address” ) ] = “”;
// Address という連想配列キー名称
m_aIniData[ _T( “url” ) ] = “”;
// url という連想配列キー名称
// 配列に値(文字列)セット
for each(std::pair<CString, CString> c in m_aIniData) {
// std::pair<CString, CString> これで型をあらわす。
//テンプレートクラス pair を CString, CString で定義している テンプレートクラス名<構築する型,構築する型,…>
//pair<,> の中の1つ目のCStringが c.first に入り、
//pair<,> の中の2つ目のCStringが c.second に入る
m_aIniData[c.first] = theApp.getIniFile(c.first);
}
// ※テンプレートクラス構文は使うには便利だが、我々のような末端プログラマは自分でテンプレートクラスを作らないこと!! 後々、わけがわからなくなるに違いないため。テンプレートクラスを理解するための勉強のプログラムならok
// 連想配列の内容取得
for each(pair<CString, CString> c in m_aIniData) {
// c.first が key
// c.second が value (値渡し)
// c.second の 参照を取得したければ m_aIniData[ c.first ] とする。これは php と同じだから、説明不要ですね
CMFCPropertyGridProperty *pPGP = new CMFCPropertyGridProperty(c.first, m_aIniData[ c.first ], FALSE);
m_grid.AddProperty(pPGP);
}
■5.Windowsのアプリの構造を理解する
SDI,MDI,ダイアログ(MDIは難しいので、SDI(CFormView)かダイアログベースで初心者は作る)
WinMain関数
ウインドウメッセージ
ウインドウメッセージループ
■6.MFCの構造、使い方を理解する
クラスウィザード
リソースエディタ
CWinApp(プロセス)→theAppグローバル変数 javascriptのwindow.documentの windowオブジェクトと同じようなものと思えばいい
└CMainFrame(メインのウインドウ)→theApp.m_pMainWnd
└CView(m_pMainWndの中に作られる。自分が作るプログラムの表示領域)
└CDocument(自分が作るプログラムに紐付くファイルなどのデータ保存処理などを書くらしいが、初心者レベルでは無視でも良いと思う)
構築順序
CWinApp::InitInstance(); // 1.
CMainFrame::PreCreateWindow(); // 2.
CView::PreCreateWindow(); // 3.
CView::OnInitialUpdate(); // 4.
この関数をjavascriptのonload()関数と同じように使えばいいと思います(SDIの場合)
CWndが見た目の部品の既定クラス →CMainFrame、CView、CEdit、CButtonなどCWndを継承して作られている
以上です。