目录
前言
前面我们学习了string类型中常见函数接口的使用,今天这篇文章我们重点是要对string做到知根知底,这样我们才知道更加熟练地使用string来处理各种问题。
一、项目结构
在模拟实现string的时候,我们需要创建一个项目,这个项目中包含一个test.cpp的文件和一个string.h的头文件。test.cpp文件主要是测试string的逻辑,string.h文件主要是声明和定义string中的代码。
二、string中的基本结构:成员变量
namespace hjt
{
class string
{
private:
char* _str;
size_t _size;
size_t _capacity;
};
}
分析:需要注意的是,我们在模拟实现自己的string的时候需要包含在我们自己的命名空间中,这样可以防止和标准库中的string发生冲突。一个string类中包含的成员变量主要是:_str,_size,_capacity
。
_str
:是char*类型的指针,主要是指向一块动态开辟的空间,空间中存放的是字符串_size
:是size_t类型的整数,主要表示string中存放的有效字符个数。_capacity
:是size_t类型的整数,主要表示string中当前的容量,以便定时知道_str指向的数组是否满了,如果满了则需要进行扩容。
三、默认成员函数
1. 构造函数
(1)普通构造函数:参数为一个字符串
- 代码:
// 构造函数
string(const char* s)
:_size(strlen(s))
,_capacity(_size)
{
// 开空间
_str = new char[_capacity + 1];
strcpy(_str, s);
}
// 测试代码
int main()
{
hjt::string s("hello hjt::string");
return 0;
}
调试结果:
注意:
- 错误的做法: 在构造函数的实现中,一定不能单纯将s的值赋给_str,首先s是const char类型的,_str是char类型的,权限会放大,不支持赋值。其次,s和_str都是字符指针,如果将s直接赋值给_str,那么结果就是使得s和_str指向了同一块空间,这个空间中存放的是常量字符串,是不允许修改的,_str指向这块空间,所以_str也就是不能修改的,这显然是不合理。
- 正确的做法:
- 使用const char* s计算处其字符个数,然后根据这个字符个数去申请一块新的空间,在申请的时候一定需要注意比实际存放的有效字符多一个空间以存储’\0’。然后再使用strcpy函数将s的字符串内容拷贝到这块空间即可。
(2)无参构造函数
- 代码:
// 无参构造函数
string()
:_size(0)
,_capacity(0)
{
_str = new char[1];
_str[0] = '\0';
}
调试结果:
注意:
- 错误做法: 实现无参构造函数的时候,一定要注意其中的_str不能设置成空指针,因为在外面访问的时候是有可能访问一个空的string对象的,如果设置成空指针,那么外面在访问空的string对象的时候就会访问到空指针从而导致程序崩溃。
- 正确做法: 对于空的string对象,可以考虑开一个字符的空间存储’\0’,表示该string对象中存放的是空字符串。
(3)全缺省参数版本的默认构造函数
- 代码:
// 全缺省参数版本的默认构造函数
string(const char* s = "")
:_size(strlen(s))
, _capacity(_size)
{
// 开空间
_str = new char[_capacity + 1];
strcpy(_str, s);
}
调试结果:
分析:可以考虑给予const char* s一个缺省值,给成一个空字符串,当没有传参的时候,s为空字符串,通过strlen(s)进行计算的结果是0,所以_size和_capacity也都是0,最终开的空间大小就是_capacity+0 = 0+1 = 1,然后将s的内容拷贝过去的时候就会将s中的’\0’拷贝到_str指向的空间,效果和无参构造函数一模一样。
2. 析构函数
string类型中有动态申请的资源,所以需要我们自己手动实现析构函数完成对象资源的清理。
~string()
{
if (_str)
{
delete[]_str;
_size = _capacity = 0;
}
}
3. 拷贝构造函数
(1)浅拷贝
我们知道,拷贝构造函数是类的默认成员函数,所以当我们没有显示实现的时候,编译器会自动帮我们生成一个浅拷贝。现在我们来测试以下编译器生成的浅拷贝构造函数做了哪些事情还要造成什么后果。
-
代码:
-
调试结果:
现象1:
现象2:
分析:
浅拷贝的过程就是直接将被拷贝的string对象中的_str的值直接赋值给新创建对象的_str,这样就会导致被拷贝对象中的_str指针和新创建的string对象的_str指针指向同一块空间,那么在对象生命周期结束时调用析构函数清理资源的时候就会发现两个对象都需要调用析构函数完成对应资源的清理,那么这块空间就会被析构两次,从而使程序崩溃。浅拷贝还有另一个问题:**因为两个对象中的指针都指向同一块空间,所以对其中一个对象数据的修改显然会影响另一个对象中的数据,这显然是不合理。**所以:对于string类型,必须手动实现深拷贝。
(2)深拷贝
- 代码:
// 拷贝构造函数:深拷贝
string(const hjt::string& str)
:_size(str._size)
,_capacity(str._capacity)
{
_str = new char[_capacity + 1];
strcpy(_str, str._str);
}
- 调试结果:
分析:对于深拷贝,两个对象中的指针显然不再是指向同一块空间,所以不存在同一块空间被释放两次,也不存在修改一个对象的数据会影响另一个对象的数据。
深拷贝的实现逻辑:利用被拷贝的对象计算出新对象的空间大小,然后向系统申请对应的空间,注意同样需要多开一个空间存储’\0’,然后再使用strcpy()
函数将被拷贝对象中的数据拷贝到新对象的空间。
4. 赋值运算符重载
赋值运算符重载和拷贝构造很类似,同样存在深拷贝和浅拷贝的问题。
(1)浅拷贝
我们知道赋值运算符重载函数同样是类的默认成员函数,所以我们不显示实现编译器会默认生成一个赋值运算符重载函数,现在我们来测试以下编译器生成的浅拷贝构造函数做了哪些事情还要造成什么后果。
- 代码:
int main()
{
hjt::string s1("hello hjt::string::operator=(const hjt::string& str);");
hjt::string s2;
s2 = s1;// 两个已经存在对象的赋值:调用赋值运算符重载函数
return 0;
}
调试结果:
现象1:
现象2:
分析:
浅拷贝的过程就是直接将被拷贝的string对象中的_str的值直接赋值给新创建对象的_str,这样就会导致被拷贝对象中的_str指针和新创建的string对象的_str指针指向同一块空间,那么在对象生命周期结束时调用析构函数清理资源的时候就会发现两个对象都需要调用析构函数完成对应资源的清理,那么这块空间就会被析构两次,从而使程序崩溃。浅拷贝还有另一个问题:**因为两个对象中的指针都指向同一块空间,所以对其中一个对象数据的修改显然会影响另一个对象中的数据,这显然是不合理。**所以:对于string类型,必须手动实现深拷贝。
(2)深拷贝
- 代码:
int main()
{
hjt::string s1("hello hjt::string::operator=(const hjt::string& str);");
hjt::string s2;
s2 = s1;// 两个已经存在对象的赋值:调用赋值运算符重载函数
return 0;
}
调试结果:
分析:对于赋值运算符重载函数,需要实现深拷贝,两个对象中的指针显然不再是指向同一块空间,所以不存在同一块空间被释放两次,也不存在修改一个对象的数据会影响另一个对象的数据。
赋值运算符重载函数深拷贝的实现逻辑:判断两个对象的地址是否是一样的,如果一样,则为自己给自己赋值,不需要进行任何操作,直接返回即可。如果地址不一样,则先利用被拷贝的对象得出新空间大小,然后向系统申请对应的空间,注意同样需要多开一个空间存储’\0’,然后再使用strcpy()
函数将被拷贝对象中的数据拷贝到新的空间,接着释放原来对象的空间,让其指针指向这块新空间,更新原来对象的_size和_capacity。
四、容量接口
1. size:返回string对象中存储的有效数据个数
- 代码:
// size()
size_t size() const
{
return _size;
}
// 测试代码
int main()
{
hjt::string s("hello hjt::string::size()");
cout << s.size() << endl;
return 0;
}
运行结果:
2. length:返回string对象中存储的字符串的长度
- 代码:
// length()
size_t length() const
{
return strlen(_str);
}
// 测试代码
int main()
{
hjt::string s("hello hjt::string::length()");
cout << s.length() << endl;
return 0;
}
运行结果:
3. capacity:返回string对象当前有效容量
- 代码:
// capacity
size_t capacity() const
{
return _capacity;
}
// 测试代码
int main()
{
hjt::string s("hello hjt::string::capacity()");
cout << s.capacity() << endl;
return 0;
}
- 运行结果:
4. reserve:扩容
- 代码:
// reserve(size_t n)
void reserve(size_t n)
{
if (n > _capacity)
{
char* tmp = new char[n + 1];
strcpy(tmp, _str);
delete[]_str;
_str = tmp;
_capacity = n;
}
}
// 测试代码
int main()
{
hjt::string s("hello hjt::string::reserve()");
cout << s.capacity() << endl;
s.reserve(100);
cout << s.capacity() << endl;
return 0;
}
运行结果:
5. resize:扩容+初始化
- 代码:
const char* c_str() const
{
return _str;
}
void resize(size_t n ,char c = '\0')
{
if (n < _size)
{
_size = n;
_str[_size] = '\0';
}
else
{
if (n > _capacity)
{
reserve(n);
}
for (int i = _size; i < n; i++)
{
_str[i] = c;
}
_size = n;
_str[_size] = '\0';
}
}
- 测试代码1:
int main()
{
hjt::string s("hello resize(size_t n,char c)");
cout << "size:" << s.size() << endl;
cout << "capacity" << s.capacity() << endl;
cout << s.c_str() << endl;
s.resize(10, 'x');
cout << "size:" << s.size() << endl;
cout << "capacity" << s.capacity() << endl;
cout << s.c_str() << endl;
return 0;
}
运行结果:
- 测试代码2:
int main()
{
hjt::string s("hello resize(size_t n,char c)");
s.reserve(50);
cout << "size:" << s.size() << endl;
cout << "capacity" << s.capacity() << endl;
cout << s.c_str() << endl;
s.resize(36, 'x');
cout << "size:" << s.size() << endl;
cout << "capacity" << s.capacity() << endl;
cout << s.c_str() << endl;
return 0;
}
运行结果:
- 测试代码3:
int main()
{
hjt::string s("hello resize(size_t n,char c)");
s.reserve(50);
cout << "size:" << s.size() << endl;
cout << "capacity" << s.capacity() << endl;
cout << s.c_str() << endl;
s.resize(100, 'x');
cout << "size:" << s.size() << endl;
cout << "capacity" << s.capacity() << endl;
cout << s.c_str() << endl;
return 0;
}
运行结果:
上面resize()函数的代码可以简化
void resize(size_t n, char c = '\0')
{
if (n > _capacity)
{
reserve(n);
}
for (int i = _size; i < n; i++)
{
_str[i] = c;
}
_size = n;
_str[_size] = '\0';
}
实现逻辑:我们只需要检查是否需要进行扩容,如果需要则进行扩容,接下来就是初始化数据,初始化到下标为n的地方,再将最后的位置置成’\0’。如果刚开始n<_size,则循环不会进去。
五、元素访问接口
1. operator[]
(1) char& operator[](size_t pos)
- 代码:
char& operator[](size_t pos)
{
assert(pos < _size);
return _str[pos];
}
int main()
{
hjt::string s("hello char& operator[](size_t pos)");
for (int i = 0; i < s.size(); i++)
{
cout << s[i] << " ";
}
cout << endl;
return 0;
}
运行结果:
(2)const char& operator[](size_t pos)
- 代码:
const char& operator[](size_t pos) const
{
assert(pos < _size);
return _str[pos];
}
int main()
{
const hjt::string s("hello const char& operator[](size_t pos)");
for (int i = 0; i < s.size(); i++)
{
cout << s[i] << " ";
}
cout << endl;
return 0;
}
运行结果:
2. at()
(1) char& at(size_t pos)
- 代码
char& at(size_t pos)
{
assert(pos < _size);
return _str[pos];
}
int main()
{
hjt::string s("hello hjt::string::at(pos)");
for (int i = 0; i < s.size(); i++)
{
cout << s.at(i) << " ";
}
cout << endl;
return 0;
}
运行结果:
(2)const char& at(size_t pos) const
- 代码
const char& at(size_t pos) const
{
assert(pos < _size);
return _str[pos];
}
运行结果:
六、迭代器
1. 普通对象的迭代器
string类型中的普通迭代器其实本质就是原生指针
- 代码
// 普通迭代器
typedef char* iterator;
iterator begin()
{
return _str;
}
iterator end()
{
return _str + _size;
}
使用代码:
int main()
{
hjt::string s("hello iterator");
hjt::string::iterator it = s.begin();
while (it != s.end())
{
cout << *it << " ";
it++;
}
cout << endl;
return 0;
}
运行结果:
2. const对象的迭代器
我们知道,类型除了有普通类型之外还有const修饰的类型,这种是不能被修改的
- 代码
// const 迭代器
typedef const char* const_iterator;
const_iterator begin() const
{
return _str;
}
const_iterator end() const
{
return _str + _size;
}
使用代码:
// 测试const迭代器
int main()
{
const hjt::string s("hello hjt::string::const_iterator");
hjt::string::const_iterator cit = s.begin();
while (cit != s.end())
{
cout << *cit << " ";
cit++;
}
cout << endl;
return 0;
}
运行结果:
普通迭代器和const迭代器的区别:普通迭代器指向的对象是支持改变的,const迭代器指向的是const修饰的对象,不支持修改
3.一个类支持了迭代器就支持范围for
- 代码1:
// 测试const迭代器
int main()
{
const hjt::string s("hello hjt::string::const_iterator");
hjt::string::const_iterator cit = s.begin();
while (cit != s.end())
{
cout << *cit << " ";
cit++;
}
cout << endl;
for (auto ch : s)
{
cout << ch << " ";
}
cout << endl;
return 0;
}
运行结果:
- 代码2:
int main()
{
hjt::string s("hello iterator");
hjt::string::iterator it = s.begin();
while (it != s.end())
{
cout << *it << " ";
it++;
}
cout << endl;
for (auto ch : s)
{
cout << ch << " ";
}
cout << endl;
return 0;
}
运行结果:
七、修改接口
1. operator+=()
string中的+=函数直接复用实现好的append()函数即可,现实在使用的过程中我们更加偏向于使用+=函数,因为使用+=函数时可以使代码更具有可读性。
(1) hjt::string& operator+=(char c)
hjt::string& operator+=(char c)
{
append(c);
return *this;
}
(2)hjt::string& operator+=(const char* s)
hjt::string& operator+=(const char* s)
{
append(s);
return *this;
}
(3)hjt::string& operator+=(const hjt::string& str)
hjt::string& operator+=(const hjt::string& str)
{
append(str);
return *this;
}
(4)测试+=
- 代码
int main()
{
hjt::string s("x");
cout << s << endl;
s += 'x';
cout << s << endl;
s += "ww";
cout << s << endl;
hjt::string s1("wo shi hjt");
s += s1;
cout << s << endl;
return 0;
}
运行结果:
2. append()
(1) hjt::string& append(char c)
(2)hjt::string& append(const char* s)
- 代码
hjt::string& append(const char* s)
{
int len = strlen(s)+_size;
if (_capacity < len)
{
reserve(len);
}
strcpy(_str + _size, s);
_size = len;
return *this;
}
int main()
{
hjt::string s("hello ");
s.append("hjt::string::append");
cout << s << endl;
return 0;
}
运行结果:
(3)hjt::string& append(const hjt::string& str)
- 代码:
hjt::string& append(const hjt::string& str)
{
append(str._str);
return *this;
}
int main()
{
hjt::string s1("hello ");
hjt::string s2("hjt::string::append(const hjt::string& str)");
s1.append(s2);
cout << s1 << endl;
return 0;
}
运行结果:
3. push_back()
- 代码:
void push_back(char c)
{
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
_str[_size] = c;
_size++;
_str[_size] = '\0';
}
int main()
{
hjt::string s;
s.push_back('x');
cout << "size:" << s.size() << endl;
cout << "capacity:" << s.capacity() << endl;
cout << s << endl;
s.push_back('x');
cout << "size:" << s.size() << endl;
cout << "capacity:" << s.capacity() << endl;
cout << s << endl;
s.push_back('x');
cout << "size:" << s.size() << endl;
cout << "capacity:" << s.capacity() << endl;
cout << s << endl;
s.push_back('x');
cout << "size:" << s.size() << endl;
cout << "capacity:" << s.capacity() << endl;
cout << s << endl;
s.push_back('x');
cout << "size:" << s.size() << endl;
cout << "capacity:" << s.capacity() << endl;
cout << s << endl;
s.push_back('x');
cout << "size:" << s.size() << endl;
cout << "capacity:" << s.capacity() << endl;
cout << s << endl;
s.push_back('x');
cout << "size:" << s.size() << endl;
cout << "capacity:" << s.capacity() << endl;
cout << s << endl;
return 0;
}
运行结果:
4. pop_back()
- 代码
void pop_back()
{
assert(_size > 0);
_size--;
_str[_size] = '\0';
}
bool empty()
{
return _size == 0;
}
int main()
{
hjt::string s("hello hjt::string::pop_back()");
while (!s.empty())
{
cout << s << endl;
s.pop_back();
}
return 0;
}
运行结果:
5. insert()
(1) string& insert(size_t pos,char ch)
- 代码
string& insert(size_t pos, char c)
{
assert(pos <= _size);
// 检查是否需要进行扩容
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
// 插入数据
int end = _size + 1;
while (end > pos)
{
_str[end] = _str[end - 1];
end--;
}
_str[pos] = c;
_size++;
return *this;
}
// 测试insert
int main()
{
hjt::string s("hello");
s.insert(0, '!');// 头插
cout << s.c_str() << endl;
// 尾插
s.insert(s.size(), '!');
cout << s.c_str() << endl;
return 0;
}
运行结果:
(2)string& insert(size_t pos,const char* s)
- 代码
string& insert(size_t pos, const char* s)
{
assert(pos <= _size);
size_t len = strlen(s);
if (len + _size > _capacity)
{
reserve(len + _size);
}
size_t end = _size + len;
while (end > pos + len - 1)
{
// end的最后一个位置是pos+len
_str[end] = _str[end - len];
end--;
}
strncpy(_str + pos, s, len);
_size += len;
return *this;
}
```cpp
int main()
{
hjt::string s("hello ");
// 尾插
s.insert(s.size(), "string");
cout << s.c_str() << endl;
// 头插
s.insert(0, "insert ");
cout << s.c_str() << endl;
return 0;
}
运行结果:
6. string& erase(size_t pos,size_t len = npos)
- 代码
string& erase(size_t pos, size_t len = npos)
{
assert(pos < _size);
if (len == npos || pos + len >= _size)
{
_size = pos;
_str[_size] = '\0';
}
else
{
// 删除len个有效字符:挪动数据
size_t begin = pos + len;
while (begin <= _size)
{
_str[begin - len] = _str[begin];
begin++;
}
_size -= len;
}
return *this;
}
// 测试erase
int main()
{
hjt::string s("hello hjt::string::erase(size_t pos,size_t len = npos)");
// 删除hello
s.erase(0, 5);
cout << s.c_str() << endl;
// 将剩下的全部删除
s.erase(0);
cout << s.c_str() << endl;
return 0;
}
运行结果:
八、操作函数接口
1. c_str():返回string对象中的字符指针
- 代码
const char* c_str() const
{
return _str;
}
int main()
{
hjt::string s("hello hjt::string::c_str()");
cout << s.c_str() << endl;
return 0;
}
运行结果:
2. find()
(1)size_t find(char c,size_t pos = 0)
- 代码:
size_t find(char c, size_t pos = 0)
{
for (int i = pos; i < _size; i++)
{
if (_str[i] == c)
{
return i;
}
}
return npos;
}
// 测试find
int main()
{
hjt::string s("hello size_t hjt::string::find(char c,size_t pos = npos)");
size_t pos = s.find('t');
if (pos != hjt::string::npos)
{
// 找到了
cout << "找到了,下标为:" << pos << endl;
}
else
{
cout << "找不到" << endl;
}
return 0;
}
运行结果:
(2)size_t find(const char* s,size_t pos = 0)
- 代码
size_t find(const char* s, size_t pos = 0)
{
const char* p = strstr(_str + pos, s);
if (p == nullptr)
{
return npos;
}
else
{
return p - _str;
}
}
// 测试find
int main()
{
hjt::string s("hello size_t hjt::string::find(char c,size_t pos = npos)");
size_t pos = s.find("size_t");
if (pos != hjt::string::npos)
{
// 找到了
cout << "找到了,下标为:" << pos << endl;
}
else
{
cout << "找不到" << endl;
}
return 0;
}
运行结果:
3. rfind()
(1)size_t rfind(char c,size_t pos = npos)
- 代码:
size_t rfind(char c, size_t pos = npos)
{
if (pos == npos)
{
pos = _size;
}
for (int i = pos; i >= 0; i--)
{
if (_str[i] == c)
{
return i;
}
}
return npos;
}
// 测试rfind
int main()
{
hjt::string s("hello size_t hjt::string::find(char c,size_t pos = npos)");
size_t pos = s.rfind('n');
if (pos != hjt::string::npos)
{
// 找到了
cout << "找到了,下标为:" << pos << endl;
}
else
{
cout << "找不到" << endl;
}
return 0;
}
测试结果:
九、非成员函数接口
1.operator<<()
- 代码
ostream& operator<<(ostream& out, const hjt::string& str)
{
for (auto& ch : str)
{
cout << ch;
}
return out;
}
int main()
{
hjt::string s("hello ostream& hjt::string::operator<<(ostream& out,const hjt::string& str)");
cout << s << endl;
return 0;
}
运行结果:
2. operator>>()
- 代码1:
istream& operator>>(istream& in, hjt::string& str)
{
char ch;
ch = in.get();
while (ch != ' ' && ch != '\n')
{
str += ch;
ch = in.get();
}
return in;
}
int main()
{
hjt::string s;
cin >> s;
cout << s << endl;
return 0;
}
运行结果:
- 代码2:
istream& operator>>(istream& in, hjt::string& str)
{
char ch;
ch = in.get();
char buff[128] = {'\0'};
int count = 0;
while (ch != ' ' && ch != '\n')
{
buff[count++] = ch;
if (count == 127)
{
str += buff;
memset(buff, '\0', 128);
count = 0;
}
ch = in.get();
}
str += buff;
return in;
}
- 代码3:
istream& operator>>(istream& in, hjt::string& str)
{
char ch;
ch = in.get();
str.reserve(128);
while (ch != ' ' && ch != '\n')
{
str += ch;
ch = in.get();
}
return in;
}
3. string对象的比较函数
(1). operator<()
- 代码:
bool operator<(const hjt::string& s1, const hjt::string& s2)
{
return strcmp(s1.c_str(), s2.c_str()) < 0;
}
(2). operator==()
- 代码:
bool operator==(const hjt::string& s1, const hjt::string& s2)
{
return strcmp(s1.c_str(), s2.c_str()) == 0;
}
(3). operator<=()
- 代码:
bool operator<=(const hjt::string& s1, const hjt::string& s2)
{
return s1<s2||s1==s2;
}
(4). operator>()
- 代码:
bool operator>(const hjt::string& s1, const hjt::string& s2)
{
return !(s1<=s2);
}
(5). operator>=()
- 代码:
bool operator>=(const hjt::string& s1, const hjt::string& s2)
{
return !(s1<s2);
}
(6). operator!=()
- 代码
bool operator!=(const hjt::string& s1, const hjt::string& s2)
{
return !(s1 == s2);
}
(7). 简单的测试比较函数代码:
int main()
{
hjt::string s1("hello");
hjt::string s2("string");
hjt::string s3(s2);
cout << (s1 < s2) << endl;
cout << (s1 == s2) << endl;
cout << (s1 > s2) << endl;
cout << (s2 == s3) << endl;
cout << (s2 != s3) << endl;
return 0;
}
运行结果: