目录
一.默认成员函数
二.迭代器
三.增
四.容量
五.[]的重载
六.比较运算符的重载
七.删
八.查
九.改
十.其他
十一.输入输出函数
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的简单模拟实现,如有问题可在评论区提出。