C++ 文字列クラス (1) - 初期化、容量操作、反復子

目次

序文

1.文字列クラス

2. 初期化

1. 高麗人参なしまたはあり

2.文字列変数で初期化する

3.文字列で初期化する

4.文字数を指定する

3. 容量操作

1、サイズ

2、プッシュバック

3.編集を追加

4. += 演算子

5. vs の下の文字列の構造

6. g++ での文字列の構造

7、予約

8、サイズ変更

4. イテレータ

1. 前方反復子

2. 逆反復子 

3. const イテレータ (順方向および逆方向)

5. OJ演習

逆文字

文字列内に 1 回出現する文字を検索する 


序文

文字列クラスのテンプレートは次のとおりですが、なぜ複数あるのでしょうか?

これらのさまざまな文字列クラス テンプレートは、さまざまな文字エンコーディングと文字セットを処理するように設計されています。各テンプレートは、特定の種類の文字データを処理するように設計されています。

  1. std::string: これは最も一般的な文字列クラス テンプレートであり、ASCII 文字セットを処理するために使用されます。シングルバイト文字表現を使用し、ほとんどの通常の文字列操作に適しています。

  2. std::wstring: これは、Unicode 文字を処理するために使用される文字列クラス テンプレートのワイド文字バージョンです。 wchar_t タイプを使用して文字を表現し、複数言語の文字セットを処理する必要がある状況に適しています。

  3. std::u16string: これは、UTF-16 でエンコードされた文字列を処理するためのテンプレートです。 UTF-16 は 16 ビット エンコーディングを使用して文字を表現し、ほとんどの Unicode 文字などの大きな文字セットの処理に適しています。

  4. std::u32string: これは、UTF-32 でエンコードされた文字列を処理するためのテンプレートです。 UTF-32 は 32 ビット エンコーディングを使用して文字を表現し、すべての Unicode 文字を含む文字セットの処理に適しています。

これらのさまざまな文字列クラス テンプレートは、さまざまなタイプのテキスト データを操作するときに文字を正しく表現および操作するために、さまざまな文字エンコーディングと文字セットのサポートを提供します。適切な文字列クラス テンプレートを選択することで、文字データがさまざまなアプリケーション シナリオで正しく処理および操作されることを保証できます。

 

まず、コンピューターがどのように文字を保存するかを理解しましょう。 

コンピュータはすべてバイナリ形式であり、文字や記号を直接記憶することができないため、このときマッピングテーブルが必要となり、ASCllが誕生しました。

 コンピュータ上で複数の国のテキストを表示するために、unicode が誕生しました

 Unicode は、Universal Code や Unicode とも呼ばれ、Unicode Alliance によって開発され、業界です。コンピュータ サイエンスの分野における標準 (文字セットコーディング スキームを含む) など。

Unicode は、従来の文字エンコーディングスキームの制限を解決するために作成されました。各言語の各文字に合わせて設計されています。独自のバイナリ エンコーディングは、言語間およびクロスプラットフォーム

Unicode 文字エンコーディング スキームは、UTF-8、UTF-16、および UTF-32 に分類され、コンピューター システムでさまざまな範囲の Unicode 文字を表現および処理するように設計されています。

  1. UTF-8: UTF-8 は、1 ~ 4 バイトを使用してさまざまな Unicode 文字を表す可変長エンコーディング スキームです。これは、ASCII 文字セットと互換性があり、一般的な文字を表すときにスペースを節約できるため、最も一般的に使用される Unicode エンコード スキームの 1 つです。 UTF-8 は、特にインターネットやコンピュータ ネットワークで広く使用されている場合、テキスト データを保存および送信する際のスペースを節約するのに適しています。

  2. UTF-16: UTF-16 は、16 ビット エンコードを使用して Unicode 文字を表す固定長または可変長のエンコード スキームです。ほとんどの一般的な Unicode 文字の場合、UTF-16 は 16 ビット エンコーディングを使用しますが、あまり使用されない一部の文字の場合は 2 つの 16 ビット エンコーディングが必要です。 UTF-16 は、多言語テキスト処理や国際化アプリケーションなど、より大きな文字セットを処理する必要がある状況に適しています。

  3. UTF-32: UTF-32 は、32 ビット エンコーディングを使用して Unicode 文字を表す固定長エンコーディング スキームです。すべての Unicode 文字は、その文字が一般的であるかどうかに関係なく、32 ビット エンコーディングを使用して表現されます。 UTF-32 は、テキスト処理や特定のフィールドでの文字レベルの操作など、すべての Unicode 文字を含む文字セットを処理する必要がある状況に適しています。

