string 的相关介绍:C++:string相关内容的简单介绍-CSDN博客
成员变量:
private:
char* _str
size_t _size
size_t _capacity
构造函数
string类的构造函数不仅需要完成空间的开辟,还需要再开辟的过程中完成字符串的拷贝,它需要将字符串拷贝到空间的内部
以此string类的构造函数需要strlen和new的帮助,使用二者来获取长度和空间,并进行一系列的赋值操作。
string (const char*str = "")
:_size(strlen(str))
{
_capacity = _size;
_str = new char[_capacity + 1];
strcpy(_str,str);
}
以上的代码设立了缺省值为空,表名了这是一个即是无参又是有参的一个构造函数。
设立缺省值为空的好处在于,当传递了一个无参的参数时,该构造函数会因为缺省值传输参数,但是参数是无效的,所以再_size时就会得到长度为0,从而应发空间为0,但是会因为 string s; 这种代码的存在,空间还是需要被开辟的,所以还是需要开辟空间。
拷贝构造函数
使用了深拷贝方式进行拷贝
string(const string& s)
{
_str = new char[s._capacity +1];
strcpy(_str,s._str);
_size = s._size;
_capacity = s._capacity;
}
深浅拷贝相关介绍:C++ : 类的简单介绍(五)————— 拷贝构造函数 & 函数传参 & 运算符重载-CSDN博客
析构函数
~string()
{
delete[]_str;
_str = nullptr;
_size=_capacity =0;
}
成员函数:
遍历相关
size()
size_t size()const
{
return _size;
}
operator[]
根据operator[]再string内部的使用情况operator[]需要分为两种,一种是const使用的一种是非const使用的,而operator的内部情况无疑于指针的遍历。
char& operator[](size_t pos)
{
assert(pos<_size);
return _str[pos];
}
const char& operator[](size_t pos)const
{
assert(pos<_size);
return _str[pos];
}
在使用operator遍历的同时需要加上assert 进行防止越界的操作机制
caopacity
size_t capacity()const
{
return _capacity;
}
迭代器
有一种说法,迭代器本质上就是两个指针,其中begin指向字符串的首个字符,而end指向的是字符串末尾的\0位置
但是,不是所有的迭代器都是指针不过对于某些编译器的迭代器而言,它们的底层其实就是指针。
typedef char* iterator
iterator begin()
{
return _str;
}
iterator end()
{
return _str+size;
}
-------------------------------------
同时迭代器也分const使用 和 非const使用
-------------------------------------
typedef const char* const_iterator
const_iterator begin()const
{
return _str;
}
const_iterator end()const
{
return _str+size;
}
范围for
围for的本质其实也是一个迭代器,只不过使用了auto进行类型的识别,但在底层也是调用了迭代器
而且,如果我们手搓了迭代器,且名字只要是迭代器的名字 iterator、 begin、 end 、const_iterator 范围for就可以使用我们手搓的迭代器
但是这几者中间名字对不上那范围for就对不上,用不了,只是所以范围for就是傻傻的替换并不会灵活的变通
插入操作
reserve
string内部的扩容函数,因为append和push_back需要一个是否需要扩容的机制,而reserve本身就有一个机制就是比当前空间小不扩容,比当前空间大扩容。
void reserve(size_t n)
{
if(n>_capacity)//扩容机制
{
char*tmp = new char[n+1];
//当然因为delete的问题,开辟空间需要额外开辟一个标识符进行使用,所以需要n+1
//这个n+1并不是给\0使用的而是给为了让delete辨认而多开辟的标识符使用的
strcpy(tmp,_str);//把原来空间的内容放到新空间内部
//delete的删除机制,删除原来的空间
delete[]_str;
_str = tmp;//指针和地址的赋予
_capacity = n;//表名空间已经完成
}
}
且需要使用扩容函数 new 开辟新空间,把原来空间的地址内部的数据进行拷贝,并且使用deltet释放原来的空间改变空间指针指向。
push_back
push_back的功能是在字符串的尾部插入一个字符,并且push_back还具有扩容的功能,并且push_back的扩容功能是需要遵循二倍扩容的规则
void push_back(char ch)
{
//进行二倍扩容
if(_size == _capacity)//如果长度和空间大小一样就需要扩容
{
reserve(_capiacty == 0? 4:2*_capacity);//同时需要考虑到长度和空间大小都是0
//但是需要插入字符,所以需要进行空间的扩容
}
_str[_size] = ch;//ch表示插入的字符,_size表示插入的长度也表示需要插入的位置
++_size;
_str[_size] = '\0';//在插入字符后面加上\0表示字符插入完成
}
append
append是在字符串尾部插入字符或者字符串,所以和push_back不同的是,它的扩容机制不能遵循二倍扩容的原则,因为如果字符串的长度过长,使用二倍的空间可能不够用,所以append扩大的空间可能需要遵循字符串的长度来扩充
void append(const char* str)
{
//扩容
size_t len = strlen(str);
if(_size + len > _capacity)//使用插入的长度和字符串长度相加 和空间大小对比
{
reserve(_size + len);//进行空间的扩大
}
strcpy(_str + _size , str);//在扩大空间后的原字符串尾部插入新的字符串
_size + = len;//表名插入了字符串
}
operator + =
operator += 需要区分const 和非 const的对象使用
insert
insert 在指定位置插入字符 或者字符串,而根据insert在string内部底层的方式和效率来看,insert的底层是需要进行数据的挪动,以此达到插入字符串的操作。
同时和string的其他插入函数一样,都具有扩容的功能,以及还有判断是否越界的功能
插入字符功能的insert:
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;
}
}
erase
在指定位置删除指定个数的字符,erase分为两种删除,同时erase的删除需要根据npos进行判断,所以需要加入前置npos ,在类之外加上 const int string::npos = -1
以及在类的成员变量中加入public的变量 static const int pos;
1.全部删除
对于全部删除满足的条件是指定删除的字符个数大于要剩下的长度或者是没有直接说明删除的字符个数,这两种情况相当于在指定位置上的字符直接变成\0
2.区间删除
区间删除,就是把这段区间后面的字符覆盖区间需要删除的空间位置,可以使用strcpy进行拷贝,把需要删除的区间的首个字符地址上传,然后把区间之后的字符串的首个字符上传
完整代码:
resize
resize分为三个情况
一:指定的长度比字符串长度小,删除多余的部分
二:指定的长度比字符串大,但是在空间大小范围内,使用\0进行填补到相对因的指定长度
三:指定的长度比字符串大,且比空间大小大,需要进行扩容,且其余的指定长度位置的范围内的空缺地方用\0填补
情况1:
情况2和情况3:
在进入情况二和情况三之前先使用reserve进行检查,因为reserve有一个检查机制,可以检查我们指定的长度是否大于空间大小
如果比空间大则扩容进入情况三,如果比空间小则不变进入情况2,检查之后就开始情况2和情况3都有的共同操作,也就说填充\0
完整代码: