の内容をstd::vector
画面に印刷するにはどうすればいいですか?
以下を実装するソリューションoperator<<
も良いでしょう:
template<container C, class T, String delim = ", ", String open = "[", String close = "]">
std::ostream & operator<<(std::ostream & o, const C<T> & x)
{
// ... What can I write here?
}
別個の関数なしで、これまでに作成したものは次のとおりです。
#include <iostream>
#include <fstream>
#include <string>
#include <cmath>
#include <vector>
#include <sstream>
#include <cstdio>
using namespace std;
int main()
{
ifstream file("maze.txt");
if (file) {
vector<char> vec(istreambuf_iterator<char>(file), (istreambuf_iterator<char>()));
vector<char> path;
int x = 17;
char entrance = vec.at(16);
char firstsquare = vec.at(x);
if (entrance == 'S') {
path.push_back(entrance);
}
for (x = 17; isalpha(firstsquare); x++) {
path.push_back(firstsquare);
}
for (int i = 0; i < path.size(); i++) {
cout << path[i] << " ";
}
cout << endl;
return 0;
}
}
ベストアンサー1
C++11 コンパイラをお持ちの場合は、範囲ベースの for ループ (下記参照) を使用するか、反復子を使用することをお勧めします。ただし、いくつかのオプションがあり、それらすべてを以下で説明します。
範囲ベースの for ループ (C++11)
C++11 (およびそれ以降) では、次のような新しい範囲ベースの for ループを使用できます。
std::vector<char> path;
// ...
for (char i: path)
std::cout << i << ' ';
char
for ループ ステートメントの型は、ベクトルの要素の型である必要がありpath
、整数インデックス型であってはなりません。つまり、path
は型 であるためstd::vector<char>
、範囲ベースの for ループに出現する型は ですchar
。ただし、明示的な型がプレースauto
ホルダー型に置き換えられることがよくあります。
for (auto i: path)
std::cout << i << ' ';
明示的な型を使用するかキーワードを使用するかに関係なくauto
、オブジェクトi
にはオブジェクト内の実際の項目のコピーである値がありますpath
。したがって、i
ループ内のへのすべての変更はそれ自体では保持されませんpath
。
std::vector<char> path{'a', 'b', 'c'};
for (auto i: path) {
i = '_'; // 'i' is a copy of the element in 'path', so although
// we can change 'i' here perfectly fine, the elements
// of 'path' have not changed
std::cout << i << ' '; // will print: "_ _ _"
}
for (auto i: path) {
std::cout << i << ' '; // will print: "a b c"
}
i
for ループ内でものコピーされた値を変更できないようにしたい場合は、 の型を次のようi
に強制することができます。const char
for (const auto i: path) {
i = '_'; // this will now produce a compiler error
std::cout << i << ' ';
}
内の項目を変更して、その変更がfor ループの外部path
でも維持されるようにしたい場合は、次のような参照を使用できます。path
for (auto& i: path) {
i = '_'; // changes to 'i' will now also change the
// element in 'path' itself to that value
std::cout << i << ' ';
}
を変更したくない場合でもpath
、オブジェクトのコピーがコストがかかる場合は、値によるコピーではなく const 参照を使用する必要があります。
for (const auto& i: path)
std::cout << i << ' ';
イテレータ
C++11 より前では、標準的な解決策はイテレータを使用することでしたが、これは今でも完全に受け入れられます。イテレータは次のように使用されます。
std::vector<char> path;
// ...
for (std::vector<char>::const_iterator i = path.begin(); i != path.end(); ++i)
std::cout << *i << ' ';
for ループ内でベクトルの内容を変更する場合は、iterator
ではなく を使用しますconst_iterator
。
補足: typedef / 型エイリアス (C++11) / auto (C++11)
これは別の解決策ではなく、上記の解決策を補足するものですiterator
。C++11 標準 (またはそれ以降) を使用している場合は、auto
読みやすさを向上させるためにキーワードを使用できます。
for (auto i = path.begin(); i != path.end(); ++i)
std::cout << *i << ' ';
ここで、 の型はi
非定数になります (つまり、コンパイラはstd::vector<char>::iterator
の型として を使用しますi
)。これは、begin
メソッドを呼び出したため、コンパイラがそこから の型を推測したためですi
。代わりに メソッドを呼び出すとcbegin
(const の場合は "c")、i
は になりますstd::vector<char>::const_iterator
。
for (auto i = path.cbegin(); i != path.cend(); ++i) {
*i = '_'; // will produce a compiler error
std::cout << *i << ' ';
}
コンパイラが型を推測することに不安がある場合は、C++11 では型エイリアスを使用して、ベクターを常に入力する必要を回避できます (身に付けておくとよい習慣です)。
using Path = std::vector<char>; // C++11 onwards only
Path path; // 'Path' is an alias for std::vector<char>
// ...
for (Path::const_iterator i = path.begin(); i != path.end(); ++i)
std::cout << *i << ' ';
C++11 コンパイラにアクセスできない場合 (または何らかの理由で型エイリアス構文が気に入らない場合) は、より伝統的な を使用できますtypedef
。
typedef std::vector<char> Path; // 'Path' now a synonym for std::vector<char>
Path path;
// ...
for (Path::const_iterator i = path.begin(); i != path.end(); ++i)
std::cout << *i << ' ';
サイドノート:
この時点で、イテレータについてこれまでに聞いたことがあるかもしれませんし、イテレータが「使用すべき」ものであると聞いたことがあるかもしれませんし、その理由を疑問に思っているかもしれません。その答えを理解するのは簡単ではありませんが、簡単に言えば、イテレータは操作の詳細からユーザーを保護する抽象化であるという考え方です。
詳細を自分で記述するよりも (「詳細」とは、ベクトルの要素に実際にアクセスするコードです)、必要な操作 (順次アクセスなど) を実行するオブジェクト (反復子) があると便利です。for ループでは、反復子に値を返すように要求しているだけであることに留意してください ( *i
、ここでi
は反復子)。反復子path
自体と直接やり取りすることはありません。ロジックは次のようになります。反復子を作成し、ループするオブジェクト ( iterator i = path.begin()
) を渡します。その後は、反復子に次の値を取得するように要求するだけです ( *i
)。反復子がそれをどのように実行するかを正確に心配する必要はありません。それは反復子の仕事であり、あなたの仕事ではありません。
わかりましたが、ポイントは何でしょうか。値の取得が簡単ではなかったらどうでしょう。少し手間がかかったらどうでしょう。心配する必要はありません。反復子がそれを処理してくれるからです。反復子が詳細を整理してくれるので、必要なのは値を尋ねることだけです。さらに、コンテナーを別std::vector
のものに変更したらどうでしょう。理論上は、新しいコンテナー内の要素にアクセスする方法の詳細が変わっても、コードは変わりません。反復子が舞台裏ですべての詳細を整理してくれるので、コードを変更する必要はまったくありません。前と同じように、反復子にコンテナー内の次の値を尋ねるだけです。
したがって、これはベクトルをループするためのやり過ぎのように思えるかもしれませんが、反復子の概念の背後には十分な理由があるため、反復子の使用に慣れたほうがよいでしょう。
インデックス作成
整数型を使用して、for ループ内のベクトルの要素を明示的にインデックスすることもできます。
for (int i=0; i<path.size(); ++i)
std::cout << path[i] << ' ';
これを実行する場合は、コンテナーのメンバー タイプが使用可能で適切であれば、それを使用する方が適切です。 には、このジョブ用にstd::vector
呼び出されるメンバー タイプがありますsize_type
。これは、メソッドによって返されるタイプですsize
。
typedef std::vector<char> Path; // 'Path' now a synonym for std::vector<char>
for (Path::size_type i=0; i<path.size(); ++i)
std::cout << path[i] << ' ';
ソリューションよりもこれを使用しないのはなぜでしょうかiterator
? 単純なケースではそれが可能ですが、 を使用すると、iterator
上で簡単に説明したいくつかの利点が得られます。そのため、正当な理由がない限り、この方法は避けることをお勧めします。
std::コピー (C++11)
見るジョシュアの答えSTL アルゴリズムを使用してstd::copy
、ベクトルの内容を出力ストリームにコピーできます。私はこの方法を使用しないということ以外、特に付け加えることはありません。ただし、習慣以外にそれを行う理由はありません。
std::ranges::copy (C++20)
完全性のために、C++20 では範囲が導入されました。これは の範囲全体に作用できるためstd::vector
、 および は必要ありませbegin
んend
。
#include <iterator> // for std::ostream_iterator
#include <algorithm> // for std::ranges::copy depending on lib support
std::vector<char> path;
// ...
std::ranges::copy(path, std::ostream_iterator<char>(std::cout, " "));
最近のコンパイラ(GCCでは明らかに少なくともバージョン10.1) の場合、C++20 の機能がいくつか利用可能であっても、範囲サポートは利用できない可能性があります。
std::ostream::operator<< をオーバーロードする
参照クリスの回答は以下これは、オーバーロードで上記のソリューションの 1 つを実装する必要があるため、他の回答を補完するものになりますが、利点はコードがはるかにクリーンになることです。上記のソリューションの使用方法は次のとおりですstd::ranges::copy
。
#include <iostream>
#include <vector>
#include <iterator> // for std::ostream_iterator
#include <algorithm> // for std::ranges::copy depending on lib support
using Path = std::vector<char>; // type alias for std::vector<char>
std::ostream& operator<< (std::ostream& out, const Path& v) {
if ( !v.empty() ) {
out << '[';
std::ranges::copy(v, std::ostream_iterator<char>(out, ", "));
out << "\b\b]"; // use two ANSI backspace characters '\b' to overwrite final ", "
}
return out;
}
int main() {
Path path{'/', 'f', 'o', 'o'};
// will output: "path: [/, f, o, o]"
std::cout << "path: " << path << std::endl;
return 0;
}
これで、オブジェクトを基本型と同じように出力ストリームに渡すことができますPath
。上記の他のソリューションのいずれかを使用するのも同様に簡単です。
結論
ここで紹介したソリューションはどれも機能します。どれが「最良」かは、あなた次第です (コンテキストやコーディング標準にもよります)。これより詳細なことは、長所と短所を適切に評価できる別の質問に残しておくのが最善でしょうが、いつものようにユーザーの好みが常に影響します。紹介したソリューションはどれも客観的に間違っているわけではありませんが、各コーダーにとってより見栄えのよいものもあるでしょう。
補遺
これは、私が以前投稿した解決策を拡張したものです。その投稿が注目を集め続けたため、それを拡張し、ここに投稿された他の優れた解決策、少なくとも私が個人的に過去に少なくとも一度は使用した解決策を参照することにしました。ただし、私が忘れていたり、知らない良い提案があるかもしれないので、読者には以下の回答を確認することをお勧めします。