これらの異なる Unicode エンコード スキームには、さまざまなトレードオフと適用性があり、特定のニーズとアプリケーション シナリオに応じて、Unicode 文字を表現および処理するために適切なエンコード スキームを選択できます。

その中で最もよく使われるのがUTF-8です。

中国では、中国語文字用の GBK エンコーディングも備えています。これにより、一部の珍しい文字がサポートされます。

1.文字列クラス

資料紹介 

  • String は、一連の文字を表すクラスです。標準の string クラスは、そのようなオブジェクトのサポートを提供します。そのインターフェイスは、標準の文字コンテナのインターフェイスに似ていますが、シングルバイト文字列の操作に特化した設計機能が追加されています。
  • string クラスは、デフォルトの char_traits およびアロケーター タイプを使用して、文字タイプとして char を使用します (テンプレートの詳細については、basic_string を参照してください)。
  • string クラスは、basic_string テンプレート クラスのインスタンスです。char を使用して、basic_string テンプレート クラスをインスタンス化し、char_traits と allocator を、basic_string のデフォルト パラメータとして使用します (テンプレートの詳細については、basic_string を参照してください)。
  • このクラスは、使用されるエンコーディングとは無関係にバイトを処理することに注意してください。マルチバイト文字または可変長文字 (UTF-8 など) のシーケンスを処理するために使用される場合、このクラスのすべてのメンバー (長さまたはサイズなど) とその反復子はプロセッサによって処理されます。 (実際のエンコードされた文字ではなく) バイト単位で動作します。
要約:
  • string は文字列を表す文字列クラスです。
  • このクラスのインターフェイスは基本的に通常のコンテナのインターフェイスと同じですが、文字列の操作に特に使用される通常の操作がいくつか追加されています。
  • 最下位レベルでは、string は実際には、basic_string テンプレート クラスのエイリアス、typedef Basic_string<char, char_traits, allocator> string; です。
  • マルチバイトまたは可変長の文字シーケンスは操作できません。
文字列クラスを使用する場合は、#include ヘッダー ファイル文字列を組み込み、名前空間 std を使用する必要があります;

2. 初期化

  1. デフォルト コンストラクター string(): 空の文字列オブジェクトを作成します。

  2. コピー コンストラクター string(const string& str): 別の文字列オブジェクト str をコピーして、新しい文字列オブジェクトを作成します。

  3. 部分文字列コンストラクター string(const string& str, size_t pos, size_t len = npos): 文字列オブジェクト str pos の指定された位置から開始して、新しい A 文字列を作成します。長さ len のオブジェクト。 len 引数が指定されていない場合は、文字列の末尾までの部分文字列がデフォルトで作成されます。

  4. C-String コンストラクターから string(const char* s): null で終わる C 文字列から新しい文字列オブジェクトを作成します s 。

  5. シーケンスからのコンストラクター string(const char* s, size_t n): C 文字列 s String オブジェクトの最初の n 文字から新しい文字を作成します。 。

  6. Fill コンストラクター string(size_t n, char c)n 文字 c を含む新しい文字列オブジェクトを作成します。

  7. 範囲コンストラクター template <class InputIterator> string(InputIterator first, InputIterator last): イテレータ範囲 [first, last) 内の文字を使用して新しい文字列オブジェクトを作成します。このコンストラクターは、ポインター、コンテナー反復子など、さまざまなタイプの反復子を受け入れることができます。

1. 高麗人参なしまたはあり

パラメータなしで初期化することも、パラメータを使用して初期化することもできます。

int main()
{
	string s1;
	string s2("hello world");
	string s3 = "hello";
	return 0;
}

 [ ] 演算子を使用して文字列内の特定の位置にアクセスすることもできます。​ 

#include<string>
int main()
{
	string s2("hello world");
	for (size_t i = 0; i < s2.size(); ++i) {
		s2[i]++;
	}
	cout << s2 << endl;
	return 0;
}

ストリーム挿入演算子 << を使用して文字列クラス オブジェクトを出力できます。​ 

これは、ストリーム挿入演算子<<が文字列クラスStreamでオーバーロードされているためです。抽出もリロードされました。

int main()
{
	string s2;
	cin >> s2;
	for (size_t i = 0; i < s2.size(); ++i) {
		s2[i]++;
	}
	cout << s2 << endl;
	return 0;
}

 

2.文字列変数で初期化する

1 つの文字の指定された位置から開始して、指定された数の文字で別の文字を初期化します。

int main()
{
	string s3 = "hello";
	string s4(s3, 2, 3);
	cout << s4 << endl;
	return 0;
}

