1 STL初识
1.1 STL的诞生
在C++发展早期,程序员在不同的项目中需要反复编写相似的数据结构和算法。重复开发带来以下问题:
代码冗余:每个项目都要重新实现基本数据结构和算法
维护困难:不同人编写的代码风格不一致,难以维护
效率低下:不同实现可能导致性能不稳定,难以优化
为了解决这些问题,1994年由Alexander Stepanov领导的团队开发了STL。
1.2 STL基本概念
- STL(标准模板库,Standard Template Library)
- 由多个模块组成,包括容器 (Containers)、算法 (Algorithms)和迭代器(Iterators),加上一些额外的工具
- 容器 和算法 之间通过迭代器进行无缝衔接
- STL几乎所有的代码都采用了模板类或者模板函数
1.3 STL核心组件
STL主要由六大部分 组成:容器 、算法 、迭代器 、适配器 、函数对象 、配置器。
1.容器(Containers)
提供多种数据结构,方便存储和管理数据。常见容器包括:
容器类型 | 特点 | 常见容器 |
---|---|---|
序列式容器 | 按照顺序存储数据 | vector,deque,list,array |
关联式容器 | 以键值对存储数据,自动排序 | set,map,multiset,multimap |
无序容器 | 使用哈希表存储数据,查找快 | unordered_set,unordered_map |
2. 算法(Algorithms)
提供大量常见算法,如排序、查找、修改等,使用时结合迭代器。
常见算法:
- 修改类:fill()、replace()、copy()
- 非修改类:find()、count()
- 排序类:sort()、stable_sort()
- 数值类:accumulate()(求和)、inner_produce()(内积)
3. 迭代器(Iterators)
用于遍历容器中的元素,类似于指针,但更灵活,适用于STL容器。
迭代器类别 | 适用容器 | 功能 |
---|---|---|
输入迭代器 | istream_iterator | 只能读取 |
输出迭代器 | ostream_iterator | 只能写入 |
前向迭代器 | forward_list | 只能前进 |
双向迭代器 | list,set,map | 可前进和后退 |
随机访问迭代器 | vector,deque,array | 可随机访问 |
4. 适配器(Adapters)
用于改变容器、迭代器或函数行为的工具。例如:
- 容器适配器(
stack
、queue
、priority_queue
) - 迭代器适配器(
reverse_iterator
) - 函数适配器(
bind()
、mem_fn()
)
函数对象(Functors 仿函数)
是重载的类,可以像函数一样被调用。可作为算法的某种策略。
1. 适配器(Allocators)
STL提供的内存管理机制,用于分配和释放内存。
1.4 STL的特点
- 泛型编程
- 高效
- 模块化
- 代码复用
1.2 容器算法迭代器初识
以STL中常见的容器vector为例,可以理解为"可变大小的数组",能够动态调整大小,支持随机访问。
1.5.1 vector 存放内置数据类型
下面用一个简单的例子,结合vector、算法sort和迭代器,看一下STL用法。
cpp
#include <iostream>
#include <vector>
#include <algorithm> //引入算法库
using namespace std;
int main(){
//创建一个vector容器,存储int类型数据
vector<int> v = { 5,2,8,1,3 };
//使用算法sort对vector进行排序
sort(v.begin(), v.end()); //排序从小到大
//使用迭代器遍历vector
cout << "排序后的数据:";
for (vector<int>::iterator it = v.begin(); it != v.end(); it++) {
cout << *it << " ";//迭代器解引用获取元素
}
cout << endl;
return 0;
}
最后输出的内容:1 2 3 5 8
在上述代码中有几条代码需要讲解:
v.begin()
指向数组起始位置,但v.end()
并非指向末尾,而是指向"最后一个元素的下一个位置",所以v.end()-1
才是最后一个元素。
sort(v.begin(), v.end());
为什么会升序排序?原因是std::sort
默认使用operator<
进行比较,默认行为是从小到大排序。那么如果想降序排列怎么办?只需要这样写就可以
sort(v.begin(), v.end(),greater<int>());
或者这样写也可以
sort(v.begin(), v.end(), [](int a, int b) {return a > b; });
1.5.2 vector存放自定义数据类型
使用vector
存储自定义数据类型(如class
)时,需要注意:
-
存储对象时,类需要支持拷贝 (默认可拷贝)
-
若要排序,需要提供比较规则
-
可以存储对象本身或指针
示例:
cpp
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
//定义Person类
class Person {
public:
string m_Name;
int m_Age;
//构造函数
Person(string name, int age) :m_Name(name), m_Age(age) {}
//显示信息
void show() const {
cout << "姓名:" << m_Name << ",年龄:" << m_Age << endl;
}
};
int main(){
//创建Vector存储Person对象
vector<Person> people;
//添加元素
people.push_back(Person("张三", 25));
people.push_back(Person("李四", 29));
people.push_back(Person("王五", 27));
//遍历输出
cout << "初始数据:" << endl;
for (vector<Person>::iterator it = people.begin(); it != people.end(); it++) {
it->show();
}
//按照年龄升序排列
sort(people.begin(), people.end(), [](const Person& a, const Person& b) {
return a.m_Age < b.m_Age; });
//排序后输出
cout << "按年龄升序排序后:" << endl;
for (vector<Person>::iterator it = people.begin(); it != people.end(); it++) {
it->show();
}
return 0;
}
这里的对象很小,所以直接存储对象没有问题。如果Person对象很大,或者不希望拷贝对象,可以存储Person*
指针:
vector<Person*> people;
people.push_back(new Person("张三", 25));
people.push_back(new Person("李四", 30));
但要记得这是指针,要delete
释放内存!
1.5.3 vector容器嵌套容器
Vector
可以存储任何类型的数据,包括另一个vector
,也就是嵌套容器。
简单的示例:vector<vector<int>>
cpp
#include<iostream>
#include<vector>
using namespace std;
int main() {
//定义一个二维vector,相当于二维数组
vector<vector<int>> matrix = {
{1,2,3},
{4,5,6},
{7,8,9}
};
//遍历输出
for (vector<vector<int>>::iterator it = matrix.begin(); it != matrix.end(); it++) {
//(*it)---> vector<int>
for (vector<int>::iterator vit = (*it).begin(); vit != (*it).end(); vit++){
cout << *vit << " ";
}
cout << endl;
}
return 0;
}
代码解析:
1.vector<vector<int>> matrix;
定义一个存储vector<int>
的vector
,形成二维数据结构。
- 初始化嵌套
vector
直接使用{}填充数据
- 遍历嵌套
vector
第一层遍历行vector<int>
第二层遍历列 int
在示例中插入的是规则矩阵,但可以存放不同长度的vector。如我插入一组数据matrix.push_back({ 2,3 });
matrix.push_back({ 4,5,6,7 });
这都是没有问题的。
vector支持任意类型嵌套,可以作为灵活的数据结构!如:
嵌套vector | 作用 |
---|---|
vector<vector> | 存储二维数据 |
vector<vector> | 存储字符串表 |
vector<vector> | 存储对象表 |
string容器
2.1 string基本概念
string
是C++STL中的字符串容器。本质上是一个封装了char*
的动态字符串类 ,管理堆区分配的字符串数组,提供一系列字符串操作方法。
在底层,std::string
维护了几个重要成员:
char*
指针(指向存储字符串的动态数组)- 长度
size_t size
(字符串的当前大小) - 容量
size_t capacity
(字符串当前分配的最大空间) - 字符存储区(在堆区分配的存储空间)
特点:
- 自动管理内存
- 动态扩展
- 提供丰富的成员函数
- 兼容C风格字符串
- STL兼容性
2.2 string构造函数
1. 默认构造(空字符串)
创建一个空字符串,长度为0,但仍可以动态扩展。
cpp
std::string str;
std::cout << "空字符串:\"" << str << "\"" << std::endl;
输出内容:

