标准库
😏1. 标准库介绍
C++ 标准库是 C++ 语言提供的标准库,它包含了多个模块(头文件),每个模块提供了不同的功能。
C++ 标准模板库(STL,Standard Template Library)是 C++ 的一部分,提供了一组通用的模板类和函数,用于实现常见的数据结构和算法。
STL主要包含以下三个组件:
-
容器(Containers):
容器是STL中用于存储和管理数据的类模板。STL提供了各种不同类型的容器,包括动态数组(vector)、双向链表(list)、队列(queue)、栈(stack)、集合(set)、映射(map)等。每种容器都具有不同的特点和适用场景,开发人员可以根据需要选择合适的容器来存储和操作数据。 -
算法(Algorithms):
算法是STL中用于处理容器中数据的函数模板。STL提供了大量的算法,包括查找、排序、合并、替换、计数等。这些算法实现了常见的数据处理操作,并且对于多数情况下都有高效的实现。开发人员可以通过简单地调用这些算法,而无需自己实现复杂的数据处理逻辑。 -
迭代器(Iterators):
迭代器是STL中用于遍历容器中元素的抽象概念。通过使用迭代器,开发人员可以在不关心具体容器实现的情况下,对容器中的元素进行迭代和访问。STL提供了多种类型的迭代器,包括输入迭代器、输出迭代器、正向迭代器、双向迭代器和随机访问迭代器。不同类型的迭代器支持不同的操作和功能,开发人员可以根据需要选择适合的迭代器。
STL的优点有:
- 可重用性:STL提供了通用的数据结构和算法,可以在不同的项目和场景中重复使用,避免了重复编写相似的代码。
- 高效性:STL中的容器和算法都经过了优化,具有高效的实现。STL使用了模板和内联函数等技术,在编译时生成高效的代码。
- 可扩展性:STL支持用户自定义类型的容器和算法,可以根据实际需求进行扩展和定制。
- 代码可读性和可维护性:STL提供了一致的接口和命名规范,使得代码更易于理解和维护。同时,STL中的容器和算法都经过了广泛测试和验证,具有较高的可靠性。
两个在线学习地址:
https://cui-jiacai.gitbook.io/c++-stl-tutorial/
https://zoupers.gitbook.io/cpp-17-stl-cookbook/
😊2. 常用容器模块
容器主要有序列容器(array、vector、dequeue、forward_list、list)和关联容器(map、set、multimap、multiset),分别用于不同的数据存储和访问方式。
string:字符串,抽象char*
std::string str("hello");
// 后面追加
str.append(" world");
// 前面插入
str.insert(0, "ok,");
// 截取子字符串
str = str.substr(3);
printf("sub str=%s\n", str.c_str());
// 空格替换为逗号
for (int i = 0; i < str.size(); ++i) {
char ch = str.at(i);
if (ch == ' ') {
str.replace(i, 1, 1, ',');
}
}
// 后面添加字符
str.push_back('!');
// 删除指定位置的字符
str.erase(str.size() - 1);
size_t pos = str.find("world");
printf("find pos=%ld\n", pos);
// 判断字符串是否相等,== 属于操作符重载
if (str == "hello,world") {
// ...
}
array静态连续数组
#include <iostream>
#include <array>
using namespace std;
int main() {
// 静态连续数组
array<int, 5> a = {1, 2, 3, 4, 5};
cout << a[0] << endl;
cout << a.at(1) << endl; // error:at(20)
// 初始化数组
array<int, 10> a1;
a1.fill(10);
for (auto &n : a1)
{
cout << n << endl;
}
// 使用迭代器遍历
array<int, 10>::iterator it = a1.begin(); // auto it
for (; it != a1.end(); it++)
{
cout << *it << endl;
}
// array也可创建自定义数据类型
return 0;
}
vector:动态数组,支持快速随机访问。
#include <iostream>
#include <vector>
#include <array>
using namespace std;
int main()
{
std::vector<int> demo{ 1,2 };
demo.push_back(1); //push_back操作
std::vector<int> test{ 3,4,5 };
demo.insert(demo.end(), test.begin(), test.end()); //inset操作
// 循环打印值
for (int i = 0; i < demo.size(); i++) {
cout << demo[i] << " ";
}
// 使用迭代器 iterator 访问值
vector<int>::iterator v = demo.begin();
while (v != demo.end()) {
cout << "value of v = " << *v << endl;
v++;
}
return 0;
}
#include <iostream>
#include <vector>
using namespace std;
int main() {
vector<int> vec(3, 100);
vec.reserve(6); // 预留5个位置
vec.push_back(10);
cout << (uintptr_t)vec.data() << endl;
vec.push_back(110); // insert
cout << (uintptr_t)vec.data() << endl;
vec.emplace_back(120); // emplace原位构造,复杂数据
cout << (uintptr_t)vec.data() << endl;
cout << vec.size() << endl;
cout << vec.capacity() << endl;
for (auto &n : vec)
{
cout << n << "\t";
}
return 0;
}
list:双向链表,支持高效的插入和删除操作。
#include <iostream>
#include <list>
int main() {
// 创建一个空的list容器
std::list<int> myList;
// 在容器尾部插入元素
myList.push_back(10);
myList.push_back(20);
myList.push_back(30);
// 在容器头部插入元素
myList.push_front(5);
// 使用迭代器遍历输出容器中的元素
std::cout << "List elements: ";
for (auto it = myList.begin(); it != myList.end(); ++it) {
std::cout << *it << " ";
}
std::cout << std::endl;
// 在指定位置插入元素
auto insertPos = std::find(myList.begin(), myList.end(), 20);
if (insertPos != myList.end()) {
myList.insert(insertPos, 15);
}
// 使用迭代器反向遍历输出容器中的元素
std::cout << "Reversed List elements: ";
for (auto rit = myList.rbegin(); rit != myList.rend(); ++rit) {
std::cout << *rit << " ";
}
std::cout << std::endl;
// 移除指定值的元素
myList.remove(10);
// 清空容器
myList.clear();
return 0;
}
forward_list单向链表
单向链表迭代器只能做自增,不能与数字相加减,也不能两个迭代器相减。
#include <iostream>
#include <forward_list>
using namespace std;
// 一个元素返回true时移除对应元素
bool pre(const int &val)
{
return val > 3; // 移除大于3的元素
}
int main() {
forward_list<int> fls = {5, 6, 2, 3, 1};
forward_list<int> fls2 = {0, 4, 17, 12, 15,18};
// 升序排序/降序排序
fls.sort(); // fls.reverse();
fls2.sort();
// 移除元素
fls.remove(3);
fls.remove_if(pre);
// 也可以用lambda表达式
fls.remove_if([](const int &val) { return val > 3; });
// 合并
fls2.merge(fls);
for (auto &v : fls2)
{
cout << v << "\t\t";
}
return 0;
}
queue:队列
std::queue<int> queue;
// 入队
queue.push(111);
queue.push(222);
queue.push(333);
printf("queue front=%d, back=%d", queue.front(), queue.back());
// 出队
while (!queue.empty()) {
int front = queue.front();
queue.pop();
}
#include <iostream>
#include <queue>
using namespace std;
int main()
{
queue<const char *> q;
// 入队,如果是复合数据类型,用emplace就地构造代替push入队
q.push("he");
q.push("ll");
q.push("o");
// 出队
while (!q.empty())
{
const char *name = q.front(); // 先获取队首元素
q.pop(); // 将队首元素出队
cout << name << "已出队,感觉良好。队里还有" << q.size() << "个元素" << endl;
}
return 0;
}
deque:双端队列,支持首尾插入和删除操作
在队列两端都可以进行操作,也可以进行随机下标访问。其操作基本上与std::vector一样,比std::vector多了在头部进行插入和移除的操作。
一般来说,std::vector用在需要频繁进行随机下标访问的场景,如果需要频繁在头部和尾部进行插入和删除操作,则用std::deque。
#include <iostream>
#include <deque>
int main() {
// 创建一个空的deque容器
std::deque<int> myDeque;
// 在容器尾部插入元素
myDeque.push_back(10);
myDeque.push_back(20);
myDeque.push_back(30);
// 在容器头部插入元素
myDeque.push_front(5);
// 使用索引访问和输出容器中的元素
std::cout << "Deque elements: ";
for (size_t i = 0; i < myDeque.size(); ++i) {
std::cout << myDeque[i] << " ";
}
std::cout << std::endl;
// 在指定位置插入元素
auto insertPos = myDeque.begin() + 2;
myDeque.insert(insertPos, 15);
// 使用迭代器遍历输出容器中的元素
std::cout << "Deque elements after insertion: ";
for (auto it = myDeque.begin(); it != myDeque.end(); ++it) {
std::cout << *it << " ";
}
std::cout << std::endl;
// 移除指定位置的元素
auto erasePos = myDeque.begin() + 1;
myDeque.erase(erasePos);
// 使用迭代器反向遍历输出容器中的元素
std::cout << "Reversed Deque elements: ";
for (auto rit = myDeque.rbegin(); rit != myDeque.rend(); ++rit) {
std::cout << *rit << " ";
}
std::cout << std::endl;
// 清空容器
myDeque.clear();
return 0;
}
set:集合,存储唯一值,并按照一定的排序规则进行自动排序。
#include <iostream>
#include <set>
int main() {
// 创建一个空的set容器
std::set<int> mySet;
// 向容器中插入元素
mySet.insert(10);
mySet.insert(20);
mySet.insert(20); // 重复的元素将被忽略
mySet.insert(30);
// 判断元素是否存在
if (mySet.find(20) != mySet.end()) {
std::cout << "Element 20 is found in the set." << std::endl;
} else {
std::cout << "Element 20 is not found in the set." << std::endl;
}
// 输出容器中的元素个数
std::cout << "Size of set: " << mySet.size() << std::endl;
// 使用范围循环遍历输出容器中的元素
std::cout << "Set elements: ";
for (const auto& element : mySet) {
std::cout << element << " ";
}
std::cout << std::endl;
// 移除元素
int key = 10;
mySet.erase(key);
// 检查容器是否为空
if (mySet.empty()) {
std::cout << "Set is empty." << std::endl;
} else {
std::cout << "Set is not empty." << std::endl;
}
return 0;
}
map:映射,存储键值对,按照键的大小进行自动排序。
#include <iostream>
#include <map>
#include <string>
using namespace std;
int main()
{
map<string,int> phone_book; // 创建一个map类容器,用于存储电话号码簿
// 创建电话簿
phone_book["zhangsan"] = 12345678; // 通过[]操作和关键字往容器中加入元素
phone_book["lisi"] = 87654321;
phone_book["wanger"] = 56781234;
phone_book.insert(std::pair<string, int("zhangtian", 65458997));
// ......
// 输出电话号码簿
cout << "电话号码簿的信息如下:\n";
for (pair<string, int item: phone_book) {
// for (auto it = phone_book,begin(); it != phone_book.end(); it++)
// C++11中引入的enhanced-for loop
cout << item.first << ": " << item.second << endl;
// 输出元素的姓名和电话号码
}
// 查找某个人的电话号码
string name;
cout << "请输入要查询号码的姓名:";
cin >> name;
map<string,int::const_iterator it; // 创建一个不能修改所指向的元素的迭代器
it = phone_book.find(name); // 查找关键字为name的容器元素
if (it == phone_book.end()) // 判断是否找到
cout << name << ": not found\n"; // 未找到
else
cout << it-first << ": " << it-second << endl; // 找到
return 0;
}
unordered_set:无序不重复元素集合,存储唯一值,并提供常数时间的查找操作。
#include <iostream>
#include <unordered_set>
int main() {
// 创建一个 unordered_set
std::unordered_set<int> mySet;
// 插入元素
mySet.insert(10);
mySet.insert(20);
mySet.insert(30);
// 查找元素
if (mySet.find(20) != mySet.end()) {
std::cout << "Element 20 found in set" << std::endl;
} else {
std::cout << "Element 20 not found in set" << std::endl;
}
// 删除元素
mySet.erase(10);
// 遍历集合
for (const auto& elem : mySet) {
std::cout << elem << " ";
}
std::cout << std::endl;
return 0;
}
unordered_map:无序映射,存储键值对,并提供常数时间的查找操作。
#include <iostream>
#include <unordered_map>
int main() {
// 创建一个 unordered_map,将字符串映射到整数
std::unordered_map<std::string, int> myMap;
// 插入键值对
myMap["apple"] = 5;
myMap["banana"] = 10;
myMap["orange"] = 7;
// 查找元素
if (myMap.find("banana") != myMap.end()) {
std::cout << "Price of banana: " << myMap["banana"] << std::endl;
} else {
std::cout << "Banana not found in map" << std::endl;
}
// 修改元素
myMap["orange"] = 8;
// 遍历 map
for (const auto& pair : myMap) {
std::cout << pair.first << ": " << pair.second << std::endl;
}
return 0;
}
😆3. 常用算法模块
sort:对容器进行排序。
std::vector<int array{3, 6, 1, 5, 9, 2, 8};
std::sort(array.begin(), array.end());
for (int it: array) {
printf("%d ", it);
}
find:在容器中查找指定元素。
#include <vector>
#include <algorithm>
#include <iostream>
using namespace std;
int main()
{
int a[10] = { 10,20,30,40 };
vector<int> v;
v.push_back(1); v.push_back(2);
v.push_back(3); v.push_back(4); //此后v里放着4个元素:1,2,3,4
// 定义迭代器,用find查找
vector<int>::iterator p;
p = find(v.begin(), v.end(), 3); //在v中查找3
if (p != v.end()) //若找不到,find返回 v.end()
cout << "1. " << *p << endl; //找到了
p = find(v.begin(), v.end(), 9);
if (p == v.end())
cout << "not found " << endl; //没找到
p = find(v.begin() + 1, v.end() - 1, 4); //在,3 这两个元素中查找4
cout << "2. " << *p << endl;
int * pp = find(a, a + 4, 20);
if (pp == a + 4)
cout << "not found" << endl;
else
cout << "3. " << *pp << endl;
return 0;
}
binary_search:二分查找
bool result = std::binary_search(array.begin(), array.end(), 8);
remove:从容器中移除指定值。
transform:对容器中的元素应用某个操作并存储结果。
accumulate:计算容器中元素的累加值。
count:计算容器中满足条件的元素个数。
reverse:反转容器中的元素顺序。
std::reverse(array.begin(), array.end());
replace:替换
std::replace(array.begin(), array.end(), 6, 666);
😆4. 其他模块
函数对象(Function Objects)
STL提供了函数对象类模板,允许用户自定义函数对象,即重载函数自定义运算符 operator() 的对象(也称为仿函数),以便在算法中使用。
函数对象是一个行为类似于函数的对象,可以重载函数调用运算符 operator()()。
使用函数对象可以实现更加灵活的算法操作,包括自定义的排序规则、条件判断等。
#include <iostream>
// 定义一个函数对象类
class MyFunctor {
public:
void operator()(int x) const {
std::cout << "Square of " << x << " is: " << x * x << std::endl;
}
};
int main() {
// 创建函数对象实例
MyFunctor myFunctor;
// 使用函数对象调用它的 operator() 方法
myFunctor(5); // 输出:Square of 5 is: 25
// 可以直接创建临时函数对象并调用
MyFunctor()(8); // 输出:Square of 8 is: 64
return 0;
}
适配器(Adapters)
STL提供了适配器类模板,用于将容器或迭代器的接口进行适配或扩展,以满足特定的需求。
常见的适配器有 stack、queue、priority_queue
,它们在底层使用了不同的容器实现,并且提供了特定的接口和功能。
#include <iostream>
#include <stack>
int main() {
std::stack<int> myStack;
// 将元素压入栈中
myStack.push(10);
myStack.push(20);
myStack.push(30);
// 访问栈顶元素
std::cout << "Top element of stack: " << myStack.top() << std::endl;
// 弹出栈顶元素
myStack.pop();
// 检查栈是否为空
if (myStack.empty()) {
std::cout << "Stack is empty" << std::endl;
} else {
std::cout << "Stack is not empty" << std::endl;
}
return 0;
}
#include <iostream>
#include <stack>
#include <string>
using namespace std;
int main()
{
stack<string> str_stack;
// 入栈, 如果是复合数据结构,用emplace就地构造代替push入栈
str_stack.push("H");
str_stack.push("e");
str_stack.push("l");
str_stack.push("lo");
// 出栈
while(!str_stack.empty())
{
string str = str_stack.top(); // 先用top获取到栈顶元素
str_stack.pop(); // 弹出栈顶元素
cout << str << "--已出栈,感觉良好。栈里还有" << str_stack.size() << "个元素" << endl;
}
return 0;
}
priority_queue优先队列
可以根据优先级的高低确定出队顺序的数据结构。如果是复合数据类型,需要提供比较函数或者重载<运算符。
#include <iostream>
#include <queue>
int main() {
// 创建一个最大堆优先队列,存储 int 类型的元素
std::priority_queue<int> maxHeap;
// 添加元素到优先队列中
maxHeap.push(3);
maxHeap.push(5);
maxHeap.push(1);
maxHeap.push(7);
maxHeap.push(2);
// 打印优先队列中的元素(不会按顺序打印)
std::cout << "Elements in priority queue:" << std::endl;
while (!maxHeap.empty()) {
std::cout << maxHeap.top() << " ";
maxHeap.pop(); // 弹出队首元素
}
std::cout << std::endl;
return 0;
}
分配器(Allocators)
STL允许用户自定义内存分配器,用于控制容器内部的内存管理和分配策略。
用户可以通过实现自己的分配器类来满足特定的内存管理需求,例如内存池、定制的内存分配策略等。
#include <iostream>
#include <vector>
#include <memory>
int main() {
// 创建使用 std::allocator 的 vector
std::vector<int, std::allocator<int>> myVector;
// 向 vector 添加元素
for (int i = 1; i <= 5; i++) {
myVector.push_back(i);
}
// 遍历并打印 vector 中的元素
for (const auto& element : myVector) {
std::cout << element << " ";
}
std::cout << std::endl;
return 0;
}
迭代器模块(Iterator Tags)
STL中引入了迭代器标签的概念,用于表示迭代器的类型和特性。
迭代器标签包括输入迭代器、输出迭代器、前向迭代器、双向迭代器和随机访问迭代器,用于指定迭代器支持的操作和功能。
-
输入迭代器(Input Iterators):只读访问容器中的元素。
-
输出迭代器(Output Iterators):只写访问容器中的元素。
-
正向迭代器(Forward Iterators):支持读写容器中的元素,并能够向前遍历。
-
双向迭代器(Bidirectional Iterators):支持正向和逆向遍历。
-
随机访问迭代器(Random Access Iterators):支持任意位置的高效随机访问。
元编程技术(Metaprogramming Techniques)
STL广泛使用元编程技术,包括模板特化、模板偏特化、模板元编程、SFINAE(Substitution Failure Is Not An Error)等。 元编程技术可以在编译期间进行计算和代码生成,以提高代码的效率和灵活性。
#include <iostream>
template <int N>
struct Fibonacci {
static const int value = Fibonacci<N - 1>::value + Fibonacci<N - 2>::value;
};
template <>
struct Fibonacci<0> {
static const int value = 0;
};
template <>
struct Fibonacci<1> {
static const int value = 1;
};
int main() {
const int result = Fibonacci<10>::value;
std::cout << "Fibonacci number at position 10 is: " << result << std::endl;
return 0;
}
异步操作
异步操作
是一种编程模型,用于处理任务的非阻塞执行和事件驱动。在传统的同步操作中,程序会等待一个任务完成后才继续执行下一个任务,而在异步操作中,任务可以在后台执行,程序可以继续执行其他任务而无需等待当前任务完成。
使用C++11提供的std::async
和std::future
来实现异步任务示例:
#include <iostream>
#include <future>
// 异步任务
int asyncTask(int input) {
// 模拟耗时的操作
std::this_thread::sleep_for(std::chrono::seconds(2));
return input * 2;
}
int main() {
// 启动异步任务,并获取 std::future 对象
std::future<int> futureResult = std::async(std::launch::async, asyncTask, 5);
// 在主线程中进行其他操作...
// 获取异步任务的结果
int result = futureResult.get();
std::cout << "异步任务的结果为:" << result << std::endl;
return 0;
}
此外,还包含memory内存管理库、thread多线程库、chrono时间库等。
数组
栈和队列
基本计算器(表达式求值)
class Solution {
public:
int calculate(string s) {
// 用栈stack这种类型 只有加减的表达式
stack<int> ops;
ops.push(1);
int sign = 1;
int ret = 0;
int n = s.length();
int i = 0;
while (i < n) {
if (s[i] == ' ') {
i++;
} else if (s[i] == '+') {
sign = ops.top();
i++;
} else if (s[i] == '-') {
sign = -ops.top();
i++;
} else if (s[i] == '(') {
ops.push(sign);
i++;
} else if (s[i] == ')') {
ops.pop();
i++;
} else {
long num = 0;
while (i < n && s[i] >= '0' && s[i] <= '9') {
num = num * 10 + s[i] - '0';
i++;
}
ret += sign * num;
}
}
return ret;
}
};
单调栈
优先队列
双端队列
滑动窗口最大值
#include <iostream>
#include <vector>
#include <deque>
using namespace std;
vector<int> slidingWindowMax(const vector<int>& nums, int k) {
vector<int> result;
deque<int> dq;
for (int i = 0; i < nums.size(); ++i) {
// 在队尾保持递减顺序
while (!dq.empty() && nums[i] >= nums[dq.back()]) {
dq.pop_back();
}
dq.push_back(i);
// 如果队头元素已不在窗口范围内,弹出
if (!dq.empty() && dq.front() <= i - k) {
dq.pop_front();
}
// 将窗口内的最大值加入结果
if (i >= k - 1) {
result.push_back(nums[dq.front()]);
}
}
return result;
}
int main() {
vector<int> nums = {1, 3, -1, -3, 5, 3, 6, 7};
int k = 3;
vector<int> result = slidingWindowMax(nums, k);
cout << "滑动窗口最大值:";
for (int num : result) {
cout << num << " ";
}
cout << endl;
return 0;
}
哈希表
多重集合和映射
以上。