STLライブラリの文字列を模倣し、基本的に一般的に使用される操作を実装します。
コンテンツ
メンバー関数インターフェース
#pragma once
#include<iostream>
#include<string.h>
#include<stdlib.h>
#include<assert.h>
using namespace std;
namespace sjj//自定义一个命名空间
{
class string
{
public:
typedef char* iterator;//string的迭代器就是原生指针
typedef const char* const_iterator;
//构造函数
//string("hello");
string(const char* str = "");
//拷贝构造函数
//s2(s1)
string(const string& s);
//赋值重载
//s2=s1
string& operator=(string s);//传值传参也是拷贝构造
//析构
~string();
//容量大小相关的函数//
size_t size()const;
size_t capacity()const;
bool empty()const;
void clear();
void reserve(size_t n);//对容量进行改变,让容量到n
void resize(size_t n, char ch = '\0');//改变size的大小
//访问下标相关的函数//
char& operator[](size_t pos);
const char& operator[](size_t pos)const;
//查找相关的函数//
//正向查找一个字符
size_t find(char c, size_t pos = 0);
//正向查找一个字符串
size_t find(char* s, size_t pos = 0);
///迭代器相关的函数/
iterator begin();
iterator end();
const_iterator begin()const;
const_iterator end()const;
///插入字符串相关的函数/
//尾插
void push_back(char c);
//追加字符串
void append(const char* str);
//加等于
string& operator+=(char c);
const string& operator+=(char* str);
//插入一个字符
string& insert(size_t pos, char c);
//插入字符串
string& insert(size_t pos, const char* s);
//删除相关的函数
//删除pos位置开始的n个字符
string& erase(size_t pos, size_t len = npos);
//其他函数//
//自己写的交换函数
void Swap(string& s);
//C形式的字符串
const char* c_str()const;
private:
char* _str;//字符串数组
size_t _size;//有效字符的个数 不含\0
size_t _capacity;//能存储有效字符的空间,不包含\0
static const size_t npos;//静态成员变量,在类外部初始化
};
const size_t string::npos = -1; //初始化
///关系运算符重载//
bool operator<(const string& s1, const string& s2);
bool operator==(const string& s1, const string& s2);
bool operator<=(const string& s1, const string& s2);
bool operator>(const string& s1, const string& s2);
bool operator>=(const string& s1, const string& s2);
bool operator!=(const string& s1, const string& s2);
// operator<< operator>>
// cout<<s1 --> operator<<(out,s1);
///流插入 流提取 重载///
ostream& operator<<(ostream& out, const string& s);
//ostream&是系统的类型,它能够实现输出内置类型的数据
istream& operator>>(istream& in, string& s);
}
デフォルトのメンバー関数
コンストラクタ
デフォルト値はヌル文字です。パラメータが渡されない場合は空の文字列が作成され、パラメータが渡される場合は渡されたパラメータが使用されます。
string(const char* str = "")//给缺省值""空字符串
:_size(strlen(str))
, _capacity(_size)
{
_str = new char[_capacity + 1];// +\0
strcpy(_str, str);//char *strcpy( char *strDestination, const char *strSource );
}
コピーコンストラクタ
まず、ディープコピーとシャローコピーに関する知識を理解する必要があります。ここをクリックしてください。
最初の書き方:伝統的な書き方
//传统写法 //要用自己写的
string(const string& s)
:_size(0)
, _capacity(0)
{
_str = new char[s._capacity + 1];//_str申请一块和s._str一样大的空间
strcpy(_str, s._str);//将两个指针指向交换
_size = s._size;
_capacity = s._capacity;
}
正直にディープコピーを行います。最初に元のスペースと同じ大きさのスペースを新しく作成し、次にデータをディープコピーして、2つのスペースが互いに影響を与えないようにします。
2番目:現代の執筆(推奨される執筆)
//现代写法————投机取巧
string(const string& s)
:_str(nullptr)
, _size(0)
, _capacity(0)
{
string tmp(s._str);//复用构造函数,构造一个tmp对象出来
this->Swap(tmp);//相当于Swap(this,tmp)
}
まとめ:
最新の書き込み方法は日和見的で、コンストラクターを再利用し、スコープ外で一時オブジェクトを開かせます。ローカルの一時オブジェクトは自動的に破棄されます。気にする必要はありません。ポインターを交換するだけで済みます。最後に、コードは非常に簡潔です。
デストラクタ
いくつかの動的リソースをクリーンアップします
~string()
{
delete[] _str;
_str = nullptr;
_size = _capacity = 0;
}
operator=代入演算子のオーバーロード
記述しない場合、デフォルトでコンパイラーによって生成されますが、浅いコピーでもあり、使用要件を満たしていません。
深くコピーするには、最初に2つのスペースの容量を考慮する必要があることを私たちは知っています:
①s3のデータ数がs1のデータ数より多い場合は、s1の拡張を考慮する必要があります。
②s1のスペースはs3のデータ数よりもはるかに大きいので、収縮の問題を考慮する必要があります
このようにして、問題を単純で失礼なものにし、s1のスペースを直接解放し、s3と同じ大きさのスペースを再度開いて、最後にそこにデータをコピーすることもできます。
最初:伝統的な文章
string& operator=(const string& s)
{
/*if (this != &s)//防止自己给自己赋值s3=s3
{
delete[] _str;//释放掉s1 默认有个隐藏的this指针,实际上是这样: this->_str
_str = new char[strlen(s._str) + 1];
strcpy(_str, s._str);
}*/
//优化版本
//new可能会失败,但是却先释放s1了,我们可以先new
if (this != &s)//防止自己给自己赋值s3=s3
{
char* tmp = new char[strlen(s._str) + 1];
strcpy(tmp, s._str);
delete[] _str;
_str = tmp;
}
return *this;//返回s1
}
デバッグウィンドウを通して見ることができます:
2番目:現代の執筆(非常に賢い)
//更NB的写法
//s1=s3 这里传值传参也是一种拷贝,s充当的就是tmp
string& operator=(string s)
{
this->Swap(s);
return *this; //返回左值,支持连续赋值
}
概要:ここで使用されている最新の記述は非常に巧妙で、値によるコピーを使用しています。これは、コピーコンストラクターの呼び出しに相当します。
容量関連機能
size()関数
\0を除いて_sizeを直接返すことができます
const size_t size()const
{
return _size;
}
capacity()関数
size_t capacity()const
{
return _capacity;
}
empty()関数
strcmp関数を使用して、2つの文字列ポインタを比較できます。
//判断是否为空
bool empty()
{
return strcmp(_str, "") == 0;
}
reserved()関数
容量をnに調整するには、最初にn + 1の新しいスペースを作成し、次に元のデータを新しいスペースにコピーし、元のスペースを解放し、2つのポインターのポインターを交換し、_capacityをnに設定します。
アニメーションのデモを見てみましょう。
void reserve(size_t n)//对容量进行改变,让容量到n
{
if (n > _capacity)
{
char* tmpstr = new char[n + 1];
strcpy(_str, tmpstr);
delete[] _str;
_str = tmpstr;
_capacity = n;
}
}
resize()関数
void resize(size_t n, char ch = '\0')//改变size的大小
{
if (n <= _size)//缩小的情况
{
_size = n;
_str[n] = '\0';
}
else//要插入数据的情况
{
if (n > _capacity)
{
reserve(n);
}
//void *memset( void *dest, int c, size_t count );
memset(_str + _size, ch, n - _size);
_size = n;
_str[n] = '\0';
}
}
サイズ変更機能には2つの状況があります。
最初:指定されたサイズの値nが_size未満です。この場合、データは切り捨てられ、最初のnのみが保持されます。
2番目のタイプ:指定されたサイズの値nは容量よりも大きいため、拡張の問題を考慮する必要があります
clear()関数
_sizeの位置をスラッシュ0に設定することは、文字列を空にすることと同じです。
void clear()
{
_size = 0;
_str[_size] = '\0';
}
下付き文字関連の関数にアクセスする
演算子[]関数
Operator []は、添え字を付けて要素にアクセスする最も一般的な方法であり、非常にシンプルで便利です。配列などの文字列にアクセスできるように実装されています。通常バージョンとconstオブジェクトバージョンを実装できます。
char& operator[](size_t pos)
{
assert(pos < _size);//防止越界
return _str[pos];//返回pos位置的引用,相当于就是把那个字符给返回了
}
//const版本
const char& operator[](size_t pos)const
{
assert(pos < _size);//防止越界
return _str[pos];//返回pos位置的引用,相当于就是把那个字符给返回了
}
関連する機能を探す
find()文字
nposは文字列クラスの静的メンバー変数であり、その値は整数の最大値です。
トラバースすることにより、最初から最後まで一致する文字列を検索し、戻り添え字を見つけ、見つからない場合はnposを返します。
//正向查找一个字符
size_t find(char c, size_t pos = 0)
{
assert(pos < _size);
for (int i = 0; i < _size; ++i)
{
if (_str[i] == c)
{
return i;
}
}
return npos;//没有找到目标字符,返回npos
}
find()文字列
strstr文字列検索関数が使用され、返された文字列の最初の文字へのポインタが検出され、NULLが検出されません。
文字列が見つかったら、ポインタの差によってターゲット文字列の位置を特定できます。
//正向查找一个字符串
size_t find(char* s, size_t pos = 0)
{
assert(pos < _size);
//const char * strstr ( const char * str1, const char * str2 );
const char* ptr = strstr(_str + pos, s);//调用strstr字符串查找函数
if (s)
{
return ptr - _str;//返回找到第一个字符的下标
}
else
{
return npos;
}
}
イテレータ関連の機能
文字列クラスのイテレータはネイティブポインタと同等ですが、ここではtypedefであるため、ここでは単にポインタとして扱います。
iterator begin()
{
return _str;
}
iterator end()
{
return _str + _size;
}
const_iterator begin()const
{
return _str;
}
const_iterator end()const
{
return _str + _size;
}
文字挿入関連機能
まず、アニメーションデモンストレーションの挿入原理を見てみましょう。
文字をinsert()
挿入機能は、任意の位置に文字を挿入する必要があります。まず、添え字の有効性を確認してから、拡張する必要があるかどうかを判断します。拡張の場合は、予約機能を使用してから、位置と文字を移動できます。その後ろに1箇所、挿入する文字を残します。位置を外してから、文字列に文字を挿入します。
//插入一个字符
string& insert(size_t pos, char c)
{
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] = c;
++_size;
return *this;
}
insert()文字列
原理は文字の挿入に似ています
//插入字符串
string& insert(size_t pos, const char* s)
{
assert(pos <= _size);//检查有效位置
size_t len = strlen(s);//记录要插入的字符串长度
if (_size + len > _capacity)
{
reserve(_size + len);//按需扩容
}
size_t end = _size + len;
while (end > pos + len)//挪动数据
{
_str[end] = _str[end - len];
--end;
}
strncpy(_str + pos, s, len);//拷贝数据到pos位置
_size += len;
return *this;
}
テールインサートpush_back()
push_backは、文字列の最後で文字をつなぎ合わせるためのものです。最初に十分なスペースがあるかどうかを検討する必要があります。十分でない場合は、予約関数を使用して2倍(必ずしも2倍ではない)拡張してから、データを挿入できます。 、最後に最後に記入します。\0で十分です。
void push_back(char c)
{
if (_size == _capacity)//扩容
{
//这里要注意,刚开始初始化给定的_capacity=0时
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
//再插入
_str[_size] = c;
++_size;
_str[_size] = '\0';
}
挿入機能を直接再利用することもできます。テール挿入とは、pos位置が_sizeの位置に挿入することを意味します。
//尾插字符
void push_back(char c)
{
insert(_size, c); //在字符串末尾插入字符ch
}
文字列append()を追加します
Appendは実際にはpush_backに似ていますが、appendが文字列をスプライスしている点が異なります。ここでの問題は、文字列が2倍になることです。1.5倍に拡大しますか?よくわかりません。ここで必要なのは、着信文字列の数です。スペースは少なくとも_size + lenである必要があり、スペースはすべての文字を満たすのに十分です。スペースを開いた後、strcpyを使用して文字列内のデータをコピーします。
//追加字符串
void append(const char* str)
{
size_t len = strlen(str);
if (_size + len > _capacity)
{
//扩容
reserve(_size + len);
}
strcpy(_str + _size, str);
_size += len;
}
元の文字列の最後に挿入できる挿入機能を再利用することもできます。
//尾插字符串
void append(const char* str)
{
insert(_size, str); //在字符串末尾插入字符串str
}
演算子+=プラスは1文字に等しい
Add equalsは、便利で簡潔で、一目で理解しやすいため、私たちがより多く使用する関数でもあります。push_backを再利用でき、新しいオブジェクトを返すことに注意してください。
//加等于一个字符
string& operator+=(char c)
{
//复用push_back
push_back(c);
return *this;
}
operator+=プラスは文字列に等しい
追加を再利用
//加等于一个字符串
const string& operator+=(char* str)
{
//复用append
append(str);
return *this;
}
消去削除機能
位置posから始まるlen文字を削除したい
2つの問題を考慮する必要があります。
最初のもの:pos位置の後のすべての位置を削除し、\0をpos位置に置きます。_size-=pos
2つ目:pos位置の後の文字の一部のみが削除され、残りの部分は前の空のスペースに埋められます。
//删除pos位置开始的n个字符
string& erase(size_t pos, size_t len = npos)
{
assert(pos < _size);
if (len == npos || pos + len > _size)//删除pos位置之后的所有字符
{
_size = pos;
_str[_size] = '\0';
}
else//删除pos位置之后的部分字符
{
strcpy(_str + pos, _str + pos + len);//用后面的剩余字符填充空位
_size -= len;
}
return *this;//返回新的对象
}
関係演算子のオーバーロードされた関数
演算子<未満
strcmp()関数は、文字列の長さではなく、文字列内の対応する位置の文字のサイズを比較します(つまり、ASCIIコード値が比較され、大文字と小文字が区別されます。着信c_strは空でないポインタ
bool operator<(const string& s1, const string& s2)
{
// size_t i1 = 0;
// size_t i2 = 0;
// while (i1 < s1.size() && i2 < s2.size())
// {
// if (s1[i1]<s2[i2])
// {
// return true;
// }
// else if (s1[i1]>s2[i2])
// {
// return false;
// }
// else
// {
// ++i1;
// ++i2;
// }
// }
// //while的条件是 且
// //一个一个的字符按照ASCII码值去比较
// //现在是有一个字符串已经走完了
// // "abcd" "abcd" false
// // "abcd" "abcde" true
// // "abcde" "abcd" false
// return i2 < s2.size() ? true : false;
//c_str是一个内容为字符串指向字符数组的临时指针
return strcmp(s1.c_str(), s2.c_str()) < 0;
}
operator==は等しい
bool operator==(const string& s1, const string& s2)
{
return strcmp(s1.c_str(), s2.c_str()) == 0;
}
演算子<=以下
上記以下を実装すると、すでに実装されている以下の演算子を再利用できます。
以下を再利用する
bool operator<=(const string& s1, const string& s2)
{
return s1 < s2 || s1 == s2;
}
演算子>より大きい
以下の逆数を再利用します
bool operator>(const string& s1, const string& s2)
{
return !(s1 < s2&& s1 == s2);
}
演算子>=以上
再利用未満
bool operator>=(const string& s1, const string& s2)
{
return !(s1 < s2);
}
演算子!=は等しくありません
多重化はに等しい
bool operator!=(const string& s1, const string& s2)
{
return !(s1 == s2);
}
ストリーム挿入ストリーム抽出関数のオーバーロード
ストリーム抽出
ostreamクラスとistreamクラスは、C ++標準出力ストリームの基本クラスであり、組み込み型を出力できます。グローバル関数として記述したり、フレンドとしてオーバーロードしたりできますが、フレンドはカプセル化を破棄します。プログラムの。、お勧めしません。
ostream& operator<<(ostream& out, const string& s)
//ostream&是系统的类型,它能够实现输出内置类型的数据
{
for (auto ch : s)
{
out << ch;
}
return out;
}
ストリーム挿入
オブジェクトを初期化するときに初期値を与えないように、最初にsオブジェクトをクリーンアップすることに注意する必要があります。
istream& operator>>(istream& in, string& s)
{
s.clear();//防止对象初始化已经给了值
char ch = in.get();
while (ch != ' ' && ch != '\n')//遇到空格或者换行就停止
{
s += ch;
ch = in.get();//再获取下一个
}
return in;
}
その他の機能
Swap()スワップ関数
スワップ関数は多くの場所で使用されており、実装も類似しているため、メンバー関数としてカプセル化できます。
//模拟string类里面的交换函数
void Swap(string& s)
{
std::swap(_str, s._str);//用C++库里面自带的交换函数
std::swap(_size, s._size);
std::swap(_capacity, s._capacity);
}
クエリを実行できます。C++グローバルライブラリにスワップ関数があり、文字列クラスにもスワップ関数があります。これら2つの関数は交換できます。
stdライブラリのスワップ関数:
文字列クラスのスワップ関数:
int main()
{
std::string s1("hello");
std::string s2("C++");
s1.swap(s2);//调用string类里面的swap函数
swap(s1,s2);//调用全局的swap函数
return 0;
}
2つのうちどちらがより効率的ですか?
回答:文字列クラスの特別な交換関数は、メンバー変数のみを交換するため、より効率的です。stdライブラリは、一時オブジェクトを作成し、3つのディープコピーを実行するテンプレートを使用して実装されますが、コストが高くなります。
c形式の文字列
null以外のポインタを返します
//C语言形式的字符串
const char* c_str()const
{
return _str;
}
見てくれてありがとう!