2. C风格字符串构造
直接使用C语言的const char*(char[])
初始化std::string
cpp
std::string str1("Hello,World!");
std::cout << "字符串:" << str1 << std::endl;
输出内容:

3. 拷贝构造
用已有的std::string
创建新字符串,内容完全相同。
cpp
std::string str1("Hello,World!");
string str2(str1);
cout << "拷贝构造:" << str2 << endl;
输出内容:

4.指定长度的字符串构造
使用char*
创建字符串,但只取前n个字符。
cpp
string str3("Hello,World!", 5);
cout << "部分截取:" << str3 << endl;
输出内容:

5. 重复字符构造
使用n个相同字符初始化string
。
cpp
string str4(10, 'A');
cout << "重复字符:" << str4 << endl;
输出内容:

6. 迭代器构造(从容器中取一部分字符串)
使用迭代器从original
中截取部分内容。
cpp
string original = "ABCDEFG";
string str5(original.begin() + 2, original.begin() + 5);
cout << "部分字符串:" << str5 << endl;
输出内容:

7. 移动构造
std::move
允许std::string
进行资源转移。
cpp
string tempVar = "Hello";
string str6 = move(tempVar);
cout << "移动后的tempVar:" << tempVar << endl;
cout << "移动到str6:" << str6 << endl;
输出内容:

