目录
1.前言
今天自己模拟实现了string,在过程中遇到了许多问题,在这做个总结。刷到的小伙伴不要划走,认真看完我下面的代码剖析,你也能有所收获!
2.模拟实现string源码
编译器用的是VS 2022
#pragma once
#include<assert.h>
namespace lizhi
{
class string {
public:
string(const char* str = "")
:_size(strlen(str))
, _capacity(_size)
{
_str = new char[_capacity + 1];
strcpy(_str, str);
}
传统写法
s2(s1)
//string(const string& s)
//{
// _str = new char[s._capacity + 1];
// strcpy(_str, s._str);
// _capacity = s._capacity;
// _size = s._size;
//}
s2 = s3 浅拷贝:数据存在对象里面(日期类) 深拷贝:数据存在指向的空间
//string& operator=(const string& s) //这里如何深拷贝的?创建新空间,把旧空间给新空间,再把旧空间释放掉
//{
// if (this != &s)
// {
// char* tmp = new char[s._capacity + 1];
// strcpy(tmp, s._str);
// delete[] _str;
// _str = tmp;
// _size = s._size;
// _capacity = s._capacity;
// }
// return *this;
// }
//现代写法
void swap(string& s)
{
std::swap(_str, s._str);
std::swap(_size, s._size);
std::swap(_capacity, s._capacity);
}
//s2(s1)
string(const string& s)
:_str(nullptr)
, _size(0)
, _capacity(0)
{
string tmp(s._str);
swap(tmp);
}
//现代写法
//s2 = s3 把别人开好的空间换给自己,同时还把自己要释放的交给tmp,tmp出了作用域自动调用析构函数,带走空间
/* string& operator=(const string& s)
{
if (this != &s)
{
string tmp(s);
swap(tmp);
}
return *this;
}*/
//s2 =s3 极致简洁的现代写法
string& operator=(string tmp)//这里是传值传参,tmp是s3拷贝构造出来的,有和s3一样大的空间,一样的值
{ //不需要加关于自己给自己赋值的判断,实际情况也不会有人自己给自己赋值,就算自己给自己赋值也没毛病
swap(tmp);
return *this;
}
typedef char* iterator; //普通迭代器
typedef const char* const_iterator; //const迭代器,指针本身能修改,指向内容不能修改
iterator begin()
{
return _str;
}
iterator end()
{
return _str + _size;
}
const_iterator begin() const
{
return _str;
}
const_iterator end()const
{
return _str + _size;
}
~string() {
delete[] _str;
_str = nullptr;
_size = _capacity = 0;
}
char& operator[](size_t pos)
{
assert(pos < _size);
return _str[pos];
}
const char& operator[](size_t pos) const
{
assert(pos < _size);
return _str[pos];
}
const char* c_str() const {
return _str;
}
size_t size() const {
return _size;
}
size_t capacity() const {
return _capacity;
}
void reserve(size_t n) {
if (n > _capacity)
{
char* tmp = new char[n + 1];
strcpy(tmp, _str);
delete[]_str;
_str = tmp;
_capacity = n;
}
}
void push_back(char ch) {
if (_size == _capacity)
reserve(_capacity == 0 ? 4 : _capacity * 2); //如果是reserve(_capacity * 2);字符串如果是空串就会有bug
_str[_size] = ch;
_size++;
_str[_size] = '\0';
}
void append(const char* str) {
size_t len = strlen(str);
if (_size + len > _capacity)
reserve(_size + len);
strcpy(_str + _size, str);
_size += len;
}
string& operator+=(char ch)
{
push_back(ch);
return *this;
}
string& operator+=(const char* str)
{
append(str);
return *this;
}
void insert(size_t pos, char ch)
{
assert(pos <= _size);
if (_size == _capacity) reserve(_capacity == 0 ? 4 : _capacity * 2);
size_t end = _size + 1;
while (pos < end) {
_str[end] = _str[end - 1];
--end;
}
_str[pos] = ch;
_size++;
}
void insert(size_t pos, const char* str)
{
assert(pos <= _size);
size_t len = strlen(str);
if (_size + len > _capacity)
{
reserve(_size + len); //扩容就用reserve
}
//挪动数据
size_t end = _size;
while (end >= (int)pos)//不强转,当pos等于0,插入'xxx',就会死循环
{
_str[end + len] = _str[end];
--end;
}
strncpy(_str + pos, str, len);
_size += len;
}
void erase(size_t pos, size_t len = npos)
{
assert(pos <= _size);
if (len == npos || pos + len >= _size)
{
_str[pos] = '\0';
_size = pos;
}
else
{
size_t begin = pos + len;
while (begin <= _size)
{
_str[begin - len] = _str[begin];
++begin;
}
_size -= len;
}
}
void resize(size_t n, char ch = '\0')
{
//两种情况:1._size变小,即删除;2._size变大,即扩容
if (n <= _size)
{ //和erase异曲同工
_str[n] = '\0';
_size = n;
}
else
{ //扩容
reserve(n);
while (_size < n)
{
_str[_size] = ch;
++_size;
}
//不确定最后一个存储的是不是'\0'.手动添加一下
_str[_size] = '\0';
}
}
size_t find(char ch, size_t pos = 0)
{
//查找默认从0开始找,这里给缺省参数目的是不写死,如果不从0开始找,可以从给定的位置找起
for (int i = pos; i < _size; i++)
{
if (_str[i] == ch)
return i;
}
}
size_t find(const char* str, size_t pos = 0)
{
const char* p = strstr(_str + pos, str);
if (p)
return p - _str;
else
return npos;
}
string substr(size_t pos, size_t len = npos)
{
string s;
size_t end = pos + len;
if (len == npos || pos + len >= _size)
{
len = _size - pos;
end = _size;
}
s.reserve(len);
for (size_t i = pos; i < end; i++)
{
s += _str[i];
}
return s;
}
bool operator<(const string& s) const
{
return strcmp(_str, s._str) < 0;
}
bool operator>(const string& s) const
{
return strcmp(_str, s._str) > 0;
}
bool operator>=(const string& s) const
{
return !(*this < 0);
}
bool operator==(const string& s)const
{
return strcmp(_str, s._str) == 0;
}
bool operator<=(const string& s) const
{
return *this < s || *this == s;
}
bool operator!=(const string& s)const
{
return !(*this == 0);
}
void clear()
//用来清理,不然test4中 string s1("hello world")前面就要加std::
{
_str[0] = '\0';
_size = 0;
}
private:
char* _str;
size_t _size;
size_t _capacity;
//静态的变量,按理来说,类内初始化,类外定义没毛病,但是这里有个特例看一下:
//const static size_t pos = -1 //特例
//const static double npos = -1.1 //不支持
public:
const static size_t npos;
};
const size_t string::npos = -1;
ostream& operator<<(ostream& out, const string& s)
{
/*for (size_t i = 0; i < s.size(); i++)
{
out << s[i];
}*/
for (auto ch : s)
out << ch;
return out;
}
istream& operator>>(istream& in, string& s)
{
s.clear();
//s.reserve(128);
char buff[129];
size_t i = 0;
char ch;
ch = in.get(); //in >> ch; 读不到' '和'\n'
while (ch != ' ' && ch != '\n')//流提取,遇到空格和换行自动结束
{
//s += ch;
in >> ch;
//ch = in.get();
buff[i++] = ch;
if (i == 128)
{
buff[i] = '\0';
s += buff;
i = 0;
}
ch = in.get();
}
if (i != 0)
{
buff[i] = '\0';
s += buff;
}
return in;
}
void test_string1()
{
string s1("hello world");
cout << s1.c_str() << endl;
string s2;
cout << s2.c_str() << endl;
for (size_t i = 0; i < s1.size(); i++)
{
cout << s1[i] << " ";
}
cout << endl;
string::iterator it = s1.begin();
while (it != s1.end())
{
(*it)++;
cout << *it << " ";
++it;
}
cout << endl;
for (auto& ch : s1)
{
ch++;
cout << ch << " ";
}
cout << endl;
cout << s1.c_str() << endl;
}
void test_string2()
{
string s1("hello world");
cout << s1.c_str() << endl;
s1.push_back(' ');
s1.append("hello lizhi hello lizhi");
cout << s1.c_str() << endl;
s1 += '#';
s1 += "*********************";
cout << s1.c_str() << endl;
string s2;
s2 += '#';
s2 += "*********************";
cout << s2.c_str() << endl;
}
void test_string3()
{
string s1("hello world");
cout << s1.c_str() << endl;
s1.insert(5, '%');
cout << s1.c_str() << endl;
s1.insert(s1.size(), '%');
cout << s1.c_str() << endl;
s1.insert(0, '%');
cout << s1.c_str() << endl;
}
void test_string4()
{
string s1("hello world");
string s2("hello world");
cout << (s1 >= s2) << endl;
s1[0] = 'z';
cout << (s1 >= s2) << endl;
cout << s1 << endl;
cin >> s1;
cout << s1 << endl;
/*char ch1, ch2;
cin >> ch1 >> ch2;*/
}
void test_string5()
{
string s1("hello world");
s1.insert(5, "abc");
cout << s1 << endl;
s1.insert(0, "xxx");
cout << s1 << endl;
s1.erase(0, 3);
cout << s1 << endl;
s1.erase(5, 100);
cout << s1 << endl;
s1.erase(2);
cout << s1 << endl;
}
void test_string6()
{
string s1("hello world");
cout << s1 << endl;
s1.resize(5);
cout << s1 << endl;
s1.resize(25, 'x');
cout << s1 << endl;
}
void test_string7()
{
string s1("test.cpp.tar.zip");
//size_t i = s1.find('.');
//size_t i = s1.rfind('.');
//string s2 = s1.substr(i);
//cout << s2 << endl;
string s3("https://legacy.cplusplus.com/reference/string/string/rfind/");
//string s3("ftp://www.baidu.com/?tn=65081411_1_oem_dg");
// 协议
// 域名
// 资源名
string sub1, sub2, sub3;
size_t i1 = s3.find(':');
if (i1 != string::npos)
sub1 = s3.substr(0, i1);
else
cout << "没有找到i1" << endl;
size_t i2 = s3.find('/', i1 + 3);
if (i2 != string::npos)
sub2 = s3.substr(i1 + 3, i2 - (i1 + 3));
else
cout << "没有找到i2" << endl;
sub3 = s3.substr(i2 + 1);
cout << sub1 << endl;
cout << sub2 << endl;
cout << sub3 << endl;
}
void test_string8()
{
string s1("hello world");
string s2 = s1;
cout << s1 << endl;
cout << s2 << endl;
string s3("xxxxxxxxxxxxxxxxxxx");
s2 = s3;
cout << s2 << endl;
cout << s3 << endl;
}
void test_string9()
{
string s1("hello world");
cin >> s1;
cout << s1 << endl;
cout << s1.size() << endl;
cout << s1.capacity() << endl;
}
}
3.实现思路
我模拟实现string的思路是这样的:先通过cplusplus网站搜string,就会有string的专栏,划到下面就会有string的各种接口,看看这些接口都有什么作用,还有如何去使用的,我主要实现如下常用的接口:
快速跳转参考网站:string
4.代码剖析
4.1迭代器
迭代器中的接口:begin和end,都有两个版本,普通版本和const版本,为不同需求而生
4.2npos
按理来说npos一般是类内初始化,类外定义的
静态成员变量不能给缺省值,但是const静态的整形可以
还是类内初始化,类外定义比较好,特例是一个坑,知道就行,不建议这样用,因为类内一般都是声明,特例
既是声明也是定义,逻辑不自洽。
4.3增删查改
4.31扩容
4.32 尾插
4.33追加字符串
很简单,判断str和string加起来需不需要扩容,然后直接拷贝过去,增加相应的size。
4.24插入和删除
插入和删除很简单,重要的就是要会画图,要插入到哪个位置,删除哪里的数据,最后结束的边界在哪里,全部画出来就一目了然了。别忘记加断言。
插入字符串这里需要注意一下
这里end和pos都是无符号数,当pos=0时,会无限死循环。
修改方法很简单,把end和pos都变成int就好了
4.25 更改字符串长度
4.26查
4.4拷贝构造的传统与现代写法(了解)
浅拷贝:数据存在对象里面(日期类) 深拷贝:数据存在指向的空间
传统写法中s2 = s3的深拷贝是如何进行的?
创建新空间,把旧空间给新空间,再把旧空间释放掉
传统写法和现代写法在效率上没有区别,区别在于是否简洁