文字列クラス
1. C言語の文字列
C 言語では、文字列は '\0' で終わる文字の集合です。操作の便宜上、C 標準ライブラリには str シリーズのライブラリ関数がいくつか用意されていますが、これらのライブラリ関数は文字列から分離されており、あまり具体的ではありません。 OOP の考え方に基づいており、その下にあるスペースはユーザー自身が管理する必要があるため、注意しないと範囲外にアクセスされる可能性があります。
2.文字列クラス
- string は文字列を表す文字列クラスです。
- このクラスのインターフェースは基本的に通常のコンテナーと同じですが、文字列を操作するために特別に使用されるいくつかの通常の操作が追加されています。
- string は実際には一番下にあります。
- マルチバイト文字または可変長文字のシーケンスは操作できません。
- 文字列クラスを使用する場合は、#include ヘッダー ファイルをインクルードし、名前空間 std を使用する必要があります。
このうち、stringクラスのインターフェイスの多くは、リンク -> stringをクリックすると表示されます。
ここではイテレータを導入する必要があります。文字列クラスでは、イテレータは実際にはネイティブ ポインタです。イテレータの使用法は次のとおりです。
#include <iostream>
#include <string>
using namespace std;
int main()
{
string s1("hello,world");
// 迭代器的使用,iterator 就是迭代器,它需要指定作用域
string::iterator it = s1.begin();
while (it != s1.end())
{
cout << *it << ' ';
it++;
}
cout << endl;
return 0;
}
このうち、s1.begin();
実際には文字列の先頭へのポインタ、s1.end()
つまり '\0' へのポインタです。
3. 文字列クラスをシミュレートする
次に、文字列クラスを実装するインターフェイスのシミュレーションを直接開始し、実装プロセスでの使用方法を説明します。より一般的で重要なインターフェイスのみをシミュレートすることに注意してください。
まず文字列クラスの宣言部分を観察し、どのインターフェイスを実装する必要があるかをプレビューしてみましょう。
0.文字列クラスの宣言
namespace Young
{
class String
{
public:
// 迭代器
typedef char* iterator;
typedef const char* const_iterator;
iterator begin();
iterator end();
const_iterator begin() const;
const_iterator end() const;
// 构造
String(const char* str = "")
:_str(new char [strlen(str) + 1])
,_size(strlen(str))
,_capacity(_size)
{
strcpy(_str, str);
}
// 析构
~String()
{
delete[] _str;
_str = nullptr;
_size = _capacity = 0;
}
// 交换
void swap(String& tmp)
{
::swap(_str, tmp._str);
::swap(_size, tmp._size);
::swap(_capacity, tmp._capacity);
}
// s2(s1)
// 拷贝构造
String(const String& str)
:_str(nullptr)
,_size(0)
,_capacity(0)
{
String tmp(str._str);
swap(tmp);
}
// s2 = s1
// 赋值运算符重载
String& operator=(const String& str)
{
if (this != &str)
{
String tmp(str._str);
swap(tmp);
}
return *this;
}
// 申请空间 -- 不改变 _size
void reserve(size_t n);
// 将空间调整为 n -- 改变 _size
void resize(size_t n, char c = '\0');
// 尾插字符
void push_back(char c);
String& operator+=(char c);
// 尾插字符串
void append(const char* str);
String& operator+=(const char* str);
// 清空字符串
void clear();
// 获取字符串长度
size_t size() const;
// 获取容量
size_t capacity() const;
// [] 重载 s[1]
const char& operator[](size_t index) const;
char& operator[](size_t index);
// 比较符号运算符重载
bool operator>(const String& s) const;
bool operator==(const String& s) const;
bool operator>=(const String& s) const;
bool operator<(const String& s) const;
bool operator<=(const String& s) const;
bool operator!=(const String& s) const;
// 返回它的字符串 -- 返回 char* 类型
const char* c_str() const;
// 判断是否为空字符串
bool empty() const;
// find -- 从pos位置开始查找字符/字符串
size_t find(char ch, size_t pos = 0) const;
size_t find(const char* str, size_t pos = 0) const;
// 获得从 pos 位置开始到 len 的子字符串;如果 len 不给值,默认到结尾
String substr(size_t pos, size_t len = npos) const;
// 在 pos 位置插入插入字符 ch 或字符串 str
String& insert(size_t pos, char ch);
String& insert(size_t pos, const char* str);
// 删除从 pos 位置开始 len 长度的字符串;如果 len 不给值就默认删到末尾
String& erase(size_t pos, size_t len = npos);
// 打印数据
void Print();
private:
char* _str;
size_t _size;
size_t _capacity;
public:
const static size_t npos = -1;
};
// 流插入、流提取 cout << s1;
ostream& operator<<(ostream& out, const String& s);
istream& operator>>(istream& in, String& s);
}
1. コンストラクター
コンストラクターはクラスを実装するときに最初に考慮されることが多く、文字列クラスのメンバー変数は別個にありますchar* _str; size_t _size; size_t _capacity;
。これらはすべて組み込み型ですが、char * 型では手動でスペースを適用する必要があるため、コンストラクターを明示的に書きます。
// 构造函数
String(const char* str = "")
:_str(new char [strlen(str) + 1])
,_size(strlen(str))
,_capacity(_size)
{
strcpy(_str, str);
}
コンストラクターには""
空の文字列であるデフォルト値を指定しました。スペースを適用するとき、スペースを保存する必要があるため、多くの場合、 str'\0'
よりも 1 つ多くのスペースを適用する必要があります。最後に、strcpy関数を使用してstrをコピーします。 _str。
2. デストラクター
文字列クラスのスペースを手動で適用するため、手動で解放する必要があります。デストラクターは次のとおりです。
// 析构函数
~String()
{
delete[] _str;
_str = nullptr;
_size = _capacity = 0;
}
deleteを使用するには、使用法が一致する必要があることに注意してください。
3. コンストラクターのコピー
ここのコピー コンストラクターでは、浅いコピーと深いコピーについて復習する必要があります。また、クラスとオブジェクト (パート 2)についても以前に学習したので、ここで復習しましょう。
- 浅いコピー:値コピーとも呼ばれ、コンパイラーはオブジェクト内の値をコピーするだけです。リソースをオブジェクトで管理すると、最終的には複数のオブジェクトが同じリソースを共有することになりますが、オブジェクトが破棄されるとリソースが解放されますが、このとき他のオブジェクトはリソースが解放されたことを知らず、それを認識していると考えられます。はまだ有効であるため、リソースに対するアクセス操作が継続すると、アクセス違反が発生します。
ディープ コピーを使用すると、シャロー コピーの問題を解決できます。つまり、各オブジェクトは独立したリソースを持ち、他のオブジェクトと共有すべきではありません。
- ディープ コピー:クラスにリソース管理が含まれる場合、そのコピー コンストラクター、代入演算子のオーバーロード、およびデストラクターを明示的に指定する必要があります。通常、ディープコピーモードで提供されます。
次に、文字列クラスのコピー コンストラクターを自分で明示的に作成します。
// String s2(s1);
// 拷贝构造
String(const String& str)
:_str(nullptr)
,_size(0)
,_capacity(0)
{
String tmp(str._str);
swap(tmp);
}
String s2(s1);
上記のコピー構造では、前提条件が次であることに注意してください: _str はs2オブジェクトに属し、str はs1オブジェクトに属します。私たちのアイデアは、最初に_strの初期化リストを空にし、 _sizeと_capacityをゼロに設定し、次にコンストラクターを使用してtmpString tmp(str._str);
オブジェクトをインスタンス化します。この時点では、 tmp はs1と同等です。最後に、s2オブジェクトとtmpオブジェクトのリソースが交換されます。つまり、 s1からs2へのコピーが完了します。また、 tmp は、次のときに自動的にデストラクターを呼び出します。それは範囲外になります。
上記のswap
関数はstringクラスにも含まれているため、自分で実装する必要があることに注意してください。実装は次のとおりです。
// 交换
void swap(String& tmp)
{
std::swap(_str, tmp._str);
std::swap(_size, tmp._size);
std::swap(_capacity, tmp._capacity);
}
実装では、標準ライブラリのswap関数を使用して、文字列クラスのswap関数を完成させます。
使い方と結果は以下の通りです。
4. 代入演算子のオーバーロード
代入演算子のオーバーロードはコピー構築に似ており、次のように実装されます。
// s2 = s1
// 赋值运算符重载
String& operator=(const String& str)
{
if (this != &str)
{
String tmp(str._str);
swap(tmp);
}
return *this;
}
使い方と結果は以下の通りです。
5. イテレータ
文字列クラスのイテレータは実際にはネイティブ ポインタであり、上記の文字列クラス宣言で宣言されているので、以下で直接実装しましょう。
// 迭代器
Young::String::iterator Young::String::begin()
{
return _str;
}
Young::String::iterator Young::String::end()
{
return _str + _size;
}
Young::String::const_iterator Young::String::begin() const
{
return _str;
}
Young::String::const_iterator Young::String::end() const
{
return _str + _size;
}
宣言と定義を別々に記述するため、iterator/const_iterator
とbegin()/end()
の前にスコープを指定する必要があります。ここで、 iterator/const_iterator は、それぞれ通常のオブジェクトによって呼び出されるイテレータとconstオブジェクトによって呼び出されるイテレータです。 begin()/end()はそれぞれ、先頭と へのポインタです。文字列の終わり。
6. 要素アクセス: [] オーバーロード
stringへのアクセスを容易にするために、次のように [] をオーバーロードして添字に直接アクセスできます。
const オブジェクト:
const char& Young::String::operator[](size_t index) const
{
assert(index < _size);
return _str[index];
}
通常のオブジェクト:
char& Young::String::operator[](size_t index)
{
assert(index < _size);
return _str[index];
}
7. ストリーム挿入とストリーム抽出のオーバーロード
stringを使用する場合、文字列の表示を容易にするために、ストリームの挿入とストリーム抽出をオーバーロードして、文字列の印刷と表示を容易にすることができます。前述のように、使用を容易にし、ストリームの挿入と抽出の使用価値を反映するために、このポインターが最初のパラメーターの位置をプリエンプトしないようにクラスの外部に実装するには、実装は次のようになります。
// 流插入 cout << s1;
ostream& Young::operator<<(ostream& out, const String& s)
{
for (size_t i = 0; i < s.size(); i++)
{
out << s[i];
}
return out;
}
ストリームの挿入では、各文字を出力するだけで済みます。
// 流提取 cin >> s
istream& Young::operator>>(istream& in, String& s)
{
s.clear();
char buff[129];
size_t i = 0;
char ch = in.get();
while (ch != ' ' && ch != '\n')
{
buff[i++] = ch;
if (i == 128)
{
buff[i] = '\0';
s += buff;
i = 0;
}
ch = in.get();
}
if (i != 0)
{
buff[i] = '\0';
s += buff;
}
return in;
}
入力文字を格納するためのbuff配列を作成します。buff配列がいっぱいの場合は、スペースが頻繁に開くことを避けるために、バッファ配列をオブジェクトに挿入します。ストリームの抽出は、ストリームの抽出が発生した時点で終了するか、デフォルト' '
で終了するため、次のメンバー関数を'\0'
使用する必要があります。cinをまたはget()
に抽出することで、終了条件を判断しやすくなります。' '
'\0'
8. 容量関連のインターフェース
(1)サイズ
文字列の有効な長さを取得して実装します。
size_t Young::String::size() const
{
return _size;
}
(2)容量
文字列の容量を取得して実装します。
size_t Young::String::capacity() const
{
return _capacity;
}
(3)クリア
文字列の内容をクリアして、次のことを実現します。
void Young::String::clear()
{
_str[0] = '\0';
_size = 0;
}
文字列の内容をクリアしてもスペースは破壊されないため、インデックス 0 に追加し'\0'
て長さを 0 に設定するだけで済みます。
(4)空
文字列が空の文字列かどうかを判断するには、以下を実装します。
bool Young::String::empty() const
{
return _size == 0;
}
_sizeが 0かどうかを判断する必要があるだけです。
上記の 4 つのインターフェイスは次のように使用されます。
(5)リザーブ
予約インターフェイスの関連ドキュメントを参照できます。
実際には、n個のスペースを申請することを意味します。reserveは、 n 個のスペースを予約することを意味します。nが_capacityより大きい場合、スペースは変更されます。n が _capacity より小さい場合、スペースは変更されません。reserve は変更されないことに注意してください_sizeの値; その実装は次のとおりです。
// 申请空间
void Young::String::reserve(size_t n)
{
if (n > _capacity)
{
char* tmp = new char[n + 1];
strcpy(tmp, _str);
delete[] _str;
_str = tmp;
_capacity = n;
}
}
n個のスペースを申請する必要があり、 n+1 個のスペースを申請し、 のスペースを予約する必要があるとします'\0'
。次に、元の文字列の内容を新しく開いたスペースにコピーし、元のスペース_strを破棄して、元のスペース_strをそのまま使用します。新しいスペースtmpをポイントします。
(6)リサイズ
予約とサイズ変更の違いは、サイズ変更はスペースのサイズを調整してスペースを初期化できるのに対し、サイズ変更は_sizeの値を変更できることです。
// 调整空间+初始化
void Young::String::resize(size_t n, char c)
{
// 如果 n 大于 _size,直接申请 n 个空间,然后从原来的尾部开始初始化
if (n > _size)
{
reserve(n);
for (size_t i = _size; i < n; i++)
{
_str[i] = c;
}
_str[n] = '\0';
_size = n;
}
// 否则,删数据
else
{
_str[n] = '\0';
_size = n;
}
}
初期化文字が明示的に渡されない場合は、宣言時に指定したデフォルト値が使用されます'\0
。
9. 文字列関連のインターフェースを変更する
(1)プッシュバック
末尾挿入、つまり文字列の末尾に文字を挿入します。まず元のドキュメントを見てみましょう。
実装は次のとおりです。
// 尾插字符
void Young::String::push_back(char c)
{
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
_str[_size++] = c;
_str[_size] = '\0';
}
テールプラグを挿入する前に、容量がいっぱいかどうかを判断する必要があります。いっぱいの場合は拡張する必要があります。容量が 0 の場合は、デフォルトで 4 つの容量を開きます。
(2)追加
文字列を追加するには、まずドキュメントを見てみましょう。
ドキュメントには多くのインターフェイスがオーバーロードされています。ここでは、最後に文字列を挿入するインターフェイスを 1 つだけ実装します。これは、上図の 3 番目のインターフェイスです。実装は次のとおりです。
// 尾插字符串
void Young::String::append(const char* str)
{
int len = strlen(str);
// 空间不够扩容
if (_size + len > _capacity)
{
reserve(_size + len);
}
strcpy(_str + _size, str);
_size += len;
}
(3) += 演算子のオーバーロード
+=演算子は、文字、文字列、および文字列オブジェクトも追加します。ここでは、文字と文字列の追加、つまり末尾挿入を実装します。実装は次のとおりです:
//尾插字符
Young::String& Young::String::operator+=(char c)
{
push_back(c);
return *this;
}
以前に実装されたpush_back
とを使用するappend
と、それらを再利用して実装するだけで済みます。
// 尾插字符串
Young::String& Young::String::operator+=(const char* str)
{
append(str);
return *this;
}
上記 4 つのインターフェイスの使用法と、ストリーム挿入とストリーム抽出の使用法は次のとおりです。
(4)インサート
Insertは、文字chまたは文字列str をpos位置に挿入することです。文字または文字列を挿入するためのインターフェイスを次のように実装します。
// 插入字符
Young::String& Young::String::insert(size_t pos, char ch)
{
assert(pos < _size);
// 满了就扩容
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
// 挪动数据
size_t end = _size + 1;
while (end > pos)
{
_str[end] = _str[end - 1];
end--;
}
// 插入字符
_str[pos] = ch;
_size++;
return *this;
}
文字列を挿入:
Young::String& Young::String::insert(size_t pos, const char* str)
{
assert(pos < _size);
// 判断插入字符串的长度是否会满
size_t len = strlen(str);
if (_size + len > _capacity)
{
reserve(_size + len);
}
// 挪动数据
size_t end = _size + len;
while (end > pos)
{
_str[end] = _str[end - len];
end--;
}
// 拷贝数据1.
/*for (size_t i = pos; i < len; i++)
{
_str[i] = str[i];
}*/
// 拷贝数据2.
strncpy(_str + pos, str, len);
_size += len;
return *this;
}
(5)消去
Erase は、位置posから始まる長さlenの文字列を削除します。lenに値が指定されていない場合は、デフォルトで最後まで削除されます。
最後に、宣言でnposの静的な符号なし変数を定義し、 -1として定義する必要があります。符号なしなので、整数の最大値です。 nposにデフォルト値を与えると、それを取得できます最後まで。宣言と定義が別々に記述されている場合、デフォルト値は宣言時点でのみ指定できることに注意してください。
実装は次のとおりです。
Young::String& Young::String::erase(size_t pos, size_t len)
{
assert(pos < _size);
// 删到末尾
if (len == npos || pos + len > _size)
{
_str[pos] = '\0';
_size = pos;
}
// 删 len 长度
else
{
strcpy(_str + pos, _str + pos + len);
_size -= len;
}
return *this;
}
挿入と消去の使用方法は次のとおりです。
10. 文字列を操作するためのインターフェース
(1)c_str
文字列を返します - char* 型を返し、以下を実装します。
const char* Young::String::c_str() const
{
return _str;
}
(2)探す
findは検索関数のインターフェイスです。posの位置から始まる文字/文字列を検索します。posに値が指定されていない場合、デフォルトの添え字は0から始まります。実装は次のとおりです:
文字を検索します。
size_t Young::String::find(char ch, size_t pos) const
{
assert(pos < _size);
for (size_t i = pos; i < _size; i++)
{
if (_str[i] == ch)
{
// 返回下标
return i;
}
}
return npos;
}
文字列を検索します。
size_t Young::String::find(const char* str, size_t pos) const
{
assert(pos < _size);
assert(str);
const char* ret = strstr(_str + pos, str);
if (ret == nullptr)
return npos;
// 下标相减,返回下标
return ret - _str;
}
strstrは一致する文字列を検索するライブラリ関数で、見つかった場合は一致する文字列の先頭を返し、見つからない場合は空を返します。
(3)部分文字列
substrは、 pos位置からlenまでの部分文字列を取得することです。 lenに値が指定されていない場合、デフォルトで最後、つまりnposになります。実装は次のとおりです:
Young::String Young::String::substr(size_t pos, size_t len) const
{
assert(pos < _size);
// 创建一个临时对象 tmp
String tmp;
// end 为取到的子串的结尾的下标
size_t end = pos + len;
// 取到末尾
if (len == npos || end > _size)
{
len = _size - pos;
end = _size;
}
// 申请 len 的空间
tmp.reserve(len);
// 开始取子串
for (size_t i = pos; i < end; i++)
{
tmp += _str[i];
}
return tmp;
}
一般に、findとsubstr は一緒に使用されます。これらの使用シナリオでは、次のように URL をプロトコル、ドメイン名、およびリソース名に分割できます。
11. 比較演算子のオーバーロード
前と同様に、実装する必要があるのは>
と==
演算子だけであり、これら 2 つを他のすべてに再利用できます。
bool Young::String::operator>(const String& s) const
{
return strcmp(_str, s._str) > 0;
}
bool Young::String::operator==(const String& s) const
{
return strcmp(_str, s._str) == 0;
}
bool Young::String::operator>=(const String& s) const
{
return *this > s || *this == s;
}
bool Young::String::operator<(const String& s) const
{
return !(*this >= s);
}
bool Young::String::operator<=(const String& s) const
{
return !(*this > s);
}
bool Young::String::operator!=(const String& s) const
{
return !(*this == s);
}
使い方と結果は以下の通りです。