前言
string类,想必大家都不陌生,这是一个用来管理字符串的类。让我们使用字符串时更方便,更遍历。所以今天我们就来简单的实现一下string类。
类的属性
我们要管理一个字符串,那么首先得有一个字符串。所以我们用char* 类型的指针,来指向一个块存储字符串的地址。_size则记录字符串的长度,_cacpcity 为字符串的空间容量
private:
char* _str;
size_t _size;
size_t _cacpcity;
构造函数
构造函数
我们在实例化一个string对象的时候,可以string s("hello world");
直接创建,也可以string s();
使它初始时为空,所以我们可以用缺省的构造函数。如果不传参数,那么默认初始化为空。
//缺省的构造函数
string(const char* str = "")
: _size(strlen(str))
,_cacpcity(_size)
{
//开辟一块内存
_str = new char[_cacpcity + 1];//有效容量是cacpcity,要多一个用来存放\0
strcpy(_str, str);
}
析构函数
析构函数我们只需要释放 _str指向的空间即可。
//析构函数
~string()
{
delete[] _str;
_str = nullptr;
_size = _cacpcity = 0;
}
拷贝构造函数
拷贝构造,就是拷贝一个字符串 例如:string s1("hello world"); string s2(s1);
。所以我们开辟一块与要拷贝字符串一样大小的空间,再把它一一复制给新字符串即可。
//拷贝构造
string(const string& s)
:_size(s._size)
, _cacpcity(s._cacpcity)
{
//开辟一块和s一样的空间
_str = new char[_cacpcity+1];
strcpy(_str, s._str);
}
操作符重载
赋值操作符重载
如果我们想用 = 操作符来给字符串赋值。那我们可以重载 = 操作符。
//赋值操作符重载
string& operator=(const string& s)
{
//如果不是自己给自己赋值
if (this != &s)
{
//创建一块新空间
char* tmp = new char[s._cacpcity+1];
//拷贝
strcpy(tmp, s._str);
//销毁旧空间
delete[] _str;
_str = tmp;
_size = s._size;
_cacpcity = s._cacpcity;
}
return *this;
}
当然也可以复用拷贝构造函数。
比较操作符重载
<重载
//字符串比较函数重载
bool operator<(const string& s)
{
return strcmp(_str, s._str) < 0;
}
== 重载
bool operator==(const string& s)
{
return strcmp(_str, s._str) == 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);
}
获取字符串长度
直接返回_size 即可
//获取长度
size_t size()
{
return _size;
}
下标访问
字符串也可以进行下标访问,所以我们重载[]即可
//下标访问
char& operator[](size_t pos)
{
return _str[pos];
}
当然,如果访问的是const修饰的字符串,那我们只能读,不能写。
//只读
const char& operator[](size_t pos) const
{
return _str[pos];
}
迭代器
//定义2个迭代器,一个是可读可写,一个是只读
typedef char* iterator;
typedef const char* const_iterator;
//迭代器开始位置
iterator begin()
{
return _str;
}
const_iterator begin() const
{
return _str;
}
//迭代器末尾位置
iterator end()
{
return _str + _size;
}
const_iterator end()const
{
return _str + _size;
}
尾插一个字符
就是在字符串末端插入一个字符,我们只需要在_size的位置插入,随后_size++即可。不过要保证容量必须充足。
//尾插一个字符
void push_back(char c)
{
//检查容量
if (_size == _cacpcity)
{
AddCacpcity(_cacpcity == 0 ? 15 : _cacpcity * 2);
}
_str[_size] = c;
_size++;
_str[_size] = '\0';
}
扩容函数
void AddCacpcity(size_t newCacpcity)
{
//末尾位置留一个\0,所以+1
char* str = new char[newCacpcity + 1];
//旧字符串的内容拷贝到新字符串
strcpy(str, _str);
//销毁旧字符串
delete[] _str;
_str = str;
_cacpcity = newCacpcity;
}
追加字符串
如果想在尾部追加字符串的话,我们可以实现一个 append()函数。
//追加一个字符串
void append(const char* str)
{
//检查容量
if (_cacpcity < (_size)+strlen(str))
{
AddCacpcity(_size + strlen(str));
}
//直接在末尾的位置加上str
strcpy(_str + _size, str);
_size += strlen(str);
}
然后我们可以重载 +=运算符,拿来复用 append()函数。
string& operator+=(const char* str)
{
append(str);
return *this;
}
然后我们重载 +运算符,但需要注意的是,+运算符不改变当前字符串,所以我们要值返回。
//+重载
string operator+(const char* str)
{
string s(*this);
s += str;
return s;
}
字符串清空
直接把第一个字符改成\0即可
//清空
void clear()
{
_str[0] = '\0';
_size = 0;
}
字符串交换
我们用std命名空间里面的swap函数,对每个成员进行交换。
//字符串交换
void swap(string& s)
{
std::swap(_str, s._str);
std::swap(_size, s._size);
std::swap(_cacpcity, s._cacpcity);
}
以C的方式返回字符
c语言的字符串其实就是一个char*指针,遇到\0结束,所以我们直接返回str就可以了。
//以c的方式返回字符串
const char* c_str()const
{
return _str;
}
判断字符串是否为空
直接判断第一个元素是否是 \0
//判断字符串是否为空
bool empty()const
{
return _str[0] == '\0';
}
修改长度
修改长度我们要分多种情况,一般长度减少时,我们不改变现有容量。容量不够时,我们才增容。 如果 长度减少,或者不变,我们只需要把 减少的新长度给_size,然后把当前位置变成\0。如果是增加长度,我们要考虑容量,不够的话需要增容,然后memset函数把增容的部分改成你指定的字符(默认\0)。
//修改长度
void resize(size_t n, char c = '\0')
{
//如果修改的值比当前长度小
if (n <= _size)
{
//截断
_size = n;
_str[_size] = '\0';
}
//如果修改的值比当前长度大
else
{
//扩容
if(n > _cacpcity)
{
AddCacpcity(n);
}
//从size位置到n的位置设置为c
memset(_str + _size, c, n - _size);
//最后位置填上\0
_size = n;
_str[_size] = '\0';
}
}
指定大小扩容
之前写过一个扩容函数,直接把指定的大小传过去即可。还是老规矩,减少不处理。
//指定容量。只增加,减少不处理
void reserve(size_t n)
{
if (n > _cacpcity)
{
AddCacpcity(n);
}
}
查找字符
该查找只会找到从指定位置开始,第一个出现的字符。如果要查找第二个,那么就在第一个字符的后面开始查找。
// 返回c在string中第一次出现的位置
size_t find(char c, size_t pos = 0) const
{
for (int i = pos; i < _size; i++)
{
if (_str[i] == c)
return i;
}
return nops;
}
nops是一个无符号的数,代表找不到返回的值。
static const size_t nops = -1;
查找子串
C语言中有strstr函数,我们可以复用。
// 返回子串s在string中第一次出现的位置
size_t find(const char* s, size_t pos = 0) const
{
char* tmp = strstr(_str, s);
if (tmp == NULL)
return nops;
return tmp - _str;
}
指定位置插入字符
只需要把pos位置后面的字符都往后移动一格,随后把字符放进pos位置。
// 在pos位置上插入字符c/字符串str,并返回该字符的位置
string& insert(size_t pos, char c)
{
//判断容量
if (_size == _cacpcity)
{
AddCacpcity(_cacpcity == 0 ? 15 : _cacpcity * 2);
}
//pos位置后往后移
size_t end = _size + 1;
while (pos < end)
{
_str[end] = _str[end - 1];
end--;
}
_str[pos] = c;
_size++;
return *this;
}
指定位置插入字符串
插入字符是都往后移动一格,插入字符串那就是把pos位置后面的字符都像后 移动字符串的长度格。然后把字符串从pos位置开始写入。
//插入字符串
string& insert(size_t pos, const char* str)
{
size_t len = strlen(str);
//判断容量
if (_cacpcity < (len + _size))
{
AddCacpcity(len + _size);
}
//移动len格
size_t end1 = _size+1;
size_t end2 = _size + len ;
while (pos < end1 )
{
_str[end2] = _str[end1-1] ;
end1--;
end2--;
}
int i = pos;
while (*str)
{
_str[i++] = *str++;
}
_size += len;
return *this;
}
删除指定区间
从pos位置开始,删除len个空间。那么我们需要先判断 len是否大于pos后面的长度,如果大于那就是后面全部删除,那么我们只需要把pos位置置空成\0即可。如果不大于就说明在中间删除,那么就从pos的第len个位置开始往pos后面的位置覆盖,覆盖到\0结束。
// 删除
string& erase(size_t pos, size_t len)
{
//如果要删除的长度大于后面的剩余长度
if (len >= _size - pos)
{
len = _size - pos;
_size -= len;
_str[pos] = '\0';
return *this;
}
//把后面的往前移,覆盖式删除
size_t begin = pos+len;
while (_str[begin])
{
_str[begin-len] = _str[begin];
begin++;
}
_size -= len;
return *this;
}
全部代码:
namespace wyl
{
class string
{
public:
typedef char* iterator;
typedef const char* const_iterator;
//缺省的构造函数
string(const char* str = "")
: _size(strlen(str))
,_cacpcity(_size)
{
//开辟一块内存
_str = new char[_cacpcity + 1];
strcpy(_str, str);
}
//析构函数
~string()
{
delete[] _str;
_str = nullptr;
_size = _cacpcity = 0;
}
//拷贝构造
string(const string& s)
:_size(s._size)
, _cacpcity(s._cacpcity)
{
//开辟一块和s一样的空间
_str = new char[_cacpcity+1];
strcpy(_str, s._str);
}
//赋值操作符重载
string& operator=(const string& s)
{
//如果不是自己给自己赋值
if (this != &s)
{
//创建一块新空间
char* tmp = new char[s._cacpcity+1];
//拷贝
strcpy(tmp, s._str);
//销毁旧空间
delete[] _str;
_str = tmp;
_size = s._size;
_cacpcity = s._cacpcity;
}
return *this;
}
//获取长度
size_t size()
{
return _size;
}
//下标访问
char& operator[](size_t pos)
{
return _str[pos];
}
//只读
const char& operator[](size_t pos) const
{
return _str[pos];
}
//迭代器开始位置
iterator begin()
{
return _str;
}
const_iterator begin() const
{
return _str;
}
//迭代器末尾位置
iterator end()
{
return _str + _size;
}
const_iterator end()const
{
return _str + _size;
}
// 扩容
void AddCacpcity(size_t newCacpcity)
{
char* str = new char[newCacpcity + 1];
strcpy(str, _str);
delete[] _str;
_str = str;
_cacpcity = newCacpcity;
}
//尾插一个字符
void push_back(char c)
{
//检查容量
if (_size == _cacpcity)
{
AddCacpcity(_cacpcity == 0 ? 15 : _cacpcity * 2);
}
_str[_size] = c;
_size++;
_str[_size] = '\0';
}
string& operator+=(char c)
{
push_back(c);
return *this;
}
//追加一个字符串
void append(const char* str)
{
if (_cacpcity < (_size)+strlen(str))
{
AddCacpcity(_size + strlen(str));
}
strcpy(_str + _size, str);
_size += strlen(str);
}
string& operator+=(const char* str)
{
append(str);
return *this;
}
//+重载
string operator+(const char* str)
{
string s(*this);
s += str;
return s;
}
string operator+(const string& str)
{
string s(*this);
s += str._str;
return s;
}
//清空
void clear()
{
_str[0] = '\0';
_size = 0;
}
//字符串交换
void swap(string& s)
{
std::swap(_str, s._str);
std::swap(_size, s._size);
std::swap(_cacpcity, s._cacpcity);
}
//以c的方式返回字符串
const char* c_str()const
{
return _str;
}
//判断字符串是否为空
bool empty()const
{
return _str[0] == '\0';
}
//修改长度
void resize(size_t n, char c = '\0')
{
//如果修改的值比当前长度小
if (n <= _size)
{
//截断
_size = n;
_str[_size] = '\0';
}
//如果修改的值比当前长度大
else
{
//扩容
if(n > _cacpcity)
{
AddCacpcity(n);
}
//从size位置到n的位置设置为c
memset(_str + _size, c, n - _size);
//最后位置填上\0
_size = n;
_str[_size] = '\0';
}
}
//指定容量。只增加,减少不处理
void reserve(size_t n)
{
if (n > _cacpcity)
{
AddCacpcity(n);
}
}
//字符串比较函数重载
bool operator<(const string& s)
{
return strcmp(_str, s._str) < 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 strcmp(_str, s._str) == 0;
}
bool operator!=(const string& s)
{
return !(*this == s);
}
// 返回c在string中第一次出现的位置
size_t find(char c, size_t pos = 0) const
{
for (int i = pos; i < _size; i++)
{
if (_str[i] == c)
return i;
}
return nops;
}
// 返回子串s在string中第一次出现的位置
size_t find(const char* s, size_t pos = 0) const
{
char* tmp = strstr(_str, s);
if (tmp == NULL)
return nops;
return tmp - _str;
}
// 在pos位置上插入字符c/字符串str,并返回该字符的位置
string& insert(size_t pos, char c)
{
//判断容量
if (_size == _cacpcity)
{
AddCacpcity(_cacpcity == 0 ? 15 : _cacpcity * 2);
}
//pos位置后往后移
size_t end = _size + 1;
while (pos < end)
{
_str[end] = _str[end - 1];
end--;
}
_str[pos] = c;
_size++;
return *this;
}
//插入字符串
string& insert(size_t pos, const char* str)
{
size_t len = strlen(str);
//判断容量
if (_cacpcity < (len + _size))
{
AddCacpcity(len + _size);
}
//移动len格
size_t end1 = _size+1;
size_t end2 = _size + len ;
while (pos < end1 )
{
_str[end2] = _str[end1-1] ;
end1--;
end2--;
}
int i = pos;
while (*str)
{
_str[i++] = *str++;
}
_size += len;
return *this;
}
// 删除
string& erase(size_t pos, size_t len)
{
//如果要删除的长度大于后面的剩余长度
if (len >= _size - pos)
{
len = _size - pos;
_size -= len;
_str[pos] = '\0';
return *this;
}
//把后面的往前移,覆盖式删除
size_t begin = pos+len;
while (_str[begin])
{
_str[begin-len] = _str[begin];
begin++;
}
_size -= len;
return *this;
}
private:
char* _str;
size_t _size;
size_t _cacpcity;
static const size_t nops = -1;
};
}
总结:
string类的细节还有很多,在这里只能简单实现一下。在实现的过程中需要注意的几点。
1.避免内存越界,否则析构时销毁会出错。
2.释放new出来的内存,避免内存泄漏。
3.需要深拷贝,否则会出现析构多次的情况。
转载:https://blog.csdn.net/Lin5200000/article/details/128519901