文章目录
- 1.初识STL
- 1.什么是STL
- 2.STL的版本
- 3.STL的六大组件
- 2.string
- 1.string类模板
- 2.string类的构造函数
- 3.string内部数据访问
- 4.string的遍历
- 5.string类的迭代器
- 6.string的Capacity相关接口
- 7.string的修改相关接口
- 8.其他接口
1.初识STL
1.什么是STL
STL(standard template libaray-标准模板库):是C++标准库的重要组成部分,不仅是一个可复用的组件库,而且是一个包罗数据结构与算法的软件框架
2.STL的版本
-
原始版本
Alexander Stepanov、Meng Lee 在惠普实验室完成的原始版本,本着开源精神,他们声明允许任何人任意运用、拷贝、修改、传播、商业使用这些代码,无需付费。唯一的条件就是也需要向原始版本一样做开源使用。 HP 版本–所有STL实现版本的始祖
-
P.J.版本
由P. J. Plauger开发,继承自HP版本,被Windows Visual C++采用,不能公开或修改,缺陷:可读性比较低,符号命名比较怪异
-
RW版本
由Rouge Wage公司开发,继承自HP版本,被C+ + Builder 采用,不能公开或修改,可读性一般
-
SGI版本
由Silicon Graphics Computer Systems,Inc公司开发,继承自HP版 本。被GCC(Linux)采用,可移植性好,可公开、修改甚至贩卖,从命名风格和编程 风格上看,阅读性非常高。我们后面学习STL要阅读部分源代码,主要参考的就是这个版本
3.STL的六大组件
对于STL的学习,这里推荐一个网站cplusplus.com
2.string
接下来就要正式进入到对STL的学习中啦,在对STL的学习过程中,需要注意的是
第一、熟悉库里面的STL的各种类模板的常用接口
第二、尝试去模拟实现库里面的类模板
注:在过程中,我们可能会遇到一些没有办法解决的问题,此时将会去查库里的源码,由于各个库中的实现方式不太一样,所以,在这里定一下,我们将会参考SGI版本的代码,结合侯捷老师的STL源码剖析去模拟实现。
1.string类模板
我们打开上文中推荐的网站,搜索string,会发现我们平常用的string其实是basic_string<char>,是类模板basic_string实例化出来的char类。
basic_string是一个类模板,可以通过传入不同的类型参数实例化出不同的类。
那么,对于string,为什么要使用一个类模板呢,在C语言中,对于字符串都是能够存放char类型的数据就可以了?
这是因为编码的问题,我们知道,在计算机中存放的数据都是0和1,为了让数据转换成人能看懂的东西,人们把特定的01序列给定义成某个字符,按照这种方式可以将计算机中的数据翻译出来,这就是编码,最初的编码方式就是将一些符号和大小写的26个字母与01序列对应,这就是ASCII码。
现在的常见编码方式
ASCII码
美国信息交换标准代码,是计算机存值和文字符号的对应关系,只有256个字符
Unicode
万国码,是为了解决传统的字符编码方案的局限而产生的,它为每种语言中的每个字符设定了统一并且唯一的二进制编码包括了utf-8,utf-16,utf-32
utf-8兼容了ASCII,utf-8使用比较普遍,也比较节省空间
gbk
gbk即国标,针对中文而设计的编码,采用双字节编码
2.string类的构造函数
首先,一个类中必不可少的就是构造函数,我们来看一下string类的构造函数有什么
以上就是string的所有构造函数了,接下来让我们通过代码来实验一下:
void Test_Construct()
{
char str[] = "hello string";//创建一个C语言的字符串
string s1;//默认构造函数,不用传递任何参数,最终是s1中只有一个\0
string s2(str);//使用C语言的字符串构造一个string类型的对象
string s3(s2);//使用s2拷贝构造一个string类型的对象
string s4(s3, 2, 5);//使用s3中的第二个位置开始长度为5的子串构造对象
string s5(str, 8);//使用C语言的字符串构造一个指定长度的string类型
string s6(10, 'a');//使用指定字符构造一个长度为10,内容为a的对象
auto first = s2.begin();
auto last = s2.end();
string s7(first, last);//使用迭代器区间构造一个[first,last)的对象
cout << "s1: " << s1 << endl;
cout << "s2: " << s2 << endl;
cout << "s3: " << s3 << endl;
cout << "s4: " << s4 << endl;
cout << "s5: " << s5 << endl;
cout << "s6: " << s6 << endl;
cout << "s7: " << s7 << endl;
}
运行上述代码,结果是:
在上面的函数原型中,好像发现了一个之前没见过的东西,npos
,什么是npos呢?查一下文档
可以看到npos是在类中定义的一个公共的静态成员常量,这个常量定义的值为-1,但是由于常量的类型是size_t,即unsigned int,所以表示的此类型可能的最大值。我们理解成字符串的结尾即可。
由于npos是string类中的成员常量,所以在使用npos的时候需要指定类域,即string::npos
.
3.string内部数据访问
可以看到,对于数据访问,string提供了4个接口,其中两个是C++11新增的,看名字也可以得出,是获得字符串首元素和最后一个元素的,不常用,所以在此不过多赘述。我们主要关注at和operator[]这两个接口。
这两个接口都是能够拿到指定位置的值,其中operator[]是对[]的运算符重载。可以看到两个接口都重载了普通版本和const版本,用来应对权限放大的问题。
void Test_Element()
{
string s = "hello string";
cout << s.at(4) << endl;
cout << s[4] << endl;
}
4.string的遍历
对于string的遍历,我们有以下几种方法:
- operator[]
- 范围for
- 迭代器
void Test_Element2()
{
string s = "0123456789";
//operator[]
for (size_t i = 0; i < s.size(); ++i)
{
cout << s[i] << " ";
}
cout << endl;
//范围for
for (auto e : s)
{
cout << e << " ";
}
cout << endl;
//迭代器
string::iterator it = s.begin();
while (it != s.end())
{
cout << *it << " ";
++it;
}
cout << endl;
}
5.string类的迭代器
在遍历中,我们讲到了迭代器的方式遍历字符串,那么迭代器是什么?为什么要有迭代器的存在?
迭代器是通过一种普适的方式去访问所有支持遍历的容器,迭代器的行为上像指针,但是本质上不全是指针
在string类中,原生指针就已经能够支持迭代器的行为,所以string的迭代器就是元素类型的指针。
可以看到string提供了很多中不同的迭代器,我们可以将它们分类
1. 正向迭代器
begin和end,begin返回的是字符串的开头位置,end返回的是最后一个有效数据的下一个位置,即迭代器是前闭后开的*[begin,end)*
可以看到,不管是begin还是end都重载了const版本用来应对权限变化的问题。
2. 反向迭代器
反向迭代器与正向迭代器的用法完全一致,只是调用反向迭代器的时候,遍历数据的顺序是反的。
void Test_Iterator()
{
string s = "0123456789";
cout << "正向迭代器" << endl;
string::iterator it1 = s.begin();
while (it1 != s.end())
{
cout << *it1 << " ";
++it1;
}
cout << endl;
cout << "反向迭代器" << endl;
string::reverse_iterator it2 = s.rbegin();
while (it2 != s.rend())
{
cout << *it2 << " ";
++it2;
}
cout << endl;
}
3. const迭代器与const反向迭代器
这四个迭代器接口都是C++11为了规范代码而增加的,但是其实在前面四个接口中已经重载了const版本,所以这四个基本用不上,就不过多介绍了,使用方法和前面的是完全一致的。
6.string的Capacity相关接口
在这么多接口中,我们最常用的有:
- size:返回字符串长度
- resize:重新设定字符串长度
- capacity:返回字符串容量
- reserve:重新设置字符串容量,如果传入的参数小于capacity则不做任何操作,如果大于capacity就开辟一段容量为n的空间,将原数据拷贝进来,然后释放原空间。
- empty:返回字符串是否为空
其余的一些接口不常用,了解即可。
void Test_capacity()
{
string s = "0123456789";
cout << "size" << s.size() << endl;
cout << "capacity" << s.capacity() << endl;
s.reserve(20);
cout << "capacity" << s.capacity() << endl;
s.resize(5);
cout << "size" << s.size() << endl;
if (!s.empty())
{
string::iterator it1 = s.begin();
while (it1 != s.end())
{
cout << *it1 << " ";
++it1;
}
cout << endl;
}
else
{
cout << "string is empty" << endl;
}
}
7.string的修改相关接口
其中,常用的有
- operator+=:追加字符串,其中有三个重载,分别是追加字符串(复用append),追加C类型字符串(复用append),追加单个字符(复用push_back)
- insert:在某个位置插入字符或者字符串
- erase:在某个位置删除字符或者长度为len的字符串
void Test_Modify()
{
string s = "abcdefg";
cout << s << endl;
s += 'h';
cout << s << endl;
s += "ijklm";
cout << s << endl;
s.insert(5, 1, 'A');
cout << s << endl;
s.erase(5, 1);
cout << s << endl;
}
8.其他接口
- c_str:以C语言字符串的方式返回一个字符指针(由于Linux是用C语言写的,在字符串的读写中,不支持string类型的读写,所以提供此接口)
- find:在字符串某一段位置中找到某个值,如果找到了就返回下标,否则返回npos
- getline:读取缓冲区的数据直到遇到换行符,这是为了防止出现cin遇到空格停止读取,无法将后续内容放入同一个字符串中
- operator>>和operator<<:重载流插入和流提取,使字符串也支持cin和cout的用法
写在最后:
1. 由于这是第一次接触到STL,所以将很多的成员函数接口讲解的比较细致,后续的STL容器的接口将会省略掉一些重复的和相似性很高的函数。
2. 关于某个类的使用,是不可能用一篇博客说明白的,还是要在实践中学习,多看看文档里的内容