std::cin
を1行ずつ繰り返し、各行をstd::string
としてアドレス指定したいと思います。どちらが良いですか:
string line;
while (getline(cin, line))
{
// process line
}
または
for (string line; getline(cin, line); )
{
// process line
}
?これを行う通常の方法は何ですか?
UncleBenが彼のLineInputIteratorを立ち上げたので、私はさらにいくつかの代替メソッドを追加すると思いました。まず、文字列プロキシとして機能する非常に単純なクラスです。
class line {
std::string data;
public:
friend std::istream &operator>>(std::istream &is, line &l) {
std::getline(is, l.data);
return is;
}
operator std::string() const { return data; }
};
これでも、通常のistream_iteratorを使用して読みます。たとえば、ファイル内のすべての行を文字列のベクトルに読み込むには、次のようなものを使用できます。
std::vector<std::string> lines;
std::copy(std::istream_iterator<line>(std::cin),
std::istream_iterator<line>(),
std::back_inserter(lines));
重要な点は、reading何かをしているときは、行を指定するということです。それ以外の場合は、文字列だけです。
もう1つの可能性は、ほとんどの人がほとんど知らない標準ライブラリの一部を使用することです。演算子>>を使用して文字列を読み取ると、ストリームは、そのストリームのロケールが空白文字であると言っている文字列までの文字列を返します。特に、すべて行指向の多くの作業を行っている場合は、新しい行を空白としてのみ分類するctypeファセットを使用してロケールを作成すると便利です。
struct line_reader: std::ctype<char> {
line_reader(): std::ctype<char>(get_table()) {}
static std::ctype_base::mask const* get_table() {
static std::vector<std::ctype_base::mask>
rc(table_size, std::ctype_base::mask());
rc['\n'] = std::ctype_base::space;
return &rc[0];
}
};
これを使用するには、読み取るストリームにそのファセットを使用するロケールを吹き込み、通常どおり文字列を読み取るだけで、文字列の演算子>>は常に行全体を読み取ります。たとえば、行を読み込み、一意の行を並べ替えられた順序で書き出す場合は、次のようなコードを使用できます。
int main() {
std::set<std::string> lines;
// Tell the stream to use our facet, so only '\n' is treated as a space.
std::cin.imbue(std::locale(std::locale(), new line_reader()));
std::copy(std::istream_iterator<std::string>(std::cin),
std::istream_iterator<std::string>(),
std::inserter(lines, lines.end()));
std::copy(lines.begin(), lines.end(),
std::ostream_iterator<std::string>(std::cout, "\n"));
return 0;
}
これは、ストリームからのすべての入力に影響することに注意してください。これを使用すると、行指向の入力を他の入力と混合することがほぼ除外されます(たとえば、stream>>my_integer
を使用してストリームから数値を読み取ると、通常は失敗します)。
私が持っているのは(演習として書かれていますが、おそらくいつか役立つことがわかります)、LineInputIteratorです:
#ifndef UB_LINEINPUT_ITERATOR_H
#define UB_LINEINPUT_ITERATOR_H
#include <iterator>
#include <istream>
#include <string>
#include <cassert>
namespace ub {
template <class StringT = std::string>
class LineInputIterator :
public std::iterator<std::input_iterator_tag, StringT, std::ptrdiff_t, const StringT*, const StringT&>
{
public:
typedef typename StringT::value_type char_type;
typedef typename StringT::traits_type traits_type;
typedef std::basic_istream<char_type, traits_type> istream_type;
LineInputIterator(): is(0) {}
LineInputIterator(istream_type& is): is(&is) {}
const StringT& operator*() const { return value; }
const StringT* operator->() const { return &value; }
LineInputIterator<StringT>& operator++()
{
assert(is != NULL);
if (is && !getline(*is, value)) {
is = NULL;
}
return *this;
}
LineInputIterator<StringT> operator++(int)
{
LineInputIterator<StringT> prev(*this);
++*this;
return prev;
}
bool operator!=(const LineInputIterator<StringT>& other) const
{
return is != other.is;
}
bool operator==(const LineInputIterator<StringT>& other) const
{
return !(*this != other);
}
private:
istream_type* is;
StringT value;
};
} // end ub
#endif
したがって、ループをアルゴリズムに置き換えることができます(C++で推奨されるもう1つの方法)。
for_each(LineInputIterator<>(cin), LineInputIterator<>(), do_stuff);
おそらく一般的なタスクは、すべての行をコンテナに格納することです。
vector<string> lines((LineInputIterator<>(stream)), LineInputIterator<>());
最初の1つ。
どちらも同じことをしますが、最初の方がはるかに読みやすく、さらにループが完了した後も文字列変数を保持できます(2番目のオプションでは、forループスコープで囲まれています)
Whileステートメントを使用してください。
スティーブマコネルによるコードコンプリート2の第16.2章(具体的には374ページと375ページ)を参照してください。
引用するには:
whileループの方が適切な場合はforループを使用しないでください。 C++、C#、およびJava)での柔軟なforループ構造の一般的な乱用は、無計画に詰め込みます。 whileループの内容をforループヘッダーに入れます。
。
C++ forループヘッダーに乱暴に詰め込まれたwhileループの例
for (inputFile.MoveToStart(), recordCount = 0; !inputFile.EndOfFile(); recordCount++) {
inputFile.GetRecord();
}
C++ whileループの適切な使用例
inputFile.MoveToStart();
recordCount = 0;
while (!InputFile.EndOfFile()) {
inputFile.getRecord();
recordCount++;
}
途中でいくつかの部分を省略しましたが、うまくいけば、それはあなたに良い考えを与えるでしょう。