2.3 string赋值操作
常见的赋值操作包括:
operator =
赋值运算符assign()
赋值函数
1. 赋值运算符operator=
可以将字符串赋值给string
对象,有几种不同的情况:
(1) 直接赋值C风格字符(char*)
将C风格字符串"Hello,World!"
赋值给string
。
cpp
string str;
str = "Hello,World!";
cout << str << endl;
输出内容:

(2) 赋值给另一个string
str2
拷贝str1
的内容,不会影响str1
。
cpp
string str1 = "Hello";
string str2;
str2 = str1;
cout << str2 << endl;
输出内容:

(3) 赋值单个字符
string
支持单个字符赋值(char a='A'
),它会被自动转换为string("A")
。
cpp
string str3;
str3 = 'A';
cout << str3 << endl;
输出内容:

(4) 使用move()进行移动赋值
将str4
的数据转移到str5
,避免拷贝,提高效率。
cpp
string str4 = "Hello";
string str5;
str5 = move(str4);
cout << "str4: " << str4 << endl; // str1 变为空
cout << "str5: " << str5 << endl; // str2 获得 "Hello"
输出内容:

2. assign()赋值函数
assign()
是string
专门提供的赋值函数,可以灵活地从字符串 、字符 、迭代器范围等方式进行赋值。
(1) 赋值C风格字符串
assign()
作用和operator =
一样,但更灵活。
cpp
string str;
str.assign("Hello,World!");
cout << str << endl;
输出内容:

(2) 赋值部分C风格字符串
从char*
开头取n
个字符赋值。
cpp
string str1;
str.assign("Hello,World!", 5);
cout << str1 << endl;
输出内容:

(3) 赋值另一个string
等价于str4=str3
。
cpp
string str3 = "Hello";
string str4;
str4.assign(str3);
cout << str4 << endl;
输出内容:

