目次
1. フレームワークの構築
まずは枠組みを構築する必要がありますが、
次に、コンテンツを入力します。
このフレームワークには主に次の点が含まれます。
1. 基本的なデフォルトのメンバー関数
2. 必須のメンバー変数
3. 一般的に使用される単純なインターフェイス (最初にコードを実行させます)
コードを見てください:
#pragma once
#include <iostream>
#include <string>
#include <assert.h>
#include <string.h>
using namespace std;
namespace xl {
class string {
private:
char* _str;
size_t _size;
size_t _capacity;
public:
string(const char* str = "")
: _size(strlen(str))
, _capacity(_size)
{
_str = new char[_capacity + 1];
strcpy(_str, str);
}
~string()
{
delete[] _str;
_str = nullptr;
_size = _capacity = 0;
}
public:
char& operator[](size_t pos) {
assert(pos < _size);
return _str[pos];
}
char& operator[](size_t pos) const {
assert(pos < _size);
return _str[pos];
}
const char* c_str() const {
return _str;
}
size_t size() const {
return _size;
}
};
}
実装される機能には次のものが含まれます。
1. コンストラクターとデストラクター
2. 基本的な [ ] アクセス
3. c_str は変換可能です
4.そして容量に関連するサイズ
最初に走査を実行できます。
#include "string.h"
int main()
{
xl::string s1("hello");
cout << s1.c_str() << endl;
for (int i = 0; i < s1.size(); i++) {
cout << s1[i] << " ";
}
cout << endl;
return 0;
}
出力:
2. イテレータの実装
イテレータはポインタである場合とそうでない場合があります。
しかし、文字列ではイテレータはポインタです。
標準ライブラリのイテレータがクラス内に存在するため、イテレータをクラスに実装します。
クラスフィールドを通じて直接アクセスできます。
コードを見てください:
public:
typedef char* iterator;
iterator begin() {
return _str;
}
iterator end() {
return _str + _size;
}
このようにして、直接実行できます。
#include "string.h"
int main()
{
xl::string s1("hello");
cout << s1.c_str() << endl;
for (int i = 0; i < s1.size(); i++) {
cout << s1[i] << " ";
}
cout << endl;
xl::string::iterator it = s1.begin();
while (it != s1.end()) {
cout << *it << " ";
it++;
}
cout << endl;
return 0;
}
出力:
しかし、ああ、これは通常のオブジェクトのイテレータのみをサポートします。
const オブジェクトもあるので、別のオブジェクトを実装する必要があります。
public:
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 イテレータの使用は実際に禁止されています。
ただし、通常の反復子を使用すると、ポイントされた値を変更できます。
void test2() {
xl::string s1("hello");
xl::string::const_iterator cit = s1.begin();
while (cit != s1.end()) {
//*cit += 1;
cout << *cit << " ";
cit++;
}
cout << endl;
xl::string::iterator it = s1.begin();
while (it != s1.end()) {
*it += 1;
cout << *it << " ";
it++;
}
cout << endl;
}
出力:
3. 文字列コピーの構築と代入(ディープコピー)
コピー構築
新しいスペースを開く必要があります:
string(const string& s) {
_str = new char[s._capacity + 1];
memcpy(_str, s._str, s.size() + 1);
_size = s._size;
_capacity = s._capacity;
}
代入構造
古いスペースを削除し、新しいスペースを開き、データをコピーする戦略を直接採用します。
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;
}
return *this;
}
上記の規則正しい方法を、私たちは伝統的な書き方と呼んでいますが、
次に、伝統的な書き方と、もちろん現代的な書き方があります。次の書き方を見てみましょう。
void swap(string& tmp) {
::swap(_str, tmp._str);
::swap(_size, tmp._size);
::swap(_capacity, tmp._capacity);
}
// 现代写法
string& operator=(string tmp) {
swap(tmp);
return *this;
}
文字列クラスにスワップを実装しました。コピー構築によって形成された tmp が作業に役立ちます。
次に、スワップを使用して tmp のコンテンツを売春することができます。
個人的には、この方法は本質的にはコピー構築の再利用であると考えています。
実際、コピー構築は現代的な方法でも書くことができます。
string(const string& s)
: _str(nullptr)
, _size(0)
, _capacity(0)
{
string tmp(s._str);
swap(tmp);
}
いいえ、コピー構築という現代の文章作成法の本質も再利用であることがわかりました。
彼が再利用するのは、私たちが実装したコンストラクターです。
4. 文字列の追加、削除、確認、変更
これらの派手なインターフェイスを実装する前に、
まず展開の問題を解決しましょう。
予約インターフェイス
指定された n のサイズに応じて、直接拡張できます。
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;
}
}
インターフェースのサイズ変更
容量を拡張するにはリサイズという方法もありますが、文字列をリサイズすることはほとんどありません。
実装を見てみましょう:
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';
}
}
プッシュバックインターフェース
stringのpush_backは最後に要素を挿入することです。
2倍拡張機構を採用(個人の好みによりますが、1.5倍拡張もあります)
void push_back(char ch) {
if (_size == _capacity) {
// 2倍扩容
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
_str[_size] = ch;
_size++;
_str[_size] = '\0';
}
インターフェースを追加
append は最後に文字列を挿入することです。
私が使用する拡張メカニズムはオンデマンド拡張です。
void append(const char* str) {
size_t len = strlen(str);
if (_size + len > _capacity) {
// 至少扩容到 _size + len
reserve(_size + len);
}
memcpy(_str + _size, str, len + 1);
_size += len;
}
もちろん、実際には += を使用することを好みます。
演算子+=()の実装
もちろん、この関数では、前に実装した Push_back と append を直接再利用できます。
string& operator+=(char ch) {
push_back(ch);
return *this;
}
string& operator+=(const char* str) {
append(str);
return *this;
}
もちろん、これははるかに使いやすいです。
void test4() {
xl::string s1("hello");
cout << s1.c_str() << endl;
s1 += " ";
s1 += "a";
s1 += "a";
s1 += "a";
s1 += "a";
s1 += "a";
s1 += "a";
s1 += " ";
s1 += "string";
cout << s1.c_str() << endl;
}
出力:
インターフェースを挿入
実際、STL の文字列は多くの冗長なオーバーロードを実装しています。
学習として、コアの呼び出しメソッドのみを実装します。
2 つのオーバーロードを実装します。
void insert(size_t pos, size_t n, char ch) {
}
void insert(size_t pos, const char* str) {
}
まずは最初のものを実装して、文字を挿入しましょう。
void insert(size_t pos, size_t n, char ch) {
assert(pos <= _size);
if (_size + n > _capacity) {
// 至少扩容到_size + n
reserve(_size + n);
}
// 挪动数据
size_t end = _size;
while (end >= pos && end != npos) {
_str[end + n] = _str[end];
end--;
}
// 填值
for (size_t i = 0; i < n; i++) _str[pos + i] = ch;
_size += n;
}
次に、文字列を挿入します。
実装方法は似ています。
void insert(size_t pos, const char* str) {
assert(pos <= _size);
size_t len = strlen(str);
if (_size + len > _capacity) {
// 至少扩容到_size + len
reserve(_size + len);
}
// 挪动数据
size_t end = _size;
while (end >= pos && end != npos) {
_str[end + len] = _str[end];
end--;
}
// 填值
for (size_t i = 0; i < len; i++) _str[pos + i] = str[i];
_size += len;
}
テストしてみましょう:
void test5() {
xl::string s1("hello");
cout << s1.c_str() << endl;
s1.insert(5, 3, 'x');
s1.insert(0, "string ");
cout << s1.c_str() << endl;
}
出力:
インターフェースの消去
削除文字数が一定文字を超えた場合、または削除文字数が指定されていない場合は、すべて削除されます。
void erase(size_t pos, size_t len = npos) {
assert(pos <= _size);
if (len == npos || pos + len >= _size) {
_size = pos;
_str[pos] = '\0';
}
else {
size_t end = pos + len;
while (end <= _size) {
_str[pos++] = _str[end++];
}
_size -= len;
}
}
テストしてみましょう:
void test5() {
xl::string s1("hello");
cout << s1.c_str() << endl;
s1.insert(5, 3, 'x');
s1.insert(0, "string ");
cout << s1.c_str() << endl;
s1.erase(10, 3);
cout << s1.c_str() << endl;
s1.erase(2, 100);
cout << s1.c_str() << endl;
}
出力:
インターフェースを見つける
単一文字の場合は、直接検索します。
size_t find(char ch, size_t pos = 0) {
for (size_t i = pos; i < _size; i++) {
if (_str[i] == ch) return i;
}
return npos;
}
文字列の場合、strstr を使用して激しく一致させることができます。
size_t find(const char* str, size_t pos = 0) {
const char* ptr = strstr(_str + pos, str);
if (ptr) return ptr - _str;
else return npos;
}
substrインターフェース
文字列をインターセプトする操作:
string substr(size_t pos = 0, size_t len = npos) {
assert(pos <= _size);
size_t n = len + pos;
if (len == npos || pos + len > _size) {
n = _size;
}
string tmp;
tmp.reserve(n);
for (size_t i = pos; i < n; i++) {
tmp += _str[i];
}
return tmp;
}
テストしてみましょう:
void test6() {
xl::string s1("hello string");
cout << s1.c_str() << endl;
size_t pos = s1.find('s');
cout << s1.substr(pos, 3).c_str() << endl;
pos = s1.find('s');
cout << s1.substr(pos, 100).c_str() << endl;
}
出力:
明確なインターフェース
ちなみに、次のように認識してください。
void clear() {
_str[0] = '\0';
_size = 0;
}
ストリームの挿入とストリームの抽出
演算子の順序により次のことが必要となるため、これをクラスの外で実装する必要があります。
まずストリームの挿入を見てみましょう。
ostream& operator<<(ostream& out, const string& s) {
for (auto e : s) cout << e;
return out;
}
ストリーム抽出をもう一度見てみましょう。
istream& operator>>(istream& in, string& s) {
s.clear();
char ch = in.get();
while (ch != ' ' && ch != '\n') {
s += ch;
ch = in.get();
}
return in;
}
テスト時間〜
void test7() {
string s1;
cin >> s1;
cout << s1 << endl;
}
出力:
5. 比較のための演算子オーバーロード関数
同じ操作を引き続き実行し、最初に 2 つを実装し、それらをすべて再利用します。
演算子<
ライブラリ関数 memcmp を使用して次のことを実現します。
bool operator<(const string& s) {
int ret = memcmp(_str, s._str, _size < s._size ? _size : s._size);
return ret == 0 ? _size < s._size : ret < 0;
}
演算子==
bool operator==(const string& s) {
return memcmp(_str, s._str, _size < s._size ? _size : s._size) == 0;
}
その他の多重化
bool operator<=(const string& s) {
return *this < s || *this == s;
}
bool operator>(const string& s) {
return !(*this <= s);
}
bool operator>=(const string& s) {
return !(*this < s);
}
bool operator!=(const string& s) {
return !(*this == s);
}
6. ソースコードの共有
#pragma once
#include <iostream>
#include <string>
#include <assert.h>
#include <string.h>
using namespace std;
namespace xl {
class string {
private:
char* _str;
size_t _size;
size_t _capacity;
const static size_t npos = -1; // 可以这样用,但不建议,违背了C++的语法准则(建议声明和定义分离)
public:
string(const char* str = "")
: _size(strlen(str))
, _capacity(_size)
{
_str = new char[_capacity + 1];
memcpy(_str, str, _size + 1);
}
传统写法
//string(const string& s) {
// _str = new char[s._capacity + 1];
// memcpy(_str, s._str, s._size + 1);
// _size = s._size;
// _capacity = s._capacity;
//}
// 现代写法
string(const string& s)
: _str(nullptr)
, _size(0)
, _capacity(0)
{
string tmp(s._str);
swap(tmp);
}
传统写法
//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;
// }
// return *this;
//}
void swap(string& tmp) {
::swap(_str, tmp._str);
::swap(_size, tmp._size);
::swap(_capacity, tmp._capacity);
}
// 现代写法
string& operator=(string tmp) {
swap(tmp);
return *this;
}
~string()
{
delete[] _str;
_str = nullptr;
_size = _capacity = 0;
}
public:
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;
}
public:
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;
}
}
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 push_back(char ch) {
if (_size == _capacity) {
// 2倍扩容
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) {
// 至少扩容到 _size + len
reserve(_size + len);
}
memcpy(_str + _size, str, len + 1);
_size += len;
}
string& operator+=(char ch) {
push_back(ch);
return *this;
}
string& operator+=(const char* str) {
append(str);
return *this;
}
void insert(size_t pos, size_t n, char ch) {
assert(pos <= _size);
if (_size + n > _capacity) {
// 至少扩容到_size + n
reserve(_size + n);
}
// 挪动数据
size_t end = _size;
while (end >= pos && end != npos) {
_str[end + n] = _str[end];
end--;
}
// 填值
for (size_t i = 0; i < n; i++) _str[pos + i] = ch;
_size += n;
}
void insert(size_t pos, const char* str) {
assert(pos <= _size);
size_t len = strlen(str);
if (_size + len > _capacity) {
// 至少扩容到_size + len
reserve(_size + len);
}
// 挪动数据
size_t end = _size;
while (end >= pos && end != npos) {
_str[end + len] = _str[end];
end--;
}
// 填值
for (size_t i = 0; i < len; i++) _str[pos + i] = str[i];
_size += len;
}
void erase(size_t pos, size_t len = npos) {
assert(pos <= _size);
if (len == npos || pos + len >= _size) {
_size = pos;
_str[pos] = '\0';
}
else {
size_t end = pos + len;
while (end <= _size) {
_str[pos++] = _str[end++];
}
_size -= len;
}
}
size_t find(char ch, size_t pos = 0) {
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) {
const char* ptr = strstr(_str + pos, str);
if (ptr) return ptr - _str;
else return npos;
}
string substr(size_t pos = 0, size_t len = npos) {
assert(pos <= _size);
size_t n = len + pos;
if (len == npos || pos + len > _size) {
n = _size;
}
string tmp;
tmp.reserve(n);
for (size_t i = pos; i < n; i++) {
tmp += _str[i];
}
return tmp;
}
public:
char& operator[](size_t pos) {
assert(pos < _size);
return _str[pos];
}
char& operator[](size_t pos) const {
assert(pos < _size);
return _str[pos];
}
bool operator<(const string& s) {
int ret = memcmp(_str, s._str, _size < s._size ? _size : s._size);
return ret == 0 ? _size < s._size : ret < 0;
}
bool operator==(const string& s) {
return _size == s._size && memcmp(_str, s._str, _size) == 0;
}
bool operator<=(const string& s) {
return *this < s || *this == s;
}
bool operator>(const string& s) {
return !(*this <= s);
}
bool operator>=(const string& s) {
return !(*this < s);
}
bool operator!=(const string& s) {
return !(*this == s);
}
const char* c_str() const {
return _str;
}
size_t size() const {
return _size;
}
void clear() {
_str[0] = '\0';
_size = 0;
}
};
ostream& operator<<(ostream& out, const string& s) {
for (auto e : s) cout << e;
return out;
}
istream& operator>>(istream& in, string& s) {
s.clear();
char ch = in.get();
while (ch != ' ' && ch != '\n') {
s += ch;
ch = in.get();
}
return in;
}
}
最後に次のように書きます。
以上が今回の記事の内容となります、読んでいただきありがとうございます。
何かを得たと感じたら、ブロガーに「いいね! 」を与えることができます。
記事の内容に抜けや間違いがある場合は、ブロガーにプライベートメッセージを送信するか、コメントエリアで指摘してください〜