前言:什么是string类?
在C语言中,字符串是以’\0’结尾的字符的集合,为了操作方便,C标准库中已经提供了一些str系列的库函 数,但是这些库函数与字符串是分离的,不符合面向对象编程的思想,而且底层空间需要用户自己管理,很可能会造成越界访问。
C++中对于string的定义为:typedef basic_string string; 也就是说C++中的string类是一个泛型类,由模板实例化出来的一个标准类,string本质上不是一个标准数据类型。
至于我们为什么不直接用string标准数据类型而用类是因为一个叫做编码的东西,我们每个国家的语言不同 比如说英语使用26个英文字母基本就能表述所有的单词 但是对于中文的字符呢?就要用其他编码方式啊(比如说utf-8 、utf-16、utf-32)
string类的模拟实现
一、四个默认成员函数
对于任何一个类,在C++98下都必须要有六个默认成员函数:构造函数,析构函数,拷贝构造函数,赋值重载函数,普通对象取地址函数,const对象取地址函数,C++11又增加了两个默认成员函数,分别是移动构造函数和移动赋值重载函数。其中取地址成员函数一般都不需要自己实现的,C++11下增加的两个成员函数会在后面C++11的内容中详述,这里主要实现前四个默认成员函数。
1.1 构造函数
构造函数是完成初始化工作的,一般都会用一个字符串构造一个string对象的,在给这个string对象开辟了空间之后还需要给它初始化,完成初始化工作的函数就是构造函数。
//构造函数
//构造string对象可以是有参数的,也可以是无参的,所以参数可以给缺省值,
//无参构造时,构造出来的string对象应该是一个空对象,所以缺省值用空串(注意:空串不是空格)
//根据string类的成员函数,需要初始化_size,_capacity,_str,_size表示
//string对象有效字符的数目,应该等于str的长度,_capacity有效字符容量可以初始化和_size一样的值
//同时,需要给_str开辟_capacity+1大小的空间,因为_capacity是有效字符的容量,而string的
//最后一个位置要放一个'\0'作为结束标志(兼容C语言,C语言字符串是以'\0'为结尾的),所以需要
//多开一个空间,开辟好空间后需要把str字符串的内容都拷贝到string对象管理的_str中,这样就完成
//了构造函数的初始化工作了(注意,要把'\0'也拷贝到_str上去)
string(const char* str = "" )
:_size(strlen(str))
, _capacity(_size)
, _str(new char[_capacity+1])
{
memcpy(_str, str, _size + 1);
}
1.2 拷贝构造函数
拷贝构造函数一定要用深拷贝,即需要手动开辟一块与原来对象一样大小的空间,然后再赋值给新的对象,如果用浅拷贝,即没有开辟新的空间存放新的对象的值,即新旧两个对象的_str指针都指向了同一块空间,那么在析构的时候就会出现一块空间被释放两次的情况,造成程序崩溃。
拷贝构造(传统写法)
string(const string& s)
{
//开辟一块与s一样大的空间,要比_capacity多一个,因为
//_capacity是有效容量,后面要预留一个位置放'\0'
char* tmp = new char[s._capacity + 1];
if (tmp == nullptr)
{
perror("string(const string& s) fail");
exit(-1);
}
//把原对象的值拷贝到新对象对应的地址上,s._size是s的有效字符的长度
//需要+1把'\0'也拷贝到tmp上去
memcpy(tmp, s._str,s._size+1);
//更新新对象的_size和_capacity
_size = s._size;
_capacity = s._capacity;
//最后把tmp交回给_str管理
_str = tmp;
}
//拷贝构造(现代写法)
//现代写法有一个很重要的细节就是新对象的_str必须要初始化为nullptr,
//因为后面的Swap函数会交换两个对象的成员函数,如果_str不初始化为nullptr,
//也就是随机值,那么交换后_str给了tmp的_str,因为tmp是临时对象,出来作用域
//会被析构的,即会对_str进行析构,而_str指向的是随机空间,所以会非法析构,造成
//程序崩溃,_size和_capacity是栈上的数据不初始化也没有关系,_str是动态申请的
//一定要置为nullptr
string(const string& s)
:_size(0)
,_capacity(0)
,_str(nullptr)
{
//用s对象的c_str()调用构造函数,得到的tmp就是我们想要的
//对象,再交换tmp和*this对象的成员变量交换即可
string tmp(s._str);
Swap(tmp);
}
void Swap(string& s)
{
swap(_str, s._str);
swap(_size, s._size);
swap(_capacity, s._capacity);
}
1.3 赋值重载函数
赋值重载函数跟拷贝构造函数一样,也存在浅拷贝对同一块空间释放两次的情况,并且浅拷贝还存在内存泄露的问题,所以也需要深拷贝来解决问题。
赋值(传统写法)
string& operator=(const string& s)
{
//可以先判断一下是否是给自己赋值
if (this != &s)
{
//先开一块和s对象一样大的空间,预留一个'\0'的位置
char* tmp = new char[s._capacity + 1];
if (tmp)
{
//把s对象的字符串内容都拷贝到临时空间tmp中
memcpy(tmp, s._str,s._size+1);
//更新_size和_capacity
_size = s._size;
_capacity = s._capacity;
//释放原来对象的_str指向的空间
delete[] _str;
//_str指向新空间
_str = tmp;
}
}
return *this;
}
//赋值(现代写法)
//现代写法是直接利用参数传参时会调用拷贝构造函数构造一个临时的对象
//刚好这个对象的值就是新的对象想要的,所以直接令这个临时对象和新对象
//交换成员变量就可以了,并且因为s是临时对象,所以赋值函数结束时会
//调用析构函数释放内存,即把旧对象所指向的内容释放掉了,无需手动释放
string& operator=(string s)
{
Swap(s);
return *this;
}
1.4 析构函数
析构函数是释放动态开辟的内存的。
//析构函数
//只需把动态申请的内存释放了即可
~string()
{
//先判断是否为nullptr吧,是nullptr就没必要释放了
if (_str)
{
//释放空间
delete[] _str;
//置空
_str = nullptr;
_size = _capacity = 0;
}
}
二、迭代器
对于string类,迭代器就是一个指针
typedef char* iterator;
typedef const char* const_iterator;
//begin就是第一个字符的地址,即_str
iterator begin()
{
return _str;
}
//const版本
const_iterator begin() const
{
return _str;
}
//end就是指向最后一个有效元素的下一个位置
iterator end()
{
return _str + _size;
}
//const版本
const_iterator end() const
{
return _str + _size;
}
三、c_str()函数
//c_str()函数的返回值就是string对象中C类型的字符串指针
const char* c_str() const
{
return _str;
}
四、size和capacity函数
const size_t size() const
{
return _size;
}
const size_t capacity() const
{
return _capacity;
}
五、reserve函数
reserve函数是扩容函数,调整空间大小用的。参数为目标空间的大小。
void reserve(size_t n)
{
//如果n>_capacity,需要扩容;如果小于,一般都不会缩容的
if (n > _capacity)
{
//扩容需要开辟一块目标大小的空间,然后把原来的数据拷贝到新空间里
char* tmp = new char[n + 1];
if (tmp)
{
//把原来数据拷贝到新空间中
memcpy(tmp, _str,_size+1);
//释放旧的空间
delete[] _str;
//把新的空间交给_str指针管理
_str = tmp;
//更新_capacity为新的空间大小
_capacity = n;
}
}
}
六、resize函数
resize函数跟reserve函数的功能类似,也是调整空间的大小,但是resize支持删除数据,并且支持把空间初始化为特定值。
void resize(size_t n, char ch = '\0')
{
//场景一
if (n <= _size)
{
_str[n] = '\0';
_size = n;
}
else
{
//如果需要扩容,则是场景三;否则,则是场景二
//场景三只不过是在场景二的前面多加了一步扩容的工作
if (n > _capacity)
{
//扩容
reserve(n);
}
//从_size开始放ch,直到n-1位置
for (int i = _size; i < n; i++)
{
_str[i] = ch;
}
//更新_size为新的大小
_size = n;
//最后一定要放上一个'\0'作为字符串的结束标志
_str[_size] = '\0';
}
}
七、push_back函数
push_back是尾插函数,即在string对象尾插字符。
void push_back(const char ch)
{
//先检查容量是否足够,不够则扩容
if (_size == _capacity)
{
//一般是两倍扩容
reserve(_capacity == 0 ? 4 : 2 * _capacity);
}
//因为_size是最后一个有效字符的下一个位置,所以尾插应该插入到_str[_size]位置
_str[_size] = ch;
_size++;
//后面记得要跟上'\0',不要漏了
_str[_size] = '\0';
}
七、append函数
append的作用是尾插一个字符串。
void append(const char* str)
{
size_t len = strlen(str);
//先判断所剩空间是否>=插入的字符串的长度
//如果小于,则需要先扩容
if (_size + len > _capacity)
{
//扩大容量到原来字符串+插入字符串的长度
reserve(_size + len);
}
//把所需尾插的字符串尾插到原字符串的后面
strcpy(_str + _size, str);
//更新_size
_size += len;
}
八、find函数(查找字符或者字符串)
//从pos位置开始查找某个字符
size_t find(char ch, size_t pos = 0)
{
//先判断pos位置是否合法
assert(pos < _size);
//遍历_str字符串比对就行了,找到了就返回下标
for (int i = pos; i < _size; i++)
{
if (_str[i] == ch)
{
return i;
}
}
//找不到返回npos,npos是整形的最大值
return npos;
}
//从pos位置开始查找字符串
size_t find(const char* str, size_t pos = 0) const
{
assert(pos < _size);
//判断str是否为_str的子串
char* ptr = strstr(_str + pos, str);
if (ptr)
{
//如果str是_str的子串,因为strstr找到后返回的是子串的第一个
//字符在源串的地址,所以减去源串的起始地址就是对应字符串在源串中的下标
return ptr - _str;
}
else
{
//找不到返回一个整形的最大值,表示找不到
return npos;
}
}
九、substr函数
substr函数是在string对象中的某个位置开始,取len长度的字符串构建一个新的string对象的函数。
string substr(size_t pos = 0, size_t len = npos)
{
assert(pos < _size);
size_t n = len;
//如果n==npos即没传len参数时,默认取pos位置后面的全部字符,
//如果n大于从pos位置往后的所有字符的长度,也是取pos后面的全部字符
if (n == npos || pos + n > _size)
{
//_size-pos就是从pos位置开始后面全部字符的长度
n = _size - pos;
}
//创建一个临时string对象
string tmp;
//开n个空间即可,因为有n个有效字符
tmp.reserve(n);
//取出从pos位置开始,后面的全部字符尾插到tmp中即可
for (int i = pos; i < pos + n; i++)
{
tmp += _str[i];
}
return tmp;
}
十、clear函数
clear函数是清空string对象的内容。
void clear()
{
//只需要在_str[0]位置放一个'\0'即可完成clear工作
_str[0] = '\0';
//清空之后需要把_size置为0
_size = 0;
}
十一、insert(字符、字符串)函数
insert(字符)函数是从pos位置插入n个字符ch。
void insert(size_t pos,size_t n,const char ch)
{
//判断pos的合法性,pos==_size的时候是尾插
assert(pos <= _size);
//如果原来字符串的长度+插入字符数目后超出容量,那么就扩容
if (_size + n > _capacity)
{
reserve(_size + n);
}
//把从pos位置开始往后的所有数据都往后移动n位,腾出n个空位置
//以便插入n个ch字符
size_t end = _size;
//要注意防止pos=0时死循环问题,end永远>=0,因为end和pos的类型都`在这里插入代码片`是size_t,
//当pos等于0时,end>=pos循环继续,也就是说end<0时才会停止,但是end不可能<0,
//因为当end减为-1的时候,类型是size_t,所以会被看作是无符号数,-1对应的无符号数
//是整形的最大值,会导致死循环,所以需要外加一个条件判断end是否减为npos了,如果是
//则循环停止,否则继续
while (end >= pos && end != npos)
{
//挪动数据
_str[end + n] = _str[end];
end--;
}
//从pos位置开始把n个ch字符插入进去
for (int i = 0; i < n; i++)
{
_str[i + pos] = ch;
}
//更新_size
_size += n;
}
insert(字符串)函数是从pos位置插入一个字符串。
void insert(size_t pos, const char* str)
{
//判断pos位置的合法性
assert(pos <= _size);
int len = strlen(str);
//判断剩余空间大小是否足够插入字符串str,不够则扩容
if (_size + len > _capacity)
{
reserve(_size + len);
}
//要注意防止pos=0时死循环问题,end永远>=0,因为end和pos的类型是都size_t,
//当pos等于0时,end>=pos循环继续,也就是说end<0时才会停止,但是end不可能<0,
//因为当end减为-1的时候,类型是size_t,所以会被看作是无符号数,-1对应的无符号数
//是整形的最大值,会导致死循环,所以需要外加一个条件判断end是否减为npos了,如果是
//则循环停止,否则继续
size_t end = _size;
while (end >= pos && end != npos)
{
//挪动数据,腾出len个位置的空间,插入字符串str
_str[end + len] = _str[end];
end--;
}
//把字符串str拷贝到从_str+pos位置开始的这段位置上
memcpy(_str + pos, str, sizeof(char) * len);
//更新_size的值
_size += len;
}
十二、erase函数
erase函数的作用是从pos位置开始删除n个字符。
void erase(size_t pos, size_t n = npos)
{
//pos==_size不是有效字符,不能删除
assert(pos < _size);
//如果n无参或者n的值大于从pos位置到结尾的长度,那就把从pos位置开始的
//所有字符都删掉,即_str[pos]='\0'即可,_size是最后一个有效元素下标+1,
//所以新的长度就是pos
if (n >= _size - pos || n==npos )
{
_str[pos] = '\0';
_size = pos;
}
else
{
//从后往前挪动数据,从pos位置开始往后覆盖即可
size_t end = pos + n;
while (end <= _size)
{
_str[pos++] = _str[end++];
}
//更新_size
_size -= n;
}
}
十三、运算符重载函数
13.1 operator[ ]
//C语言中[]能直接访问字符串中的某个位置的元素,并且能做修改,所以
//返回的该字符的引用
char& operator[](size_t pos)
{
assert(pos < _size);
return _str[pos];
}
//const版本的operator[]是只读的
const char& operator[](size_t pos) const
{
assert(pos < _size);
return _str[pos];
}
13.2 operator+=一个字符
operator+=一个字符等于是尾插一个字符的意思,所以可以复用push_back函数,但是+=是有返回值的,返回值是+=后的对象。
string& operator+=(char ch)
{
push_back(ch);
return *this;
}
13.3 operator+=一个字符串
对象+=字符串是尾插一个字符串的意思,所以可以服用append,但是+=是有返回值的,返回值是+=后的对象。
string& operator+=(const char* str)
{
append(str);
return *this;
}
13.4 operator比较大小的运算符
bool operator<(const string& s) const
{
int i1 = 0;
int i2 = 0;
//本质上是比较两个字符串的大小
while (i1 < _size && i2 < s._size)
{
//遇到小于,返回true
if (_str[i1] < s[i2])
{
return true;
}
//遇到大于,返回false
else if (_str[i1] > s[i2])
{
return false;
}
//遇到等于,继续往后比较
else
{
i1++;
i2++;
}
}
if (i1 == _size && i2 < s._size)
{
return true;
}
else
{
return false;
}
}
bool operator==(const string& s) const
{
//本质上是比较两个字符串是否相等
int i1 = 0;
int i2 = 0;
while (i1 < _size && i2 < s._size)
{
if (_str[i1] != s[i2])
{
return false;
}
else
{
i1++;
i2++;
}
}
if (i1 == _size && i2 == s._size)
{
return true;
}
else
{
return false;
}
}
bool operator<=(const string& s) const
{
return (*this) < s || (*this) == s;
}
bool operator>(const string& s) const
{
return !(*this <= s);
}
bool operator>=(const string& s) const
{
return !(*this < s);
}
bool operator!=(const string& s) const
{
return !(*this == s);
}
十四、重载“流插入–cout”,“流提取–cin”运算符
ostream& operator<<(ostream& out, const string& s)
{
//for (int i = 0; i < s.size(); i++)
//{
// out << s[i];
//}
//把s对象的_str字符串的内容逐一打印出来即可
//这里不建议直接打印c_str(),因为c_str()是按照C语言
//的规则,即'\0'为结束标志的,但是string类的是按照_size
//来确定对象的字符数目的,所以在"abc\0def"这样特殊的string类,
//打印时如果直接打印c_str(),只能打印出abc,而根据string类的要求
//应该以_size为大小打印,所以会打印出abcdef,所以这里还是要区分一下,
//不用c_str()的形式打印
for (const auto& e : s)
{
out << e;
}
//流插入需要返回一个ostream对象,作为连续流插入的前提,可以是引用,因为out出了这个函数不销毁
return out;
}
istream& operator>>(istream& in, string& s)
{
//调用流提取函数时需要先清空对象本身的内容
s.clear();
//为了减少频繁地扩容,可以创建一个缓冲区(字符数组)先存数据,待缓冲区满了之后
//再把缓冲区(字符数组)的内容尾插到s对象中
char buff[128] = { 0 };
//读取第一个字符
char ch = in.get();
//先去掉前导空格或者换行
while (ch == ' ' || ch == '\n')
{
ch = in.get();
}
//按照流提取,遇到空格或者换行就停止读取,否则继续
int i = 0;
while (ch != ' ' && ch != '\n')
{
//读到的字符存放在缓冲区buff中
buff[i++] = ch;
//当缓冲区填满后,尾插到s中
if (i == 127)
{
//因为字符串需要以'\0'结尾,所以先在最后一个位置放'\0'再尾插
buff[i] = '\0';
s += buff;
//重新回到缓冲区的0下标开始放字符
i = 0;
}
//从stdin中一个字符一个字符地读取上来
ch = in.get();
}
//如果i!=0,证明缓冲区里还有字符,需要添加'\0'成为字符串再尾插到s对象中
if (i != 0)
{
buff[i] = '\0';
s += buff;
}
//流提取需要返回istream,作为连续流提取的前提,可以返回引用,因为in对象
// 出了这个函数后不销毁
return in;
}
十五、代码汇总
string.h文章来源:https://www.toymoban.com/news/detail-548857.html
#pragma once
#include <cstring>
#include <stdlib.h>
#include <iostream>
#include <assert.h>
using namespace std;
namespace kb
{
class string
{
public:
typedef char* iterator;
typedef const char* const_iterator;
//begin就是第一个字符的地址,即_str
iterator begin()
{
return _str;
}
//const版本
const_iterator begin() const
{
return _str;
}
//end就是指向最后一个有效元素的下一个位置
iterator end()
{
return _str + _size;
}
//const版本
const_iterator end() const
{
return _str + _size;
}
//构造函数
//构造string对象可以是有参数的,也可以是无参的,所以参数可以给缺省值,
//无参构造时,构造出来的string对象应该是一个空对象,所以缺省值用空串(注意:空串不是空格)
//根据string类的成员函数,需要初始化_size,_capacity,_str,_size表示
//string对象有效字符的数目,应该等于str的长度,_capacity有效字符容量可以初始化和_size一样的值
//同时,需要给_str开辟_capacity+1大小的空间,因为_capacity是有效字符的容量,而string的
//最后一个位置要放一个'\0'作为结束标志(兼容C语言,C语言字符串是以'\0'为结尾的),所以需要
//多开一个空间,开辟好空间后需要把str字符串的内容都拷贝到string对象管理的_str中,这样就完成
//了构造函数的初始化工作了(注意,要把'\0'也拷贝到_str上去)
string(const char* str = "" )
:_size(strlen(str))
, _capacity(_size)
, _str(new char[_capacity+1])
{
memcpy(_str, str, _size + 1);
}
//拷贝构造(传统写法)
//string(const string& s)
//{
// //开辟一块与s一样大的空间,要比_capacity多一个,因为
// //_capacity是有效容量,后面要预留一个位置放'\0'
// char* tmp = new char[s._capacity + 1];
// if (tmp == nullptr)
// {
// perror("string(const string& s) fail");
// exit(-1);
// }
// //把原对象的值拷贝到新对象对应的地址上,s._size是s的有效字符的长度
// //需要+1把'\0'也拷贝到tmp上去
// memcpy(tmp, s._str,s._size+1);
// //更新新对象的_size和_capacity
// _size = s._size;
// _capacity = s._capacity;
// //最后把tmp交回给_str管理
// _str = tmp;
//
//}
//赋值(传统写法)
//string& operator=(const string& s)
//{
// //可以先判断一下是否是给自己赋值
// if (this != &s)
// {
// //先开一块和s对象一样大的空间,预留一个'\0'的位置
// char* tmp = new char[s._capacity + 1];
// if (tmp)
// {
// //把s对象的字符串内容都拷贝到临时空间tmp中
// memcpy(tmp, s._str,s._size+1);
// //更新_size和_capacity
// _size = s._size;
// _capacity = s._capacity;
// //释放原来对象的_str指向的空间
// delete[] _str;
// //_str指向新空间
// _str = tmp;
// }
// }
// return *this;
//}
void Swap(string& s)
{
swap(_str, s._str);
swap(_size, s._size);
swap(_capacity, s._capacity);
}
//拷贝构造(现代写法)
//现代写法有一个很重要的细节就是新对象的_str必须要初始化为nullptr,
//因为后面的Swap函数会交换两个对象的成员函数,如果_str不初始化为nullptr,
//也就是随机值,那么交换后_str给了tmp的_str,因为tmp是临时对象,出来作用域
//会被析构的,即会对_str进行析构,而_str指向的是随机空间,所以会非法析构,造成
//程序崩溃,_size和_capacity是栈上的数据不初始化也没有关系,_str是动态申请的
//一定要置为nullptr
string(const string& s)
:_size(0)
,_capacity(0)
,_str(nullptr)
{
//用s对象的c_str()调用构造函数,得到的tmp就是我们想要的
//对象,再交换tmp和*this对象的成员变量交换即可
string tmp(s._str);
Swap(tmp);
}
//赋值(现代写法)
//现代写法是直接利用参数传参时会调用拷贝构造函数构造一个临时的对象
//刚好这个对象的值就是新的对象想要的,所以直接令这个临时对象和新对象
//交换成员变量就可以了,并且因为s是临时对象,所以赋值函数结束时会
//调用析构函数释放内存,即把旧对象所指向的内容释放掉了,无需手动释放
string& operator=(string s)
{
Swap(s);
return *this;
}
//c_str()函数的返回值就是string对象中C类型的字符串指针
const char* c_str() const
{
return _str;
}
const size_t size() const
{
return _size;
}
const size_t capacity() const
{
return _capacity;
}
//C语言中[]能直接访问字符串中的某个位置的元素,并且能做修改,所以
//返回的该字符的引用
char& operator[](size_t pos)
{
assert(pos < _size);
return _str[pos];
}
//const版本的operator[]是只读的
const char& operator[](size_t pos) const
{
assert(pos < _size);
return _str[pos];
}
void reserve(size_t n)
{
//如果n>_capacity,需要扩容;如果小于,一般都不会缩容的
if (n > _capacity)
{
//扩容需要开辟一块目标大小的空间,然后把原来的数据拷贝到新空间里
char* tmp = new char[n + 1];
if (tmp)
{
//把原来数据拷贝到新空间中
memcpy(tmp, _str,_size+1);
//释放旧的空间
delete[] _str;
//把新的空间交给_str指针管理
_str = tmp;
//更新_capacity为新的空间大小
_capacity = n;
}
}
}
void resize(size_t n, char ch = '\0')
{
//场景一
if (n <= _size)
{
_str[n] = '\0';
_size = n;
}
else
{
//如果需要扩容,则是场景三;否则,则是场景二
//场景三只不过是在场景二的前面多加了一步扩容的工作
if (n > _capacity)
{
//扩容
reserve(n);
}
//从_size开始放ch,直到n-1位置
for (int i = _size; i < n; i++)
{
_str[i] = ch;
}
//更新_size为新的大小
_size = n;
//最后一定要放上一个'\0'作为字符串的结束标志
_str[_size] = '\0';
}
}
void push_back(const char ch)
{
//先检查容量是否足够,不够则扩容
if (_size == _capacity)
{
//一般是两倍扩容
reserve(_capacity == 0 ? 4 : 2 * _capacity);
}
//因为_size是最后一个有效字符的下一个位置,所以尾插应该插入到_str[_size]位置
_str[_size] = ch;
_size++;
//后面记得要跟上'\0',不要漏了
_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
_size += len;
}
string& operator+=(char ch)
{
push_back(ch);
return *this;
}
string& operator+=(const char* str)
{
append(str);
return *this;
}
//size_t find(const char ch) const
//{
// for (int i = 0; i < _size; i++)
// {
// if (_str[i] == ch)
// {
// return i;
// }
// }
// return npos;
//}
//size_t find(const char* str, size_t pos = 0) const
//{
// const char* tmp = strstr(_str + pos, str);
// if (tmp)
// {
// return tmp - _str;
// }
// return npos;
//}
void insert(size_t pos,size_t n,const char ch)
{
//判断pos的合法性,pos==_size的时候是尾插
assert(pos <= _size);
//如果原来字符串的长度+插入字符数目后超出容量,那么就扩容
if (_size + n > _capacity)
{
reserve(_size + n);
}
//把从pos位置开始往后的所有数据都往后移动n位,腾出n个空位置
//以便插入n个ch字符
size_t end = _size;
//要注意防止pos=0时死循环问题,end永远>=0,因为end和pos的类型是size_t,
//当pos等于0时,end>=pos循环继续,也就是说end<0时才会停止,但是end不可能<0,
//因为当end减为-1的时候,类型是size_t,所以会被看作是无符号数,-1对应的无符号数
//是整形的最大值,会导致死循环,所以需要外加一个条件判断end是否减为npos了,如果是
//则循环停止,否则继续
while (end >= pos && end != npos)
{
//挪动数据
_str[end + n] = _str[end];
end--;
}
//从pos位置开始把n个ch字符插入进去
for (int i = 0; i < n; i++)
{
_str[i + pos] = ch;
}
//更新_size
_size += n;
}
void insert(size_t pos, const char* str)
{
//判断pos位置的合法性
assert(pos <= _size);
int len = strlen(str);
//判断剩余空间大小是否足够插入字符串str,不够则扩容
if (_size + len > _capacity)
{
reserve(_size + len);
}
//要注意防止pos=0时死循环问题,end永远>=0,因为end和pos的类型是都size_t,
//当pos等于0时,end>=pos循环继续,也就是说end<0时才会停止,但是end不可能<0,
//因为当end减为-1的时候,类型是size_t,所以会被看作是无符号数,-1对应的无符号数
//是整形的最大值,会导致死循环,所以需要外加一个条件判断end是否减为npos了,如果是
//则循环停止,否则继续
size_t end = _size;
while (end >= pos && end != npos)
{
//挪动数据,腾出len个位置的空间,插入字符串str
_str[end + len] = _str[end];
end--;
}
//把字符串str拷贝到从_str+pos位置开始的这段位置上
memcpy(_str + pos, str, sizeof(char) * len);
//更新_size的值
_size += len;
}
void erase(size_t pos, size_t n = npos)
{
//pos==_size不是有效字符,不能删除
assert(pos < _size);
//如果n无参或者n的值大于从pos位置到结尾的长度,那就把从pos位置开始的
//所有字符都删掉,即_str[pos]='\0'即可,_size是最后一个有效元素下标+1,
//所以新的长度就是pos
if (n >= _size - pos || n==npos )
{
_str[pos] = '\0';
_size = pos;
}
else
{
//从后往前挪动数据,从pos位置开始往后覆盖即可
size_t end = pos + n;
while (end <= _size)
{
_str[pos++] = _str[end++];
}
//更新_size
_size -= n;
}
}
//从pos位置开始查找某个字符
size_t find(char ch, size_t pos = 0)
{
//先判断pos位置是否合法
assert(pos < _size);
//遍历_str字符串比对就行了,找到了就返回下标
for (int i = pos; i < _size; i++)
{
if (_str[i] == ch)
{
return i;
}
}
//找不到返回npos,npos是整形的最大值
return npos;
}
//从pos位置开始查找字符串
size_t find(const char* str, size_t pos = 0) const
{
assert(pos < _size);
//判断str是否为_str的子串
char* ptr = strstr(_str + pos, str);
if (ptr)
{
//如果str是_str的子串,因为strstr找到后返回的是子串的第一个
//字符在源串的地址,所以减去源串的起始地址就是对应字符串在源串中的下标
return ptr - _str;
}
else
{
//找不到返回一个整形的最大值,表示找不到
return npos;
}
}
string substr(size_t pos = 0, size_t len = npos)
{
assert(pos < _size);
size_t n = len;
//如果n==npos即没传len参数时,默认取pos位置后面的全部字符,
//如果n大于从pos位置往后的所有字符的长度,也是取pos后面的全部字符
if (n == npos || pos + n > _size)
{
//_size-pos就是从pos位置开始后面全部字符的长度
n = _size - pos;
}
//创建一个临时string对象
string tmp;
//开n个空间即可,因为有n个有效字符
tmp.reserve(n);
//取出从pos位置开始,后面的全部字符尾插到tmp中即可
for (int i = pos; i < pos + n; i++)
{
tmp += _str[i];
}
return tmp;
}
void clear()
{
//只需要在_str[0]位置放一个'\0'即可完成clear工作
_str[0] = '\0';
//清空之后需要把_size置为0
_size = 0;
}
bool operator<(const string& s) const
{
int i1 = 0;
int i2 = 0;
//本质上是比较两个字符串的大小
while (i1 < _size && i2 < s._size)
{
//遇到小于,返回true
if (_str[i1] < s[i2])
{
return true;
}
//遇到大于,返回false
else if (_str[i1] > s[i2])
{
return false;
}
//遇到等于,继续往后比较
else
{
i1++;
i2++;
}
}
if (i1 == _size && i2 < s._size)
{
return true;
}
else
{
return false;
}
}
bool operator==(const string& s) const
{
//本质上是比较两个字符串是否相等
int i1 = 0;
int i2 = 0;
while (i1 < _size && i2 < s._size)
{
if (_str[i1] != s[i2])
{
return false;
}
else
{
i1++;
i2++;
}
}
if (i1 == _size && i2 == s._size)
{
return true;
}
else
{
return false;
}
}
bool operator<=(const string& s) const
{
return (*this) < s || (*this) == s;
}
bool operator>(const string& s) const
{
return !(*this <= s);
}
bool operator>=(const string& s) const
{
return !(*this < s);
}
bool operator!=(const string& s) const
{
return !(*this == s);
}
//析构函数
//只需把动态申请的内存释放了即可
~string()
{
//先判断是否为nullptr吧,是nullptr就没必要释放了
if (_str)
{
//释放空间
delete[] _str;
//置空
_str = nullptr;
_size = _capacity = 0;
}
}
private:
size_t _size;
size_t _capacity;
char* _str;
public:
static const size_t npos;
};
const size_t string::npos = -1;
ostream& operator<<(ostream& out, const string& s)
{
//for (int i = 0; i < s.size(); i++)
//{
// out << s[i];
//}
//把s对象的_str字符串的内容逐一打印出来即可
//这里不建议直接打印c_str(),因为c_str()是按照C语言
//的规则,即'\0'为结束标志的,但是string类的是按照_size
//来确定对象的字符数目的,所以在"abc\0def"这样特殊的string类,
//打印时如果直接打印c_str(),只能打印出abc,而根据string类的要求
//应该以_size为大小打印,所以会打印出abcdef,所以这里还是要区分一下,
//不用c_str()的形式打印
for (const auto& e : s)
{
out << e;
}
//流插入需要返回一个ostream对象,作为连续流插入的前提,可以是引用,因为out出了这个函数不销毁
return out;
}
istream& operator>>(istream& in, string& s)
{
//调用流提取函数时需要先清空对象本身的内容
s.clear();
//为了减少频繁地扩容,可以创建一个缓冲区(字符数组)先存数据,待缓冲区满了之后
//再把缓冲区(字符数组)的内容尾插到s对象中
char buff[128] = { 0 };
//读取第一个字符
char ch = in.get();
//先去掉前导空格或者换行
while (ch == ' ' || ch == '\n')
{
ch = in.get();
}
//按照流提取,遇到空格或者换行就停止读取,否则继续
int i = 0;
while (ch != ' ' && ch != '\n')
{
//读到的字符存放在缓冲区buff中
buff[i++] = ch;
//当缓冲区填满后,尾插到s中
if (i == 127)
{
//因为字符串需要以'\0'结尾,所以先在最后一个位置放'\0'再尾插
buff[i] = '\0';
s += buff;
//重新回到缓冲区的0下标开始放字符
i = 0;
}
//从stdin中一个字符一个字符地读取上来
ch = in.get();
}
//如果i!=0,证明缓冲区里还有字符,需要添加'\0'成为字符串再尾插到s对象中
if (i != 0)
{
buff[i] = '\0';
s += buff;
}
//流提取需要返回istream,作为连续流提取的前提,可以返回引用,因为in对象
// 出了这个函数后不销毁
return in;
}
void test1(void)
{
string s1("hello world");
cin >> s1;
//s1 += ' ';
//s1 += 'k';
//s1 += 'o';
//s1 += "be";
cout << s1 << endl;
s1.insert(3,"######");
s1.erase(3, 30);
//s1.push_back('k');
//s1.push_back('o');
//s1.append("be");
string s2(s1);
s1.reserve(20);
s1.resize(13, '#');
string s3;
s3 = s2;
}
void test2(void)
{
//string s1("bb");
//string s2("aaa");
//cout << (s1 < s2) << endl;
string s1("hello");
string s2("hello");
cout << (s1 < s2) << endl;
cout << (s1 > s2) << endl;
cout << (s1 == s2) << endl << endl;
string s3("hello");
string s4("helloxxx");
cout << (s3 < s4) << endl;
cout << (s3 > s4) << endl;
cout << (s3 == s4) << endl << endl;
string s5("helloxxx");
string s6("hello");
cout << (s5 < s6) << endl;
cout << (s5 > s6) << endl;
cout << (s5 == s6) << endl << endl;
}
}
以上就是string类的模拟实现啦!内容属实有点多哦,你学会了吗?希望大家下去也能自己好好地写一下代码,实现以下这个string类,如果您能自己实现出来,那么您对string类的理解一定会达到一个新的高度的。好啦,今天的分享就到这里,如果这篇文章对你有所帮助的话,那么点点小心心,点点关注呗,您的小心心和关注就是对我莫大的支持和鼓励哦,后期还会持续更新C++相关知识哦,我们下期见啦!!!!!!!!!!!!!文章来源地址https://www.toymoban.com/news/detail-548857.html
到了这里,关于C++模拟实现string类的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!