首先由于自定义类不能和已有类重名,所以在自定义命名空间中进行string类的模拟
namespace aiyimu
{
//string类的模拟
class string
{
public:
//成员函数的实现
private:
//成员变量的定义
char* _str;
size_t _size;//开辟空间大小
size_t _capacity;//有效容量
};
//非成员函数的实现
}
构造函数进行成员变量的初始化:给_str申请空间,并将_size和_capacity初始化
参数使用缺省值:
string str("hello");
,此时会设置好相应的大小/容量,然后将传入的数据拷贝给_str(即创建的str)string(const char* str = "")//用'\0'也可
:_size(strlen(str))
,_capacity(_size)
{
_str = new char[_capacity + 1];
strcpy(_str, str);
}
传统写法*
传统写法中直接将传入的字符串的相应变量赋值给_str,最后进行new空间给_str
但该写法有一个缺点:没有对内存分配过程进行异常处理,在调用new运算符分配内存时发生了内存不足或者其他错误,程序会抛异常,可能会导致程序崩溃等。
string(const string& s)
:_size(s._size)
,_capacity(s._capacity)
{
_str = new char[s._capacity + 1];
strcpy(_str, s._str);
}
现代写法*
现代写法在此处进行了优化:
该方法避免了构造函数和赋值运算符重载中内存分配和释放的重复操作,提高了代码的效率。
//交换两个字符串
void swap(string& s)
{
std::swap(_str, s._str);
std::swap(_size, s._size);
std::swap(_capacity, s._capacity);
}
string(const string& s)
:_str(nullptr)
,_size(0)
,_capacity(0)
{
string tmp(s._str);//tmp临时存储s._str字符串的内容
//this->swap(tmp)
swap(tmp);//直接交换
}
注:自行实现的
swap()
函数后续依然有用,所以建议写成函数
析构函数进行资源的清理释放:
~string()
{
delete[] _str;
_str = nullptr;
_size = _capacity = 0;
}
传统写法*
string& operator=(const string& s)
{
//自己赋值自己,不进入if语句,直接返回
if (this != &s)
{
//开辟空间
char* tmp = new char[s._capacity + 1];
strcpy(tmp, s._str);//将字符串拷贝到开辟的空间
_str = tmp;
_size = s._size;
_capacity = s._capacity;
}
return *this;
}
现代写法*
赋值就是赋值,直接赋值即可
既然已有自定义的swap函数,那就用吧:
此处调用先前拷贝构造时写的swap,直接把s的成员变量给_str
string& operator=(string s)
{
//this->swap(s);
//直接交换
swap(s);
return *this;
}
方括号用于取下标,在不越界的前提下返回_str[pos]即可
//可读 + 可修改
char& operator[](size_t pos)
{
//防止越界
assert(pos < _size);
return _str[pos];
}
//只读
const char& operator[](size_t pos) const
{
//防止越界
assert(pos < _size);
return _str[pos];
}
+=单个字符
push_back()
(尾插)的操作即可string& operator+=(char ch)
{
push_back(ch);
return *this;
}
+=字符串
string& operator+=(const char* str)
{
append(str);
return *this;
}
push_back(), append()
都在下文
c_str()
该函数返回一个指向字符串中第一个字符的常量指针,用来兼容c的字符串,同时便于我们测试打印
const char* c_str() const //不改变_str的函数推荐后const,const和非const都能用
{
return _str;
}
size()
size()用于得到此时string的大小(元素个数)
size_t size() const
{
return _size;
}
reserve()
reserve()
是扩容函数:预留出一定的空间void reserve(size_t n)
{
if (n > _capacity)
{
char* tmp = new char[n + 1];//多开辟一个存'\0'
strcpy(tmp, _str);
delete[] _str;
_str = tmp;
//_capacity += n; //预留n个元素的内存空间
_capacity = n;
}
}
_capacity = n
将空间扩容为n
_capacity += n
预留出n个元素的空间
resize()
reserve()
理解为扩容,那么resize()
可以认为扩容+初始化 / 缩容reserve()
只改变_capacity的大小,而resize()
会改变_size,_capacity的大小,支持增容和缩容。void resize(size_t n, char ch = '\0')
{
//缩容
//若要改变的空间大小小于原本空间容量,直接缩小把最后一位给'\0'
if (n <= _size)
{
_size = n;
_str[_size] = '\0';
}
else
{
//判断是否需要扩容
if (n > _capacity)
{
reserve(n);//直接利用reserve扩容
}
//将多出的空间全部设为ch,共有n - _size个元素
memset(_str + _size, ch, n - _size);
_size = n;//更新_size
_str[_size] = '\0';
}
}
push_back()
void push_back(char ch)
{
//判断是否需要增容
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
_str[_size] = ch;
//尾插后把最后一位置'\0'
++_size;
_str[_size] = '\0';
}
append()
void append(const char* str)
{
size_t len = strlen(str);
//判断扩容
if (_size + len > _capacity)
{
reserve(_size + len);
}
strcpy(_str + _size, str);//把str拷贝到_str最后位置
_size += len;
}
找到指定字符
size_t find(char ch)
{
for (size_t i = 0; i < _size; i++)
{
if (_str[i] == ch)
{
return i;
}
}
return npos;
}
找到指定字符串
strstr()
(找字符串的子串)strstr()
:找不到返回空,找得到返回该位置的指针(即该位置到后面所有的字符)find()
找不到我们返回npos,找到返回该位置下标size_t find(const char* s, size_t pos = 0)
{
//_str的第pos位找是否有子串s
const char* ptr = strstr(_str + pos, s);
if (ptr == nullptr)
{
//没有该子串
return npos;
}
else
{
return ptr - _str;
}
}
指定位置插入字符
string& insert(size_t pos, char ch)
{
//防止pos越界
assert(pos <= _size);
//判断扩容
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
//需要将pos后的元素后移,记录size后一位位置
size_t end = _size + 1;
while (end >= pos)
{
//把pos后的所有元素后移一位
_str[end] = _str[end - 1];
--end;
}
_str[pos] = ch;//插入
++_size;//更新size
return *this;
}
指定位置插入字符串
append()
与push_back()
的区别一致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)
{
_str[end] = _str[end - len];//把pos后的元素后移len位,给将要插入的字符串留空
--end;
}
strncpy(_str + pos, s, len);//将s插入到留的空中
return *this;
}
erase()
pos + len > _size
时,此时要从pos位删除的数据已经超出pos位后的所有元素,直接将pos位置为’\0’,然后更新_size即可string& erase(size_t pos = 0, size_t len = npos)
{
//防止pos越界
assert(pos < _size);
//pos位置后面的全部删除
if (pos + len >= _size)
{
_str[pos] = '\0';
_size = pos;
}
else
{
strcpy(_str + pos, _str + pos + len);
_size -= len;
}
return *this;
}
clear
清空s的元素,用于流插入
void clear()
{
_str[0] = '\0';
_size = 0;
}
法一
s1[i1] < s2[i2]
,说明s1比s2小,返回true;如果s1[i1] > s2[i2]
,说明s1比s2大,返回false;如果s1[i1] == s2[i2]
,说明当前位置上两个字符串相同,向后移动下标继续比较。bool operator<(const string& s1, const string& s2)
{
size_t i1 = 0, 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;
}
}
//其中一个走完,还有元素的字符串更大
return i2 < s2.size() ? true : false;
}
法二
c_str()
函数,可以兼容c的字符串,这里直接用c库函数strcmp()
比较即可bool operator<(const string& s1, const string& s2)
{
return strcmp(s1.c_str(), s2.c_str()) < 0;
}
==
和 <
的法二相同,调用strcmp()
即可
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);
}
>=
bool operator>=(const string& s1, const string& s2)
{
return !(s1 < s2);
}
!=
bool operator!=(const string& s1, const string& s2)
{
return !(s1 == s2);
}
string类的模拟实现+测试用例