目录
string是C++用来表示字符串的类,下面我们来模拟实现一个string类的增删查改。
4个主要默认成员函数,需要注意构造函数的初始化列表
- //默认构造函数
- string(const char* str = "")//缺省值为空
- :_capacity(strlen(str))
- , _size(_capacity)
- {
- _str = new char[_capacity + 1];//多开1存放'\0'
- strcpy(_str, str);//拷贝内容
- }
-
- //拷贝构造函数
- string(const string& s)
- :_str(nullptr)//VS会对内置进行处理,但其他编译器不会,防止未初始化_str导致delete崩溃
- , _size(0)
- , _capacity(0)
- {
- //传统写法
- /*_str = new char[s._size + 1];//多开1空间是存放'\0'
- strcpy(_str, s._str);
- _size = s._size;
- _capacity = s._capacity;*/
-
- //现代写法
- string tmp(s._str);
- swap(tmp);
- }
-
- //赋值重载函数
- string& operator=(string s)//传值传参已经拷贝了
- {
- //传统写法 参数为(const string& s)
- /*if (this != &s)
- {
- _str = new char[s._size + 1];//多开1空间是存放'\0'
- strcpy(_str, s._str);
- _size = s._size;
- _capacity = s._capacity;
- }
- return *this;*/
-
- //现代写法 参数为(string s)
- if (this != &s)
- {
- swap(s);
- }
- return *this;
- }
-
- //析构函数
- ~string()
- {
- delete[] _str;//释放_str指向的空间
- _str = nullptr;
- _size = 0;
- _capacity = 0;
- }
string的迭代器就是原生指针包装成的。
- typedef char* iterator;//迭代器
- typedef const char* const_iterator;//const迭代器
-
- //迭代器是左闭右开
- iterator begin()
- {
- return _str;//指向第一个字符
- }
-
- iterator end()
- {
- return _str + _size;//指向'\0'
- }
-
- const_iterator begin() const
- {
- return _str;
- }
-
- const_iterator end() const
- {
- return _str + _size;
- }
这里只实现了增加字符和字符串的函数,不过基本涵盖到了大部分情况,对于增加string类的函数和增加字符串的基本一样。
- void push_back(char c)//增加字符
- {
- if (_size + 1 > _capacity)//判断是否超出当前容量
- {
- reserve(_capacity == 0 ? 4 : _capacity * 2);//扩两倍,需注意原始容量为0的情况
- }
- _str[_size] = c;//写入字符
- _size++;//增加当前字符个数
- _str[_size] = '\0';//补上'\0'
- }
-
- string& operator+=(char c)//增加字符
- {
- push_back(c);//复用即可
- return *this;
- }
-
- void append(const char* str)//增加字符串
- {
- size_t len = strlen(str);//算出增加的字符串的长度
- if (_size + len > _capacity)//判断是否超出当前容量
- {
- reserve(_size + len);//扩容
- }
- for (size_t i = 0; i < len; i++, _size++)
- {
- _str[_size] = str[i];//追加字符串
- }
- _str[_size] = '\0';//补上'\0'
- }
-
- string& operator+=(const char* str)//增加字符串
- {
- append(str);//复用即可
- return *this;
- }
-
- void clear()//清除string,只是修改_size
- {
- _size = 0;
- _str[_size] = '\0';
- }
-
- void swap(string& s)//交换两个string类的成员变量
- {
- std::swap(_str, s._str);
- std::swap(_size, s._size);
- std::swap(_capacity, s._capacity);
- }
-
- const char* c_str() const//返回_str的函数
- {
- return _str;
- }
下面是两个扩容函数和一些基本函数。
- size_t size() const//返回当前string类中的字符个数
- {
- return _size;
- }
-
- size_t capacity() const//返回当前string类的容量大小
- {
- return _capacity;
- }
-
- bool empty() const//返回string是否为空
- {
- return _size == 0;
- }
-
- void resize(size_t n, char c = '\0')//让string类的_size变成n,多的会用c补足,少的会截断
- {
- if (n < _size)//如果是少的,直接截断
- {
- _size = n;
- _str[_size] = '\0';
- }
- else if (n > _size)
- {
- if (n > _capacity)//判断目标容量是否超出当前容量
- {
- reserve(n);//是就扩容
- }
- for (size_t i = _size; i < n; i++)
- {
- _str[i] = c;//用c补足n和_size之间的位置
- }
- _size = n;
- _str[_size] = '\0';//补充'\0'
- }
- }
-
- void reserve(size_t n)//容量扩容到n,小于当前容量就不理会
- {
- if (n > _capacity)//有点类似realloc扩容
- {
- char* tmp = new char[n + 1];//多开1空间存放'\0'
- strcpy(tmp, _str);
- delete[] _str;
- _capacity = n;
- _str = tmp;
- _str[_size] = '\0';
- }
- }
要针对const对象和非const对象写两个[]的重载,做到读和写分离。
- char& operator[](size_t index)//返回引用说明可以修改
- {
- assert(index < _size);//断言查询的下标有没有越界
- return _str[index];
- }
-
- const char& operator[](size_t index) const
- {
- assert(index < _size);//断言查询的下标有没有越界
- return _str[index];
- }
比较运算符可以实现两个其他复用,复用和我之前的日期类完全一样。这里我比较大小使用了strcmp函数,这个函数在我之前的博客当中也有讲过实现原理和模拟实现,所以这里就不具体说明了。
- bool operator<(const string& s)//strcmp函数前者小于后者就会返回负数相等就会返回0,前者大于后者就会返回大于正数
- {
- return strcmp(this->_str, s._str) < 0;
- }
-
- bool operator==(const string& s)
- {
- return !strcmp(this->_str, s._str);
- }
-
- 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);
- }
删要诺数据,需要注意下标,稍有不慎就会越界或者出错。
- string& erase(size_t pos, size_t len)//删除pos位置后len个字符
- {
- assert(pos <= _size);//断言有没有越界
- if (pos + len >= _size)//如果要删除pos之后的所有的字符就像resize一样直接截断
- {
- _size = pos;
- _str[_size] = '\0';
- }
- else
- {
- //从前往后走,把右边的字符挪到左边去,这样不会出现覆盖的问题
- size_t end = pos + len;//这里要注意下标
- while (end <= _size)
- {
- _str[end - len] = _str[end];
- end++;
- }
- _size = pos;//更新当前字符的数量
- }
- return *this;
- }
查找的函数有查找一个字符以及一个字符串,查找字符串复用了strstr函数,这个在我之前的博客有讲解以及实现。
- size_t find(char c, size_t pos = 0) const//找第一个出现在string中的c,找到了就返回下标,否则就返回npos(size_t -1)
- {
- for (size_t i = pos; i < _size; i++)
- {
- if (_str[i] == c)//找到就返回下标
- return i;
- }
- return npos;
- }
-
- size_t find(const char* s, size_t pos = 0) const//返回子串s在string中第一次出现的位置的下标
- {
- char* res = strstr(_str + pos, s);//在前者字符串中找后者字符串,返回找到的字符串的指针(前者),没有找到就会返回空指针
- if (res != nullptr)
- return res - _str;//堆中的数据是从下往上的!!!,所以是res-_str
- return npos;//没找到就返回npos
- }
insert函数是在指定下标处插入一个字符或字符串,需要注意挪数据的方向和下标。
- string& insert(size_t pos, char c)//在pos位置插入字符c
- {
- assert(pos <= _size);//断言有没有越界
- if (_size + 1 > _capacity)//判断有没有超过当前容量
- {
- reserve(_capacity == 0 ? 4 : _capacity * 2);//注意当前容量为0的情况
- }
- //从后往前走,从左往右挪数据
- size_t end = _size + 1;//注意起始位置的下标,这里是指向'\0'的下一个位置
- while (end > pos)//这里需注意如果end指向'\0'就得是end>=pos,假如pos为0就会死循环,因为pos和end都是size_t,就算把end改为int也会死循环,因为隐式类型转换,end和pos相比还是会转换成size_t
- {
- _str[end] = _str[end - 1];//从左往右挪数据
- end--;
- }
- _size++;//增加当前字符数量
- _str[pos] = c;//插入字符
- return *this;
- }
-
- string& insert(size_t pos, const char* str)//在pos位置插入字符串
- {
- assert(pos <= _size);//断言有没有越界
- size_t len = strlen(str);//记录str的长度
- if (_size + len > _capacity)//判断有没有超过当前容量
- {
- reserve(_size + len);//扩容,因为不知道len长度和2*_capacity谁长,所以干脆直接扩容插入后的长度
- }
- //这里和insert字符基本一样,挪数据的间隔不是1而是len
- size_t end = _size + len;//注意下标
- while (end > pos)
- {
- _str[end] = _str[end - len];//从左往右挪数据
- end--;
- }
- for (size_t i = 0; i < len; i++, pos++)
- {
- _str[pos] = str[i];//插入字符串
- }
- _size += len;//增加当前字符数量
- return *this;
- }
substr函数就是截取一段字符串。
- string substr(size_t pos = 0, size_t len = npos) const//从pos位置往后len字符截取字符串
- {
- string s;//创建字符串
- size_t end = pos + len;//假设end是pos之后len个字符
- if (len == npos || pos + len >= _size)//如果len或len+pos的位置超过了当前字符数量就令end指向'\0'
- {
- end = _size;
- len = _size - pos;
- }
- s.reserve(len);//提前开好空间,防止多次扩容造成性能浪费
- for (size_t i = pos; i < end; i++)
- {
- s += _str[i];//截取字符串
- }
- return s;//返回
- }
cout的实现比较简单,就是打印字符串的每一个字符,cin稍微复杂。这里是实现成全局函数,并且是string类的友元。
- ostream& _string::operator<<(ostream& _cout, const _string::string& s)
- {
- for (auto ch : s)//范围for直接打印
- {
- _cout << ch;
- }
- return _cout;
- }
-
- istream& _string::operator>>(istream& _cin, _string::string& s)
- {
- char buff[129];//开一个字符数组
- char ch;
- ch = _cin.get();//从输入流中读取字符
- size_t size = 0;//记录buff数组存了几个字符
- while (ch != ' ' && ch != '\n')//没遇到空格和换行就继续
- {
- buff[size++] = ch;//读入字符
- if (size == 128)//如果读到上限就写入到s里
- {
- buff[129] = '\0';//记得要补上'\0'
- s += buff;
- size = 0;//重置size
- }
- ch = _cin.get();//读取字符
- }
- buff[size] = '\0';//buff可能还有数据,写入到s中即可
- s += buff;
- return _cin;
- }
以上就是string的简单模拟实现,如有问题可在评论区提出。