取得する文字数が合計文字長を超える場合は、最後まで取得します。​ 

int main()
{
	string s3 = "hello";

	string s4(s3, 2, 3);
	cout << s4 << endl;

	string s5(s3, 2, 10);
	cout << s5 << endl;
	return 0;
}

 

3 番目のパラメータはデフォルトで文字整数にすることができ、指定された位置から最後まで取得されます。​ 

int main()
{
	string s3 = "hello";

	string s4(s3, 2, 3);
	cout << s4 << endl;

	string s5(s3, 2);
	cout << s5 << endl;
	return 0;
}

3.文字列で初期化する

割り当てる文字列を最初のパラメータの位置に直接入力し、2 番目のパラメータに割り当てられる文字の数を指定することもできます。​ 

int main()
{
	string s7("hello world", 5);
	cout << s7 << endl;
	string s8("hello world", 5 , 6);
	cout << s8 << endl;

	return 0;
}
  • 最初のコンストラクターに文字列以外のパラメーターが 1 つしかない場合、デフォルトで最初のパラメーターのサイズと長さが文字列の最初の文字から割り当てられます。
  • 2 番目のコンストラクターでは、文字列に 2 つのパラメーターがある場合、最初のパラメーターは指定された開始位置、2 番目のパラメーターは指定された初期化長です。長さが実際の文字列の長さよりも大きい場合は、文字列の長さが切り捨てられます。実際の文字列の長さ。

 

4.文字数を指定する

int main()
{
	string s9(10, '$');
	cout << s9 << endl;

	return 0;
}

3. 容量操作

1、サイズ

size() と length() の基本的な実装原則はまったく同じで、どちらも文字列の有効な文字長を取得するために使用されます size() の導入 他のコンテナのインターフェースとの整合性を図るためで、一般的には size() が使用されます。
int main()
{
	string s1("hello world");
	cout << s1.size() << endl;
	cout << s1.length() << endl;

	return 0;
}

以下の2つが理解できます。

  • max_size()この関数は、文字列オブジェクトが保持できる最大文字数を表す符号なし整数を返します。通常、この値はシステムの制限に依存するため、オペレーティング システムとコンパイラによって異なる場合があります。
  • capacity()この関数は、文字列オブジェクトに現在割り当てられているメモリ領域のサイズを示す符号なし整数を返します。文字列クラスは通常、展開用に余分なスペースを確保しているため、この値は文字列に含まれる実際の文字数よりも大きくなる場合があります。​ 
int main()
{
	string s1("hello world");
	cout << s1.max_size() << endl;
	cout << s1.capacity() << endl;

	return 0;
}

 64 ビットでの出力結果:

2、プッシュバック

文字列の末尾に 1 文字を追加します 

int main()
{
	string s1("hello");
	s1.push_back(' ');
	s1.push_back('!');
	cout << s1 << endl;

	return 0;
}

3、追加

また、append を使用して文字列の末尾に 1 つの文字または文字列を追加することもできます。これが最も一般的に使用される形式です。

int main()
{
	string s1("hello");
	s1.push_back(' ');
	s1.push_back('!');
	cout << s1 << endl;

	s1.append("world");
	cout << s1 << endl;
}

 

4. += 演算子

+= 演算子を使用して、文字列の末尾に文字または文字列を追加することもできます。+= の最下層は、push_back または append を呼び出します。文字列の末尾に文字を追加する場合は、 s.push_back(c) / s.append(1, c) / s += 'c' の 3 つの実装メソッドは似ています。一般に、string クラスの += 演算の方が頻繁に使用されます。+= 演算は、単一の文字だけでなく文字列も接続します。

int main()
{
	string s1("hello");
	s1 += ' ';
	s1 += '!';
	s1 += "world";
	cout << s1 << endl;
}

 

5、vs の文字列の構造

まず次のコードを見てください。 

int main()
{
	size_t sz = s.capacity();
	cout << "making s grow:\n";
	cout << "capacity changed: " << sz << '\n';
	for (int i = 0; i < 100; ++i)
	{
		s.push_back('c');
		if (sz != s.capacity())
		{
			sz = s.capacity();
			cout << "capacity changed: " << sz << '\n';
		}
	}
}

