文字列、ベクトル反復子

  • C++ 言語では、抽象データ型の豊富なライブラリも定義されています。その中で、string と Vector の 2 つの最も重要な標準ライブラリ タイプは、前者は可変長の文字列をサポートし、後者は可変長のコレクションを表します。もう 1 つの標準ライブラリ タイプはイテレータです。これは文字列とベクトルのサポート タイプであり、文字列内の文字やベクトル内の要素にアクセスするためによく使用されます。組み込み配列はより基本的な型であり、string と l Vector はそれを抽象化したものですこの章では、配列と標準ライブラリの文字列型とベクトル型をそれぞれ紹介します。

  • 組み込み型は、C++ 言語によって直接定義されます。数字や文字などのこれらの型は、ほとんどのコンピューター ハードウェアに固有の機能を表します標準ライブラリは、コンピュータ ハードウェアにまだ直接実装されていない、より高度なプロパティを持つ追加の型のセットを定義しますString は文字の可変長シーケンスを表し、vector は指定された型のオブジェクトの可変長シーケンスを格納します。他の組み込み型と同様、配列の実装はハードウェアと密接に関係しています。したがって、標準ライブラリの文字列型やベクトル型と比較して、配列は柔軟性が若干劣ります。

  • 使用されるライブラリ関数は基本的に名前空間 std に属し、プログラムでもこの点を明示的にマークします。たとえば、std::cin は標準入力からの読み取りを意味します。ここでスコープ演算子 ( :: ) を使用する意味は、コンパイラが演算子の左側の名前によって示されるスコープの右側の名前を検索する必要があるということです。したがって、std: :cin は、名前空間 std で cin という名前を使用することを意味します。名前空間のメンバーは、より簡単な方法でも使用できます。このセクションでは、最も安全な方法の 1 つである using 宣言を学習します。using 宣言を使用すると、特別な接頭辞 (namespace: : など) なしで目的の名前を使用できます。using ステートメントの形式は次のとおりです。

    • using namespace::name;
      
  • 上記のステートメントを宣言すると、名前空間内の名前に直接アクセスできます。

    • ここに画像の説明を挿入します
  • C++ 言語の形式は比較的自由なので、1 行に using ステートメントを 1 つだけ配置することも、複数の using ステートメントを 1 行に配置することもできます。ただし、使用する各名前には独自の宣言ステートメントが必要であり、各ステートメントはセミコロンで終わる必要があることに注意してください。

  • ヘッダー ファイル内のコードでは、通常、using ステートメントを使用しないでくださいこれは、ヘッダー ファイルの内容がコピーそれを参照するすべてのファイルに移動します。ヘッダー ファイルに using ステートメントがある場合、ヘッダー ファイルを使用するすべてのファイルにこのステートメントが含まれます。一部のプログラムでは、いくつかの名前が誤って含まれているために、予期しない名前の競合が発生する可能性があります。

  • 標準ライブラリの文字列型は、可変長の文字シーケンスを表します。文字列型を使用するには、まず文字列ヘッダー ファイルをインクルードする必要があります。標準ライブラリの一部として、string は名前空間 std で定義されます。次の例では、次のコードが含まれていることを前提としています。

    • #include <string>
      using std::string
      
  • C++ 標準は、ライブラリ タイプによって提供される操作を詳細に指定する一方で、ライブラリ実装者にいくつかのパフォーマンス要件を課します。したがって、標準ライブラリ タイプは、一般的なアプリケーションには十分に効率的です。

  • クラスのオブジェクトがどのように初期化されるかは、クラス自体によって決まります。クラスはオブジェクトを初期化するさまざまな方法を定義できますが、これらのメソッドは異なるものである必要があります。つまり、初期値の数が異なるか、初期値のタイプが異なるかのいずれかです。文字列オブジェクトを初期化する最も一般的な方法をいくつかリストします。例をいくつか示します。

    • 	string sl;//默认初始化,s1是一个空字符串
      	string s2 = sl;// s2是s1的副本
      	string s3 = "hiya";// s3是该字符串字面值的副本
      	string s4(10, 'c');// s4的内容是cccccccccc
      
  • デフォルトで文字列オブジェクトを初期化すると、空の文字列が得られます。つまり、文字列オブジェクトに文字が含まれていません文字列リテラルが指定された場合、最後の null 文字を除くリテラル内のすべての文字が、新しく作成された文字列オブジェクトにコピーされます。数字と文字が指定された場合、文字列オブジェクトの内容は、指定された文字を連続して数回繰り返すことによって得られるシーケンスになります。

  • C++ 言語にはいくつかの異なる初期化メソッドがあり、文字列を通じて、これらの初期化メソッド間の違いと関連性が明確にわかります。等号 (=) を使用して変数を初期化すると、実際にはコピー初期化が実行され、コンパイラは等号の右側の初期値を新しく作成されたオブジェクトにコピーします対照的に、等号が使用されない場合は、直接初期化が実行されます。

    • 	string s5 = "hiya";//拷贝初始化
      	string s6("hiya");//直接初始化
      	string s7(10, 'c');//直接初始化,s7的内容是cccccccccc
      	string s8 = string(10, 'c');//拷贝初始化,s8的内容是cccccccccc
      
  • s8 の初期値は string(10, 'c') で、これは実際には 2 つのパラメータ (数値 10 と文字 c) で作成された文字列オブジェクトであり、この文字列オブジェクトが s8 にコピーされます。

  • クラスは、オブジェクトを初期化する方法を指定するだけでなく、オブジェクトに対して実行できる操作も定義する必要があります。その中で、このクラスは、sales_item クラスの isbn 関数のように、関数名を通じて呼び出される操作を定義できるだけでなく、このクラスのオブジェクトに対する << や + などのさまざまな演算子の新しい意味を定義することもできます。

    • os<<s 出力ストリーム os に を書き込み、os を返します。
      >> です is から文字列を読み取り、s に割り当てます。文字列は空白で区切られ、is が返されます。
      getline(is, s) is から行を読み取り、それを s に割り当て、is を返します。
      s.empty() s が空の場合は true を返し、それ以外の場合は false を返します
      s.size() s 内の文字数を返します。
      s[n] s の n 番目の文字への参照を返します。位置 n は 0 から始まります。
      s1 + s2 s1とs2を接続した結果を返します
      sl = s2 s1 の元の文字を s2 のコピーで置き換えます
      s1 == s2 s1 と s2 に含まれる文字がまったく同じである場合、それらは等しい、つまり文字列オブジェクトは等しいです。
      s1 != s2 等価判定は文字の大文字と小文字を区別します
      <、<= 、>、>= 比較は辞書内の文字の順序を使用して行われ、大文字と小文字が区別されます。
  • 組み込み型の入出力操作と同様、文字列オブジェクトに対するこのような操作も、演算子の左側のオペランドを結果として返します。したがって、複数の入力または複数の出力を一緒に書き込むことができます。

    • string s1,s2;
      cin>>s1>>s2;
      cout<<s1<<s2<<endl;
      
  • 最終文字列への入力中に空白文字を保持したい場合がありますが、この場合は、元の >> 演算子の代わりに getline 関数を使用する必要がありますgetline 関数のパラメータは入力ストリームと文字列オブジェクトです。この関数は、改行文字に遭遇するまで指定された入力ストリームからコンテンツを読み取り (改行文字も読み込まれることに注意してください)、その後、読み取ったコンテンツを Go に保存します。その文字列オブジェクトにコピーします (改行文字は保存されないことに注意してください)ゲットラインのみ改行文字が見つかるとすぐに読み取り操作を終了します入力が改行文字で始まっている場合でも、結果を返します。入力が実際に改行文字で始まる場合、結果は空の文字列になります

  • empty 関数は、文字列オブジェクトが空かどうかに基づいて、対応するブール値を返します。sales_item クラスの isbn メンバーと同様に、empty も string のメンバー関数です。この関数の呼び出し方法は非常に簡単で、ドット演算子を使用して空の関数を実行したオブジェクトを示すだけですsize 関数は、文字列オブジェクトの長さ (つまり、文字列オブジェクト内の文字数) を返します。size 関数を使用する 80 文字を超える行のみを出力できます。

    • 	string line;
      	while (getline(cin, line))
      		if (!line.empty())
      			if(line.size()>80)
      				cout << line << endl;
      
  • 文字列クラスとその他のほとんどの標準ライブラリ型では、いくつかのサポート型が定義されています。これらのサポート型は、標準ライブラリ型のマシンに依存しない性質を体現しており、size_type 型もその 1 つです。特に使用する場合、スコープ演算子は、名前 size_type がクラス文字列で定義されていることを示すために使用されます。

  • string::size_type 型の詳細についてはあまりわかっていませんが、1 つ確かなことは、これは符号なしの値であり、任意の文字列オブジェクトのサイズを保持できるということです文字列クラスのサイズ関数の戻り値を格納するために使用されるすべての変数は、文字列型:size_type である必要があります。

  • size 関数は符号なしの整数を返すため、式の中で符号付きの数値と符号なしの数値を混在させると、予期しない結果が生じる可能性があることに注意してください。たとえば、n が負の値の int であると仮定すると、式 s.size () < n はほぼ確実に true と評価されます。それの訳は負の値 n は、より大きな符号なしの値に自動的に変換されます。式にすでに size() 関数がある場合は、 int を使用しないでください。これにより、 int と unsigned の混合によって発生する可能性のある問題を回避できます

  • 等価演算子 (== および !=) は、2 つの文字列オブジェクトが等しいかどうかをそれぞれテストします。文字列オブジェクトが等しいとは、それらが同じ長さで同じ文字を含むことを意味します。関係演算子 <、<=、>、>= はそれぞれ、文字列オブジェクトが別の文字列オブジェクトより小さいか、以下であるか、大きいか、または大きいか等しいかをテストします。上記の演算子はすべて (大文字と小文字が区別される) 辞書順に並んでいます。

    • 2 つの文字列オブジェクトの長さが異なり、短い方の文字列オブジェクトの各文字が長い方の文字列オブジェクトの対応する文字と同じである場合、短い文字列オブジェクトは長い文字列オブジェクトより小さいと言われます。

    • 2 つの文字列オブジェクトの対応する位置の一部で矛盾がある場合、文字列オブジェクトの比較結果は、実際には文字列オブジェクト内の異なる文字の最初のペアの比較結果になります

  • 一般に、標準ライブラリ タイプを設計するときは、使いやすさの点で組み込みタイプと一致するように努めるため、ほとんどのライブラリ タイプは代入操作をサポートします文字列クラスの場合、あるオブジェクトの値を別のオブジェクトに割り当てることができます。

    • string st1(10,"c"),st2;
      st1=st2;//此时均为空
      
  • 2 つの文字列オブジェクトを追加すると、新しい文字列オブジェクトが生成されます。その内容は、左側のオペランドと右側のオペランドを連結することによって形成されますつまり、文字列オブジェクトに対して加算演算子 (+) を使用した結果は新しい文字列オブジェクトであり、それに含まれる文字は 2 つの部分で構成されます。前半は左側の文字列オブジェクトに含まれる文字です。プラス記号の右側と後半 プラス記号の右側の文字列オブジェクトに含まれる文字です。さらに、複合代入演算子 (+=) は、右側の文字列オブジェクトの内容を左側の文字列オブジェクトに追加する役割を果たします

    • 	string s1 = "hello, ", s2 = "world\n";
      	string s3 = s1 + s2; // s3的内容是hello, world\n
      	sl += s2;//等价于sl = s1 + s2
      
  • 標準ライブラリでは文字リテラルと文字列リテラルを文字列オブジェクトに変換できるため、文字列オブジェクトが必要な場合はこれら 2 つのリテラルを代わりに使用できますステートメント内で文字列オブジェクトと文字リテラルおよび文字列リテラルを混在させる場合は、各加算演算子 (+) の両側のオペランドの少なくとも 1 つが文字列であることを確認する必要があります。

  • cctype ヘッダー ファイル内の関数

    • isalnum(c) c が文字または数字の場合は True
      イアルファ(c) c が文字の場合は True
      iscntrl(c) c が制御文字の場合は True
      isdigital(c) c が数値の場合は True
      イスグラフ(c) c がスペースではないが印刷可能な場合は True
      下のほう(c) c が小文字の場合は True
      スプリント (c) c が印刷可能な文字の場合 (つまり、 c がスペースであるか、c が視覚的な形式を持っている場合)、True になります。
      ispunct(c) c が句読点の場合 (つまり、 c が制御文字、数字、文字、または印刷可能な空白類ではない場合)、True
      isspace(c) c が空白の場合 (つまり、c がスペース、水平タブ、垂直タブ、復帰、改行、および送り文字のいずれかである) の場合は True
      isupper( c) c が大文字の場合は True
      isxdigital( c) c が 16 進数の場合は True
      下(c) c が大文字の場合は対応する小文字を出力し、それ以外の場合は c をそのまま出力します。
      タッパー(c) c が小文字の場合は対応する大文字を出力し、そうでない場合は c をそのまま出力します。
  • C++標準ライブラリは、C+言語独自の関数が定義されているほか、C言語の標準ライブラリと互換性があります。C 言語のヘッダー ファイルは name.h の形式であり、C++ ではこれらのファイルに cname という名前が付けられます。つまり、拡張子 .h が削除され、ファイル名の前に文字 c が追加されます (ここでの c は、これが C 言語の標準ライブラリに属する​​ヘッダー ファイルであることを示します)。

  • したがって、cctype ヘッダー ファイルと ctype.h ヘッダー ファイルの内容は同じですが、命名規則という点では C++ 言語の要件により準拠しています。特に、cname という名前のヘッダー ファイルで定義された名前は名前空間 std に属しますが、.h という名前のヘッダー ファイルで定義された名前は名前空間 std に属しません

  • 一般に、C++ プログラムでは、name.h ではなく cname という名前のヘッダー ファイルを使用する必要があります。標準ライブラリの名前は、常に名前空間 std にあります。.h 形式のヘッダー ファイルを使用する場合、プログラマは、どれが C 言語から継承されたもので、どれが C++ 言語に固有のものであるかを念頭に置く必要があります。

  • 文字列オブジェクト内の各文字に対して何かを実行したい場合、現時点での最良の方法は、新しい C++11 標準によって提供されるステートメントである range for (range for) ステートメントを使用することです。このタイプのステートメントは、指定されたシーケンス内の各要素を反復処理し、シーケンス内の各値に対して何らかの操作を実行します。その構文は次のとおりです。

    • for (declaration : expression)
      statement
      
  • このうち式部分はシーケンスを表現するためのオブジェクトです。宣言部分は、シーケンス内の基本要素にアクセスするために使用される変数を定義します。反復ごとに、宣言部分の変数は式部分の次の要素の値に初期化されます

  • 文字列オブジェクトは一連の文字を表すため、文字列オブジェクトは range for ステートメントの式部分として使用できます。簡単な例を挙げると、 range for ステートメントを使用して、文字列オブジェクト内の文字を 1 行に 1 つずつ出力できます。

    • 	string str(" some string");//每行输出str中的一个字符。
      	for (auto c : str)//对于str中的每个字符
      		cout << c << endl;//输出当前字符,后面紧跟一个换行符
      
  • for ループは変数 c と str を接続します。ループ制御変数を定義する方法は、通常の変数を定義するのと同じです。この例では、コンパイラは auto キーワードを使用して変数 c の型を決定します。ここで、 c の型は char です各反復で str の次の文字が c にコピーされるため、ループは「文字列 str 内の各文字 c に対して」あれこれの操作を実行するものとして読み取ることができます。この例の「XXX 演算」は 1 文字を出力してから改行します。

  • 文字列オブジェクト内の文字の値を変更する場合は、ループ変数を参照型として定義する必要があります。参照は特定のオブジェクトのエイリアスにすぎないため、参照をループ制御変数として使用すると、その変数は実際にはシーケンスの各要素に順番にバインドされることに注意してくださいこの参照を使用して、バインドされている文字を変更できます。

  • 新しい例では、句読点の数はカウントされなくなりました。文字列を大文字に書き換えたいとします。これを行うには、標準ライブラリ関数 toupper を使用します。この関数は文字を受け取り、対応する大文字形式を出力します。このようにして、文字列オブジェクト全体を大文字に変換するには、各文字に対して toupper 関数を呼び出し、結果を元の文字に割り当てるだけです。

    • 	string s("Hello world!!!");//转换成大写形式。
      	for (auto& c : s)
      		//对于s 中的每个字符(注意:c是引用)
      		c = toupper(c);
      	//c是一个引用,因此赋值语句将改变s中字符的值
      	cout << s << endl;
      //输出:HELLO WORLD !!!
      
  • 文字列オブジェクト内のすべての文字を処理したい場合は、range for ステートメントを使用することをお勧めします。ただし、1 つの文字にのみアクセスする必要がある場合や、複数の文字にアクセスする必要があるが、特定の条件が発生すると停止する場合があります。たとえば、同じ文字が大文字に変更されますが、新しい要件は、文字列全体に対してこれを行うことではなく、文字列オブジェクトの最初の文字または最初の単語のみを大文字にすることです。

  • 文字列オブジェクト内の 1 文字にアクセスするには 2 つの方法があります。1つは添字を使用する方法、もう 1 つはイテレータを使用する方法です添字演算子 ([ ]) によって受け取られる入力パラメータは、string 型の値です: :size_type。このパラメータは、アクセスされる文字の位置を示します。戻り値は、その位置にある文字への参照です。文字列オブジェクトの添字は 0 から始まります。文字列オブジェクト s に少なくとも 2 文字が含まれる場合、s[0] が最初の文字、s[1] が 2 番目の文字、s[s.size()-1] が最後の文字になります。

  • 文字列オブジェクトの添字は 0 以上、s.size() 未満である必要があります。この範囲を超える添字を使用すると、予期しない結果が発生します。添字を使用して空の文字列にアクセスすると、予期しない結果が生じると推測できます結果添え字の値を「添え字」または「インデックス」と呼び、整数値であればどんな式でもインデックスとして使用できます。ただし、インデックスが符号付きタイプの場合、値は string::size_type で表される符号なしタイプに自動的に変換されます。

  • 指定された文字にアクセスする前に、まず s が空かどうかを確認します。実際、文字列オブジェクトで添え字を使用するときは常に、その位置に実際に値が存在することを確認する必要があります。s が空の場合、s[0] の結果は不定になります。

  • 論理 AND 演算子 (&&)。操作に関係する両方のオペランドが true の場合、論理 AND の結果は true になり、それ以外の場合、結果は false になります。この演算子について最も重要なことは、C++ 言語では、左側のオペランドが true の場合にのみ右側のオペランドの条件がチェックされると規定されていることですこの例に示すように、このルールにより、添字の値が妥当な範囲内にある場合にのみ、その添字が文字列へのアクセスに実際に使用されることが保証されます。つまり、インデックスが s.size() に達するまで、s[index] は実行されません。インデックスが増加しても s.size () の値を超えることはできないため、インデックスは s.size () よりも小さいことが保証されます。

  • 標準ライブラリの型ベクトルは、すべて同じ型のオブジェクトのコレクションを表します。コレクション内の各オブジェクトには対応するインデックスがあり、オブジェクトにアクセスするために使用されますベクターは他のオブジェクトを「保持」するため、コンテナーと呼ばれることがよくあります。ベクターを使用するには、適切なヘッダー ファイルをインクルードする必要があります。以降の例では、次の using ステートメントが作成されると想定します。

    • 	#include <vector>
      	using std::vector;
      
  • C++ 言語にはクラス テンプレートと関数テンプレートの両方があり、vector はクラス テンプレートです。C++ をかなり深く理解していないと、テンプレートを作成できません。幸いなことに、テンプレートの作成方法がまだ分からない場合でも、まずテンプレートを使用してみることができます。テンプレート自体はクラスや関数ではなく、コンパイラによって生成されたクラスや関数を記述するための命令と考えることができますコンパイラーがテンプレートに基づいてクラスまたは関数を作成するプロセスはインスタンス化と呼ばれます。テンプレートを使用する場合は、コンパイラーがクラスまたは関数をどの型にインスタンス化するかを指定する必要があります。

  • クラス テンプレートの場合、追加情報を提供することで、テンプレートがどのような種類のクラスをインスタンス化するかを指定します。どのような情報を提供する必要があるかは、テンプレートによって決まります情報を提供する方法は常に次のようになります。テンプレート名の後に山かっこを付け、情報をかっこ内に置きます。

    • 	vector<int> ivec;// ivec保存int类型的对象
      	vector<sales_item> sales_vec;//保存sales_item类型的对象
      	vector<vector<string>> file;//该向量的元素是vector对象
      
  • Vector はほとんどのタイプのオブジェクトをその要素として収容できますが、参照はオブジェクトではないため、参照を含む Vector は存在しませんさらに、他のほとんどの (非参照) 組み込み型およびクラス型はベクトル オブジェクトを形成でき、ベクトルを構成する要素もベクトルにすることができます。

  • C++ 標準の以前のバージョンでは、vector の要素がまだベクトル (または他のテンプレート タイプ) だった場合、その定義形式は現在の C++11 新しい標準とは若干異なることに注意してください。以前は、外側のベクトル オブジェクトの右山括弧とその要素 type の間にスペースを追加する必要がありました。たとえば、vector<vector> ではなく、vector<vector> と記述する必要がありました。

    • ベクトル v1 v1 は空のベクトルであり、その潜在的な要素は T 型であり、デフォルトの初期化が実行されます。
      ベクトルv2(v1) v2 には v1 のすべての要素のコピーが含まれています
      ベクトル v2 = v1 v2(v1) と同等、v2 には v1 のすべての要素のコピーが含まれます
      ベクトル v3(n, val) v3 には n 個の繰り返し要素が含まれており、各要素の値は val です
      ベクトル v4(n) v4 には値の初期化を繰り返し実行する n 個のオブジェクトが含まれています
      ベクトル v5{ a、b、c… } v5 には初期値を持つ要素の数が含まれており、各要素には対応する初期値が割り当てられます。
      ベクトル v5 = {a、b、c… .} v5{ a,b,c… } と同等
  • もちろん、ベクター オブジェクトを定義するときに要素の初期値を指定することもできます。たとえば、あるベクトル オブジェクトの要素を別のベクトル オブジェクトにコピーすることができます。このとき、新しいベクトル オブジェクトの要素は、元のベクトル オブジェクトの対応する要素のコピーになります。2 つのベクトル オブジェクトは同じタイプである必要があることに注意してください

    • vector<int> ivec;//初始状态为空
      //在此处给ivec添加一些值
      vector<int> ivec2(ivec);//把ivec的元素拷贝给ivec2
      vector<int> ivec3 = ivec; // 把ivec的元素拷贝给ivec3
      vector<string> svec(ivec2);//错误: svec的元素是string对象,不是int
      
  • 通常、初期値を省略せずに、ベクトル オブジェクトが保持できる要素の数のみを指定できます。この時点で、ライブラリは値が初期化された要素の初期値を作成し、それをコンテナ内のすべての要素に割り当てます。この初期値は、ベクトル オブジェクト内の要素のタイプによって決まります。

  • Vector オブジェクトの要素が int などの組み込み型の場合、要素の初期値は自動的に 0 に設定されます。要素が文字列などの特定のクラス型である場合、要素はクラスによってデフォルトで初期化されます。

    • vector<int> ivec(10);//10个元素,每个都初始化为0
      vector<string> svec(10);//10个元素,每个都是空string对象
      
  • この初期化方法には 2 つの特別な制限があります: まず、一部のクラスでは、初期値を明示的に指定する必要があります。ベクトル オブジェクト内の要素の型がデフォルトの初期化をサポートしていない場合は、初期要素値を指定する必要がありますこのタイプのオブジェクトの場合、初期値を設定せずに要素数を指定するだけでは初期化を完了できません。

    • vector<int> vi = 10;//错误:必须使用直接初始化的形式指定向量大小
      
  • ここの 10 は、ベクトル オブジェクトを初期化する方法を示すために使用されています。これを使用する本来の目的は、数値 10 をベクトルに「コピー」するのではなく、初期化された値を持つ 10 個の要素を含むベクトル オブジェクトを作成することです。したがって、現時点ではコピー初期化を使用するのは適切ではありません。

  • ある場合には、初期化の本当の意味は、初期値を渡すために中かっこを使用するか括弧を使用するかによって異なります。たとえば、ベクトルを整数で初期化する場合、その整数の意味は、ベクトル オブジェクトの容量または要素の値である可能性があります。同様に、2 つの整数を使用してベクトルを初期化する場合、2 つの整数のうち 1 つはベクトル オブジェクトの容量で、もう 1 つは要素の初期値であるか、またはベクトル内の 2 つの要素の初期値である可能性があります。容量が 2 のオブジェクト。これらの意味は、中括弧または括弧を使用することで区別できます。

    • vector<int> v1(10);// v1有10个元素,每个的值都是О
      vector<int> v2{
              
               10 };// v2有1个元素,该元素的值是10
      vector<int> v3(10, 1); // v3有10个元素,每个的值都是1
      vector<int> v4{
              
               101 }; // v4有2个元素,值分别是10和1
      
  • 括弧が使用されている場合は、指定された値がベクトル オブジェクトの構築に使用されていると言えます。たとえば、v1 の初期値はベクトル オブジェクトの容量を示し、v3 の 2 つの初期値はそれぞれベクトル オブジェクトの容量と要素の初期値を示します。

  • 中括弧が使用されている場合は、ベクトル オブジェクトをリスト初期化することを示すことができます。つまり、初期化処理では中括弧内の値を可能な限り要素の初期値のリストとして扱い、リストの初期化が実行できない場合にのみ他の初期化方法が考慮されます上記の例では、v2 と v4 に提供された初期値は両方とも要素値として使用できるため、両方ともリストの初期化を実行します。ベクトルオブジェクト v2 には 1 つの要素が含まれ、ベクトル オブジェクト v4 には 2 つの要素が含まれます

  • 一方、初期化中に中括弧形式が使用されているが、指定された値がリストの初期化に使用できない場合は、そのような値を使用してベクター オブジェクトを構築することを検討してください。たとえば、文字列オブジェクトを含むベクトル オブジェクトをリスト初期化する場合は、文字列オブジェクトに割り当てられる初期値を指定する必要があります。この時点で、ベクトル オブジェクトの要素をリストで初期化するか、指定された容量値でベクトル オブジェクトを構築するかを区別するのは難しくありません。

    • vector<string> v5{
              
               "hi" }; //列表初始化:v5有一个元素
      vector<string> v6("hi");//错误:不能使用字符串字面值构建vector对象
      vector<string> v7{
              
              10};//v7有10个默认初始化的元素
      vector<string> v8{
              
               10,"hi" };// v8有10个值为"hi"的元素
      
  • 上記の例では 2 番目のステートメントを除いて中括弧が使用されていますが、実際にリストの初期化を行うのは v5 のみです。ベクトル オブジェクトをリスト初期化するには、中括弧内の値が要素と同じ型である必要があります。当然のことながら、文字列オブジェクトは int で初期化できないため、v7 および v8 で提供される値を要素の初期値として使用することはできません。リストの初期化を実行できないことを確認した後、コンパイラはデフォルト値でベクトル オブジェクトを初期化しようとします。

  • ベクトル オブジェクトの場合、直接初期化は次の 3 つの状況に適しています。初期値がわかっていて数が小さい、初期値が別のベクトル オブジェクトのコピーである、すべての要素の初期値が同じである、ですただし、より一般的な状況は、ベクター オブジェクトを作成するときに、実際に必要な要素の数が不明であり、要素の値が不確実であることがよくあります場合によっては、要素の初期値がわかっていても、これらの値の合計数が大きくて異なる場合、ベクター オブジェクトを作成するときに初期化操作を実行するのが煩雑になります。

  • より良いアプローチは、最初に空のベクトルを作成し、実行時にそのベクトルのメンバー関数を使用することです。プッシュバックそれに要素を追加しますPush_back は、ベクター オブジェクトの末尾要素として、ベクター オブジェクトの「最後 (後ろ)」に値を「プッシュ」する役割を果たします。例えば:

    • vector<int> v2;//空vector对象
      for (int i = 0; i != 100; ++i)
      	v2.push_back(i); //依次把整数值放到v2尾端//循环结束后v2有100个元素,值从0到99
      
  • 同様に、ベクター オブジェクト内の要素の正確な数が実行時までわからない場合は、今説明した方法を使用してベクター オブジェクトを作成し、それに値を割り当てる必要があります。たとえば、場合によっては、リアルタイムでデータを読み取り、それをベクトル オブジェクトに割り当てる必要があります。

    • // 从标准输入中读取单词,将其作为vector对象的元素存储string word;
      vector<string> text;// 空vector对象
      while (cin >> word) {
              
              
      	text.push_back(word); // 把 word添加到text后面
      
  • C++ 標準では、実行時にベクターが効率的かつ迅速に要素を追加できる必要があります。したがって、ベクター オブジェクトは効率的に成長できるため、ベクター オブジェクトを定義するときにサイズを設定する必要はありませんが、実際にサイズを設定するとパフォーマンスが低下する可能性があります。唯一の例外は、すべての要素が同じ値を持つことです。要素の値が異なる場合は、最初に空のベクター オブジェクトを定義し、実行時にそれに特定の値を追加する方が効率的です。Vector は、要素を動的に追加するパフォーマンスをさらに向上させるメソッドも提供します。最初に空のベクター オブジェクトを作成し、実行時に要素を動的に追加する方法は、C やその他のほとんどの言語での組み込み配列型の使用方法とは異なります。特に C や Java に慣れている場合は、ベクター オブジェクトを作成するときにその容量を指定するのが最善であることが予想されます。しかし実際には、通常はその逆です。

  • 要素をベクター オブジェクトに効率的かつ便利に追加できるため、多くのプログラミング タスクが大幅に簡素化されます。ただし、この単純さには、プログラムを作成するためのいくつかの高度な要件も伴います。その 1 つは、特にループがベクトル オブジェクトの容量を変更する可能性がある場合に、作成されたループが正しいことを確認することです。ベクトルをさらに使用するにつれて、他の暗黙の要件についても徐々に学習していきます。そのうちの 1 つをここで指摘します。ループ本体にベクトル オブジェクトに要素を追加するステートメントが含まれている場合、ループに範囲を使用することはできません

  • Push_back に加えて、vector は他のいくつかの操作も提供しますが、そのほとんどは文字列関連の操作に似ており、より重要なもののいくつかを以下に示します。

    • v.empty() v に要素が含まれていない場合は true を返し、それ以外の場合は false を返します。
      v.size() v の要素の数を返します。
      v.push_back(t) 値 t の要素を v の末尾に追加します。
      v[n] v の位置 n にある要素への参照を返します。
      v1 = v2 v1 の要素を v2 の要素のコピーで置き換えます
      v1 = { a、b、c… } v1 の要素をリスト内の要素のコピーに置き換えます。
      v1==v2 v1 と v2 は、同じ数の要素を持ち、対応する位置の要素の値が同じである場合にのみ等しいです。
      v1 != v2
      <、<=、>、>= 名前が示すように、比較は辞書順に行われます。
  • 访问vector对象中元素的方法和访问string 对象中字符的方法差不多,也是通过元素在 vector对象中的位置。例如,可以使用范围for语句处理vector对象中的所有元素:

    • vector<int> v{
              
               1,2,3,4,5,6,7,8,9 }; 
      	for (auto& i : v)//对于v中的每个元素(注意:i是一个引用)
      		i *= i;//求元素值的平方
      	for (auto i : v)//对于v中的每个元素
      		cout << i << " ";//输出该元素
      	cout << endl;
      
  • 第一个循环把控制变量 i 定义成引用类型,这样就能通过 i 给 v 的元素赋值,其中 i 的类型由auto关键字指定。这里用到了一种新的复合赋值运算符。如我们所知,+=把左侧运算对象和右侧运算对象相加,结果存入左侧运算对象;类似的,*=把左侧运算对象和右侧运算对象相乘,结果存入左侧运算对象。最后,第二个循环输出所有元素。

  • vector的 empty和 size两个成员与string的同名成员功能完全一致: empty检查vector对象是否包含元素然后返回一个布尔值; size则返回vector对象中元素的个数,返回值的类型是由vector定义的size_type类型

  • 各个相等性运算符和关系运算符也与string 的相应运算符(参见3.2.2节,第79页)功能一致。两个vector对象相等当且仅当它们所含的元素个数相同,而且对应位置的元素值也相同。关系运算符依照字典顺序进行比较:如果两个vector对象的容量不同,但是在相同位置上的元素值都一样,则元素较少的vector对象小于元素较多的vector对象;若元素的值有区别,则 vector对象的大小关系由第一对相异的元素值的大小关系决定

  • 刚接触C++语言的程序员也许会认为可以通过vector对象的下标形式来添加元素,事实并非如此。下面的代码试图为vector对象ivec添加10个元素:

    • vector<int> ivec; // 空vector对象
      for (decltype (ivec.size()) ix = 0; ix != 10; ++ix)
      	ivec[ix] = ix; //严重错误:ivec不包含任何元素
      
  • 然而,这段代码是错误的: ivec是一个空vector,根本不包含任何元素,当然也就不能通过下标去访问任何元素!如前所述,正确的方法是使用push_back:

    • for (decltype (ivec.size()) ix = 0; ix != 10; ++ix)
      	ivec.push_back(ix); //正确:添加一个新元素,该元素的值是ix
      
  • 关于下标必须明确的一点是:只能对确知已存在的元素执行下标操作。例如,

    • vector<int> ivec;// 空vector对象
      cout << ivec[0];// 错误:ivec不包含任何元素
      vector<int> ivec2(10); // 含有10个元素的vector对象
      cout << ivec2[10];//错误:ivec2元素的合法索引是从0到9
      
  • 试图用下标的形式去访问一个不存在的元素将引发错误, 不过这种错误不会被编译器发现,而是在运行时产生一个不可预知的值。不幸的是, 这种通过下标访问不存在的元素的行为非常常见, 而且会产生很严重的后果。所谓的缓冲区溢出(buffer overflow)指的就是这类错误,这也是导致PC及其他设备上应用程序出现安全问题的一个重要原因

  • 我们已经知道可以使用下标运算符来访问 string对象的字符或vector对象的元素,还有另外一种更通用的机制也可以实现同样的目的,这就是迭代器(iterator)。除了vector之外,标准库还定义了其他几种容器。所有标准库容器都可以使用迭代器,但是其中只有少数几种才同时支持下标运算符。严格来说,string对象不属于容器类型,但是string支持很多与容器类型类似的操作。vector支持下标运算符,这点和 string一样; string支持迭代器,这也和vector是一样的。

  • 类似于指针类型,迭代器也提供了对对象的间接访问。就迭代器而言,其对象是容器中的元素或者string对象中的字符。使用迭代器可以访问某个元素,迭代器也能从一个元素移动到另外一个元素。迭代器有有效和无效之分,这一点和指针差不多。有效的迭代器或者指向某个元素,或者指向容器中尾元素的下一位置:其他所有情况都属于无效。

  • 和指针不一样的是,获取迭代器不是使用取地址符,有迭代器的类型同时拥有返回迭代器的成员。比如,这些类型都拥有名为begin和 end的成员,其中 begin 成员负责返回指向第一个元素(或第一个字符)的迭代器。如有下述语句:

    • //由编译器决定b和e的类型. b表示v的第一个元素,e表示v尾元素的下一位置
      auto b = v.begin(), e = v.end(); //b和e的类型相同
      
  • end成员则负责返回指向容器(或string对象)“尾元素的下一位置(one past the end)”的迭代器,也就是说,该迭代器指示的是容器的一个本不存在的“尾后(off the end)”元素。这样的迭代器没什么实际含义,仅是个标记而已,表示我们已经处理完了容器中的所有元素。end 成员返回的迭代器常被称作尾后迭代器(off-the-end iterator)或者简称为尾迭代器(end iterator)。特殊情况下如果容器为空,则 begin和 end返回的是同一个迭代器

  • 表列举了迭代器支持的一些运算。使用==和!=来比较两个合法的迭代器是否相等,如果两个迭代器指向的元素相同或者都是同一个容器的尾后迭代器,则它们相等;否则就说这两个迭代器不相等。

    • *iter 返回迭代器iter所指元素的引用
      iter->mem 解引用iter并获取该元素的名为mem的成员,等价于(*iter).mem
      ++iter 令iter指示容器中的下一个元素
      –iter 令iter指示容器中的上一个元素
      iter1 == iter2 判断两个迭代器是否相等(不相等),如果两个迭代器指示的是同一个元素或者它们是同一个容器的尾后迭代器,则相等; 反之,不相等
      iter1 != iter2
  • 和指针类似,也能通过解引用迭代器来获取它所指示的元素,执行解引用的迭代器必须合法并确实指示着某个元素。试图解引用一个非法迭代器或者尾后迭代器都是未被定义的行为。

  • 举个例子,利用下标运算符把string对象的第一个字母改为了大写形式,下面利用迭代器实现同样的功能:

    • string s(""some string" ) ;
      if (s.begin() != s.end()) {
              
              // 确保s 非空
      	auto it = s.begin();// it表示s 的第一个字符
      	*it = toupper(*it);//将当前字符改成大写形式
      }
      //输出:Some string
      
  • 迭代器使用递增(++)运算符来从一个元素移动到下一个元素。从逻辑上来说,迭代器的递增和整数的递增类似,整数的递增是在整数值上“加1”,迭代器的递增则是将迭代器“向前移动一个位置”。

  • 因为 end返回的迭代器并不实际指示某个元素,所以不能对其进行递增或解引用的操作

  • //依次处理s的字符直至我们处理完全部字符或者遇到空白
    for (auto it = s.begin(); it != s.end() && !isspace(*it); ++it)
    	*it = toupper(*it); //将当前字符改成大写形式
    
  • 和上文的那个程序一样,上面的循环也是遍历s的字符直到遇到空白字符为止,只不过之前的程序用的是下标运算符,现在这个程序用的是迭代器。循环首先用s.begin的返回值来初始化it,意味着it指示的是s 中的第一个字符(如果有的话)。条件部分检查是否已到达s 的尾部,如果尚未到达,则将it解引用的结果传入isspace函数检查是否遇到了空白。每次迭代的最后,执行++it令迭代器前移一个位置以访问s的下一个字符。循环体内部和上一个程序if语句内的最后一句话一样,先解引用it,然后将结果传入toupper函数得到该字母对应的大写形式,再把这个大写字母重新赋值给it所指示的字符。

  • 原来使用C或Java的程序员在转而使用C++语言之后,会对for循环中使用!=而非<进行判断有点儿奇怪。C++程序员习惯性地使用!=,其原因和他们更愿意使用迭代器而非下标的原因一样:因为这种编程风格在标准库提供的所有容器上都有效。之前已经说过,只有string和 vector等一些标准库类型有下标运算符,而并非全都如此。与之类似,所有标准库容器的迭代器都定义了==和!-,但是它们中的大多数都没有定义<运算符。因此,只要我们养成使用迭代器和!=的习惯,就不用太在意用的到底是哪种容器类型。

  • 就像不知道string和 vector的size_type成员到底是什么类型一样,一般来说我们也不知道(其实是无须知道)迭代器的精确类型。而实际上,那些拥有迭代器的标准库类型使用iterator和const_iterator来表示迭代器的类型:

    • vector<int>:: iterator it;// it能读写vector<int>的元素
      string :: iterator it2;// it2能读写string对象中的字符
      vector<int> :: const_iterator it3; // it3只能读元素,不能写元素
      string :: const_iterator it4; // it4 只能读字符,不能写字符
      
  • const_iterator和常量指针差不多,能读取但不能修改它所指的元素值。相反,iterator的对象可读可写。如果vector对象或string对象是一个常量,只能使用const_iterator;如果vector对象或string对象不是常量,那么既能使用iterator也能使用const_iterator。

  • 迭代器这个名词有三种不同的含义:可能是迭代器概念本身,也可能是指容器定义的迭代器类型,还可能是指某个迭代器对象。重点是理解存在一组概念上相关的类型,我们认定某个类型是迭代器当且仅当它支持一套操作,这套操作使得我们能访问容器的元素或者从某个元素移动到另外一个元素。每个容器类定义了一个名为 iterator 的类型,该类型支持迭代器概念所规定的一套操作。

  • begin和 end返回的具体类型由对象是否是常量决定,如果对象是常量,begin和end返回const_iterator;如果对象不是常量,返回iterator:

    • vector<int> v;
      const vector<int> cv;
      auto it1 = v.begin(); // it1的类型是vector<int> : : iterator
      auto it2 = cv.begin(); // it2的类型是vector<int> : : const_iterator
      
  • 有时候这种默认的行为并非我们所要。如果对象只需读操作而无须写操作的话最好使用常量类型(比如 const_iterator)。为了便于专门得到const_iterator类型的返回值,C++11新标准引入了两个新函数,分别是cbegin和 cend:

    • auto it3 = v.cbegin(); // it3的类型是vector<int> : : const_iterator
      
  • 解引用迭代器可获得迭代器所指的对象,如果该对象的类型恰好是类,就有可能希望进一步访问它的成员。例如,对于一个由字符串组成的vector对象来说,要想检查其元素是否为空,令it是该vector对象的迭代器,只需检查it所指字符串是否为空就可以了

    • (*it).empty()//解引用it,然后调用结果对象的empty成员
      * it.empty()//错误:试图访问it的名为empty的成员,但it是个迭代器,//没有empty成员
      
  • 上面第二个表达式的含义是从名为it的对象中寻找其empty成员,显然it是一个迭代器,它没有哪个成员是叫empty的,所以第二个表达式将发生错误。

  • 为了简化上述表达式,C++语言定义了箭头运算符(->)。箭头运算符把解引用和成员访问两个操作结合在一起,也就是说,it->mem和(*it).mem表达的意思相同。

  • 例如,假设用一个名为text的字符串向量存放文本文件中的数据,其中的元素或者是一句话或者是一个用于表示段落分隔的空字符串。如果要输出text中第一段的内容,可以利用迭代器写一个循环令其遍历text,直到遇到空字符串的元素为止:

    • //依次输出text的每一行直至遇到第一个空白行为止
      for (auto it = text.cbegin ();it != text.cend() && !it->empty0); ++it)
      	cout << *it << endl;
      
  • 我们首先初始化it令其指向text的第一个元素,循环重复执行直至处理完了text的所有元素或者发现某个元素为空。每次迭代时只要发现还有元素并且尚未遇到空元素,就输出当前正在处理的元素。值得注意的是,因为循环从头到尾只是读取text的元素而未向其中写值,所以使用了cbegin和 cend来控制整个迭代过程。

  • 虽然vector对象可以动态地增长,但是也会有一些副作用。已知的一个限制是不能在范围for循环中向vector对象添加元素。另外一个限制是任何一种可能改变vector对象容量的操作,比如 push_back,都会使该vector对象的迭代器失效。谨记,但凡是使用了迭代器的循环体,都不要向迭代器所属的容器添加元素

  • 迭代器的递增运算令迭代器每次移动一个元素,所有的标准库容器都有支持递增运算的迭代器。类似的,也能用==和!=对任意标准库类型的两个有效迭代器进行比较。string和 vector的迭代器提供了更多额外的运算符,一方面可使得迭代器的每次移动跨过多个元素,另外也支持迭代器进行关系运算。所有这些运算被称作迭代器运算( iterator arithmetic)。

  • 要访问顺序容器和关联容器中的元素,需要通过“迭代器(iterator)”进行。迭代器是一个变量,相当于容器和操纵容器的算法之间的中介。迭代器可以指向容器中的某个元素,通过迭代器就可以读写它指向的元素。从这一点上看,迭代器和指针类似。不同容器的迭代器,其功能强弱有所不同。容器的迭代器的功能强弱,决定了该容器是否支持 STL 中的某种算法。例如,排序算法需要通过随机访问迭代器来访问容器中的元素,因此有的容器就不支持排序算法。

  • 不同容器的迭代器的功能

    • 容器 迭代器功能
      vector 随机访问
      deque 随机访问
      list 双向
      set / multiset 双向
      map / multimap 双向
      stack 不支持迭代器
      queue 不支持迭代器
      priority_queue 不支持迭代器
  • 可以令迭代器和一个整数值相加(或相减),其返回值是向前(或向后)移动了若干个位置的迭代器。执行这样的操作时,结果迭代器或者指示原vector对象(或string对象)内的一个元素,或者指示原vector对象(或string对象)尾元素的下一位置。

    • // 计算得到最接近vi中间元素的一个迭代器
      auto mid = vi.begin() + vi.size() / 2;
      
  • 如果vi有20个元素,vi.size()/2得10,此例中即令mid等于vi.begin ( )+10。已知下标从О开始,则迭代器所指的元素是vi[10],也就是从首元素开始向前相隔10个位置的那个元素。对于string或vector的迭代器来说,除了判断是否相等,还能使用关系运算符(<、<=、>、>=)对其进行比较。参与比较的两个迭代器必须合法而且指向的是同一个容器的元素(或者尾元素的下一位置)

  • 只要两个迭代器指向的是同一个容器中的元素或者尾元素的下一位置,就能将其相减,所得结果是两个迭代器的距离。所谓距离指的是右侧的迭代器向前移动多少位置就能追上左侧的迭代器,其类型是名为difference_type 的带符号整型数。string 和vector都定义了difference_type ,因为这个距离可正可负,所以difference_type是带符号类型的

  • 使用迭代器运算的一个经典算法是二分搜索。二分搜索从有序序列中寻找某个给定的值。二分搜索从序列中间的位置开始搜索,如果中间位置的元素正好就是要找的元素,搜索完成;如果不是,假如该元素小于要找的元素,则在序列的后半部分继续搜素;假如该元素大于要找的元素,则在序列的前半部分继续搜索。在缩小的范围中计算一个新的中间元素并重复之前的过程,直至最终找到目标或者没有元素可供继续搜索。

    • // text必须是有序的
      // beg 和end表示我们搜索的范围
      auto beg - text.begin(), end = text.end();
      auto mid = text.begin() + (end - beg) / 2; // 初始状态下的中间点
      //当还有元素尚未检查并且我们还没有找到sought时执行循环
      while (mid != end & &*mid != sought) {
              
              
      	if (sought < *mid)//我们要找的元素在前半部分吗 ?
      		end = mid;// 如果是,调整搜索范围使得忽略掉后半部分
      	else//我们要找的元素在后半部分
      		beg = mid + 1; // 在mid之后寻找
      	mid = beg + (end - beg) / 2;//新的中间点
      
  • 程序的一开始定义了三个迭代器: beg 指向搜索范围内的第一个元素、end指向尾元素的下一位置、mid指向中间的那个元素。初始状态下,搜索范围是名为text 的vector的全部范围。

  • 循环部分先检查搜索范围是否为空,如果mid和end 的当前值相等,说明已经找遍了所有元素。此时条件不满足,循环终止。当搜索范围不为空时,可知 mid指向了某个元素,检查该元素是否就是我们所要搜索的,如果是,也终止循环。

  • 当进入到循环体内部后,程序通过某种规则移动beg 或者end来缩小搜索的范围。如果mid所指的元素比要找的元素sought大,可推测若text含有sought,则必出现在mid所指元素的前面。此时,可以忽略mid后面的元素不再查找,并把mid赋给end即可。另一种情况,如果*mid 比 sought小,则要找的元素必出现在mid所指元素的后面。此时,通过令 beg 指向mid 的下一个位置即可改变搜索范围。因为已经验证过mid不是我们要找的对象,所以在接下来的搜索中不必考虑它。

  • 循环过程终止时,mid或者等于end或者指向要找的元素。如果mid等于end,说明text中没有我们要找的元素。

  • 按照迭代器的功能强弱,可以把迭代器分为以下几种类型:

    • 输入迭代器 (input iterator)

    • 输出迭代器 (output iterator)

    • 前向迭代器 (forward iterator)

    • 双向迭代器 (bidirectional iterator)

    • ランダムアクセス反復子

  • イテレータは C++ STL のコンポーネントの 1 つです。イテレータはコンテナを走査するために使用され、コンテナの要素を走査するための汎用的な方法です。コンテナがどのようなデータ構造に基づいているかに関係なく、データ構造が異なれば特性も異なります。要素を走査する方法ですが、イテレータを使用して異なるコンテナを走査するコードはまったく同じです。

おすすめ

転載: blog.csdn.net/weixin_43424450/article/details/132344639