目录
前言
string类是C++标准库中一个重要的组件,它提供了一种方便的字符串操作方式。与C语言中字符数组相比,string类更加灵活和易用,可以方便地处理动态大小的字符串,同时也提供了许多重要字符串操作方式,如拼接,分割,替换,查找等。
string类本质上是一个封装了一个字符串的数组,可以在运行时根据需要调整字符串的大小。其设计的重要优点是内存自动管理,即它负责调整内存大小,避免了内存溢出或空间浪费的问题。接下来我们来模拟实现string的常用接口。
如有错误,欢迎大家指正。
string的成员变量
private:
char* _str;//指向字符串
size_t _size;//有效数据
size_t _capacity;//字符数组空间的大小
1._size表示有效字符的个数不包括' \0 '的空间。
2._capacity表示有效空间大小同样不包括' \0 '的空间。
3.在最初构造一个string对象时_size和_capacity的大小一样。
4.向内存申请空间存放字符串时,开辟的空间大小比_capacity大1用于存放 ' \0 '。
如何创建string对象
string s1;//无参构造
string s2 = "hello world";//有参构造
string s3(s2);//拷贝构造
string s4 = s3;//赋值重载构造
无参构造可以在有参构造函数中给一个缺省值,缺省值为“ ”字符串自带' \0 '
构造函数
string(const char* str = " ") :_size(strlen(str))
{
_capacity = _size;
_str = new char[_capacity + 1];
strcpy(_str, str);
}
拷贝构造
我们在堆区申请了内存,如果不显示定义拷贝构造函数,编译器默认生成的拷贝构造函数完成的是浅拷贝,当我们调用编译器默认生成的拷贝构造函数去构造一个对象时,这个对象的字符串指针也指向参数对象的字符串指针指向的空间,在对象生命周期结束时,编译器会自动调用析构函数,从而对同一块空间二次析构。
浅拷贝
//浅拷贝
string(const string& s)
{
_size = s._size;
_capacity = s._capacity;
_str = s._str;
}
深拷贝
在堆区重新申请一块空间,将内容拷贝进去,让字符指针变量指针向该空间;
string(const string& s):_size(s._size),_capacity(s._capacity )
{
_str = new char[s._capacity + 1];
strcpy(_str, s._str);
}
赋值重载
string& operator=(const string& str)
{
if (&str != this)
{
char* tmp = new char[_capacity + 1];
strcpy(tmp, str._str );
delete[] _str;
_str = tmp;
_size = str._size;
_capacity = str._capacity;
}
return *this;
}
析构函数
~string()
{
delete[] _str;
_str = nullptr;
_capacity = _size = 0;
}
字符串输出
调用该函数得到对象指向字符串的指针,进行打印;
const char* c_str()
{
return _str;
}
[ ]操作符重载
在某些情况我们需要从某个位置开始输出字符串,如对字符串"hello world"子打印world,这时我们就需要[ ]操作符了,对于自定义类型,我们要自己对[ ]操作符进行重载。
//只能读取 不能改变字符串
const char& operator[](size_t pos) const
{
assert(pos < _size);
return _str[pos];
}
//可以读取也可以改变
char& operator[](size_t pos)
{
assert(pos < _size);
return _str[pos];
}
从输出可以看出字符串的ASCII码加了1。
迭代器
行为像指针一样,VS环境下的迭代器不是用指针实现的,但我们可以封装一个指针去模拟。
typedef char* iterator;
//begin() 指向字符串的第一个元素
iterator begin()
{
return _str;
}
//end() 指向 \0
iterator end()
{
return _str+_size;
接下来我们就可以用迭代器去遍历字符串了。范围for只是简单替换迭代器。
string s1 = "1234567890";
clastr::string::iterator it1;
for (it1 = s1.begin(); it1 != s1.end(); ++it1)
{
cout << (*it1);
}
cout << endl;
//范围for
for (auto ch: s1)
{
cout << ch<<" ";
}
cout << endl;
改变容量
当我们对字符串进行增删时,字符串的容量是改变的,所以我们要去扩容。向堆区申请一定容量的内存,拷贝原内存空间的内容,释放原空间。以下是扩容的实现步骤。
void reserve(size_t n)
{
if (n > _capacity)//不缩容
{
char* tmp = new char[n + 1];
strcpy(tmp, _str);
delete[] _str;
_str = tmp;
_capacity = n;
}
}
上面是内存空间大小的变化,接下来我们来分析有效字符串大小的变化,变化有三种情况。
有效字符串下标n小于等于原本的_size时,只需要将_size的值改为n,并且将下标为n的位置处放一个’\0’。
有效字符串下标n大于原本的_size,并小于原本的_capacity,此时不需要进行扩容只需要将原本的_size设置成n即可,并且将相比于原本字符多出来的位置用形参ch来填充,如果没有传ch,就使用缺省值’\0’。
有效字符串下标n大于原本的_capacity,此时需要进行扩容,之后的操作和上面的一样。
void resize(size_t n, char ch = '\0')
{
if (n <= _size)
{
_size = n;
_str[_size] = '\0';
}
else
{
if (n > _capacity)
{
reserve(n);
}
size_t i = _size;
while (i<n)
{
_str[i++] = ch;
}
_size = n;
_str[_size] = '\0';
}
}
插入
插入字符:在原有的字符串的最后插入一个字符。
string& push_back(char ch)
{
if (_size + 1 > _capacity)
{
reserve(_capacity*2);
}
_str[_size] = ch;
++_size;
_str[_size] = '\0';
return *this;
}
插入字符串
string& append(const char* str)
{
size_t len = strlen(str);
if (_size + len > _capacity)
{
reserve(_size+len);
}
strcpy(_str + _size,str);
_size += len;
return *this;
}
+=操作
string& operator+=(char ch)
{
push_back(ch);
return *this;
}
string& operator+=(const char* str)
{
append(str);
return *this;
}
插入insert
在指定位置插入字符。
在指定位置插入字符串。
string& insert(size_t pos, const char* str)
{
assert(pos <= _size);
size_t len = strlen(str);
if (_size + len > _capacity)//检查容量
{
reserve(_size + len);
}
size_t end = _size + len;
while (end > pos + len - 1)
{
_str[end] = _str[end - len];//挪动数据
--end;
}
strncpy(_str + pos, str, len);
_size = _size + len;
return *this;
/*size_t len = strlen(str);
if (_size + len>_capacity)
{
reserve(_size + len);
}
int end = _size + len - 1;
int gap = _size - pos + 1;
while (gap--)
{
_str[end] = _str[end - len];
--end;
}
strncpy(_str + pos, str, len);
_str[_size + len] = '\0';
_size = _size + len;*/
}
删除
在这里我们定义一下npos,这是一个静态成员变量,在类内进行声明的时候,给它一个缺省值-1,在定义的时候就会使用到这个缺省值。
静态成员变量的声明在类内,定义必须在类外,但是有一个特殊类型,整型家族的静态成员就可以在声明的时候给一个缺省值。
由于是size_t类型的-1,所以它的实际大小就是32个1的二进制,转换成十进制大致是42亿9千万。
const static size_t npos=-1;
string& erase(size_t pos, size_t len = npos)
{
assert(pos < _size);
if (len == npos || pos + len >= _size)
{
_str[pos] = '\0';
_size = pos;
}
else
{
strcpy(_str + pos, _str + pos + len);
_size -= len;
}
return *this;
}
查找
size_t find(char ch,size_t pos=0)
{
assert(pos < _size);
for (size_t i = 0; i < _size; ++i)
{
if (_str[i] == ch)
{
return i;
}
}
return npos;
}
size_t find(const char* str, size_t pos = 0)
{
assert(pos < _size);
char* p = strstr(_str + pos,str);
if (p == nullptr)
{
return npos;
}
else
{
return p - _str;
}
}
流插入(<<)和流提取(>>)重载
流插入(<<)
ostream& operator<<(ostream& out, const string& s)
{
for (size_t i = 0; i < s.size(); ++i)
{
out << s[i];
}
return out;
}
流提取(>>)
istream& operator>>(istream& in, string& s)
{
s.clear();
char buff[128] = { '\0' };
size_t i = 0;
char ch = in.get();
while (ch != ' ' && ch != '\n')
{
if (i == 127)
{
s += buff;
i = 0;
}
buff[i++] = ch;
ch = in.get();
}
if (i >= 0)
{
buff[i] = '\0';
s += buff;
}
return in;
}