上記は、push_back() 関数を使用して文字列オブジェクトに文字を追加し、文字列の容量の変化を観察する方法を示しています

  • まず、 型の変数が main() 関数 で宣言され、文字に初期化されます。文字列オブジェクト ( が以前に定義された文字列オブジェクトであると仮定します)。 size_tszss
  • 次に、cout オブジェクトと << 演算子を使用して、「s を成長させる:」というプロンプト メッセージが出力されます。
  • 次に、ループを使用して、0 ~ 99 の文字 'c' を文字列オブジェクトsに 1 つずつ追加します。各文字を追加した後、 以前に記録された容量szと現在の文字列オブジェクトsの容量を比較して、容量が変更されたかどうかを判断します。
  • 容量が変更された場合は、新しい容量値を sz に割り当て、cout オブジェクトと << 演算子を使用します。 「容量が変更されました:」というプロンプト メッセージと新しい容量値が出力されます。

ここで出力される容量には \0 が含まれていません。つまり、実際の容量に 1 を加算する必要があります。

モニタリングでわかるように、文字は実際には _Buf 配列に格納されています。

        C++ の実装では、std::string クラスは通常 2 つの配列を使用して文字列の文字を格納します。文字列の長さが 15 文字以下の場合 (16 文字目は \0)、文字列の文字は _Buf という名前の内部固定サイズ配列に格納されます。配列は 15 です。これにより、動的なメモリ割り当てが回避され、パフォーマンスが向上します。

        文字列の長さが 15 文字を超える場合、文字列の文字は _Ptr という名前の動的に割り当てられた配列に格納され、配列の長さは必要に応じて変更されます。動的調整。これにより、より長い文字列に対応できるようになり、必要に応じてメモリを動的に割り当てることができます。

        この設計により、文字列が短い場合にはメモリを節約でき、文字列が長い場合には十分な記憶領域を提供できます。具体的な実装はコンパイラや標準ライブラリによって異なる場合がありますが、短い文字列と長い文字列を区別するこの戦略は一般的な最適化手法の 1 つです。

vs: string の下の string の構造は合計 28 バイトを占め、内部構造は少し複雑です。まず、string 内の文字列の記憶領域を決定するために使用される共用体があります。
  • 文字列の長さが 15 以下の場合、それを格納するために内部固定文字配列が使用されます。
  • 文字列の長さが 16 以上の場合、ヒープからスペースが割り当てられます。
union _Bxty
{ // storage for small buffer or pointer to larger one
	value_type _Buf[_BUF_SIZE];
	pointer _Ptr;
	char _Alias[_BUF_SIZE]; // to permit aliasing
} _Bx;
  • この設計も理にかなっています。ほとんどの場合、文字列の長さは 16 未満です。文字列オブジェクトが作成された後、内部には 16 文字の配列用の固定スペースがすでに存在します。ヒープ、非常に効率的です。
  • 2 番目:文字列の長さを格納する size_t フィールドと、ヒープ上に開かれたスペースの合計容量を格納する size_t フィールドもあります
  • 最後に: 他のことを行うポインタもあります。
  • したがって、合計 16+4+4+4=28 バイトを占めます。

文字列クラスオブジェクトのサイズを確認するための出力 

string s;

cout << sizeof(s) << endl;

 これは 28 であるため、ヒープ上の開始サイズは 32 であることがわかります。

拡張状況を観察すると、32サイズからは毎回1.5倍ずつ容量が拡張されていることがわかります。​ 

 

6. g++ での文字列の構造

G++ では、文字列はコピーオンライトによって実装され、文字列オブジェクトは合計 4 バイトを占め、内部にはポインタが 1 つだけ含まれます。
針は、次のフィールドを含む将来のヒープ領域を指します。
  • 合計スペースサイズ
  • 文字列の有効長
  • 参照カウント
struct _Rep_base
{
    size_type _M_length;
    size_type _M_capacity;
    _Atomic_word _M_refcount;
};

文字列を格納するために使用されるヒープ領域へのポインタ。

7、予約

必要なスペースのサイズがわかっている場合は、リザーブを使用して事前にスペースを空けることができ、容量の拡大を抑え、効率を向上させることができます。​ 

 

一度に 100 文字のスペースを開くには、反転を使用します。​ 

int main()
{
	string s;

	s.reserve(100);
	size_t sz = s.capacity();
	cout << "making s grow:\n";
	cout << "capacity changed: " << sz << '\n';
	for (int i = 0; i < 100; ++i)
	{
		s.push_back('c');
		if (sz != s.capacity())
		{
			sz = s.capacity();
			cout << "capacity changed: " << sz << '\n';
		}
	}
}

このとき、何度も容量を拡張する必要はありません。​ 

 文字列を操作する場合、入力する文字数を大まかに見積もることができれば、最初にreserveを使用してスペースを予約できます。

8、サイズ変更

