標準入出力
 
(このコンテンツはメールマガジンの STL & iostream 入門に手を加えたものです。「 STL と iostream が使えるかのチェック」等はメールマガジンの方のページをご覧ください)
 
 標準入出力 ( #14 )
 
 皆さんの想像通り、「標準入出力」に対しても、これまで紹介してきた std::strstream や std::fstream と同様に操作することができます。しかしやはり、違う部分も存在します。
 そのひとつが、標準入出力の場合にはすでに専用の「グローバル変数」が存在するという点です。
  std::strstream を使う時には std::strstream 型変数を、 std::fstream を使う時には std::fstream 型変数を作りました。ところが、標準入出力の場合にはその変数がすでに「グローバル変数」として存在するのです。
 それは std::cout と std::cin です。では使ってみましょう。
[注:ウィンドウズのウィンドウアプリケーションでは、基本的に標準入出力を使用できません。コンソールアプリケーションとして作成して試してみてください]
 
////////////////////////////////////////////////////////////////
//  標準入出力と整数値のやりとりをします。
#include <iostream>    // をインクルードしてください。

bool UseCinAndCout()
{
    int iGotData = 0;    // データを受け取る変数です。
    //    データを受け取ります。
    std::cin
        >> iGotData;
    //    データを出力します。
    std::cout
        << iGotData
        << std::endl;
    return true;
}

/////////////////////////////
	
 
 この関数を実行すると、コンソール上で「入力待ち」状態になります。これは std::cin が数字を入力するのを待っているからです。「標準入力」を行うグローバル変数 std::cin は、 std::fstream 型変数などと同じような方法でデータの入力を受け取ります。
 受け取ったら、入力した値を出力してみます。 std::cout は「標準出力」を行うグローバル変数です。これも std::fstream 型変数などと同じようにデータを出力します。おそらくコンソール上に、先ほど入力した整数値がそのまま表示されることでしょう。
 ここで注意して欲しいのは「入力は std::cin 」「出力は std::cout 」ということです。 #10 で見たように、 iostream は「入力」と「出力」「入出力」の3種類のクラスが用意されています。標準入出力は、この内「入出力」のクラスは最初からは用意されていないということです。
 
 さて、先ほどの例では「整数値を入力する」ということをしていました。ここで「文字列」を入力したらどうなるでしょう。
 結果は、何も入力されません。同時に std::cin.fail() で true が返ってきます。これは #09 で説明したことと同じです。実際に入力されたデータと、受け取る変数が違うとエラーが発生するわけです。
 おそらくファイルや文字列よりも、「間違ったデータが入力される」可能性は標準入出力の方が高いでしょう。そのためのエラーチェックは必ずする必要があります。
 
////////////////////////////////////////////////////////////////
//  標準入出力と整数値のやりとりをします(エラー処理付き)。
#include <iostream>    // をインクルードしてください。

bool UseCinAndCoutWithErrorCheck()
{
    int iGotData;

    while( 1 )
    {
        std::cout
            << "Please input integer."
            << std::endl;

        std::cin
            >> iGotData;
        if( !std::cin.fail() )    // 整数値が入力されました。
            break;    //ループから抜けます。
                
        // 整数値以外が入力されました。
        std::cin.clear();    // エラーをリセットします。
        std::cin.ignore( 1024, '\n' );    // 文字列を破棄します。
    }

    //    データを出力します。
    std::cout
        << "Inpud Data: "
        << iGotData
        << std::endl;

    return true;
}

// 結果
Please input integer.
abcde
Please input integer.
3
Inpud Data: 3

/////////////////////////////
	
 
 このコードでは、ちゃんと整数値を入力したか std::cin.fail() を使ってチェックしています。入力されていればループから抜けて、先ほどと同じように出力します。
 入力されていない場合、もう一度入力し直すことになります。ここで std::cin を、再び入力できる状態に戻します。まず std::cin.clear() を呼び出して、エラー状態をクリアします。これも前章で使った std::fstream::clear() と同じです。
 そして注意して欲しいのは、そのあと std::cin.ignore() を呼び出していることです。 std::cin は内部バッファ内に文字列が貯まっていると、次の読み込みをしてくれません。そのため、 std::cin.ignore() を呼び出す必要があります。この関数は、内部バッファのデータを破棄してくれます。
  std::cin.clear() と std::cin.ignore() のふたつを呼び出すことで、再びデータを入力できるようになります。
 
透明
透明
■ C++ : bool 型 ( #14 )
 C++ で新しく追加された組込型のひとつに「 bool 型」があります。
 bool 型は true か false しか受け取れない特殊な型です。一般に true は「成功」や「正しい」、 false は「失敗」や「間違い」を意味するフラグとして使用します。
 C 言語では、これと同じ機能として BOOL 型、そして TRUE と FALSE という値を使用していました。ですが、これは組込型ではなく、単なる int 型と整数値です。さらに、 TRUE が「なんの値か」が決められていないためしばしばバグの原因にもなっていました。 FALSE はゼロと決められていたので、 FALSE と比較した方がいいと言われるのはこの理由からです。
 C++ の bool 型なら、 true の値が決まっているため、 true との比較も行うことができます。わざわざ ! を使って false と比較する必要はなくなったというわけです。
 
 ……だったらいいんですけどねぇ。
 iostream の中で、 std::strstream::fail() とか std::fstream::eof() といったメンバ関数が登場します。この号でも使ってますね。このメンバ関数は、基本的には bool 型を返します。
 ところが、古いライブラリは int を返します。そのため、この古いライブラリを使用する場合には true でチェックすることができません。
 この号のサンプルコードを見てください。これらのメンバ関数に対して、true や false 、 FALSE を使用せずに直接 if でチェックしていますね。これはこの問題を回避するためのものなのです。
 
 STL と iostream には、このようなライブラリごとの細かい違いがたくさんあります。iostream は古くからあるライブラリのため、 C++ の改訂やコンパイラのバージョンアップに合わせてどんどん変化していき、古いものと新しいものとのギャップが大きくあります。
 STL は比較的新しいライブラリのため、そういった問題はありません。ところが STL は「ヒューレットパッカード版」と「シリコングラフィックス版」の2種類が出回っていて、このふたつの間でもいくつかの差異が存在します。 STL を使う場合にはこの点に注意する必要があります。
 
 標準 C++ ライブラリは「標準だからどのコンパイラでも使える」ことがウリのひとつになっています。ですが、絶対的信頼をおけるとはとても言えないだけの、ライブラリ間の差異が存在します。このライブラリを使い始めたばかりなら、そういったことよりも「可読性」「汎用性」「拡張性」といったメリットを中心に使用していくのがいいでしょう。
透明
透明
 
 
 iostream の連携 ( #15 )
 
 前回は std::cin を使ったときのエラーチェックについて見てみました。
 もうひとつ、同じようにエラーチェックを行う方法を見てみましょう。今度は std::cin からは文字列として読み取り、それを std::strstream を使って整数値として読み取ってみます。
 
////////////////////////////////////////////////////////////////
//  標準入出力と文字列のやりとりをします( std::strstream 使用版)。
#include <iostream>
#include <strstream>
#include <Iomanip>

bool UseCinAndCoutWithStrStream()
{
    char chTempData[130];
    char ch[130];
    std::strstream cStrStream( ch, 128, std::ios::in | std::ios::out );
    int iData = 0;

    while( 1 )
    {
        std::cout
            << "Please input integer."
            << std::endl;
        std::cin
            >> std::setw( 128 )   // 入力文字数を制限します。
            >> chTempData;        // 標準入力から文字列を読み取ります。
        cStrStream
            << chTempData         // 文字列を一度書き込みます。
            << std::ends;
        cStrStream
            >> iData;             // 文字列から整数値を読み取ります。
        if( !cStrStream.fail() )  // 整数値が入力されました。
            break;

        cStrStream.clear();       // エラーをリセットします。
        cStrStream.seekp( 0 );    // 書き込みポインタを元に戻します。
        cStrStream.seekg( 0 );    // 読み取りポインタを元に戻します。
    }

    // データを出力します。
    std::cout
        << "Inpud Data: "
        << iData
        << std::endl;
    return true;
}

// 結果
Please input integer.
abcde
Please input integer.
3
Inpud Data: 3

/////////////////////////////
	
 
 まず std::setw() に注目してください。このマニピュレーターは入力文字数を制限します。
 iostream の「入力系クラス」と >> を使って「文字列」を取り出そうとすると、改行や空白があるまで取り込みます。ファイルや文字列の場合と違って、キー入力の場合には「うっかりキーを押しっぱなしだった」ということなどでものすごい量の文字列が送られる場合があります。このすべてが文字配列へと書き込まれてしまうと、オーバーランでメモリ上のデータが破壊されてしまいます。
 それを防ぐのが std::setw() です。このマニピュレーターの第1引数にセットした値以上の文字列はカットされるので、オーバーランを確実に防ぐことができます。
 ちなみにもうお気づきのことと思いますが、 std::setw() は #03 で出てきたものとまったく同じマニピュレーターです。 #03 では「書き込み」を行うときに使いましたが、このように「読み取り」の時にも「枠」を指定することができるということです。
 
 もうひとつの注意点は、 std::cin からまず chTempData という文字列領域にコピーしていることです。一見、 std::strstream が持つ文字列領域へと直接送ってしまって良さそうですが、すでに結びつけられている std::strstream クラスを無視するのはルール違反です。一度別の領域に取って置いて、それを std::strstream 型変数を通して書き込んで、さらに整数値として取り出す、という方法を取ってください。
 
 エラー処理は std::strstream と同じ処理を行います。 std::strstream::clear() でエラーをリセットし std::strstream::seekp() と std::strstream::seekg() でポインタを先頭に戻します。これで、再び読み取ることができるようになります。
 
 このように、使い慣れた std::strstream を使うこともできますし、その知識を用いて std::cin を使うこともできます。このような「共通した直感的使用方法」が iostream の特長のひとつです。ひとつの方法に縛られず、柔軟に対処できるようにしましょう。
(C)KAB-studio 2001 ALL RIGHTS RESERVED.