一、引言
对于c++的学习,了解与学习STL(standard template libaray-标准模板库)是非常重要的,网上有句话说:“不懂STL,不要说你会C++”,为什么呢?首先我们得先了解什么是STL,STL是C++标准库的重要组成部分,不仅是一个可复用的组件库,而且是一个包罗数据结构与算法的软件框架。出现了关键词“数据结构”与“算法”,这就意味着我们实现许多底层的数据结构以及算法都不需要自己重新造轮子,不需要像c语言一样每个数据结构要自己来实现底层代码,光这一点就可以看出来STL的重要性了。何况STL一共有六大组件(容器、算法、迭代器、仿函数、适配器、分配器),不仅仅是数据结构简单了,还有算法和其他一些东西都有轮子了,我们就可以直接用。不过学习C++可不止会用就行了,还得至少了解一些基础的底层实现原理,和模拟实现一些基础的东西才行。

二、string的概念与常用接口说明
什么是string?
string可以称为类,也可以称为容器。STL 中对容器的定义是能够存储和管理一组数据的对象,string满足这一条件,也就是说它是可以用来存储字符串和管理字符串的容器。但同时它也是通过std::basic_string模板实例化得到的类。
为什么从string开始学习STL?
这里选择从string开始对STL的学习主要是因为学习要从易到难,循序渐进。string是只针对字符串,相对来说简单一点,同时它也会涉及到很基础的迭代器使用。
怎么去学习string?
学习一个类首先得了解类的成员变量,再去了解最基础的默认成员函数如构造函数,析构函数等,最后再去了解它的其他成员函数。
1.string的成员变量
class string {
private:
char* _str; // 指向字符串的指针(存储实际字符串)
size_t _size; // 当前字符串长度(不包含末尾 '\0',但实际实现可能和 capacity 关联)
size_t _capacity; // 缓冲区总容量(提前分配的内存,避免频繁扩容)也不包含末尾 '\0'
};
C++ 标准并没有明确规定std::string底层具体的成员变量形式,不同的编译器和标准库实现对std::string的底层成员变量定义有所不同,这里选择了更加普遍且简单一点的实现方式,也就是简化版本的。有三个成员变量,一个是指向字符串的指针_str,一个是存储字符串的长度无符号整形_size,还有一个是存储容量大小的无符号整形_capacity。
2.string的构造函数

首先参考一下比较权威一点的网站的string构造函数声明。一共七种,作为新手最后一种先不做了解,因为涉及到了迭代器,就先了解一下前六种。

(1)空字符串构造函数:创建长度为 0 的空字符串,也就是说不传任何参数的时候字符串长度是默认为0的。
(2)拷贝构造函数:复制另一个 string 的内容。
(3)子串构造函数:复制原字符串从 pos 开始、长度为 len 的子串(超出范围则到末尾)。这里的缺省值是npos,指的是-1,对于无符号的整形-1相当于最大值。也就是说不传长度默认到末尾。
(4)C 风格字符串构造函数:从以 \0 结尾的 C 字符串 (const char*) 复制内容。
(5)缓冲区构造函数:从字符数组 s 复制前 n 个字符。
(6)填充构造函数:用 n 个字符 c 填充字符串。
2.string的析构函数

这里的析构函数就是将我们为字符串存储申请的空间释放出来,任何将指针置为nullptr,容量和长度置为0。
3.string关于容量的函数

| 函数名 | 功能说明(均为公有成员函数 ) |
|---|---|
size | 返回字符串的长度 |
length | 返回字符串的长度(与 size 功能相同 ) |
max_size | 返回字符串可达到的最大长度 |
resize | 调整字符串的大小 |
capacity | 返回已分配存储容量的大小(即当前为字符串预留的内存能容纳的字符数 ) |
reserve | 请求更改容量(提前预留内存 ,避免频繁扩容 ) |
clear | 清空字符串内容 |
empty | 检测字符串是否为空 |
shrink_to_fit | 收缩到合适大小(C++11 及以上特性 ,释放多余未使用内存 ) |
由于是第一次学习容器,这里将基本上所有容器相关接口函数都介绍一下,因为这些接口对于我们后面的容器学习也是十分重要的。不过后面很少用的接口就不过多介绍,了解功能即可。
1.size函数

用const修饰this指针,防止函数中去修改成员变量_size的值,返回_size的值即可。
2.resize函数

这个函数主要用来调整字符串的长度,这里分两种情况,第一种当n(请求更改的大小)小于当前的长度,就会减小长度到n,另一种情况当n大于当前的长度,这个时候就增加长度,不过这里当n大于容量的时候也需要先进行扩容操作,然后在原来字符串基础上增加字符c长度到n。
3.capacity函数

用const修饰this指针,防止函数中去修改成员变量_capacity的值,返回_capacity的值即可。
4.reserve函数

