記事ディレクトリ
メンバー変数とビューインターフェイス
文字列クラスには、有効な要素の数、容量、文字列をそれぞれ記録する 3 つのメンバー変数があります。静的変数npos=1 は整数の最大値を表し、クラスの外でのみ代入できます。
メンバー変数:
private:
size_t _size; //个数
size_t _capacity; //容量
char* _str; //字符数组
public:
const static size_t npos; //表示整形最大值
イテレータ (読み取りおよび読み取り/書き込み)
typedef char* iterator;
typedef const char* const_iterator;
iterator begin()
{
return _str;
}
iterator end()
{
return _str + _size;
}
const_iterator begin() const
{
return _str;
}
const_iterator end() const
{
return _str + _size;
}
演算子[] (読み取りおよび読み取り/書き込み)
constメンバー関数の前の戻り値にもconst を追加する必要があります。this->_str は変更できません。ポインターは組み込み型です。ポインターは変更できませんが、ポインターが指す領域の内容は変更できません。変更できるため、戻り値の前にconstを追加します。
char& operator[](size_t pos)
{
assert(pos < _size);
return _str[pos];
}
const char& operator[](size_t pos)const
{
assert(pos < _size);
return _str[pos];
}
c_str()
const char* c_str() const
{
return _str;
}
サイズ()
size_t size() const
{
return _size;
}
コンストラクタ
文字列で構築される
パラメーターがない場合は、デフォルト値と空の文字列が指定されます。スペースを含めることはできず、スペースも有効な文字であることに注意してください。new がスペースを開くときは、文字列の長さ+ 1を開く必要があります。strlen()関数は有効な文字の数のみを記録します。\0文字用のスペースを追加する必要があります。
string(const char* str = "")
{
_size = strlen(str);
_capacity = _size;
_str = new char[_capacity + 1];
memcpy(_str, str, _size + 1);
}
オブジェクトを使用して構築する (2 つのメソッド)
方法1では、 strcpyの代わりにmemcpyライブラリ関数を使用します。文字列"abc \0 def"の場合、 strcpyはそれを処理できません。\0に遭遇すると終了します。memcpyは、メモリ内のバイトをコピーする 方法2です。swap()ライブラリ関数が呼び出され、文字列コンストラクタが再利用されます。_strは初期化する必要があることにも注意してください。これはポインタ型であり、コンパイラは自動的に初期化しません。tmpと交換した後、 tmpのデストラクタ不正な領域が破壊され、エラーが報告されます。
//方法 1
string(const string& s)
{
_str = new char[s._capacity + 1];
memcpy(_str, s._str, s._size + 1);
_size = s._size;
_capacity = s._capacity;
}
//方法 2
string(const string& s)
:_size(0)
,_capacity(0)
,_str(nullptr)
{
string tmp(s._str);
swap(tmp);
}
破壊する
~string()
{
delete[] _str;
_str = nullptr;
_size = _capacity = 0;
}
代入演算子のオーバーロード
方法 1 は比較的原始的なもので、実装方法はreserve()関数と同じで、同じサイズの領域をオープンし、データをコピーし、元の領域を破棄して、_str、_capacity、_size を更新します。この問題はswap()
ライブラリ内の まず、ライブラリ内のswap()関数の実装を見てください。その中のオブジェクト交換が相互に割り当てられる必要があり、その割り当てでoperator()を呼び出す必要があること、そしてoperator()が呼び出す必要があることを見つけるのは難しくありません。 swap() を順番に呼び出します。これはデッド再帰になります。
//方法一
string& operator=(const string& s)
{
if (this != &s)
{
char* tmp = new char[s._capacity + 1];
memcpy(tmp, s._str, s._size+1);
delete[] _str;
_str = tmp;
_size = s._size;
_capacity = s._capacity;
}
return *this;
}
//方法二
string& operator=(string tmp)
{
//std::swap(*this,tmp);//这样写死递归
std::swap(_str, tmp._str);
std::swap(_size, tmp._size);
std::swap(_capacity, tmp._capacity);
return *this;
}
拡張と調整
拡張には、 reserve()関数が外部からも利用できることを 考慮してリモート拡張の方法を採用していますので、判断を追加し、リモート拡張後は必ず元の領域を解放してください
予約()
void reserve(size_t n)
{
//异地扩容
if (n > _capacity)
{
char* tmp = new char[n + 1];
memcpy(tmp, _str, _size + 1);
delete[] _str;
_str = tmp;
_capacity = n;
}
}
サイズ変更()
スペースが小さくなった場合は、 _capacity を 変更せず、_sizeを変更して\0を割り当てます。スペースが大きい場合は、別の場所で容量を拡張し、デフォルト値\0をchに追加します。かわった
void resize(size_t n, char ch = '\0')
{
if (n < _size) {
_size = n;
_str[_size] = '\0';
}
else {
reserve(n);
for (size_t i = _size; i < n; i++)
{
_str[i] = ch;
}
_size = n;
_str[_size] = '\0';
}
}
クリア()
void clear()
{
_size = 0;
_str[_size] = '\0';
}
クラッド
プッシュバック()
void push_back(const char ch)
{
//扩容×2
if (_size == _capacity)
{
//有可能用空串初始化的
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
_str[_size] = ch;
++_size;
_str[_size] = '\0';
}
追加()
void append(const char* str)
{
size_t len = strlen(str);
if (_size + len > _capacity)
{
reserve(_size + len);
}
//strcpy(_str + _size, str);
memcpy(_str + _size, str, len + 1);
_size += len;
}
演算子+=()
前の 2 つの関数Push_back() と append() を直接再利用し、これら 2 つの関数を呼び出し、スペースが十分かどうかを検討し、十分でない場合は容量を拡張して挿入します。
string& operator+=(const char ch)
{
push_back(ch);
return *this;
}
string& operator+=(const char* str)
{
append(str);
return *this;
}
insert() (文字と文字列)
まず挿入位置の正当性を確認し、endが unsigned 型だとします。posが 0 であることを考えると無限ループになります。endは -1 になりますが、整数の最大値です。したがって、先ほど設定した静的変数npos は機能しました。最後に、拡張が必要かどうかの境界問題に注意してください。
void insert(size_t pos, size_t n, char ch)
{
assert(pos <= _size);
if (_size + n > _capacity)
{
reserve(_size + n);
}
size_t end = _size;
_size += n;
//end是无符号类型,考虑pos为0时 的死循环
while (end >= pos && end != npos)
{
_str[end + n] = _str[end];
end--;
}
for (size_t i = 0; i < n; i++)
{
_str[pos + i] = ch;
}
}
void insert(size_t pos, const char* str)
{
assert(pos <= _size);
size_t len = strlen(str);
if (len + _size > _capacity)
{
reserve(len + _size);
}
size_t end = _size;
_size += len;
while (end >= pos && end != npos)
{
_str[end + len] = _str[end];
end--;
}
for (size_t i = 0; i < len; i++)
{
_str[pos + i] = str[i];
}
}
消去()
削除文字数には境界判定に便利なデフォルト値を与えています。posの合法性を考慮した後、境界問題に対処します。削除作業は後ろから前へ繰り返すことになります。
void erase(size_t pos, size_t len = npos)
{
assert(pos <= _size);
if (len == npos || pos + len >= _size){
_str[pos] = '\0';
_size = pos;
}
else {
size_t end = pos + len;
while (end <= _size)
{
_str[end-len] = _str[end];
++end;
}
_size -= len;
}
}
find() (文字と文字列)
見つからない場合は、nposが返されます。デフォルト値は 0 から始まります。文字列を検索するには、KMP、BF などの多くのアルゴリズムがあります。ここでは、簡単に実装するためにstrstr()ライブラリ関数を直接呼び出します。
size_t find(char ch, size_t pos = 0)
{
assert(pos < _size);
for (size_t i = pos; i < _size; i++)
{
if (_str[i] == ch)
{
return i;
}
}
return npos;
}
size_t find(const char* str, size_t pos = 0)
{
assert(pos < _size);
const char* ptr = strstr(_str + pos, str);
if (ptr){
return ptr - _str;
}
else {
return npos;
}
}
部分文字列()
pos の合法性 を確認した後、カットされた文字列の長さを計算する必要があります。返されるのは一時的な文字列クラスであることに注意してください。スペースを開いてから、operator+=関数を呼び出します。これにより、次の\0が自動的に処理されます。
string substr(size_t pos=0, size_t len = npos)
{
assert(pos < _size);
size_t n = len;
if (len == npos || pos + len >= _size)
{
n = _size - pos;
}
string tmp;
tmp.reserve(n);
for (size_t i = pos; i < pos + n; i++)
{
tmp += _str[i];
}
return tmp;
}
文字列サイズの比較
演算子<()
"abc" や "abcd" など、文字列比較には多くの種類があるため、ライブラリ関数strcmp および memcmpは適用できなくなりました。これらは同じ長さの文字列比較に適しており、異なる長さの文字列比較には一致しない傾向があります。 - 境界の問題 ここでは、自分で直接実現します
bool operator<(const string& s) const
{
size_t lt1 = 0;
size_t lt2 = 0;
while (lt1 < _size && lt2 < s._size)
{
if (_str[lt1] < s._str[lt2]) {
return true;
}
else if (_str[lt1] > s._str[lt2]) {
return false;
}
else {
lt1++;
lt2++;
}
}
return _size < s._size;
}
演算子==()
bool operator==(const string& s) const
{
return _size == s._size && memcmp(_str, s._str, _size) == 0;
}
再利用
bool operator<=(const string& s) const
{
return *this < s || *this == s;
}
bool operator>(const string& s) const
{
return !(*this <= s);
}
bool operator>=(const string& s) const
{
return *this > s || *this == s;
}
bool operator!=(const string& s) const
{
return !(*this == s);
}
私流
演算子<<()
ostream& operator<<(ostream& out, const string& s)
{
for (auto ch : s)
{
out << ch;
}
return out;
}
演算子>>()
バッファの前にあるスペースと改行は挿入前に処理する必要があり、スペースと改行はループによって解決されます。ストリーム挿入を行う場合、一般的な配列を設定し、挿入されたデータを最初に buff 配列に配置し、複数の演算子 +=関数の呼び出しと複数の展開判定を回避し、最後に後続の\0を処理します。
istream& operator>>(istream& in, string& s)
{
s.clear();
char ch = in.get();
//除去缓冲区的空格和换行
while (ch == ' ' || ch == '\n')
{
ch = in.get();
}
//用一个中将容器,减少扩容次数
char buff[128];
size_t i = 0;
while (ch != ' ' && ch != '\n')
{
buff[i++] = ch;
if (i == 127)
{
//加上'\0'以免+=出问题
buff[i] = '\0';
s += buff;
i = 0;
}
ch = in.get();
}
if (i != 0)
{
buff[i] = '\0';
s += buff;
}
return in;
}