目录
一 string使用的前置条件
注意包含头文件和标准命名空间域。
由于string是std的一个子集,使用string的时候需要声明标准命名空间域。如果不整体展开也可以展开特定的,这在之前的类与对象中有介绍。
#include<string>
using namespace std;
二 构造函数
1 无参构造函数(默认构造函数)
string s1;
我们看一下他的底层设计
size是保存的有效字符的个数,capacity标识这个容器最开始的容量。
即使是一个无参的构造函数构造出的s1,编译器也会为他分配好对应的size和capacity。需要注意的是,由于string是一个字符串,里面存储的是一个个char,即使size是0他也是存储着\0的,符合C语言字符串的规范
2 带参构造函数
①用字符串初始化
string s2("hello world");
其实他是一个拷贝构造函数,用hello world这个字符串拷贝给s2.也可以这样实现
string s3="hello world";
这里在我之前写的编译器优化的博客中有所涉及。
如果用=的话,首先会去构造一个临时变量,再用这个临时变量拷贝给对应的 string。经过编译器的优化, 不生成临时变量了,直接在这一步之前返回给string,相当于直接构造。
其实她的设计是这样子的,一个string的类有_str,_size,_capacity三个成员变量,当我们去拷贝构造的时候,涉及深拷贝,因为浅拷贝的话一个值的修改可能会引起另一个值的修改,两者公用一段内存空间,这显然是不行的。因此我们需要在内存中新开辟一块空间,_str指向这块新开辟的空间,开辟capacity大小的空间,并且提前设置好对应的size,再把对应的值拷贝在新开辟的空间里。
如果画图大概是这个样子:
②用字符串初始化(部分初始化)
string s4(s2,5);
她的原型是这样的:string (const string&str,size_t pos,size_t len=npos)
她的意思是传入三个参数,第一个参数传入对应的字符串,第二个位置表示这个字符串开始拷贝的位置,第三个表示要拷贝多少个字符,如果默认不传的话,就是npos,npos其实是是-1,由于第三个参数是size_t,因此表示整形的最大值,意思就是如果不传入对应的值,默认将pos位置开始的字符全部拷贝给对应的字符串。
归纳一下,他可以实现如果指定大小,那么就从规定的位置拷贝对应的大小;如果没有指定,默认直接从pos位置剩下的所有。
③用字符来初始化
string s5(5,'h');
这个语句标识用五个h来初始化s5.
还可以用迭代器来进行迭代器区间范围内的初始化,这一点在之后迭代器讲解之后有所涉及。
三 析构函数
函数原型:~string()
他出了函数的作用域就会自动调用这样的一个析构函数,完成对资源的清理工作。string是一个动态增长的字符串,涉及到new的使用,在堆上开辟,如果不进行资源清理就会造成内存泄漏。
四 运算符的重载
1 =
string重载的=,可以实现拷贝赋值,字符串赋值,字符赋值。
string s1;
string s2 = "hello world";
s1 = s2;
s1 = "xxxxxx";
s1 = 'y';
第一种就是上面介绍过的,第二种的话,编译器会去判断空间是不是够,如果不够的话,先会扩容之后再将数据拷贝到string中。
其实有了第一个就够用了。
2 +=
可以加入字符,字符串,string类的对象。相当于一个追加的功能。
3 []
这个非常好用,他有两种,一种带const,一种不带。
不带const的,可读可写,直接可以对对应位置进行修改,带const的话,只读。有时候可能我们只需要读取。这是由编译器自动匹配给我们最合适的。
并且他有越界检查的机制,发生越界就会检查。(assert检查)如果没有越界的话,就返回对应pos这个位置的字符,他是传引用返回,不生成对应的拷贝。既提升效率,又可以实现对返回位置进行修改。
实际上重载[]逻辑链是这样实现的:
s1[0]->s1.operator[](0)->对应的函数
[]的底层重载了运算符,是封装的一个体现
对应[],其实string类有一个at,它的功能是和[]一样的,但是唯一不同的就是他处理失败异常的机制。他是抛异常处理。
五 迭代器
迭代器是什么?
迭代器是一个类似于指针的东西,但是有可能不是指针,但是对于物理空间连续的类似于string之类的,其实就是原生的指针。它实现的功能主要是遍历。他非常重要,不仅仅是因为他是stl的六大组件之一,而且也因为使用迭代器可以实现用同样的针对不同类型的容器实现遍历的功能。它是所有容器的通用访问方式。
在不暴露底层的情况下,给我们提供了通用的,便捷的,类似的一种访问方式。
基本使用:
string::iterator it = s1.begin();
while (it != s1.end())
{
cout << *it << " ";
it++;
}
迭代器属于类域,因此需要声明属于string::。
s1.begin()是下标为0的位置的字符,s1.end()是\0的位置,是一个左闭右开的区间。
有的时候支持while用<有的时候不支持,主要看它内部是否是指针。如果是指针的话物理空间一般是连续的。但是list这种,物理空间不连续的,
但是string的迭代器使用的不多,因为[]配合其他的等就可以实现对应的迭代功能。
需要注意的是,对于list(底层是带头双向循环链表),如果一直++的话,可能会回到最开始的位置。
其实迭代器也可以用范围for来实现,他自动迭代,自动判断结束,但是其实范围for底层其实封装的就是迭代器。
for (auto &ch : s1)
{
cout << ch << " ";
}
一般加上引用,便于修改。
迭代器一般根据是否有const修饰,以及是否是反向的,有很多种。
迭代器分类
1 反向迭代器
rbegin和rend,其实反向迭代器很好理解,就是原先的begin变成了rend,原先的end变成了rbegin,r是reverse的意思,翻转。
如果这种迭代器去++的话,一般是和begin和end相反遍历的。
比如rbegin,++的话,就会从后往前遍历
string s1 = "hello world!";
string::reverse_iterator rit = s1.rbegin();
while (rit != s1.rend())
{
cout << *rit << " ";//! d l r o w o l l e h
*rit++;
}
可以实现字符串的逆置
2 const迭代器
主要是传参的过程中规定了对应的迭代器读写权限,一般这种迭代器不可写。
如果进行写操作就会报错。
3 被const修饰的反向迭代器
string s1 = "hello world!";
string:: const_reverse_iterator crit = s1.rbegin();
while (crit != s1.rend())
{
crit++;
}
注意的就是反向迭代器的话,要用rend和rbegin。
总结:根据是否正向,是否被const修饰,有四种迭代器:iterator/const_iterator/reverse_iterator/const_reverse_iterator,基于此产生begin/end rbegin/rend cbegin/cend crbegin/crend
这一篇先介绍到这里,之后会介绍string中一些基本函数的使用