size は文字列を n 文字の長さに変更します。

  • n が現在の文字列長より小さい場合は、現在の値を最初の n 文字に短縮し、n 番目の文字以外の文字を削除します。
  • n が現在の文字列長より大きい場合は、n のサイズに達するまで、末尾に文字 0 をできるだけ多く挿入することによって、現在のコンテンツが拡張されます。
  • パディング文字 c が指定されている場合、新しい要素は c のコピーに初期化されます。それ以外の場合、それらは値で初期化された文字 (NULL 文字) になります。
int main()
{
	// 扩容
	string s1("hello world");
	s1.reserve(100);
	cout << s1.size() << endl;
	cout << s1.capacity() << endl;

	// 扩容+初始化
	string s2("hello world");
	s2.resize(100);
	cout << s2.size() << endl;
	cout << s2.capacity() << endl;

	return 0;
}

サイズ変更(size_t n) とサイズ変更(size_t n, char c) は両方とも、文字列内の有効な文字数を n に変更します。違いは、文字数が増加した場合、サイズ変更(n) は余分な文字を 0 要素スペースで埋めることです。 、resize(size_t n, char c) は文字 c を使用して余分な要素スペースを埋めます。注: サイズ変更によって要素の数が変更される場合、要素の数が増加すると、基礎となる容量のサイズが変更される可能性がありますが、要素の数が減少すると、基礎となる領域の合計サイズは変更されません。

reserve(size_t res_arg=0): 有効な要素の数を変更せずに文字列のスペースを予約します。reserve のパラメータが string の基礎となるスペースの合計サイズより小さい場合、reserve は容量を変更しません。

4. イテレータ

1. 前方反復子

begin は文字列の最初の文字を指し、end は文字列の最後の文字の後の位置を指し、その機能はポインタの機能と似ています。

int main()
{
	string s1("hello world");
	string::iterator it = s1.begin();
	while (it != s1.end()) {
		cout << *it << " ";
		++it;
	}
    return 0;
}

 

range for の最下層はイテレータを呼び出すことによって実装されます。​ 

int main()
{
	string s1("hello world");
	string::iterator it = s1.begin();
	while (it != s1.end()) {
		cout << *it << " ";
		++it;
	}
	cout << endl;
	for (auto ch : s1) {
		cout << ch << " ";
	}
	cout << endl;
}

 

2. 逆反復子 

rbegin は文字列の最後の文字を指し、rend は文字列の最初の文字の前の位置を指します。​ 

 

int main()
{
	string::reverse_iterator rit = s1.rbegin();
	while (rit != s1.rend()) {
		cout << *rit << " ";
		++rit;
	}
	cout << endl;
    return 0;
}

3. const イテレータ (順方向および逆方向)

  const 順方向反復子と逆方向反復子は、データの走査と読み取りのみが可能です。

int main()
{
	string s1("hello world");

	string::const_iterator it = s1.begin();
	while (it != s1.end()) {
		cout << *it << " ";
		++it;
	}
	cout << endl;

	string::const_reverse_iterator rit = s1.rbegin();
	while (rit != s1.rend()) {
		cout << *rit << " ";
		++rit;
	}
	cout << endl;

	cout << s1 << endl;

	return 0;
}

このとき、auto を使用すると、タイプを自動的に推測できます。​ 

int main()
{
	//string::const_iterator it = s1.begin();
	auto it = s1.begin();
	while (it != s1.end()) {
		cout << *it << " ";
		++it;
	}
	cout << endl;

	//string::const_reverse_iterator rit = s1.rbegin();
	auto rit = s1.rbegin();
	while (rit != s1.rend()) {
		cout << *rit << " ";
		++rit;
	}
	cout << endl;

	return 0;
}

 

5. OJ演習

逆文字

917. 文字を反転するだけ - LeetCode

クイックソートのアイデアを使用します。​ 

class Solution {
public:
    string reverseOnlyLetters(string s) {
        size_t begin = 0, end=s.size()-1;
        while(begin<end){
            while(begin<end&&!isalpha(s[begin]))
                ++begin;
            while(begin<end&&!isalpha(s[end]))
                --end;
            swap(s[begin],s[end]);
            ++begin;
            --end;
        }
    return s;
    }
};

文字列内に 1 回出現する文字を検索する 

 387. 文字列内の最初の一意の文字 - LeetCode

カウントソートのアイデアを使用します。​ 

class Solution {
public:
    int firstUniqChar(string s) {
        int count[26]={0};
        for(auto ch:s){
            count[ch-'a']++;
        }
        for(int i=0;i<s.size();i++){
            if(count[s[i]-'a']==1)
                return i;
        }
        return -1;
    }
};

おすすめ

転載: blog.csdn.net/m0_73800602/article/details/134744293