getline

実は、basic_stringの非メンバ関数に、もう一つのgetlineがある。

getline - cpprefjp C++日本語リファレンス

stringに対応しており、非メンバなので、"std::getline"として呼び出すだけである。

stringなので、バッファ上限は不要。

$ ls
getline  getline.cpp  test.txt
$ cat test.txt
ABC
DEF
GHIXYZ
JKL
MNO
$ vi getline.cpp
$ g++ getline.cpp -o getline
$ ./getline
str = [ABC]
str = [DEF]
str = [GHIXYZ]
str = [JKL]
str = [MNO]
EOF
fail
$

このように、行ごとの文字数は気にせず、読み込める。

Sample/cpp/getline/getline/src/getline at master · bg1bgst333/Sample · GitHub

basic_istream::getline

basic_istream::getlineは、1行ごとに指定のバッファに読み込む。

basic_istream::getline - cpprefjp C++日本語リファレンス

よく見ると、char型などの文字配列のバッファには対応してるが、stringオブジェクトには対応してない模様。
となると、バッファを超えた時どうなるか。

fin.getlineの戻り値はfinの参照なので、finがtrueの時は読み込めたということ。
バッファサイズを6にしたので、5文字までは入るが、6文字だとどうなるか。

$ ls
basic_istream  basic_istream.cpp  test.txt
$ vi test.txt
$ cat test.txt
ABC
DEF
GHI
JKL
MNO
$ vi basic_istream.cpp
$ g++ basic_istream.cpp -o basic_istream
$ ./basic_istream
buf = [ABC]
buf = [DEF]
buf = [GHI]
buf = [JKL]
buf = [MNO]
EOF
fail
$

すべての行が5文字以下なので、すべて読み込んでから、最終的にEOFにたどり着いて、failにもなっている。
まあ、EOFまで来ているので問題はない。

$ vi test.txt
$ cat test.txt
ABC
DEF
GHIXYZ
JKL
MNO
$ ./basic_istream
buf = [ABC]
buf = [DEF]
fail
$

この場合、3行目が5文字を超えるので、そこでfailになってしまう。
EOFじゃないので、途中で終了したとわかる。
これを何とかするには、無理矢理続きを読み込ませるなどの方法もあるが、stringを使える方法にした方がいい。

Sample/cpp/basic_istream/getline/src/basic_istream at master · bg1bgst333/Sample · GitHub

basic_istream::tellg

basic_istream::tellgは、ストリームの現在の読み取り位置を取得する。

basic_istream::tellg - cpprefjp C++日本語リファレンス

前回のプログラムで、basic_istream::getで読み込む前、読み込んだ後、の位置を確認してみる。


$ vi basic_istream.cpp
$ g++ basic_istream.cpp -o basic_istream
$ ./basic_istream
fin.tellg() = 0
ABC
fin.tellg() = 3

fin.tellg() = -1
fail
$

0、つまり先頭だったのが、読み込むと、3、つまり区切り文字のカンマ(',')のあるところを指している。
これだと、次に読み込む部分の先頭がカンマ(',')になるので、進まないというか、よくわからんが次読み込んだらfail状態(そしてtellgの値が-1)になる・・・。

Sample/cpp/basic_istream/tellg/src/basic_istream at master · bg1bgst333/Sample · GitHub

basic_istream::get(文字列の取得)

basic_istream::getには、文字列を取得できるオーバーロードメンバ関数がある。

basic_istream::get - cpprefjp C++日本語リファレンス

これで、

$ cat test.txt
ABC
DEF
GHI$

catでこのように見えるテキストファイルを行文字列ごとに取得できるか試してみる。

今回、ファイルなので、ifstreamで読み込む。(ifstreamもbasic_istreamの派生。)
basic_istream::getを2回やって2行読み込めるか、fail状態になるかチェック。

$ ls
basic_istream  basic_istream.cpp  test.txt
$ cat test.txt
ABC
DEF
GHI$ vi basic_istream.cpp
$ g++ basic_istream.cpp -o basic_istream
$ ./basic_istream
ABC

fail
$

わかっていたから、こういうプログラムにしたけど、やはり2行目が読み込めずfail状態。

ファイル入出力をごっちゃにしないために

どうもgetの場合は、読み込み後のファイルポインタの位置が区切り文字より前になっている模様。
(区切り文字のないオーバーロードメンバ関数は、改行('\n')が区切り文字。)

Sample/cpp/basic_istream/get_1/src/basic_istream at master · bg1bgst333/Sample · GitHub

getdelim

getdelimは、getlineの引数に加えて、区切り文字(デリミタともいう)を指定できる。

Man page of GETLINE

test.txtを、

$ cat test.txt
ABCDE;FGHIJ
KLMNO;PQRST
UVWXYZ$

catで見ると、このようなテキストに編集する。
改行とは別にセミコロン(';')でも区切ってくれるように指定する。

どうも、1つしか区切り文字は指定できない模様。
とはいえ、ほぼ、そのまま、getlineをgetdelimに置き換えた。

$ vi getdelim.c
$ cat test.txt
ABCDE;FGHIJ
KLMNO;PQRST
UVWXYZ$ gcc getdelim.c -o getdelim
$ ./getdelim
line = [ABCDE;]
line = [FGHIJ
KLMNO;]
line = [PQRST
UVWXYZ]
$

前後の'['と']'でわかるが、セミコロンで区切られたが、改行では区切られていない。
そして、セミコロンも含められている。
manでは、getlineと同じ並びにあるが、あくまでも1つだけ指定できる区切り文字のみで区切ることしかしない。(しかも区切り文字を含めている。)

