string类的接口有很多,这里我来梳理一下自己觉得有意思的几个,并模拟实现一下可以凑合用的my_string,话不多说直接开干:
注意事项
为了和库里的string冲突,所以就将自己实现的my_string放在一个命名空间里
namespace cr
成员变量
private: char* _arr; size_t _size; size_t _capacity; public: const static size_t npos;
和往常一样,static修饰的成员变量要单独定义,毕竟该成员属于整个类
const size_t cr::my_string::npos = -1;
构造函数
my_string(const char* arr = "");
cr::my_string::my_string(const char* arr) :_size(strlen(arr)) ,_capacity(_size) { _arr = new char[_capacity+1];//加一个\0 strcpy(_arr, arr); }
为了避免无惨初始化的情况,这里是要给个缺省值的,空字符串也是有一个元素的('\0'),这里的字符指针_arr是不走初始化列表的,因为我们声明时的顺序会影响着初始化列表执行的顺序,所以最好单独初始化。而string类最容易出错的地方就是末尾的'\0',所以在开辟空间时要多开一个存放'\0'
拷贝构造函数
void cr::my_string::swap(my_string& s) { std::swap(_arr, s._arr); std::swap(_size, s._size); std::swap(_capacity, s._capacity); }
cr::my_string::my_string(const my_string& tmp)//拷贝构造函数 :_arr(nullptr) ,_size(0) ,_capacity(0) { my_string s(tmp._arr);//this并没有初始化,没有处理 swap(s); }
拷贝构造的形参一定不要忘记引用接收,防止陷入死循环(因为在调用拷贝构造是就已经反生了一次传参,自定义类型数据传参又会调用拷贝构造函数)。这里的拷贝构造函数是有点不一样的,这里调用了构造函数
my_string s(tmp._arr);//tmp对象就是需要拷贝的内容
仅仅通过一个构造函数就实现了深拷贝,后续的swap会将从tmp对象拷贝好的对象s和this进行交换,但是this里的数据并没有进行初始化,所以交换就可能出问题,所以在这之前在初始化列表中将this初始化一下。所以这里相对于平常的拷贝构造就可以少些很多代码(实际交给函数执行了)
赋值运算符
cr::my_string& cr::my_string::operator=(my_string tmp) { swap(tmp); return *this; }
这里是不是更加不可思议了呢,实际上就是调用了拷贝构造函数而已,传参的时候,tmp就是通过拷贝构造得到的数据,再swap将得到的数据换给this达到目的,所以就完成赋值操作,结束后而且还自动帮你释放tmp这个临时“中间人”的内存。
operator[]
char& operator[](size_t i); const char& operator[](size_t i) const;
char& cr::my_string::operator[](size_t i) { assert(i < _size); return _arr[i]; } const char& cr::my_string::operator[](size_t i) const { assert(i < _size); return _arr[i]; }
这里库中实现的就是断言,不仅仅是在string中,STL也是断言判断的。而且最好是实现成const和非const两种,一个只可读,一个可读可改,保障了安全性。
迭代器
typedef char* iterator; typedef const char* const_iterator; iterator begin(); iterator end(); const_iterator begin()const; const_iterator end()const;
cr::my_string::iterator cr::my_string::begin() { return _arr; } cr::my_string::iterator cr::my_string::end() { return &_arr[_size];//_arr+_size } cr::my_string::const_iterator cr::my_string::begin()const { return _arr; } cr::my_string::const_iterator cr::my_string::end()const { return &_arr[_size];//_arr+_size }
这里要注意的是end()函数返回的值可不是指向最后一个有效数据,指向的是最后一个有效数据的下一个(即'\0')同样是实现了两种。
reserve与resize
这两个成员函数的功能一定要区分开。
void cr::my_string::reserve(size_t n) { if(n>_capacity)//防止外面调用该函数时出错 { _capacity = n; char* tmp = new char[_capacity + 1]; strcpy(tmp, _arr); delete[]_arr; _arr = tmp; } }
reserve的功能就是开空间,一般的使用场景是在提前已经知道数据空间大小的情况,改变的是_capacity的值。如果原数据的_capacity比你reserve的_capacity还要大时,是不会进行缩容的,所以reserve就是扩容的作用。
void cr::my_string::resize(int n,char p) { if (n > _size) { reserve(n); for (int i = size(); i < n; i++) { _arr[i] = p; } _arr[n] = 0; } else { _arr[n] = 0; } _size = n;//改变_size的值 }
resize的功能也是对数据空间的操作,但是resize不仅仅改变_capacity还改变_size的值。所以resize不仅仅可以扩容还可以缩容,当resize的空间小于原_size时就是缩容,将多的数据都扔掉,那么如果resize的空间大于原_size时,就是扩容,如果没有第二个参数的话,多的空间会给个缺省值‘\0’,否则就给传过来的值。
就说这么多了,其他的几个常见的成员函数的源码就放在下面了:
源码
void cr::my_string::push_back(char p)
{
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
_arr[_size] = p;
_size++;
_arr[_size] = 0;
}
void cr::my_string::append(const char* p)
{
int len = strlen(p);
reserve(len + _capacity);
strcat(_arr, p);//该函数会将\0拷贝进去
_size += len;
}
void cr::my_string::operator+=(const char* p)
{
append(p);
}
void cr::my_string::operator+=(char p)
{
push_back(p);
}
void cr::my_string::operator+=(cr::my_string& s)
{
*this += s._arr;//s自定义类型,执行结束会调用析构函数
}
void cr::my_string::insert(size_t pos, char p)
{
assert(pos <= _size);
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
size_t end = _size + 1;
while (end > pos)
{
_arr[end] = _arr[end-1];
end--;
}
_arr[pos] = p;
_size++;
}
void cr::my_string::insert(size_t pos,const char* p)
{
assert(pos <= _size);
int len = strlen(p);
if (_size+len>_capacity)
{
reserve(_capacity+len);
}
size_t end = _size + len;
while (end >= pos + len)
{
_arr[end] = _arr[end - len];
end--;
}
for (int i = 0; i < len; i++)
{
_arr[pos++] = p[i];
}
_size += len;
}
void cr::my_string::erase(size_t pos, size_t len)
{
if (len == npos || len + pos >= size())
{
_arr[pos] = 0;
_size = pos;
return;
}
for (int i = pos + len; i <= size(); i++)//i最后指向\0
{
_arr[pos++] = _arr[i];
}
_size -= len;
}
bool cr::my_string::operator<(const my_string& s)const
{
return strcmp(this->_arr, s._arr) < 0;
}
bool cr::my_string::operator>(const my_string& s)const
{
return strcmp(this->_arr, s._arr) > 0;
}
bool cr::my_string::operator==(const my_string& s)const
{
return !(*this < s) && !(*this > s);
}
bool cr::my_string::operator>=(const my_string& s)const
{
return *this > s || *this == s;
}
bool cr::my_string::operator<=(const my_string& s)const
{
return *this < s || *this == s;
}
bool cr::my_string::operator!=(const my_string& s)const
{
return !(*this == s);
}
ostream& cr::operator<<(ostream& out, const my_string& s)
{
//for(int i=0;i<s.size();i++)
// out << s[i];
for (auto ch : s)
{
out << ch;
}
return out;
}
void cr::my_string::clear()
{
_arr[0] = 0;
_size = 0;
}
istream& cr::operator>>(istream& in, my_string& s)
{
//in>>会以空格和换行进行分割,不会读入
s.clear();
char tmp[100]; int i = 0;
char ch;
ch = in.get();//会读取每一个字符
while (ch != ' ' && ch != '\n')
{
if(i<99)
{
tmp[i++] = ch;
}
else
{
tmp[i++] = 0;
s += tmp;//可以存起来再插入,少扩几次
i = 0;
}
ch = in.get();
}
tmp[i++] = 0;
s += tmp;
return in;
}
size_t cr::my_string::find( char p,int pos)
{
assert(pos < _size);
for (int i = pos; i < _size; i++)
{
if (_arr[i] == p)
return i;
}
return npos;
}
size_t cr::my_string::find(const char* p,int pos)
{
assert(pos < _size);
char* q = strstr(_arr + pos, p);
if (q)
{
return q - _arr;
}
return npos;
}
cr::my_string cr::my_string::substr(size_t pos, size_t len)
{
my_string s;
int n;
if (len == npos || len + pos >= _size)
n = _size;
else
n = len + pos;
for (int i = pos; i < n; i++)
{
s += _arr[i];
}
return s;
}
cr::my_string::~my_string()
{
delete[]_arr;
_arr = nullptr;
_size = _capacity = 0;
}