关于reserve函数内容描述有点多,简单来说就是变更容量,这里分两种情况,第一种当n(请求更改的大小)大于当前的容量,就会增长容量到n,另一种情况当n小于当前的容量,这个时候具体缩容不就是不确定的,不同编译器有不同的选择。不过必须保证不会影响字符串的长度,不能更改其内容。在vs中是不会进行缩容的操作,也就是n小于容量不处理。
5.clear函数

这个函数主要是用来清楚字符串里面的内容,长度变为0,不过并不改变它的容量大小。
6.empty函数

这个函数就是判断字符串是否为空,实现也很简单,也是const修饰this不能够改变成员变量的值。
4.string关于元素访问的函数

| 函数 / 操作符 | 功能描述(public member function 即 “公有成员函数” ) |
|---|---|
operator[] | 获取字符串的字符(通过下标运算符,直接按索引访问字符 ) |
at | 获取字符串中的字符(按索引访问,会进行越界检查 ) |
back | 访问最后一个字符(C++11 及以上标准支持 ) |
front | 访问第一个字符(C++11 及以上标准支持 ) |
1.操作符重载operaotr[]

这里操作符重载[]主要是获取字符串的字符返回对字符串中位置pos处字符的引用。重载有两种,一种是可以修改字符串内容的重载,另一种是const修饰的不能修改字符串内容的重载,这里若 pos 等于字符串长度(即访问 “末尾之后” 的位置 ),且字符串是const限定的(即常量字符串对象 ),函数会返回对空字符('\0')的引用。at和这个类似,不过两个报错不同,at会抛异常。
2.back函数

back函数主要是用于返回字符串的最后一个字符,这里要注意字符串不能够为空字符。
3.front函数

front函数和back函数对应,返回首字符,也不能够为空字符串。它和string::begin有区别,它返回的是字符的引用,而string::begin是返回迭代器iterator。
5.string关于变更操作的函数

| 函数名 | 翻译 & 功能说明 |
|---|---|
operator+= | 字符串拼接(公有成员函数 ) 用 += 语法直接拼接字符串,如 s += "abc"; |
append | 字符串拼接(公有成员函数 ) 更灵活的拼接方式,支持拼接子串、迭代器区间等 |
push_back | 字符追加(公有成员函数 ) 向字符串末尾追加单个字符,如 s.push_back('!'); |
assign | 内容赋值(公有成员函数 ) 替换字符串内容,支持从其他字符串、C 风格字符串赋值 |
insert | 插入内容(公有成员函数 ) 在指定位置插入字符、字符串或迭代器区间内容 |
erase | 擦除字符(公有成员函数 ) 删除指定位置或区间的字符,如 s.erase(2, 3); |
replace | 替换内容(公有成员函数 ) 替换指定区间的字符为新字符串、子串或字符 |
swap | 交换内容(公有成员函数 ) 交换两个字符串的内容,如 s1.swap(s2); |
pop_back | 删除末尾字符(公有成员函数,C++11 及以上 ) 移除字符串最后一个字符,如 s.pop_back(); |
1.操作符重载operaotr+=

这个重载实现的作用和我们的尾插类似,这里可以传一个字符,一个字符串,还可以传string类对象的引用,具体实现可以用后面的push_back,append等函数进行复用,实现起来很简单。
2.append函数
append函数是在原来的字符串后面插入一个字符串。这里重载了很多种,其实就是先分为传参的类型是传的string类的引用,字符串,还是n个字符或者是迭代器区间。然后分为是指定插入的字符串插入区间,或者是插入的字符个数。
3.push_back函数

这个函数就是用来尾插字符,在底层实现也需要注意需不需要扩容和字符串末尾的‘\0’。
4.insert函数

这个函数和前面的append最大的差别就是它是指定位置插入,而不是直接尾插。所以它也有很多的重载,总体的分类方式和前面说的append类似,只是多了用迭代器指定位置的重载。
5.erase函数

这个函数是用来在指定位置删除字符串,这里的len用来指定删除的长度,npos前面介绍过是-1,还有就是迭代器的版本,其实也就是指定删除的区间。
6.swap函数

交换函数,将当前字符串容器的内容与另一个字符串对象str的内容进行交换。两个字符串的长度可能不同。调用此成员函数后,当前对象的值会变为str调用前的值,而str的值会变为当前对象调用前的值。库里面的std::swap针对于字符串也会调用这里的,因为库里面的swap如果不调用这里的swap会进行深拷贝,交换存在多次的拷贝构造,对于内置类型效率还好,对于自定义类型效率会很低,谨慎使用。
7.pop_back函数

尾删函数,删除最后一个字符,字符串必须为非空。
6.string关于非变更操作的函数