#### (4) 赋值重复字符
创建5个'A'组成的字符串。
```cpp
string str5;
str5.assign(5, 'A');
cout << str5 << endl;
输出内容:

(5) 赋值迭代器范围
使用迭代器范围截取部分字符串赋值。
cpp
string str6 = "ABCDEFG";
string str7;
str7.assign(str6.begin() + 2, str6.begin() + 5);
cout << str7 << endl;
输出内容:

assign()函数原型
cpp
string& assign(const string & str); // 赋值另一个 string
string& assign(const string & str, size_t pos, size_t len = npos); // 截取部分赋值
string& assign(const char* s); // 赋值 C 风格字符串
string& assign(const char* s, size_t n); // 取 C 字符串的前 n 个字符
string& assign(size_t n, char c); // 赋值 n 个字符 c
template <class InputIterator>
string& assign(InputIterator first, InputIterator last); // 迭代器范围赋值
2.4 string字符串拼接
在C++string容器中,字符串拼接有多种方式,主要包括:
- 使用
+
运算符 - 使用
+=
运算符 - 使用
append()
函数
1.使用+运算符
+
用于拼接两个字符串,可以拼接string
和char*
,但不能char
和string
直接相加。
示例:
cpp
string str1 = "I";
string str2 = " love";
string result1 = str1 + str2; //拼接两个字符串
cout << result1 << endl;
string result2 = result1 + " you!";//拼接string和C字符串
cout << result2 << endl;
输出结果:

注意:string
不能直接拼接单个字符。
错误示例:
cpp
string result3 = "Hello" + 'A';
cout << result3 << endl;
输出结果:

为什么?这是因为代码中存在类型不匹配和指针运算(指针偏移)问题。
问题分析:
"Hello"
的类型:"Hello"
是一个C风格字符串(const char*
类型)。'A'
的类型:'A'
是一个字符(char
类型),其ASCll值为65。"Hello"+'A'
实际上等价于:const char* ptr ="Hello"+65
;
也就是说,它让"Hello"
的指针向后移动了65个字节,但"Hello"
实际上只有6个字节(包括\0
),因此访问到了随机的内存地址,导致了未定义行为(可能输出Fancyptr
或者是其他的值,也可能崩溃)
正确做法:
string result3 =string("Hello")+'A';
//显示转换为string再拼接
或者
string result3 = "Hello"+string(1,'A');
//先把'A'变成string再拼接
2.使用+=运算符
+=直接在原字符串上追加内容,可以拼接string
、char*
和char
。
示例:
cpp
string str = "Closed off";
str += " from love"; //拼接C风格字符串
cout << str << endl;
str += '!';//追加单个字符
cout << str << endl;
输出内容:

注意事项:不能拼接数字类型。
错误示范:str += 123;
那么会输出什么?

为什么会输出一个'{'?
关键问题:
123是整数(int
),但string
的operator+=
没有定义直接接收int
作为参数。因此,C++
试图找到一个兼容的重载,但没有匹配的string::operator+=(int)
。隐式转换发生了,int
被转换成char
,而char(123)
的ASCll码对应的字符是'{
'。
正确做法:
str += to_string(123);
//先转换为字符串
输出内容:

不要忘记头文件#include<string>
3.使用append()函数
append()
适用于更复杂的拼接需求,支持拼接部分字符串、指定数量字符等。
- 追加整个字符串
cpp
string str = "I didn't";
str.append(" need the pain");
cout << str << endl;
输出内容:

- 追加部分字符串
cpp
string str1 = "Once or twice";
str1.append(" was enough and", 11);//只追加前11个字符
cout << str1 << endl;
输出内容:

- 追加重复字符
cpp
string str2 = "and it was all vain";
str2.append(3, '!');//追加3个'!'
cout << str2 << endl;
输出内容:

- 追加另一个string的部分内容
cpp
string str3 = "Time starts to";
string str4 = " pass before";
str3.append(str4, 0, 5);//追加str4的前5个字符
cout << str3 << endl;
输出内容:

string::append()函数原型
cpp
string& append(const string & str); // 追加整个字符串
string& append(const string & str, size_t subpos, size_t sublen); // 追加 str 的一部分
string& append(const char* s); // 追加 C 风格字符串
string& append(const char* s, size_t n); // 追加 C 字符串前 n 个字符
string& append(size_t n, char c); // 追加 n 个字符 c
template <class InputIterator>
string& append(InputIterator first, InputIterator last); // 追加迭代器范围内容
2.5 string查找和替换
在C++string容器中,我们可以使用一系列成员函数来查找子字符串的位置,并进行替换。
1.find()--查找子字符串
find()
用于查找子字符串在当前string
对象中的起始位置。
函数原型
cpp
size_t find(const string & str, size_t pos = 0) const;
size_t find(const char* s, size_t pos = 0) const;
size_t find(char ch, size_t pos = 0) const;
str/s
:要查找的字符串或字符
pos
:从字符串的pos位置 开始查找(默认为0)
返回值:
- 找到:返回子串第一个匹配字符的索引
- 未找到:返回
string::npos
示例
cpp
string str = "Before you know it you're frozen";
//查找子字符串"know"
size_t pos = str.find("know");
if (pos != string::npos)
cout << "找到\"know\",位置:" << pos << endl;
else
cout << "未找到\"know\"" << endl;
//查找字符'o',从索引5开始查找
pos = str.find('o', 5);
cout << "找到'o',位置:" << pos << endl;
输出结果

在上述例子有看到使用的类型是size_t,那么这个是否可以替换为int?那就要先说一说它们之间的区别:
size_t
是无符号整数类型,通常表示内存大小、数值索引等非负值。int
是有符合整数,可以存储负数、范围更小。
那么可以替换吗?
也不建议。虽然在小型程序中,如果没有太大的字符串(最大索引不超过int
范围),那么可能不会导致错误。如果找到了,那没什么问题,但若没找到,find()
函数返回的是std::string::npos
,是一个特殊的常量,它的类型是size_t
,通常是一个非常大的无符号整数,在32为系统通常是(2^32-1),其二进制表示与-1的二进制补码相同。那么如果用int
回被表示成-1。在64位系统,如果赋值给int,会导致溢出,结果是未定义的。
2. rfind()--逆向查找
与find()
相同,但从字符串的末尾向前查找,返回最后一次出现的位置。
示例
cpp
string str1 = "But something";
size_t pos1 = str1.rfind('n');
cout << "最后一次'n'出现的位置:" << pos1 << endl;
输出

3.find_first_of()--查找多个字符中的第一个匹配项
查找字符串中任意一个字符首次出现的位置
示例
cpp
string str2 = "happened for";
size_t pos2 = str2.find_first_of("pe");
cout << "字符 'p' 或 'e' 第一次出现的位置:" << pos2 << endl;
输出

4.find_last_of()--逆向查找多个字符
查找字符串中任意一个字符最后一次出现的位置。
示例
cpp
string str3 = "the very firt time with you";
size_t pos3 = str.find_first_of("ry");
cout<<"字符 'r' 或 'y' 最后一次出现的位置: " << pos3 << endl;
输出

5. replace()--替换子字符串
可以用新字符串替换已有字符串的某部分。
函数原型
cpp
string& replace(size_t pos, size_t len, const string & str);
string& replace(size_t pos, size_t len, const char* s);
pos
:要替换的起始位置len
:要替换的长度str/s
:用于替换的新字符串
示例
cpp
string str4 = "My liver melted to the ground";
//替换"liver"为"heart"
str4.replace(str4.find("liver"), 5, "heart");
cout << str4 << endl;
输出

6. erase()--删除子字符串
可以删除string中的某部分内容
函数原型
string& erase(size_t pos = 0, size_t len = npos);
pos
:删除的起始位置len
:删除的字符数(默认为npos,即删除到末尾)
示例
cpp
string str5 = "Founddd something true";
str5.erase(5, 2);
cout << str5 << endl;
输出

2.6 string字符串比较
在C++中,string
提供了多种比较方式。主要比较方式包括:
- 使用==、!=、<、>等运算符
- 使用
compare()
函数进行字符串比较
1.运算符比较
C++string
类重载了"==、!=、<、>、<=、>="这些运算符,比较方式如下:
- 按ASCll码顺序逐字符比较
- 若某个字符不同,则比较ASCll码的大小
- 若前缀相同,则短字符串小于长字符串。
示例
cpp
//运算符比较
string str1 = "apple";
string str2 = "banana";
string str3 = "apple";
//比较相等
cout << (str1 == str2) << endl; //0(false)
cout << (str1 == str3) << endl; //1(true)
//比较不相等
cout << (str1 != str2) << endl; //1(true)
//大小比较(按ASCll码表顺序)
cout << (str1 < str2) << endl; //1(true)
cout << (str1 > str2) << endl; //0(false)
输出

2. compare()方法
string::compare()
方法提供了更灵活的字符串比较,它返回一个整数值:
0
:两个字符串相等<0
:当前字符串小于目标字符串>0
:当前字符串大于目标字符串
compare()语法:
int compare(const string & str) const;
int compare(size_t pos, size_t len, const string & str) const;
int compare(size_t pos, size_t len, const string & str, size_t subpos, size_t sublen) const;
1. 直接比较两个字符串:
cpp
string str4 = "stand";
string str5 = "apart";
string str6 = "stand";
cout << str4.compare(str5) << endl;//正数
cout << str4.compare(str6) << endl;//0
cout << str5.compare(str4) << endl;//负数
输出

2.只比较字符串的一部分
cpp
string str7 = "abcdef";
string str8 = "cde";
// 比较 str7[2] 开始的3个字符 ("cde") 和 str8 ("cde")
cout << str7.compare(2, 3, str8) << endl; // 0,相等
// 比较 str7[2] 开始的3个字符 ("cde") 和 "cdf"
cout << str7.compare(2, 3, "cdf") << endl; // 负数,因为 'e' < 'f'
输出

2.7 string字符串访问和修改
1.访问字符串中的字符
1.使用[]下标访问
string
允许像数组一样使用[]访问字符:
cpp
string str = "parse";
cout << str[0] << endl; //p
cout << str[4] << endl; //e
输出
[]
不会进行范围检查,如果访问超出范围,可能会导致未定义行为。
2.使用at()方法
at()
方法与[]
类似,但它会检查索引是否超出范围,如果超出,则抛出out_of_range
异常。
cpp
string str = "parse";
cout << str.at(1) << endl;//a
cout << str.at(10) << endl;//抛出异常
输出

异常:_Xout_of_range("invalid string position");
3.访问首尾字符
cpp
string str = "parse";
cout << "首字符:" << str.front() << endl;
cout << "尾字符:" << str.back() << endl;
输出

2.修改字符串
使用[]
或at()
修改字符。
cpp
string str = "parse";
str[0] = 'H';//修改第一个字符
str.at(4) = 'h';//使用at()修改
cout << str << endl;
输出

2.8 string插入和删除
1.插入字符
insert(pos,string)
insert(pos,string)
方法用于在pos
位置插入字符串:
cpp
string str = "thoug";
str.insert(5, "ht");//在索引5处插入"ht"
cout << str << endl;
输出
insert(pos,n,char)
在pos
位置插入n个相同字符:
cpp
string str = "thought";
str.insert(7, 3, '!');
cout << str << endl;
输出

2.删除字符
erase(pos,len)
从pos
开始删除len
个字符:
cpp
string str1 = "Just do it";
str1.erase(4, 6);//从索引4开始删除6个字符
cout << str1 << endl;
输出
pop_back()
删除字符串的最后一个字符:
cpp
string str2 = "Just do it!";
str.pop_back();//删除最后一个字符
cout << str << endl;
输出

2.9 获取string子串
1.substr()
方法
函数原型:
string substr(size_t pos = 0, size_t len = npos) const;
pos
:子串的起始位置len
:要截取的字符数量(默认npos
表示截取到末尾)- 返回值:返回指定范围的新字符串,不会修改原字符串
2.substr()基本用法
示例1:获取指定范围的子串
cpp
string str = "Hello, World!";
string sub1 = str.substr(7, 5); //从索引7开始,截取5个字符
cout << sub1 << endl; //World
输出

示例2:截取从某个位置到结尾的子串
如果不提供len
,则默认从pos
开始一直截取到字符串末尾。
cpp
string str = "C++ STL is powerful";
string sub2 = str.substr(6); //从索引6开始,一直到末尾
cout << sub2 << endl; //STL is powerful
输出

示例3:获取整个字符串(等于拷贝)
如果pos =0
,且len = npos
,等价于复制整个字符串:
cpp
string str = "Hello,C++!";
string copy_str = str.substr(); //复制整个字符串
cout << copy_str << endl;
输出

3.substr()
与find()
结合使用
可以先使用find()
定位子串的位置,再用substr()
提取。
cpp
string str = "Welcome to C++ programming";
//找到C++在字符串中的位置
size_t pos = str.find("C++");
//提取"C++"及后面的内容
string sub = str.substr(pos);
cout << sub << endl;
输出
