接上一篇知识。 ## C++标准库容器 ### std::vector
*
std::vector
是一种动态数组,可以根据需要自动调整大小,能够存储任意类型的元素。 ####
向向量中添加元素 * push_back
和 emplace_back
都用于向向量中添加元素。 * emplace_back
通常比
push_back
更高效,因为它直接在内存中创建元素,而不需要先构造一个临时对象然后再移动。
1 2
| point_vector.push_back(Point(35, 36)); point_vector.emplace_back(37, 38);
|
#### 遍历向量 * 直接/用下标遍历元素
1 2 3 4 5
| std::cout << "Printing the items in point_vector:\n"; for (size_t i = 0; i < point_vector.size(); ++i) { point_vector[i].PrintPoint(); }
|
#### 删除向量中的元素 1 2
| int_vector.erase(int_vector.begin() + 2); int_vector.erase(int_vector.begin() + 1, int_vector.end());
|
#### 使用
std::remove_if
删除符合条件的元素 * remove_if
会将符合条件的元素移动到容器末尾,返回一个指向第一个符合条件元素的迭代器,然后通过
erase
实际删除这些元素。 1 2 3 4
| point_vector.erase( std::remove_if(point_vector.begin(), point_vector.end(), [](const Point &point) { return point.GetX() == 37; }), point_vector.end());
|
###
std::set
* std::set
是一个存储唯一元素的容器,且元素会自动按顺序排列。通常,std::set
使用红黑树来实现,确保元素在插入时能够保持有序。
* 支持插入、查找、删除等基本操作; *
不支持按索引访问,只能通过迭代器遍历 #### 插入元素 *
通过 insert
和 emplace
两种方式向
int_set
集合中插入元素。 * 其中 emplace
允许直接构造对象,而不需要先创建临时对象。 1 2 3 4 5 6 7 8 9
| for (int i = 1; i <= 5; ++i) { int_set.insert(i); }
for (int i = 6; i <= 10; ++i) { int_set.emplace(i); }
|
#### 查找元素 *
使用 find
函数查找元素,返回的是一个迭代器:如果找到了元素,返回的迭代器不等于
end()
,否则等于 end()
。 1 2 3 4
| std::set<int>::iterator search = int_set.find(2); if (search != int_set.end()) { std::cout << "Element 2 is in int_set.\n"; }
|
####
计数元素 * count
函数用于查找某个元素的个数,在
std::set
中,每个元素最多只能出现一次,所以返回值要么是
0(元素不存在),要么是 1(元素存在)。 #### 删除元素
erase(int_set.find(9), int_set.end())
用于删除从元素 9
开始直到集合末尾的所有元素。 *
由于set
有序,即为:删除大于等于9的元素 #### 遍历集合
1 2 3 4 5 6 7 8 9
| for (std::set<int>::iterator it = int_set.begin(); it != int_set.end(); ++it) { std::cout << *it << " "; } std::cout << "\n";
std::cout << "Printing the elements of the iterator with a for-each loop:\n"; for (const int &elem : int_set) { std::cout << elem << " "; }
|
### std::unordered_map
*
std::unordered_map
是一个哈希表实现的容器,用于存储键值对;每个键是唯一的;且容器中的元素不保证按任何特定顺序排列,而是根据哈希值分布存储。
* 查找、插入和删除操作的平均时间复杂度为常数级别 *
支持插入、查找、删除和迭代操作 1 2 3 4
| #include <iostream> #include <unordered_map> #include <string> #include <utility>
|
####
插入元素 * std::make_pair
用于创建一个键值对。
1 2 3 4
| map.insert({"foo", 2}); map.insert(std::make_pair("jignesh", 445)); map.insert({{"spam", 1}, {"eggs", 2}, {"garlic rice", 3}}); map["bacon"] = 5;
|
#### 查找元素 *
通过迭代器可以访问到键值对,result->first
获取键,result->second
获取值 1 2 3 4
| std::unordered_map<std::string, int>::iterator result = map.find("jignesh"); if (result != map.end()) { std::cout << "Found key " << result->first << " with value " << result->second << std::endl; }
|
###
使用auto
关键字 * auto
关键字用于告诉编译器根据变量的初始化表达式来推断变量的类型。这样,开发者不需要显式地指定类型,编译器会根据右侧的赋值表达式自动推导类型。这对于复杂的类型声明,尤其是模板类或容器类型,非常有用。
#### 复杂类型的推导示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| template <typename T, typename U> class Abcdefghijklmnopqrstuvwxyz { public: Abcdefghijklmnopqrstuvwxyz(T instance1, U instance2) : instance1_(instance1), instance2_(instance2) {}
void print() const { std::cout << "(" << instance1_ << "," << instance2_ << ")\n"; }
private: T instance1_; U instance2_; };
template <typename T> Abcdefghijklmnopqrstuvwxyz<T, T> construct_obj(T instance) { return Abcdefghijklmnopqrstuvwxyz<T, T>(instance, instance); }
int main() { Abcdefghijklmnopqrstuvwxyz<int, int> obj = construct_obj<int>(2); auto obj1 = construct_obj<int>(2); }
|
#### 复制与引用 *
auto
默认会进行深拷贝 *
在此例中,copy_int_values
是 int_values
的一个副本,修改 copy_int_values
不会影响
int_values
* 如果希望避免深拷贝,可以使用
auto&
来创建一个引用,这样对
ref_int_values
的修改将直接影响 int_values
1 2 3 4 5
| std::vector<int> int_values = {1, 2, 3, 4};
auto copy_int_values = int_values;
auto& ref_int_values = int_values;
|
#### 用于迭代容器 1 2 3 4
| for (auto it = map.begin(); it != map.end(); ++it) { std::cout << "(" << it->first << "," << it->second << ") "; }
|
## C++标准库动态内存
智能指针:是用于内存管理的一个数据结构,尤其是在没有内存管理内建机制(如垃圾回收)的语言中(例如
C++)。智能指针的主要功能是自动处理内存的分配与释放。在现代
C++ 标准库中,常用的智能指针有 std::unique_ptr
和
std::shared_ptr
,它们都在内部封装了原始指针,自动管理内存。
### std::unique_ptr
*
具有唯一所有权的特性。即同一个对象只能被一个
std::unique_ptr
管理,不能共享对象的所有权。 *
它无法进行拷贝,但可以通过 std::move
转移所有权。 * 当 std::unique_ptr
离开作用域时,它所管理的对象会被自动释放。这避免了手动释放内存时可能出现的错误(如内存泄漏)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
| #include <iostream> #include <memory> #include <string> #include <utility>
class Point { public: Point() : x_(0), y_(0) {} Point(int x, int y) : x_(x), y_(y) {} inline int GetX() { return x_; } inline int GetY() { return y_; } inline void SetX(int x) { x_ = x; } inline void SetY(int y) { y_ = y; }
private: int x_; int y_; };
void SetXTo445(std::unique_ptr<Point>& ptr) { ptr->SetX(445); }
int main() { std::unique_ptr<Point> u1; std::unique_ptr<Point> u2 = std::make_unique<Point>(); std::unique_ptr<Point> u3 = std::make_unique<Point>(2, 3);
if (u1) { std::cout << "u1's value of x is " << u1->GetX() << std::endl; }
if (u2) { std::cout << "u2's value of x is " << u2->GetX() << std::endl; }
std::cout << "Pointer u1 is " << (u1 ? "not empty" : "empty") << std::endl; std::cout << "Pointer u2 is " << (u2 ? "not empty" : "empty") << std::endl; std::cout << "Pointer u3 is " << (u3 ? "not empty" : "empty") << std::endl;
std::unique_ptr<Point> u4 = std::move(u3);
std::cout << "Pointer u3 is " << (u3 ? "not empty" : "empty") << std::endl; std::cout << "Pointer u4 is " << (u4 ? "not empty" : "empty") << std::endl;
SetXTo445(u4);
std::cout << "Pointer u4's x value is " << u4->GetX() << std::endl;
return 0; }
|
std::shared_ptr
std::shared_ptr
实现了共享所有权的机制,这意味着多个
shared_ptr
实例可以指向同一个对象,且每个
shared_ptr
的析构都会降低对象的引用计数。当引用计数降至 0
时,所管理的对象会被自动销毁。 *
拷贝构造与赋值:std::shared_ptr
支持拷贝构造和赋值操作符。这些操作会导致引用计数(use_count
)增加。
* 转移所有权:可以通过 std::move
转移
shared_ptr
的所有权,使得原来的 shared_ptr
成为“空”指针,而新的 shared_ptr
会接管原来的对象。 ####
创建和初始化shared_ptr
1 2 3
| std::shared_ptr<Point> s1; std::shared_ptr<Point> s2 = std::make_shared<Point>(); std::shared_ptr<Point> s3 = std::make_shared<Point>(2, 3);
|
引用计数与拷贝构造
- 初始时,
s3
是唯一一个指向 Point
对象的指针,因此引用计数为 1;将 s3
拷贝给 s4
后,引用计数增加到 2,表示 s3
和 s4
都指向同一块内存。
- 修改
s3
的数据会影响
s4
,因为它们共享同一个对象。shared_ptr
确保当任何一个指针修改对象时,所有的共享指针都会看到这个变化。
1 2 3
| std::cout << "s3 use_count: " << s3.use_count() << std::endl; std::shared_ptr<Point> s4 = s3; std::cout << "s3 use_count after copy: " << s3.use_count() << std::endl;
|
转移所有权
- 使用
std::move
可以将 s5
的所有权转移到
s6
。此时,s5
变为空指针,而 s6
接管了原来 s5
所指向的对象。 1
| std::shared_ptr<Point> s6 = std::move(s5);
|
通过引用和右值引用传递shared_ptr
- 通过引用传递时,原始的
shared_ptr
仍然保持所有权;
- 通过右值引用传递时,所有权会转移到函数内的指针。
1 2
| void modify_ptr_via_ref(std::shared_ptr<Point> &point) { point->SetX(15); } void modify_ptr_via_rvalue_ref(std::shared_ptr<Point> &&point) {point->SetY(645);}
|
C++标准库同步原语
在并发编程中,同步原语用于管理线程之间的访问顺序、共享资源的访问权限以及防止数据竞争。在
C++
标准库(STL)中,提供了一些同步原语,用于实现线程安全和协调多个线程的执行。以下是
STL 中常见的同步原语及其说明。 ### std::mutex
*
std::mutex
提供了基本的互斥锁功能,保证在同一时刻只有一个线程可以访问共享资源。
* lock()
和 unlock()
用于手动获取和释放锁,确保线程在访问共享资源时是独占的。 * C++11 引入了
std::lock_guard
和 std::unique_lock
等工具,这些工具通过
RAII(资源获取即初始化)模式简化了锁的管理,自动管理锁的获取和释放。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| #include <iostream> #include <mutex> #include <thread>
int count = 0; std::mutex m;
void add_count() { m.lock(); count += 1; m.unlock(); }
int main() { std::thread t1(add_count); std::thread t2(add_count);
t1.join(); t2.join();
std::cout << "Printing count: " << count << std::endl; return 0; }
|
std::scoped_lock
std::scoped_lock
是 C++11
引入的一个新的互斥锁包装类,它遵循 RAII(资源获取即初始化)范式。在构造
std::scoped_lock
对象时,它会自动获取指定的互斥锁,并在作用域结束时自动释放锁。
- 这使得锁的管理变得更加简洁和安全,避免了手动调用
lock()
和 unlock()
的错误;
- 它确保互斥锁会被自动释放,无论函数如何退出(正常结束或抛出异常);
std::scoped_lock
会按传递顺序自动获取多个锁:如果需要在同一作用域中锁定多个互斥锁,可以使用
std::scoped_lock
来确保按正确的顺序获取锁,避免死锁问题。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| #include <iostream> #include <mutex> #include <thread>
int count = 0; std::mutex m;
void add_count() { std::scoped_lock slk(m); count += 1; }
int main() { std::thread t1(add_count); std::thread t2(add_count);
t1.join(); t2.join();
std::cout << "Printing count: " << count << std::endl; return 0; }
|
条件变量std::condition_variable
主要功能
- 条件变量使得线程可以在满足某个条件之前挂起(等待);
- 通知机制:线程可以通过条件变量,通知等待线程某个条件的改变(例如,条件变为true);
关键组件
std::mutex
:用于保护共享资源,防止数据竞争;
std::condition_variable
:用于实现条件同步;
std::unique_lock
:配合条件变量使用,允许线程在等待期间释放锁,以便其他线程可以访问共享资源;当条件满足时,unique_lock
会重新获得锁。
cv.wait(lk, pred)
:该函数使线程阻塞,直到
pred
条件返回
true
。在等待期间,线程会释放锁,当条件成立时会重新获取锁。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| #include <condition_variable> #include <iostream> #include <mutex> #include <thread>
int count = 0; std::mutex m; std::condition_variable cv;
void add_count_and_notify() { std::scoped_lock slk(m); count += 1; if (count == 2) { cv.notify_one(); } }
void waiter_thread() { std::unique_lock lk(m); cv.wait(lk, []{ return count == 2; }); std::cout << "Printing count: " << count << std::endl; }
int main() { std::thread t1(add_count_and_notify); std::thread t2(add_count_and_notify); std::thread t3(waiter_thread); t1.join(); t2.join(); t3.join(); return 0; }
|
C++ STL
示例:读写锁(RWLock)模拟
C++ 标准库(STL)并没有直接提供读写锁(Reader-Writer
Lock)的实现,但可以通过
std::shared_mutex
、std::shared_lock
和
std::unique_lock
来模拟读写锁的行为。
关键组件
std::shared_mutex m
:共享互斥锁,保护对
count
变量的访问
- 共享锁(
std::shared_lock
):在
read_value()
中,std::shared_lock
允许多个线程同时读取 count
,但不能同时进行写入操作。
- 独占锁(
std::unique_lock
):在
write_value()
中,std::unique_lock
确保每次只有一个线程可以修改
count
,从而避免了数据竞争。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| #include <iostream> #include <mutex> #include <shared_mutex> #include <thread>
int count = 0; std::shared_mutex m;
void read_value() { std::shared_lock lk(m); std::cout << "Reading value " + std::to_string(count) + "\n" << std::flush; }
void write_value() { std::unique_lock lk(m); count += 3; }
int main() { std::thread t1(read_value); std::thread t2(write_value); std::thread t3(read_value); std::thread t4(read_value); std::thread t5(write_value); std::thread t6(read_value);
t1.join(); t2.join(); t3.join(); t4.join(); t5.join(); t6.join();
return 0; }
|