string 是什么
string通俗来说就是一个支持增删查改功能的字符串
特点
动态大小:string对象的大小可以在运行时动态改变,可以添加或删除字符而不用担心固定大小的限制。
方便的操作:提供了大量成员函数来执行常见的字符串操作,如查找、替换、拼接等。
安全性:string类处理了内存管理,减少了缓冲区溢出和内存泄漏的风险。
兼容性:与C风格的字符串(以空字符’\0’结尾的字符数组)兼容。
下面我们来说说模拟实现,这里我们模拟实现string统称mystring
首先看看构造函数
构造函数
//默认构造
mystring(const char* str = "")
{
_size = strlen(str);
_capacity = _size;
_str = new char[_capacity + 1];//给\0开空间
strcpy(_str, str);
}
这里我们的参数给了一个缺省参数,缺省值是一个空字符串,size代表string中存放数据的个数(不包括\0),capacity代表string总共空间大小,最后在调用库函数的strcpy(字符串拷贝函数)把输入的字符串拷贝到我们的string里面的_str里去。
拷贝构造函数
//拷贝构造
mystring(const mystring& str)
{
this->_size = str._size;
this->_capacity = str._capacity;
this->_str = new char[str._capacity + 1];
strcpy(_str,str._str);
}
这里我们必须要写拷贝构造函数,因为我们申请了空间,不能调用编译器默认的拷贝构造函数,我们需要对拷贝创建的对象新开一个内存空间,这样两个mystring的对象的内存地址才不会犯冲突,最后在调用字符串拷贝函数拷贝过来就可以了
析构函数
//析构函数
~mystring()
{
delete[] _str;
_str = nullptr;
_size = 0;
_capacity = 0;
}
这里直接调用delete函数,把mystring类里面的_str指向的内存空间给释放掉,在把这个指针指向空,最后把_size,_capacity设置成0就行了
迭代器的实现
迭代器的本质上就是一个指针,我们这里可以直接用原生指针,因为string和vector一样,他们的内存地址都是连续的,不需要像list那样自己手动实现一个迭代器(通过返回当前节点指向的下一个节点实现++功能等),
我们需要typedef一下我们的迭代器
typedef char* iterator;
typedef const char* const_iterator;
为什么typedef const char* const_iterator;不能直接写成const iterator,这是因为,const iterator,这个const是修饰iterator本身,假设这个iterator是个指针,const就修饰的是这个iterator指针变量的本身,而不是这个指针所指向的对象,所以要重新定义一下
//迭代器的实现
iterator begin()
{
return _str;
}
iterator end()
{
return _str+_size;
}
const_iterator begin() const
{
return _str;
}
const_iterator end() const
{
return _str + _size;
}
begin()返回头指针,end()通过对头指针的向后计算,计算出相应的尾指针。还有const修饰版本
申请空间
void reserve(size_t n)//申请空间
{
char* temp = nullptr;
if (n > _capacity)
{
temp = new char[n + 1];//为\0申请多一个
}
strcpy(temp, _str);
delete[] _str;
_str = temp;
_capacity = n;
}
这里我们首先要创建一个临时变量temp用来存放我们新开的空间,然后在把原来string里面的数据拷贝到新的空间里面去,最后在删除原来的空间,把temp给给_str,更新一下总共大小(capacity)
尾插
void push_back(char ch)
{
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : 2 * _capacity);
}
_str[_size] = ch;
_size++;
_str[_size] = '\0';
}
这里首先要判断空间是否足够我们插入,如果不够就按照2倍扩容,扩完之后直接在后面插入即可(_size指向存放元素的下一个空间,因为是从0地址开始存放的5个数据占用0-4个空间)插入完之后更新size在放置\0即可
插入(单字符,字符串)
插入单字符
void insert(size_t pos, char ch)
{
assert(pos<=_size);
if (_size == _capacity)
{
reserve(_capacity == 0?4:2*_capacity);
}
size_t end = _size + 1;
while (end > pos)
{
_str[end] = _str[end-1];
end--;
}
_str[pos] = ch;
_size++;
}
这个代表在指定pos位置插入单个字符,我们需要一个end来把整个字符串向后面移动一位,把pos位置给腾出来,在往里面插入,最后更新一下size即可
插入字符串
//插入字符串
void insert(size_t pos, const char* str)
{
assert(pos <=_size);
size_t len = strlen(str);
if (_size + len >= _capacity)
{
reserve(_size +len>2*_capacity ? _size + len : 2 * _capacity);
}
size_t end = _size + len;
while (end > pos+len-1)
{
_str[end] = _str[end - len];
end--;
}
for (int i = 0; i < len; i++)
{
_str[pos+i] = str[i];
}
_size = _size + len;
}
插入字符串相较于插入单字符要难一些,同样这里考虑扩容的方式还是2倍的方式,只不过先考虑_size+len大不大于 2倍当前内存空间,这里要分两步,第一步是把pos到len的空间给移动出来,第二步才是从pos位置开始插入
删除
//删除
void erase(size_t pos, size_t len)
{
assert(pos < _size);
if (len > _size - len)
{
_str[pos] = '\0';
_size = pos;
}
for (size_t i = pos + len; i <= _size; i++)
{
_str[i - len] = _str[i];
}
_size -= len;
}
这里代表从pos位置开始向后面删除len个长度的字符串,这个if条件代表如果想从pos位置后面删除的数据长度超过了总共数据长度的话,就全部删除,这个就很简单,直接把pos位置置为\0(字符串结束标志)就行了
如果不是就要像上面那样,循环去删除,这里i等于size是为了把字符串结尾的\0也一同搬过去,最后就在更新一下size就行了
查找(字符,字符串)
查找字符
//查找
size_t find(char ch,size_t pos)
{
assert(pos < _size);
for (size_t i = 0; i < _size; i++)
{
if (i == pos)
{
if (_str[i] == ch)
{
return i;
break;
}
}
}
return npos;
}
这个就简单,直接循环查找,有的话就返回当前字符的下标,如果没有就返回nops
查找字符串
//查找字符串
size_t find(const char* str, size_t pos)
{
assert(pos < _size);
char* ptr = strstr(_str + pos, str);
if (ptr == nullptr)
{
return npos;
}
else
{
return ptr - _str;
}
}
这里我们用了一个比较两个字符串的库函数,如果比较成功就返回比较的字符串的起始地址,如果比较失败就直接返回空指针返回npos就可以了,如果比较成功了,就返回当前匹配成功字符串的首地址
添补append
//添补
void append(const char* str)
{
size_t len = strlen(str);
if (_size + len >= _capacity)
{
reserve(_size + len > 2 * _capacity ? _size + len : 2 * _capacity);
}
strcpy(_str + _size, str);
_size = _size + len;
}
这个函数和push_back很像,这个是在字符串尾部插入一个字符串,首先检查空间,不够的话扩容,之后在调用字符串拷贝函数,把要添加的字符串添加到string中,最后更新一下大小
字符串+=
mystring& operator+=(char ch)
{
push_back(ch);
return *this;
}
mystring& operator+=(const char* str)
{
append(str);
return *this;
}
一个是+=一个字符,就直接调用push_back一个是+=一串字符串,就调用append函数,最后返回this指针
substr
mystring substr(size_t pos, size_t len)
{
assert(pos < _size);
mystring sub;
if (len > _size - pos)
{
len = _size - pos;
}
for (size_t i = 0; i < len; i++)
{
sub.operator+=(_str[pos+i]);
}
return sub;
}
这个代表将原string中的从pos位置向后len个长度的字符串拷贝到一个新的string对象中去
同样,我们首先需要判断他的长度大不大于_size-pos大于就全部拷贝过去,最后一个for循环来进行拷贝调用运算符重载+=函数
赋值重载
void swap(mystring& inputstr)
{
std::swap(_str, inputstr._str);
std::swap(_size, inputstr._size);
std::swap(_capacity, inputstr._capacity);
}
//赋值重载
mystring& operator=(mystring inputstr)
{
swap(inputstr);
return *this;
}
这里我们处理赋值,很巧妙用了一个swap函数,来交换两个string对象指向的空间,就假设把s2赋值给s1就交换他们的空间大小,指针指向的字符串。因为这里是值传递,假设把s2赋值给s1,当函数结束后s2在swap函数里面就会销毁,s1变成s2
c_str
const char* c_str() const
{
return _str;
}
返回一个字符串,像c语言一样
[ ]下标访问
char& operator[](size_t pos)
{
assert(pos < _size);
return _str[pos];
}
//const版本
const char& operator[](size_t pos) const
{
assert(pos < _size);
return _str[pos];
}
运算符重载
bool operator<(const string& s1, const string& s2)
{
return strcmp(s1.c_str(), s2.c_str()) < 0;
}
bool operator>(const string& s1, const string& s2)
{
return !(strcmp(s1.c_str(), s2.c_str()) < 0);
}
bool operator==(const string& s1, const string& s2)
{
return strcmp(s1.c_str(), s2.c_str()) == 0;
}
bool operator>=(const string& s1, const string& s2)
{
return operator==(s1, s2) || operator>(s1, s2);
}
bool operator<=(const string& s1, const string& s2)
{
return operator==(s1, s2) || operator<(s1, s2);
}
bool operator!=(const string& s1, const string& s2)
{
return !operator==(s1, s2);
}
这里主要调用库函数的比较
流插入
ostream& operator<<(ostream& out, const mystring& str)
{
for (auto ch:str)
{
out << ch;
}
return out;
}
把str里面的内容插入到out里
流提取
istream& operator>>(istream& in,mystring& str)
{
str.clear();
const int N = 256;
char buffer[N] = { 0 };
size_t i = 0;
char ch = 0;
ch = in.get();
while (ch != ' ' && ch != '\n')
{
buffer[i++] = ch;
if (i == N - 1)
{
buffer[i] = '\0';
str += buffer;
i = 0;
}
ch = in.get();
}
if (i > 0)
{
buffer[i] = '\0';
str += buffer;
}
return in;
}
这里主要是把io流里面输入的字符串插入到str中去,这里我们用了一个buffer缓存来临时存放要插入的数据,这样为了减少内存频繁插入,节省时间的消耗不会一个字符一个字符的提取。
源码
#define _CRT_SECURE_NO_WARNINGS 1
#pragma once
# include<iostream>
# include<string>
# include<assert.h>
using namespace std;
namespace thr
{
class mystring
{
public:
typedef char* iterator;
typedef const char* const_iterator;
//默认构造
mystring(const char* str = "")
{
_size = strlen(str);
_capacity = _size;
_str = new char[_capacity + 1];//给\0开空间
strcpy(_str, str);
}
//拷贝构造
mystring(const mystring& str)
{
this->_size = str._size;
this->_capacity = str._capacity;
this->_str = new char[str._capacity + 1];
strcpy(_str,str._str);
}
//析构函数
~mystring()
{
delete[] _str;
_str = nullptr;
_size = 0;
_capacity = 0;
}
iterator begin()
{
return _str;
}
iterator end()
{
return _str+_size;
}
const_iterator begin() const
{
return _str;
}
const_iterator end() const
{
return _str + _size;
}
size_t size()
{
return _size;
}
size_t capacity()
{
return _capacity;
}
const char* c_str() const
{
return _str;
}
void clear()
{
_str[0] = '\0';
_size = 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];
}
//尾插
void push_back(char ch)
{
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : 2 * _capacity);
}
_str[_size] = ch;
_size++;
_str[_size] = '\0';
}
void reserve(size_t n)//申请空间
{
char* temp = nullptr;
if (n > _capacity)
{
temp = new char[n + 1];
}
strcpy(temp, _str);
delete[] _str;
_str = temp;
_capacity = n;
}
void swap(mystring& inputstr)
{
std::swap(_str, inputstr._str);
std::swap(_size, inputstr._size);
std::swap(_capacity, inputstr._capacity);
}
//赋值重载
mystring& operator=(mystring inputstr)
{
swap(inputstr);
return *this;
}
//插入单个字符
void insert(size_t pos, char ch)
{
assert(pos<=_size);
if (_size == _capacity)
{
reserve(_capacity == 0?4:2*_capacity);
}
size_t end = _size + 1;
while (end > pos)
{
_str[end] = _str[end-1];
end--;
}
_str[pos] = ch;
_size++;
}
//打印
void print()
{
cout <<"size:" << _size << endl;
cout <<"capacity:" << _capacity << endl;
cout <<"_str:"<< _str << endl;
cout << endl;
}
//插入字符串
void insert(size_t pos, const char* str)
{
assert(pos <=_size);
size_t len = strlen(str);
if (_size + len >= _capacity)
{
reserve(_size +len>2*_capacity ? _size + len : 2 * _capacity);
}
size_t end = _size + len;
while (end > pos+len-1)
{
_str[end] = _str[end - len];
end--;
}
for (int i = 0; i < len; i++)
{
_str[pos+i] = str[i];
}
_size = _size + len;
}
//删除
void erase(size_t pos, size_t len)
{
assert(pos < _size);
if (len > _size - len)
{
_str[pos] = '\0';
_size = pos;
}
for (size_t i = pos + len; i <= _size; i++)
{
_str[i - len] = _str[i];
}
_size -= len;
}
//查找
size_t find(char ch,size_t pos)
{
assert(pos < _size);
for (size_t i = 0; i < _size; i++)
{
if (i == pos)
{
if (_str[i] == ch)
{
return i;
break;
}
}
}
return npos;
}
//查找字符串
size_t find(const char* str, size_t pos)
{
assert(pos < _size);
char* ptr = strstr(_str + pos, str);
if (ptr == nullptr)
{
return npos;
}
else
{
return ptr - _str;
}
}
//添补
void append(const char* str)
{
size_t len = strlen(str);
if (_size + len >= _capacity)
{
reserve(_size + len > 2 * _capacity ? _size + len : 2 * _capacity);
}
strcpy(_str + _size, str);
_size = _size + len;
}
mystring& operator+=(char ch)
{
push_back(ch);
return *this;
}
mystring& operator+=(const char* str)
{
append(str);
return *this;
}
mystring substr(size_t pos, size_t len)
{
assert(pos < _size);
mystring sub;
if (len > _size - pos)
{
len = _size - pos;
}
for (size_t i = 0; i < len; i++)
{
sub.operator+=(_str[pos+i]);
}
return sub;
}
private:
char* _str = nullptr;
size_t _size = 0;
size_t _capacity = 0;
static const size_t npos = -1;
};
bool operator<(const string& s1, const string& s2)
{
return strcmp(s1.c_str(), s2.c_str()) < 0;
}
bool operator>(const string& s1, const string& s2)
{
return !(strcmp(s1.c_str(), s2.c_str()) < 0);
}
bool operator==(const string& s1, const string& s2)
{
return strcmp(s1.c_str(), s2.c_str()) == 0;
}
bool operator>=(const string& s1, const string& s2)
{
return operator==(s1, s2) || operator>(s1, s2);
}
bool operator<=(const string& s1, const string& s2)
{
return operator==(s1, s2) || operator<(s1, s2);
}
bool operator!=(const string& s1, const string& s2)
{
return !operator==(s1, s2);
}
ostream& operator<<(ostream& out, const mystring& str)
{
for (auto ch:str)
{
out << ch;
}
return out;
}
istream& operator>>(istream& in,mystring& str)
{
str.clear();
const int N = 256;
char buffer[N] = { 0 };
size_t i = 0;
char ch = 0;
ch = in.get();
while (ch != ' ' && ch != '\n')
{
buffer[i++] = ch;
if (i == N - 1)
{
buffer[i] = '\0';
str += buffer;
i = 0;
}
ch = in.get();
}
if (i > 0)
{
buffer[i] = '\0';
str += buffer;
}
return in;
}
}