| 函数名 | 翻译 & 功能说明 |
|---|---|
c_str | 获取 C 风格字符串等效形式(公有成员函数 ) 返回 const char*,兼容 C 风格 API |
data | 获取字符串数据(公有成员函数 ) 返回 const char*,直接访问字符串底层字符数组 |
get_allocator | 获取分配器(公有成员函数 ) 返回字符串内部使用的内存分配器(一般用于高级内存管理 ) |
copy | 从字符串复制字符序列(公有成员函数 ) 将字符串字符拷贝到外部缓冲区 |
find | 在字符串中查找内容(公有成员函数 ) 查找子串 / 字符首次出现的位置 |
rfind | 在字符串中查找内容的最后一次出现(公有成员函数 ) 从后往前找子串 / 字符 |
find_first_of | 在字符串中查找字符(公有成员函数 ) 找参数中任意字符首次出现的位置 |
find_last_of | 从字符串末尾开始查找字符(公有成员函数 ) 从后往前找参数中任意字符 |
find_first_not_of | 在字符串中查找不匹配的字符(公有成员函数 ) 找不包含参数字符的首个位置 |
find_last_not_of | 从字符串末尾开始查找不匹配的字符(公有成员函数 ) 从后往前找不包含参数的字符 |
substr | 生成子串(公有成员函数 ) 提取字符串的子串(返回新字符串,原串不变 ) |
compare | 比较字符串(公有成员函数 ) 按字典序比较两个字符串,返回差异结果(类似 strcmp ) |
这里主要介绍一下c_str,find,rfind,substr和compare,因为其他一方面是接口通用性很低,另一方面功能相似性高有一点冗余。
1.c_str函数

这个函数返回一个指向字符数组的指针,该数组包含一个以空字符(\0)结尾的字符序列(即 C 风格字符串),表示当前字符串对象的值。此数组包含构成字符串对象值的相同字符序列,且在末尾额外添加了一个终止空字符('\0' )。这里的data函数也和这个函数类似,在C++11之前有一点差别,不过之后功能几乎等价就不做过多介绍了。
2.find函数

find函数就是用来在字符串中查找一个字符或者另一个字符串,返回首次出现的位置。也可以指定位置开始查找,没有指定就默认从起始位置开始查找。
3.rfind函数

rfind函数功能和find相同,不过它是从指定位置往前面进行查找,没有指定位置默认从末尾开始查找。
4.substr函数

substr函数的作用就是通过指定字符串位置进行拷贝给另一个新构造的string类的字符数组。原字符串不变,这里参数都有默认参数,不传是默认从起始位置到末尾。
5.compare函数

了解这个函数之前得先了解字符串比较的逻辑是什么,字符串比较的核心是ASCII码顺序,字符串的每个字符依次比较ASCII码值,相等比较下一个,谁值大则这个字符串更‘大’,完全相等返回0,小于返回负数,大于返回正数,不等于返回非0,大于等于返回正数或0,小于等于返回负数或0。这里compare函数比较的逻辑就是这样,不过可以指定位置,长度进行比较。
7.string的非成员函数重载

| 函数 / 运算符 | 功能描述 |
|---|---|
operator+ | 拼接字符串(函数) |
relational operators | 用于字符串的关系运算符(函数) |
swap | 交换两个字符串的值(函数) |
operator>> | 从流中提取字符串(函数) |
operator<< | 将字符串插入流中(函数) |
getline | 从流中读取一行内容到字符串(函数) |
这里主要重点介绍operator>>,operator<<和getline 对于这里的swap前面有做介绍,operator+前面有介绍operator+=原理其实很类似,功能也差别不大,relational operators和前面讲到的compare很类似,功能也是差别不大,都不做过多介绍了。
1.操作符重载operator>>

这里是对流输入操作符进行重载,它的重载是因为对于类来说,直接使用流输入操作符输入是会报错的,需要让它完成我们需要的输入方式就需要我们直接去写重载函数。它的功能基本上都是对于字符串就相当于一个个尾插到指定的string类对象中,不过要注意输入流的提取操作会将空白字符(如空格、换行等 )用作分隔符,不过下面的getline就可以解决这个问题。
2.操作符重载operator<<

这里对流输出操作符重载一样是因为对于类没办法直接使用,需要重载实现我们想要它输出的内容,对于string类我们一般都是让它能够去输出字符串的内容。
3.getline函数

getline函数在我关于C++输入与输出相关介绍博文中有详细介绍,这里就简单介绍一下,它也是像push_back一样将字符一个个插入到指定的字符串之中去,不过它可以指定结束的delim也就是定界符,指定界限插入,不过定界符不会被插入到指定的字符串之中去,而且可以读取空格换行如果在使用getline之前指定的字符串之中有字符会全部清空。
三、总结
string是最适合我们学习STL容器的开始,这里对于string的成员函数以及非成员函数的介绍将比较权威网站的介绍和我的理解结合起来希望能够帮助到你更加简单便捷的理解string,如果有错误的地方也请温柔的指出来谢谢,我们一起进步,加油!

2175

被折叠的 条评论
为什么被折叠?



