Version 18.10
iostream と std::string
「前回はイベントハンドラをメンバ関数に分けてみました」
『ああすると MFC っぽくて分かりやすいね』
「さて今回は、その分けた部分のひとつ、 OnEqual() メンバ関数の話」
『 = ボタン押したときのイベントハンドラだね』
「これまで、このメンバ関数では C ランタイムライブラリを使っていまし
たが、これを Version 5.09 ( No.074 ) とかで使用した iostream 系の
クラスを使ってみます」
『あー、あの >> とか << 使うやつね』
「加えて、今回は std::string クラスという、 CString クラスの代わりに
なるクラスも紹介します」
『ええっ、そんな便利なクラスがあるの!? それがあれば CString クラス
いらないじゃん!』
「そういうこと。これや iostream 系クラスが使いこなせるようになれば、
MFC の必要性がかなり減ってくるかな」
『……? iostream って MFC と関係ある?』
「あるよ。 OnEqual() メンバ関数を見てみて。エディットボックスから
文字列取り出して、それを数値に変換して、計算結果を文字列に変換して、
エディットボックスにセットする、ってしてるでしょ」
『そか、 MFC だとそういうのって ClassWizard でメンバ変数と結びつけ
ちゃえば気にしなくていいんだもんね』
「そう、 Version 3.10 ( No.035 ) の時みたいに、エディットボックスと
ダイアログクラスのメンバ変数を結びつけちゃえば、
UpdateData() メンバ関数で簡単に変換できるのが MFC 」
『それを手作業でしなきゃいけない……』
「 iostream 系クラスを使うことで、その辺が少し楽になるかな」
『少しぃ?』
「ま、それは次回もう少し改善されるから」
『?』
「さて、まずは修正後のプログラムを見てみてください」
// NewCalcDialog.cpp
#include <Windows.h>
#include <stdio.h>
#include <string> // この行を追加。
#include <sstream> // この行を追加。
#include <strstream> // この行を追加。
#include "resource.h"
#include "Dialog.h"
#include "NewCalcDialog.h"
// 略
// = ボタンが押された時のイベントハンドラ。
void CNewCalcDialog::OnEqual()
{
// 各エディットボックスのウィンドウハンドルを取得します。
HWND hLeftWnd = GetDlgItem( m_hWnd, IDC_E_LEFT );
HWND hRightWnd = GetDlgItem( m_hWnd, IDC_E_RIGHT );
HWND hAnswerWnd = GetDlgItem( m_hWnd, IDC_E_ANSWER );
// 各エディットボックス用文字列を用意します。
char pchLeft[256];
char pchRight[256];
// IDC_E_LEFT と IDC_E_RIGHT の文字列を取得します。
GetWindowText( hLeftWnd, pchLeft, 255 );
GetWindowText( hRightWnd, pchRight, 255 );
// 文字列から数値として取り出すためのストリームを用意します。
std::istrstream cIStrStreamLeft( pchLeft );
std::istrstream cIStrStreamRight( pchRight );
// それぞれ int 型に変換します。
int iLeft;
int iRight;
cIStrStreamLeft >> iLeft;
cIStrStreamRight >> iRight;
// 入力チェックを行います。
if (
( cIStrStreamLeft.fail() ) ||
( cIStrStreamRight.fail() )
)
{
MessageBox
( m_hWnd
, "入力に誤りがあります。"
, "エラー"
, MB_OK
);
return;
}
// 文字列変換するためのストリームを用意します。
std::ostringstream cOStringStream;
// 足した結果を文字列変換します。
cOStringStream << ( iLeft + iRight );
// 中にある std::string クラスの、参照を取り出します。
std::string &rcStringAnswer = cOStringStream.str();
// それを IDC_E_ANSWER にセットします。
SetWindowText( hAnswerWnd, rcStringAnswer.data() );
}
『なんかだいぶ違うね……』
「まずは、3つのファイルをインクルードします」
#include <string> // この行を追加。
#include <sstream> // この行を追加。
#include <strstream> // この行を追加。
「 Version 5.09 ( No.074 ) で説明したように、 iostream 系クラスや
std::string クラスが入っている【 C++ 標準ライブラリ】の
ヘッダーファイルは〈 .h 〉がついてないからね」
『やっぱり違和感があるねこれ……』
「次に OnEqual() メンバ関数を見てみます。まず最初の、
GetWindowText() 関数でエディットボックスから文字列を取得する所までは
同じ」
『んー、ここってもう少しなんとかなんない?』
「今のところちょっと無理だね。あとで紹介する std::string クラスには、
CString クラスの GetBuffer() メンバ関数にあたるものがないっていうの
もあるかな」
『 Version 11.08 ( No.208 ) のあれね。でも ReleaseBuffer() メンバ関数
も呼ばなきゃいけないから結構めんどいよね……』
「次に、 Version 5.17 ( No.082 ) で紹介した std::istrstream クラスを
使って、 int 型の整数値として取り出します」
// 文字列から数値として取り出すためのストリームを用意します。
std::istrstream cIStrStreamLeft( pchLeft );
std::istrstream cIStrStreamRight( pchRight );
// それぞれ int 型に変換します。
int iLeft;
int iRight;
cIStrStreamLeft >> iLeft;
cIStrStreamRight >> iRight;
『あー、そういえばこういうの使った!』
「まず std::istrstream クラスの変数を作ります。その際、引数に対象の
文字列を渡します」
『あれ? 前使った時って第2引数に文字列の長さを渡してたよね。今回は
必要ないの?』
「うん、そのときは ReadFile() 関数でファイルから取り出した文字列を
直接渡していて、それだと \0 で文字列の最後が閉じられていないから指定
する必要があったんです」
『普通の文字列だと絶対 \0 が最後にあるけど、 ReadFile() 関数で取り出
すとそうじゃないんだ』
「バイナリー形式で取得するだけだからね。でも今回は普通の文字列を使う
からその必要はないんです」
『なんかややこしい……』
「でも長さ指定しなくていいから簡単でしょ」
『まぁそりゃそうだけど』
「で、そのあと >> で中にある整数値を取り出します」
『これは簡単ねー』
「取り出したら、ちゃんと取り出せたか fail() メンバ関数でチェックしま
す」
// 入力チェックを行います。
if (
( cIStrStreamLeft.fail() ) ||
( cIStrStreamRight.fail() )
)
{
MessageBox
( m_hWnd
, "入力に誤りがあります。"
, "エラー"
, MB_OK
);
return;
}
「 fail() メンバ関数を呼び出すと、直前の入出力でエラーがあったかわか
ります。エラーがあると TRUE が返されます」
『これって必要?』
「とても必要。たとえば文字列が入力されたときや、 int 型に入りきらな
い長さの数字が入力されていたときには、計算すると変な結果になっちゃう
からね」
『そか、そういう入力チェックが必要なんだ』
「実際、 C ランタイムライブラリの atoi() 関数だとこの入力チェックが
できないから、そのためにもこの std::istrstream クラスを使うべきかも
ね」
『はーい』
「入力値の取り出しとチェックができたら、今度は文字列化します」
// 文字列変換するためのストリームを用意します。
std::ostringstream cOStringStream;
// 足した結果を文字列変換します。
cOStringStream << ( iLeft + iRight );
『ん、なんか似てる……けど、初めてみる書き方かも』
「まず std::ostringstream クラスから。このクラスは初めて紹介する
クラスです。中に文字列を持っていて、そこに他の文字列や int 型や
double 型の値をどんどん追加することができるクラスです」
『便利そう!』
「使い方は簡単。まずこのクラスの変数を作って、その変数に << 演算子を
使って渡せばOK 」
『なるほど、さっきは取り出す方だったから >> 使ったけど、今度は << を
使えばいいわけね。 sprintf() 関数と違って "%d" みたいに型を教える必要
ないのも楽そう』
「この std::ostringstream クラスの中には、 std::string クラスが入って
ます」
『お、やっと出てきた!』
「このクラスは CString クラスと同じように文字列を中に入れることがで
きて、しかも長さが自由に変わります」
『つまり配列と違ってどんどん文字列を入れられるわけね。ってことはこの
std::ostringstream クラスも長さを気にする必要がないんだ』
「そういうこと。で、この std::ostringstream クラスの中には
std::string クラスのメンバ変数があって、その参照を str() メンバ関数
で取り出すことができます」
// 中にある std::string クラスの、参照を取り出します。
std::string &rcStringAnswer = cOStringStream.str();
『参照ってあまり使ってなかったからなんか懐かしい……』
「参照については Version 3.22 ( No.047 ) を参考にしてね。これが
std::ostringstream クラスの中の std::string クラスのメンバ変数を参照
しています。で、この data() メンバ関数を呼び出すと、その中の文字列を
取り出すことができます」
// それを IDC_E_ANSWER にセットします。
SetWindowText( hAnswerWnd, rcStringAnswer.data() );
『 std::string クラスの data() メンバ関数で文字列を取り出せる、と』
「それを SetWindowText() 関数に渡せば完了。このようにすることで、
C ランタイムライブラリと同じ処理が C++ 標準ライブラリでできる、とい
うわけです」
/*
Preview Next Story!
*/
『ぶー、 std::string クラスの説明が少ないー!』
「確かに……でも CString クラスと似てるから大丈夫だと思うよ」
『 + 演算子のオーバーロードとかあるの?』
「もちろん」
『なら大丈夫』
「というわけで次回」
< Version 18.11 コントロールとデータのやりとりをするクラスを作る >
『につづく!』
「大丈夫なら次に進めるね」
『えっ、いやそういうのはちょっと』