本篇文章主讲String类型常用函数的使用
一、容量(capacity)
熟悉:size length resize capacity reserve clear empty
重点: resize和reserve
而在此之前要先理解string类型结构的底层
在32位下string类型下对象z的大小
理解:
_capacity _size _prt 的成员变量很好理解分别对应 容量大小 字符个数 一个指针指向存储的字符
而char _BUF[16] 则是vs编译器设计出来的目的是当创建的string类型对象的字符串长度小于16便不需要再去申请堆上的空间,直接使用内部固定的字符串数组buf来存好,效率提高即空间换时间的方式
当然这是VS编译器下的方式并不是所有的编译器都是采取如此的方式,可以理解为string类型只是规定了使用规则,具体的底层每个编译器可以选择适用的方法(如LINUX下的gcc编译器在64位下只有一个指针即大小为8)
熟悉size length capacity的使用
1 size和length 输出相同(尽量使用size) 因此都 既可以表示 字符串的长度、字符串有效字符的个数、下次填写字符串的下标
2 capacity 即为容量大小 且需要额外留出\0的空间 因此_BUF[16]下capacity并不是16而是15
熟悉 resize reserve使用(重点)
功能:reserve和resize都是一次性开辟你所需的空间(减少开辟空间的次数提高效率)
两个的不同处:
1 reserve不会对开辟的空间初始化,而resize则是会初始化默认为\0 且可以指定初始化的内容
2 reserve不会改变size的值,而resize则是会根据开辟的空间使size等于开辟空间的值(这也对应了初始化的结果,方便之后若需要在字符串后添加数据)
因此结合上述reserve和resize的特性模拟实现的代码即为:
//1 缩容(此缩容只改变size大小并不会改变capacity大小)
//2 扩容(既更改size大小又更改capacity大小)
void resize(size_t n, char ch = '\0')
{
if (n < _size)
{
// 删除数据--保留前n个
_size = n;
_str[_size] = '\0';
}
else if (n > _size)
{
if (n > _capacity)
{
reserve(n);
}
size_t i = _size;
while (i < n)
{
_str[i] = ch;
++i;
}
_size = n;
_str[_size] = '\0';
}
}
//reserve不会缩容也不会更改字符的值
void reserve(size_t n)
{
if (n > _capacity)
{
char* tmp = new char[n + 1];
strcpy(tmp, _str);
delete[] _str;
_str = tmp;
_capacity = n;
}
}
理解:编译器轻易不会为了节省空间而真的去缩容即改变capacity的值,因为并没有函数能实现部分释放空间,只能先释放全部再申请空间效率低
熟悉:clear empty
clear清空字符串,empty判断字符串是否为空(空true非空false)
二、修改(Modifiers)
熟悉:operator+= 、append、push_back、insert、erase、swap
重点: operator+=
熟悉:append、push_back、operator+=
append和push_back功能:尾插数据,一个是字符串一个是字符
而append功能设计的很冗余主要就是插入字符串
上述为不太用到
基本用法
但由于运算符重载operator+=的存在,在日常使用时候append和push_back都不会去使用,但是要去知道实际上operator+=的底层就是调用了append和push_back。
append、push_back、operator+= 模拟实现
void push_back(char ch)
{
if (_size + 1 > _capacity)
{
reserve(_capacity * 2);
}
_str[_size] = ch;
++_size;
_str[_size] = '\0';
//insert(_size, ch);
}
void append(const char* str)
{
size_t len = strlen(str);
if (_size + len > _capacity)
{
reserve(_size + len);
}
strcpy(_str + _size, str);
//strcat(_str, str);
_size += len;
//insert(_size, str);
}
string& operator+=(char ch)
{
push_back(ch);
return *this;
}
string& operator+=(const char* str)
{
append(str);
return *this;
}
熟悉: insert、erase
功能:pos位置的插入和删除(效率较低,最差的头插头删效率为O(n))
insert的设计也很多,这里只熟悉插入字符串
insert、erase的模拟实现
string& insert(size_t pos, const char* str)
{
assert(pos <= _size);
size_t len = strlen(str);
if (_size + len > _capacity)
{
reserve(_size + len);
}
// 挪动数据
size_t end = _size + len;
while (end > pos + len - 1)
{
_str[end] = _str[end - len];
--end;
}
// 拷贝插入
strncpy(_str + pos, str, len);
_size += len;
return *this;
}
string& erase(size_t pos, size_t len = npos)
{
assert(pos < _size);
if (len == npos || pos + len >= _size)
{
_str[pos] = '\0';
_size = pos;
}
else
{
strcpy(_str + pos, _str + pos + len);
_size -= len;
}
return *this;
}
熟悉:swap
功能:即交换两个string类型对象
通过下面swap的模拟实现代码理解string类型swap效率高
void swap(string& s)
{
std::swap(_str, s._str);
std::swap(_capacity, s._capacity);
std::swap(_size, s._size);
}
理解std命名空间的swap和string的swap的区别
问:std已经有swap为什么string类型还设计了自己的swap
理解:
若string类型没有设计swap那么直接调用std的swap如上述 1先拷贝构造一个对象 2再会调用两次赋值运算符的重载函数;
而string类型设计的swap都只是对内置类型的成员变量简单进行交换(_size,_capacity,对于_str理解为只是改变了指针的执行)因此效率更高。
三、成员函数(Member functions)
构造函数constructor
string构造函数设计冗余
主要使用下面
构造函数的模拟实现
string(const char* str = "")
:_size(strlen(str))
{
_capacity = _size == 0 ? 3 : _size;
_str = new char[_capacity + 1];
strcpy(_str, str);
}
// s3(s2)
string(const string& s)
:_size(s._size)
, _capacity(s._capacity)
{
_str = new char[s._capacity + 1];
strcpy(_str, s._str);
}
理解我这里并没有设计char BUF[16]所以成员变量如下
private:
char* _str;
size_t _capacity;
size_t _size;
赋值运算符重载
使用
string& operator= (const char* s)的模拟实现
string& operator=(const string& s)
{
if (this != &s)
{
char* tmp = new char[s._capacity + 1];
strcpy(tmp, s._str);
delete[] _str;
_str = tmp;
_size = s._size;
_capacity = s._capacity;
}
return *this;
}
四、遍历string对象
第一种:调用迭代器
我现在对string类型的迭代器简单的就理解为类似指针的模型,但是vs的底层确实不同,只是当作理解,因此对于迭代器的模拟实现理解即可
熟悉:begin end rbegin rend
begin end 搭配使用
对于const_iterator begin() const和const_iterator end() const这两个接口用于const string& 类型的对象调用迭代器(理解const类型的对象不能调用非const的接口,原理权限的放大)
理解begin和end的模拟实现([begin,end)左闭右开)
typedef char* iterator;
typedef const char* const_iterator;
iterator begin()
{
return _str;
}
iterator end()
{
return _str + _size;
}
const_iterator begin() const
{
return _str;
}
const_iterator end() const
{
return _str + _size;
}
rbegin rend搭配使用
rbegin和rend理解为反向迭代器,遍历字符串是从后往前遍历(用法与begin和end一样)
第二种语法糖
实际本质这个语法糖的底层就是调用迭代器
第三种 [ ]
[ ]的底层是设计了运算符的重载
[ ]运算符重载的模拟实现
const char& operator[](size_t pos) const
{
assert(pos < _size);
return _str[pos];
}
char& operator[](size_t pos)
{
assert(pos < _size);
return _str[pos];
}
五、 查找
熟悉:find、rfind、find_first_of、find_last_of
find、rfind
find返回值为下标 既可以查找对应的字符串的下标也可以查找单个字符的下标,其中pos从下标pos出开始往后查找,若没查找到返回npos即无符号位整数的-1(4294967295)
理解了find那么rfind就一样,只不过rfind是从后往前找,find是从前往后找。
find_first_of、find_last_of
是查找字符串中所有的字符。first是从前往后找,last则是从后往前找。
理解下面的例子就够了