资源管理
RAII(Resource Acquisition Is Initialization,资源获取即初始化)的核心思想是将资源的生命周期与栈对象的生命周期绑定在一起,通过对象的构造函数来获取资源,在析构函数中释放资源。这样可以确保即使在发生异常的情况下,资源也能被正确地释放,避免资源泄露。
void f(int i, int j) {
X* p = new X; // 分配一个新的X
std::unique_ptr<X> sp{new X}; // 分配一个新的X,把它的指针赋给unique_ptr//
if (i < 99) throw Z{}; // 可能会抛出异常
if (j < 77) return; // 可能会“过早地”返回
p->do_something(); // 可能会抛出异常
sp->do_something(); // 可能会抛出异常
// ...
delete p; // 销毁*p
}
在这段代码中,如果 i<99
或者 j<77
,我们会“忘记”释放掉指针 p
。另一方面,unique_ptr
确保不论我们以哪种方式(通过抛出异常,或者通过执行 return 语句,或者跳转到了函数末尾)退出 f()
都会释放掉它的对象。
通过使用 unique_ptr
,我们还可以把自由存储上申请的对象传递给函数或者从函数中传出来:
#include <iostream>
class Entity
{
public:
Entity() {
std::cout << "Entity created" << std::endl;
}
~Entity() {
std::cout << "Entity destroyed" << std::endl;
}
void DoSomething() {
std::cout << "Do something" << std::endl;
}
};
// 这样是 OK 的
std::unique_ptr<Entity> CreateEntity() {
return std::make_unique<Entity>();
}
// 多数情况,传引用
void EntityDoSomething(std::unique_ptr<Entity>& entity) {
entity->DoSomething();
}
// 很少这样使用
void EntityDoSomething2(std::unique_ptr<Entity> entity) {
entity->DoSomething();
}
int main() {
auto entity = CreateEntity();
std::cout << "---------------" << std::endl;
entity = CreateEntity(); // 这里调用的是 unique_ptr 的移动赋值操作符
std::cout << "---------------" << std::endl;
EntityDoSomething(entity); // 这里调用的是 unique_ptr 的引用传递
std::cout << "---------------" << std::endl;
//EntityDoSomething2(entity); 这是不可以的,除非参数是 std::move(entity) ,但这样会转移所有权
}
运行结果:
Entity created
---------------
Entity created
Entity destroyed
---------------
Do something
---------------
Entity destroyed
unique_ptr
和 shared_ptr
的细节对比:
#include <iostream>
class Entity
{
public:
Entity() {
std::cout << "Entity created" << std::endl;
}
~Entity() {
std::cout << "Entity destroyed" << std::endl;
}
void DoSomething() {
std::cout << "Do something" << std::endl;
}
};
int main() {
auto entity1 = std::make_unique<Entity>();
auto entity2 = std::make_unique<Entity>();
//entity1 = entity2; // 不OK,unique_ptr不支持拷贝赋值操作
entity1 = std::move(entity2); // OK,unique_ptr支持移动赋值操作
// 所以会有一次 destroy,发生在 entity1 移动时
std::cout << "--------------------------" << std::endl;
auto entity3 = std::make_shared<Entity>();
{
auto entity4 = std::make_shared<Entity>();
entity3 = entity4; // OK,shared_ptr支持拷贝赋值操作。entity3 原本的 Entity 被释放
}
// 所以会有一次 destroy,发生在 entity3 拷贝时
std::cout << "--------------------------" << std::endl;
auto entity5 = std::make_shared<Entity>();
{
auto entity6 = std::make_shared<Entity>();
entity5 = entity6;
// 现在 entity6 指向的对象有两份引用
entity6 = std::move(entity5); // OK,shared_ptr 支持移动赋值操作。entity5 把所有权交给 entity6。
// 引用变成一份(entity6持有),离开作用域就会被释放。
}
// 所以会有两次 destroy,一次是 entity5 拷贝时,一次是 entity6
std::cout << "--------------------------" << std::endl;
}
运行结果:
Entity created
Entity created
Entity destroyed
--------------------------
Entity created
Entity created
Entity destroyed
--------------------------
Entity created
Entity created
Entity destroyed
Entity destroyed
--------------------------
Entity destroyed
Entity destroyed
总结:
-
unique_ptr
- 支持移动:转移所有权
- 不支持拷贝
-
shared_ptr
- 支持移动:转移所有权
- 支持拷贝:共享所有权
资源管理方式的优先级:容器 > 智能指针 > 裸指针
并发
任务和 thread
我们称那些可以与其他计算并行执行的计算为任务(task)。线程(thread)是任务在程序中的系统级表示。若要启动一个与其他任务并发执行的任务,我们可以构造一个 std::thread
(在 <thread>
中)并将任务作为它的实参。这里的任务是以函数或函数对象的形式出现的:
#include <iostream>
#include <thread>
// 普通函数
void f() {
std::cout << "Function f is running in thread " << std::this_thread::get_id() << std::endl;
}
// 函数对象
class F {
public:
void operator()() {
std::cout << "Function object F is running in thread " << std::this_thread::get_id() << std::endl;
}
};
int main() {
// 使用普通函数f创建线程t1
std::thread t1(f);
// 使用函数对象F创建线程t2
F ff;
std::thread t2(ff);
// 在独立的线程中执行f和F
std::cout << "Main thread " << std::this_thread::get_id() << " is waiting for t1 and t2 to finish." << std::endl;
// 等待t1完成
t1.join();
// 等待t2完成
t2.join();
std::cout << "Both threads have finished. Main thread " << std::this_thread::get_id() << " is exiting." << std::endl;
return 0;
}
运行结果(注意,这里会发生数据竞争,所以每次的结果可能都不一样):
Main thread 5228 is waiting for t1 and t2 to finish.
Function f is running in thread 19728
Function object F is running in thread 3456
Both threads have finished. Main thread 5228 is exiting.
join
会等待线程结束,从字面意思上,可以理解成”两个线程回合“,在这里就是 Main 等待子线程了。
与进程不同,一个程序的所有线程共享单一地址空间,所以线程之间可以通过共享对象相互通信。为了防止数据竞争,通常采用锁等机制。
传递参数
任务通常需要处理数据,我们可以将数据(或指向数据的指针或引用)作为参数传递给任务:
#include <iostream>
#include <thread>
#include <vector>
// 普通函数
void f1(std::vector<int> vec) {
for (int i = 0; i < vec.size(); ++i) {
vec[i] *= 2;
}
}
void f2(std::vector<int>& vec) {
for (int i = 0; i < vec.size(); ++i) {
vec[i] *= 2;
}
}
// 函数对象
class F {
private:
std::vector<int>& vec;
public:
F(std::vector<int>& v) : vec(v) {}
void operator()() {
for (int i = 0; i < vec.size(); ++i) {
vec[i] += 1;
}
}
};
int main() {
// 使用普通函数f创建线程t1
std::vector<int> vec1{ 1, 2, 3, 4, 5 };
std::vector<int> vec2{ 1, 2, 3, 4, 5 };
std::vector<int> vec3{ 1, 2, 3, 4, 5 };
std::thread t1(f1, vec1); // 按值传递,可以这样写
std::thread t2(f2, std::ref(vec2)); // 按引用传递,必须要加 std::ref
// 使用函数对象F创建线程t2
F ff(vec3);
std::thread t3(ff);
// 等待t1完成
t1.join();
// 等待t2完成
t2.join();
// 等待t2完成
t3.join();
std::cout << "vec1: ";
for (int i = 0; i < vec1.size(); ++i) {
std::cout << vec1[i] << " ";
}
std::cout << std::endl;
std::cout << "vec2: ";
for (int i = 0; i < vec2.size(); ++i) {
std::cout << vec2[i] << " ";
}
std::cout << std::endl;
std::cout << "vec3: ";
for (int i = 0; i < vec3.size(); ++i) {
std::cout << vec3[i] << " ";
}
std::cout << std::endl;
}
运行结果:
vec1: 1 2 3 4 5
vec2: 2 4 6 8 10
vec3: 2 3 4 5 6
需要注意的是,如果参数是引用,则必须使用 std::ref
返回结果
将返回变量以非 const 引用或指针的形式传递到函数中,函数将返回值写入其中。
共享数据
对于共享数据,可以利用锁解决数据竞争问题:
#include <iostream>
#include <thread>
#include <mutex>
#include <vector>
int shared_data_without_lock = 0;
// 共享数据
int shared_data_with_lock = 0;
// 用于保护共享数据的互斥锁
std::mutex mtx;
void incrementWithLock() {
// 使用unique_lock自动管理锁的生命周期
std::unique_lock<std::mutex> lock{ mtx };
// 临界区开始
for (int i = 0; i < 100000; ++i) {
shared_data_with_lock++;
}
// 临界区结束,lock在这里自动释放
}
void incrementWithoutLock() {
for (int i = 0; i < 100000; ++i) {
shared_data_without_lock++;
}
}
int main() {
std::vector<std::thread> threadsWithLock;
// 创建10个线程,每个线程都会增加shared_data
for (int i = 0; i < 10; ++i) {
threadsWithLock.push_back(std::thread(incrementWithLock));
}
// 等待所有线程完成
for (auto& th : threadsWithLock) {
th.join();
}
std::vector<std::thread> threadsWithoutLock;
for (int i = 0; i < 10; ++i) {
threadsWithoutLock.push_back(std::thread(incrementWithoutLock));
}
for (auto& th : threadsWithoutLock) {
th.join();
}
std::cout << "Shared data with lock: " << shared_data_with_lock << std::endl;
std::cout << "Shared data without lock: " << shared_data_without_lock << std::endl;
}
运行结果(由于数据竞争,每次结果可能不一样):
Shared data with lock: 1000000
Shared data without lock: 177654
死锁
下面代码,两个线程以相反的顺序获取锁,会陷入了死锁,永远不会结束:
#include <iostream>
#include <thread>
#include <mutex>
#include <chrono>
// 两个互斥锁
std::mutex mtx1;
std::mutex mtx2;
void lockBoth() {
// 锁定mtx1
std::unique_lock<std::mutex> lock1{ mtx1 };
// 休眠 1s,模拟任务
std::this_thread::sleep_for(std::chrono::seconds(1));
// 锁定mtx2
std::unique_lock<std::mutex> lock2{ mtx2 };
}
void lockBothReverse() {
// 锁定mtx2
std::unique_lock<std::mutex> lock2{ mtx2 };
// 休眠 1s,模拟任务
std::this_thread::sleep_for(std::chrono::seconds(1));
// 锁定mtx1
std::unique_lock<std::mutex> lock1{ mtx1 };
}
int main() {
std::thread t1(lockBoth);
std::thread t2(lockBothReverse);
t1.join();
t2.join();
return 0;
}
通过 std::lock
可以获取一系列锁,并避免死锁:
#include <iostream>
#include <thread>
#include <mutex>
#include <chrono>
// 两个互斥锁
std::mutex mtx1;
std::mutex mtx2;
void lockBoth() {
std::unique_lock<std::mutex> lock1{ mtx1, std::defer_lock };
std::unique_lock<std::mutex> lock2{ mtx2, std::defer_lock };
std::lock(lock1, lock2);
// 休眠 1s,模拟任务
std::this_thread::sleep_for(std::chrono::seconds(1));
}
void lockBothReverse() {
std::unique_lock<std::mutex> lock2{ mtx2, std::defer_lock };
std::unique_lock<std::mutex> lock1{ mtx1, std::defer_lock };
std::lock(lock2, lock1);
// 休眠 1s,模拟任务
std::this_thread::sleep_for(std::chrono::seconds(1));
}
int main() {
std::thread t1(lockBoth);
std::thread t2(lockBothReverse);
t1.join();
t2.join();
return 0;
}
关于
std::lock
,cppreference.com 的解释是:Locks the given Lockable objects lock1, lock2,
...
, lockn using a deadlock avoidance algorithm to avoid deadlock.The objects are locked by an unspecified series of calls to lock
, try_lock
, and unlock
. If a call to lock
or unlock
results in an exception, unlock
is called for any locked objects before rethrowing.翻译成中文:
将给定的可锁定对象 lock1、lock2、…、lockn 使用死锁避免算法进行锁定,以避免死锁。这些对象通过一系列未指定的
lock
、try_lock
和 unlock
调用来锁定。如果调用 lock
或 unlock
导致异常,则在重新抛出异常之前,调用 unlock
以解锁任何已锁定的对象。
等待事件
通过外部事件实现线程间通信的基本方法是使用 condition_variable
,它定义在 <condition_variable>
中。condition_variable
提供了一种机制,允许一个 thread 等待另一个 thread。特别是,它允许一个 thread 等待某个条件(condition,通常称为一个事件)发生,这种条件通常是其他 thread 完成工作产生的结果。
一个简单的例子如下:
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <queue>
std::mutex mtx; // 互斥锁
std::condition_variable cv; // 条件变量
std::queue<int> dataQueue; // 数据队列
// 生产者函数
void producer(int id) {
for (int i = 0; i < 5; ++i) {
std::this_thread::sleep_for(std::chrono::seconds(1)); // 模拟生产耗时
std::unique_lock<std::mutex> lock(mtx); // 锁定互斥锁
dataQueue.push(id * 100 + i); // 生产数据
std::cout << "Producer " << id << " produced: " << id * 100 + i << std::endl;
lock.unlock(); // 解锁互斥锁
cv.notify_one(); // 通知一个等待的消费者线程
}
}
// 消费者函数
void consumer() {
while (true) {
std::unique_lock<std::mutex> lock(mtx); // 锁定互斥锁
while (dataQueue.empty()) { // 如果队列为空,则等待
cv.wait(lock); // 等待生产者通知
}
int data = dataQueue.front(); // 获取队列中的数据
dataQueue.pop(); // 从队列中移除数据
lock.unlock(); // 解锁互斥锁
std::cout << "Consumer consumed: " << data << std::endl;
if (data == 109) { // 假设当消费者消费到109时结束
break;
}
}
}
int main() {
std::thread t1(producer, 1); // 创建生产者线程
std::thread t2(consumer); // 创建消费者线程
t1.join(); // 等待生产者线程结束
t2.join(); // 等待消费者线程结束
return 0;
}
运行结果(注意,这个不会结束,需要手动终止):
Producer 1 produced: 100
Consumer consumed: 100
Producer 1 produced: 101
Consumer consumed: 101
Producer 1 produced: 102
Consumer consumed: 102
Producer 1 produced: 103
Consumer consumed: 103
Producer 1 produced: 104
Consumer consumed: 104
关于
std::condition_variable::wait
,cppreference.com 的解释是:void wait( std::unique_lock<std::mutex>& lock ); // (1) (since C++11) template< class Predicate > void wait( std::unique_lock<std::mutex>& lock, Predicate pred ); // (2) (since C++11)
wait
函数使当前线程阻塞,直到条件变量被通知或发生伪唤醒。可以选择性地提供pred
以检测伪唤醒。(1) 原子性地调用
lock.unlock()
并在*this
(这里的this
是condition_variable
)上阻塞。线程将在调用notify_all()
或notify_one()
时被解除阻塞,也可能会因为伪唤醒而被解除阻塞。当解除阻塞后,会调用lock.lock()
(可能会阻塞在锁上),然后返回。(2) 此重载可以在等待特定条件成立时忽略伪唤醒。等价于
while (!pred()) wait(lock);
任务通信
标准库提供了一些特性,允许程序员在抽象的任务层(工作并发执行)进行操作,而不是在底层的线程和锁的层次直接进行操作。
-
future
和promise
用来从一个独立线程上创建出的任务返回结果。 -
packaged_task
是帮助启动任务以及连接返回结果的机制。 -
async()
以非常类似调用函数的方式启动一个任务。
这些特性都定义在 <future>
中。
future
和 promise
使用方法:
- 首先定义一个“承诺”
promise
,然后从其中取出“期货”future
- 一个线程通过
set_value
或set_exception
向promise
“发货” - 另外一个线程通过
get
从future
中取货
如下,是一个简单的例子:
#include <iostream>
#include <future>
#include <thread>
#include <stdexcept>
// 一个可能抛出异常的函数,用来计算一些值
int computeSomething(bool throwException) {
if (throwException) {
throw std::runtime_error("Something went wrong!");
}
// 模拟一些计算
std::this_thread::sleep_for(std::chrono::seconds(1));
return 42;
}
int main() {
// 创建一个std::promise<int>对象
std::promise<int> promise;
// 创建一个std::future对象,与promise关联
std::future<int> future = promise.get_future();
// 在新线程中运行函数
std::thread t([&promise]() {
try {
// 计算完成后,我们设置promise的值
int result = computeSomething(true); // 假设这次我们让它抛出异常
promise.set_value(result); // 设置值
}
catch (...) {
// 如果有异常发生,我们传递异常给future
promise.set_exception(std::current_exception());
}
});
// 在主线程中,我们等待future的值
std::cout << "Waiting for the result..." << std::endl;
try {
int result = future.get(); // 这会阻塞,直到promise设置了值或异常
std::cout << "Result is " << result << std::endl;
}
catch (const std::exception& e) {
// 捕获并处理异常
std::cout << "Exception caught: " << e.what() << std::endl;
}
// 确保线程t已经完成
t.join();
return 0;
}
packaged_task
标准库提供了 packaged_task
类型简化任务连接 future
和 promise
的设置。packaged_task
提供了一层包装代码,负责把某个任务的返回值或异常放入一个 promise
中。如果通过调用 get_future()
来向一个 packaged_task
发出请求,它会返回给你对应 promise
的 future
。
#include <iostream>
#include <future>
#include <thread>
#include <functional>
// 定义一个函数,计算两个整数的和
int add(int a, int b) {
return a + b;
}
int main() {
// 创建一个packaged_task对象,封装add函数
std::packaged_task<int(int, int)> task(add);
// 获取一个future对象,与task关联
std::future<int> result = task.get_future();
// 在新线程中异步执行任务
std::thread t(std::move(task), 5, 3);
// 在主线程中获取结果
int sum = result.get(); // 这里会阻塞,直到任务完成
std::cout << "The sum is: " << sum << std::endl;
// 等待线程结束,回收资源
t.join();
return 0;
}
注意:packaged_task
不能被拷贝,所以 std::move()
操作是必需的。
async()
在 C++ 中,std::async
是一个用于异步执行函数的函数模板,它返回一个 std::future
对象,该对象可以用来获取异步操作的结果。下面是一个使用 std::async
的简单例子:
#include <iostream>
#include <future>
#include <thread>
#include <chrono>
// 一个简单的函数,用于演示异步执行
int compute(int x) {
std::this_thread::sleep_for(std::chrono::seconds(1)); // 模拟耗时操作
return x * x;
}
int main() {
// 启动异步任务
std::future<int> result = std::async(std::launch::async, compute, 10);
// 在这里可以执行其他任务,或者仅仅是等待异步任务完成
std::cout << "Doing other work while compute is running..." << std::endl;
// 获取异步任务的结果
int value = result.get(); // 这会阻塞,直到异步任务完成
std::cout << "The result is " << value << std::endl;
return 0;
}
async()
的第一个参数可以指定策略,支持以下两种策略:
-
std::launch::deferred
:任务被延迟执行,直到std::future
的get
方法被调用。 -
std::launch::async
:任务会在新线程中异步执行。
小工具组件
时间
处理时间的标准库功能定义在 <chrono>
头文件中,属于子名字空间 std::chrono
。
#include <iostream>
#include <chrono>
#include <thread>
int main() {
// 启动异步任务
auto t1 = std::chrono::high_resolution_clock::now();
std::this_thread::sleep_for(std::chrono::seconds(1));
auto t2 = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(t2 - t1).count();
std::cout << "Duration: " << duration << " milliseconds" << std::endl;
return 0;
}
类型函数
类型函数(type function) 是指在编译时求值的函数,它接受一个类型作为实参或者返回一个类型作为结果。标准库提供了大量的类型函数,这些函数可以帮助库的实现者及程序员在编写代码时充分利用语言、标准库以及其他代码的优势。
iterator_traits
#include <iostream>
#include <vector>
#include <forward_list>
#include <algorithm>
#include <iterator>
// 辅助函数,用于随机访问迭代器
template<typename RandomAccessIter>
void sort_helper(RandomAccessIter beg, RandomAccessIter end, std::random_access_iterator_tag) {
std::sort(beg, end);
}
// 辅助函数,用于前向迭代器
template<typename ForwardIter>
void sort_helper(ForwardIter beg, ForwardIter end, std::forward_iterator_tag) {
std::vector<typename std::iterator_traits<ForwardIter>::value_type> v(beg, end);
std::sort(v.begin(), v.end());
std::copy(v.begin(), v.end(), beg);
}
// 排序函数,根据容器的迭代器类型选择排序算法
template<typename C>
void sort(C& c) {
using Iter = typename C::iterator;
sort_helper(c.begin(), c.end(), typename std::iterator_traits<Iter>::iterator_category());
}
int main() {
// 测试vector
std::vector<int> vec = { 4, 2, 7, 1, 9 };
std::cout << "Before sorting vector: ";
for (int num : vec) std::cout << num << " ";
std::cout << std::endl;
sort(vec);
std::cout << "After sorting vector: ";
for (int num : vec) std::cout << num << " ";
std::cout << std::endl;
// 测试forward_list
std::forward_list<int> flst = { 4, 2, 7, 1, 9 };
std::cout << "Before sorting forward_list: ";
for (int num : flst) std::cout << num << " ";
std::cout << std::endl;
sort(flst);
std::cout << "After sorting forward_list: ";
for (int num : flst) std::cout << num << " ";
std::cout << std::endl;
return 0;
}
std::iterator_traits<Iter>::iterator_category()
会根据 Iter
的类型构建一个标签值:
- 如果 C 的选代器支持随机访问,则取值为
std::random_access_iterator_tag
- 如果 C 的选代器支持前向访问,则取值为
std::forward_iterator_tag
类型谓词
标准库类型谓词是一种简单的类型函数,它负责回答一个关于类型的问题
#include <iostream>
#include <string>
#include <type_traits>
int main() {
// 测试vector
bool b1 = std::is_arithmetic<int>(); // Yes,int 是一种算术类型
bool b2 = std::is_arithmetic<std::string>(); // No,std:string 不是一种算术类型
std::cout << "b1: " << b1 << std::endl;
std::cout << "b2: " << b2 << std::endl;
return 0;
}
pair 和 tuple
#include <iostream>
#include <utility> // 包含pair
#include <tuple> // 包含tuple
int main() {
// 使用pair
std::pair<int, std::string> person1;
person1.first = 25; // 赋值给第一个元素
person1.second = "Tom"; // 赋值给第二个元素
std::cout << "Pair: " << person1.first << ", " << person1.second << std::endl;
std::pair<int, std::string> person2{ 25, "Tom" }; // 使用初始化列表
std::cout << "Pair: " << person2.first << ", " << person2.second << std::endl;
std::pair<int, std::string> person3 = std::make_pair(25, "Tom"); // 使用make_pair函数
std::cout << "Pair: " << person3.first << ", " << person3.second << std::endl;
// 使用tuple
std::tuple<int, std::string, double> measurements1;
std::get<0>(measurements1) = 100; // 赋值给第一个元素
std::get<1>(measurements1) = "cm"; // 赋值给第二个元素
std::get<2>(measurements1) = 75.5; // 赋值给第三个元素
std::cout << "Tuple: " << std::get<0>(measurements1) << " " << std::get<1>(measurements1) << " " << std::get<2>(measurements1) << std::endl;
std::tuple<int, std::string, double> measurements2{ 100, "cm", 75.5 }; // 使用初始化列表
std::cout << "Tuple: " << std::get<0>(measurements2) << " " << std::get<1>(measurements2) << " " << std::get<2>(measurements2) << std::endl;
std::tuple<int, std::string, double> measurements3 = std::make_tuple(100, "cm", 75.5); // 使用make_tuple函数
std::cout << "Tuple: " << std::get<0>(measurements3) << " " << std::get<1>(measurements3) << " " << std::get<2>(measurements3) << std::endl;
return 0;
}
正则表达式
#include <iostream>
#include <regex>
#include <string>
#include <vector>
// 从给定的文本中找到所有匹配的电子邮件地址
std::vector<std::string> findEmails(const std::string& text) {
std::vector<std::string> emails;
std::regex emailPattern(R"(\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b)");
std::sregex_iterator iter(text.begin(), text.end(), emailPattern);
std::sregex_iterator end;
while (iter != end) {
emails.push_back(iter->str());
iter++;
}
return emails;
}
int main() {
// 示例文本,包含多个电子邮件地址
std::string text = R"(Hello, you can contact me at [email protected] or [email protected].
Feel free to reach out to our sales team at [email protected] for more information.
Our marketing department can be reached at [email protected].
Please do not reply to this email as it is not monitored: [email protected])";
// 查找文本中的所有电子邮件地址
std::vector<std::string> emails = findEmails(text);
// 打印找到的电子邮件地址
std::cout << "Found the following email addresses:" << std::endl;
for (const auto& email : emails) {
std::cout << email << std::endl;
}
return 0;
}
数学计算
在 <cmath>
中包含着很多“有用的数学函数”,如 sqrt()
、log()
和 sin()
等,它们支持各种各样的实参类型(float
、double
、long double
)。这些函数的复数版本则定义在 <complex>
中。
随机数
标准库在 <random>
中提供了很多种不同的随机数生成器。随机数生成器包括两部分:
- 一个引擎(engine),负责生成一组随机值或者伪随机值
- 一种分布(distribution),负责把引擎产生的值映射到某个数学分布上
#include <iostream>
#include <random>
#include <functional>
int main() {
// 初始化随机数引擎,使用随机设备作为种子
std::random_device rd;
std::mt19937 gen(rd());
// 定义一个均匀分布的整数范围
std::uniform_int_distribution<int> distrib_int(1, 100);
// 使用std::bind将分布和引擎绑定,创建一个可以直接调用的函数对象
auto random_int = std::bind(distrib_int, gen); // 相当于 distrib_int(gen)
// 定义一个均匀分布的浮点数范围
std::uniform_real_distribution<double> distrib_real(0.0, 1.0);
// 使用std::bind将分布和引擎绑定,创建一个可以直接调用的函数对象
auto random_real = std::bind(distrib_real, gen); // 相当于 distrib_real(gen)
// 生成并打印随机整数
std::cout << "Random integer between 1 and 100: " << random_int() << std::endl;
std::cout << "Random integer between 1 and 100: " << random_int() << std::endl;
std::cout << "Random integer between 1 and 100: " << random_int() << std::endl;
std::cout << "Random integer between 1 and 100: " << random_int() << std::endl;
// 生成并打印随机浮点数
std::cout << "Random double between 0.0 and 1.0: " << random_real() << std::endl;
std::cout << "Random double between 0.0 and 1.0: " << random_real() << std::endl;
std::cout << "Random double between 0.0 and 1.0: " << random_real() << std::endl;
std::cout << "Random double between 0.0 and 1.0: " << random_real() << std::endl;
return 0;
}
向量运算
标准库在 <valarray>
中提供了一个类似于 vector
的模板 valarray
。与 vector
相比,valarray
的通用性不强,但是对于数值计算进行了必要的优化:
#include <iostream>
#include <valarray>
int main() {
// 定义两个向量
std::valarray<double> vectorA = {1.0, 2.0, 3.0};
std::valarray<double> vectorB = {4.0, 5.0, 6.0};
// 计算点积
double dotProduct = (vectorA * vectorB).sum(); // 使用乘法运算符和sum()函数
std::cout << "Dot Product: " << dotProduct << std::endl;
return 0;
}
数值限制
在 <limits>
中,标准库提供了描述内置类型属性的类,比如 float
的最高阶以及 int
所占的字节数等。
#include <iostream>
#include <limits>
int main() {
// 检查char是否是带符号的
if (std::numeric_limits<char>::is_signed) {
std::cout << "char is signed." << std::endl;
} else {
std::cout << "char is unsigned." << std::endl;
}
// 输出int的最大值
std::cout << "The maximum value of int is " << std::numeric_limits<int>::max() << std::endl;
// 输出int的最小值
std::cout << "The minimum value of int is " << std::numeric_limits<int>::min() << std::endl;
// 使用静态断言检查int的最大值是否大于某个值
static_assert(std::numeric_limits<int>::max() > 100000, "int max value is not greater than 100000");
return 0;
}
注意:因为 std::numeric_limits<int>::max()
是一个 constexpr 函数,所以静态断言有效。