Sample/unixsyscall/getdelim/getdelim/src/getdelim at master · bg1bgst333/Sample · GitHub

getline

C++のgetline(後で扱う)に行く前に、C言語にもgetlineはある。

Man page of GETLINE

行ごとに文字列を取得していくが、C++の場合と違って、区切り文字(デリミタ)指定の引数が無い。
(その場合は、getdelimを使う模様・・・。)
また、ISO非標準(POSIX標準)なので、"UNIXシステムコール"カテゴリにした。
(この辺のカテゴリ分けも考えないと・・・。)
さらに、NULLのメモリ領域ポインタポインタを渡すと、動的にバッファを確保してくれるらしいが、今回は自前で用意してみる。

feofで使ったtest.txtを持ってきて読み込む。

$ cat test.txt
ABCDE
FGHIJ
KLMNO$ vi getline.c
$ gcc getline.c -o getline
$ ./getline
line = [ABCDE
]
line = [FGHIJ
]
line = [KLMNO]
$

取得した行文字列を'['と']'で括ったが、行文字列に改行は含まれている。

Sample/unixsyscall/getline/getline/src/getline at master · bg1bgst333/Sample · GitHub

basic_istream::readsome

basic_istream::readsomeは、指定された文字数以下の文字列を入力ストリームから読み取る。

basic_istream::readsome - cpprefjp C++日本語リファレンス

readは文字数ちょうどなので、文字数に達しなかった場合、EOFやfailとなったが、こちらはそうならない。

まず、readのおさらい。

// ヘッダのインクルード
#include <iostream> // C++標準入出力
#include <sstream> // 新文字列ストリーム

// main関数
int main(){

  // 変数の初期化.
  std::istringstream iss("ABCDE"); // issを"ABCDE"で初期化.
  char str[] = "XXXXXXXXXX"; // strを"XXXXXXXXXX"で初期化.

  // readで読み込み.
  iss.read(str, 5); // strに5文字読み込み.
  std::cout << "str = " << str << std::endl; // strを出力.

  // issの状態
  if (iss.eof()){ // EOFのチェック.
    std::cout << "EOF" << std::endl; // "EOF"を出力.
  }
  if (iss.fail()){ // fail状態のチェック.
    std::cout << "fail" << std::endl; // "fail"を出力.
  }

  // プログラムの終了
  return 0; // 0を返して正常終了.

}

iss.readで"ABCDE"の5文字ぴったりを読み込む。

$ vi basic_istream.cpp
$ g++ basic_istream.cpp -o basic_istream
$ ./basic_istream
str = ABCDEXXXXX
$

成功。

// ヘッダのインクルード
#include <iostream> // C++標準入出力
#include <sstream> // 新文字列ストリーム

// main関数
int main(){

  // 変数の初期化.
  //std::istringstream iss("ABCDE"); // issを"ABCDE"で初期化.
  std::istringstream iss("ABC"); // issを"ABC"で初期化.
  char str[] = "XXXXXXXXXX"; // strを"XXXXXXXXXX"で初期化.

  // readで読み込み.
  iss.read(str, 5); // strに5文字読み込み.
  std::cout << "str = " << str << std::endl; // strを出力.

  // issの状態
  if (iss.eof()){ // EOFのチェック.
    std::cout << "EOF" << std::endl; // "EOF"を出力.
  }
  if (iss.fail()){ // fail状態のチェック.
    std::cout << "fail" << std::endl; // "fail"を出力.
  }

  // プログラムの終了
  return 0; // 0を返して正常終了.

}

issのコンストラクタに指定する文字列を"ABC"に変える。
文字数が足りないので、

$ vi basic_istream.cpp
$ g++ basic_istream.cpp -o basic_istream
$ ./basic_istream
str = ABCXXXXXXX
EOF
fail
$

EOFもfailも出る。

// ヘッダのインクルード
#include <iostream> // C++標準入出力
#include <sstream> // 新文字列ストリーム

// main関数
int main(){

  // 変数の初期化.
  std::istringstream iss("ABCDE"); // issを"ABCDE"で初期化.
  //std::istringstream iss("ABC"); // issを"ABC"で初期化.
  char str[] = "XXXXXXXXXX"; // strを"XXXXXXXXXX"で初期化.

  // readsomeで読み込み.
  //iss.read(str, 5); // strに5文字読み込み.
  iss.readsome(str, 5); // strに5文字読み込み.
  std::cout << "str = " << str << std::endl; // strを出力.

  // issの状態
  if (iss.eof()){ // EOFのチェック.
    std::cout << "EOF" << std::endl; // "EOF"を出力.
  }
  if (iss.fail()){ // fail状態のチェック.
    std::cout << "fail" << std::endl; // "fail"を出力.
  }

  // プログラムの終了
  return 0; // 0を返して正常終了.

}

文字列を"ABCDE"に戻して、今度はreadではなくreadsomeで読み込む。

$ vi basic_istream.cpp
$ g++ basic_istream.cpp -o basic_istream
 ./basic_istream
str = ABCDEXXXXX
$

文字数はぴったりなので、当然成功。
さて、問題はここから。

issのコンストラクタに再び"ABC"を指定。
文字数は足りないが、readsomeで読み込むと、

$ vi basic_istream.cpp
$ g++ basic_istream.cpp -o basic_istream
$ ./basic_istream
str = ABCXXXXXXX
$

ここではfailどころかEOFも出なかった。

Sample/cpp/basic_istream/readsome/src/basic_istream at master · bg1bgst333/Sample · GitHub