
🎈主页传送门**:良木生香**
🔥个人专栏:《C语言》 《数据结构-初阶》 《程序设计》《鼠鼠的C++学习之路》
🌟人为善,福随未至,祸已远行;人为恶,祸虽未至,福已远离

前言:在上一篇文章中我们已经学习了STL的组成部分以及编码的又来(即为什么要学习String),那么这篇文章就是正式进入STL的学习了,在String部分我将会分成大概四篇文章的版图进行讲解,话不多说,我们直接开始吧
目录
[2.5、根据序列构造(from sequence)](#2.5、根据序列构造(from sequence))
一、String类
根据String的定义,我们可以知道,String是由basic_string重命名而来的:

二、String的构造:
String的构造有以下几种方式:

我们会重点讲解1,2,3,4,5点的构造方法:
2.1、defualt构造方法
defualt就是string的默认构造,将string实例化出的字符串构造成空:
cpp
#include<iostream>
#include<string>
using namespace std;
int main() {
string s;
if (s == "") {
cout << "s is nullptr" << endl;
}
return 0;
}
运行结果为:
2.2、copy拷贝构造
copy顾名思义就是将已有的string字符串用来初始化另一刚创建的字符串:

2.3、使用substring子字符串构造

对于文档中的
string (const string& str, size_t pos, size_t len = npos);
为什么在size_t len = npos这个东西呢?官方给出的答案是这样的:

就算输入的len已经超过了该字符串的范围,但是依旧不会有问题。
2.4、使用C_string字符串构造
我们知道,string的诞生就是为了解决C语言中char类型字符串的不足,所以自然是兼容C语言的字符串,我们就可以用C语言的字符串初始化string类型:

2.5、根据序列构造(from sequence)
即从C语言风格的字符串中取前n个字符对string进行初始化

与前面的substring不同的是,这个只能取前n个字符,不能从任意地方开始取字符串。
三、string的遍历与修改:
3.1、下标+[]访问

我们可以使用size()函数来返回string字符串的长度,再根据这个长度对字符串进行遍历:
cpp
#include<iostream>
using namespace std;
int main() {
string s = "hello world";
size_t size_string = s.size();
cout << size_string << endl;
for (int i = 0; i < size_string; i++) {
cout << s[i] << " ";
}
return 0;
}
运行结果如下:

因为string的底层说实话还是数组,所以可以用**[]+下标**的方式对元素进行访问。
同时我们可以看到,operator[]的返回值是引用返回,这就导致了它还有有一个特性:可以修改返回值:

在s[0]中,返回的是'h',同时被修改成'x'。
3.2、at访问
除了下标+[]访问的形式,在string中还有一种名为 at 的访问形式,是这么用的:

cpp
#include<iostream>
using namespace std;
int main() {
string s = "he is a good man";
char ch = s.at(10);
cout << ch << endl;
return 0;
}
其返回值是pos位置的引用,运行结果如下:

效果与下标+[]一致。
3.3、越界检查
在string中有严格的越界访问检查:
cpp
#include<iostream>
using namespace std;
int main() {
string s = "hello world";
s[100] = 'x';
cout << s << endl;
return 0;
}
当我们使用下标+[]越界访问时就会触发string的断言。

但是如果用at()函数,那么触发的就不是断言,而是抛出错误:

这两者有这个差异,可以留意一下。
3.4、size()与length()
在对string进行求长度的时候,通常会看到这两种方式,很多人会疑惑为什么要有两种 ?有length()不就够用了吗?
其实在早期的时候,确实是使用ength()更多一点,但是后来STL不断完善,接口也变得更加的丰富,size()的使用场景就更多了,总而言之,length()几乎是只给string使用,但是size()能适用于u所有容器,因为string本身就是一种容器。
两者在计算string长度大小时都不会带上'\0',因为'\0'只是字符串结束的标志,属于无效字符。
3.5、迭代器
在容器中有这么一个东西,叫做迭代器,我们可以将它理解成行为像指针一样的东西,其底层确实也是由指针实现的,但是还有有很多地方与指针不相同,在string类中的迭代器有以下几种:

我们一组一组来讲:
3.5.1、begin()与end()



begin()指向的是string的第一个字符,而end()指向的是最后一个字符的下一个位置,属于左闭右开的特性。那这跟迭代器有什么关系呢?
3.5.2、迭代器
string中的迭代器是这样的:string::iterators
使用时就是这样子的:
cpp
#include<iostream>
#include<string>
using namespace std;
int main() {
string s = "hello world";
string::iterator it = s.begin();
while (it != s.end()) {
cout << *it << " ";
it++;
}
cout << endl;
return 0;
}
运行结果:

总的来说,迭代器不等价于指针,但是像指针,也是由指针作为底层实现的。
在begin()和end()的定义中我们也可以看到迭代器是有const(string::cosnt_iterator) 和 非const两种的,就是用于const string和非cosnt string.
3.5.3、反向迭代器
顾名思义,反向迭代器就是将头和尾进行对调,使用方法如下:
cpp
#include<iostream>
using namespace std;
int main() {
string s = "hello world";
string::reverse_iterator rit = s.rbegin();
while (rit != s.rend()) {
cout << *rit << " ";
rit++;
}
cout << endl;
return 0;
}
运行结果如下:


代码中的rit++其实就是从后往前遍历,如写成rit就会出现错误。
3.5.4、迭代器的作用
迭代器的作用就跟指针类似,那我为什么不直接使用指针?迭代器有什么作用?
1、提供统一的遍历和修改容器的方式,这样就能保证多个容器使用的时候效率能够提高。
2、有了迭代器,在使用STL中算法就是实现泛型编程,即在对数据及进行遍历的时候能够有统一的方式,实现STL高性能运算的能力。
对于一下这几种迭代器的接口,就是针对const类型的。了解就行。

四、C++11的一些小特性:
4.1、auto语法
auto关键字:通过初始化的类型能够自主推导对象类型。
1、在早期C/C++中auto的含义是:使用auto修饰的变量,是具有自动存储器的局部变量,后来这不重要了。C++11中,标准委员会变废为宝赋予了auto全新的含义即:auto不再是一个存储类指示符,而是作为一个新的类型指示符来指示编译器,auto声明的变量必须由编译器在编译时期推导而得。
2、用auto声明指针类型时,用auto和auto*没有任何区别,但用auto声明引用类型时则必须加&
3、当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译器实际只对第一个类型进行推导,然后用推导出来的类型定义其他变量。
4、auto不能作为函数的参数,可以做返回值,但是建议谨慎使用
5、auto不能直接用来声明数组
有什么用呢?像在刚才写的string::iterator就可以写成:
cpp
#include<iostream>
using namespace std;
int main() {
string s = "hello world";
//string::iterator it = s.begin();
auto it = s.begin();
while (it != s.end())
{
cout << *it << " ";
it++;
}
return 0;
}

这时候编译器就能自动推断出it 的类型是string::iterator类型.
但是这就带来了一个弊端:降低了程序的了可读性。
同时,auto也可以是指针:
cpp
#include<iostream>
using namespace std;
int main() {
int i = 10;
auto p1 = &i;
auto* p2 = &i; //指定只能存放地址
auto& R1 = i; //R1是对i的引用
auto R2 = R1; //将R1的值赋给R2
auto& R3 = R1;
cout <<"i:"<< i << endl;
cout <<"p1:"<< p1 << endl;
cout <<"p2:"<< p2 << endl;
cout <<"&R1:"<< & R1 << endl;
cout <<"&R2:"<< & R2 << endl;
cout <<"&R3:"<< & R3 << endl;
return 0;
}
运行结果如下:

4.2、范围for
C++11中提供了一种比较新颖的遍历方式:范围for,它能够自动对容器进行遍历:
1、对于一个有范围的集合而言,由程序员来说明循环的范围是多余的,有时候还会容易犯错误。因此C++11中引入了基于范围的for循环。for循环后的括号由冒号" :"分为两部分:第一部分是范围内用于迭代的变量,第二部分则表示被迭代的范围,自动迭代,自动取数据,自动判断结束。
2、范围for可以作用到数组和容器对象上进行遍历
3、范围for的底层很简单,容器遍历实际就是替换为迭代器,这个从汇编层也可以看到。

像代码中的auto e : s,这时候编译器就会自动取容器值,自动++,碰到结尾自动结束。
如果我们尝试对它的值进行运算呢?比如++之类的,也是可以的但是推荐最好是用上&
:


其实范围for的底层就是迭代器,这就可以让支持迭代器的容器都能使用范围for,数组也支持,因为用指针遍历和用迭代器遍历是同一个原理:

范围for就是一个语法糖,因为用起来非常的方便。
以上呢,我们讲了三种别遍历string类的方式:
- 下标+[ ];
- 迭代器
- 范围for
那么以上就是本次所有的内容了
文章是自己写的哈,有什么描述不对的、不恰当的地方,恳请大佬指正,看到后会第一时间修改,感谢您的阅读~~~~
博主手写笔记:


