16 STL中的函数对象与适配器

16 STL中的函数对象与适配器

在C++的标准模板库(STL)中,函数对象(Function Objects)和适配器(Adapters)是非常重要的概念。通过这些内容,您可以更加灵活和高效地使用STL中的算法与容器。本文将详细介绍这两个概念及其在实际编程中的应用。

1. 函数对象

1.1 什么是函数对象?

函数对象,顾名思义,是一个可以像函数一样被调用的对象。在C++中,任何重载了 operator() 的类实例都可以称为函数对象。

1.2 创建函数对象

下面是一个简单的函数对象示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <iostream>

class Adder {
public:
Adder(int num) : num(num) {}

// 重载 operator()
int operator()(int x) const {
return x + num;
}

private:
int num;
};

int main() {
Adder add5(5); // 创建一个函数对象,包含常量 5
std::cout << "5 + 10 = " << add5(10) << std::endl; // 输出 15
return 0;
}

在这个示例中,Adder 类是一个函数对象,它接收一个整数,并将其与内部的 num 相加。通过创建 Adder 类的实例 add5,我们可以调用 add5(10),得到结果 15

2. 函数适配器

函数适配器是将普通函数或函数对象转换为不同形式的对象,以便可以用于算法或其他函数对象中。STL提供了一些内置的函数适配器,例如 std::bindstd::not_fn

2.1 std::bind

std::bind 用于将函数或函数对象中的参数固定为特定值,从而创建一个新的可调用对象。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
#include <iostream>
#include <functional> // 引入 <functional> 支持 std::bind

void printSum(int a, int b) {
std::cout << "Sum: " << a + b << std::endl;
}

int main() {
auto boundFunc = std::bind(printSum, 10, std::placeholders::_1); // 固定第一个参数为10
boundFunc(20); // 输出 "Sum: 30"
return 0;
}

在这个示例中,我们使用 std::bind 绑定 printSum 函数的第一个参数为 10,留下第二个参数为可变的。通过调用 boundFunc(20),输出结果为 30

2.2 std::not_fn

std::not_fn 用于创建一个返回 false 的谓词函数对象,它适用于已经存在的函数或函数对象。

示例:

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
#include <iostream>
#include <functional>
#include <vector>
#include <algorithm>

bool isEven(int x) {
return x % 2 == 0;
}

int main() {
std::vector<int> nums = {1, 2, 3, 4, 5, 6};

// 使用 std::not_fn
auto oddPredicate = std::not_fn(isEven);

// 过滤出奇数
std::cout << "Odd numbers: ";
for (const auto& num : nums) {
if (oddPredicate(num)) {
std::cout << num << " "; // 输出 1 3 5
}
}
std::cout << std::endl;

return 0;
}

在这个示例中,我们定义了一个简单的函数 isEven 来判断数字是否为偶数。通过 std::not_fn 创建一个返回 true 的谓词函数 oddPredicate,该函数用于过滤出奇数。

3. 使用函数对象与适配器的优势

  1. 灵活性:函数对象和适配器允许您在算法中使用用户自定义的操作,增加了可重用性。
  2. 性能:由于函数对象是对象,它们可以携带状态,这使得能在调用过程中避免参数的拷贝,从而提升性能。
  3. 功能丰富:STL的适配器功能强大,可以帮助简化常见的编程模式,提高代码的可读性。

4. 总结

通过对函数对象与适配器的学习,您可以更好地利用STL中的算法,提高程序的灵活性和效率。在实际项目中,灵活运用这些技术能显著提升代码质量与开发效率。

16 C++ 循环语句小节

16 C++ 循环语句小节

在 C++ 中,循环语句允许你重复执行一段代码,直至满足某个条件。C++ 有三种主要的循环语句:forwhiledo-while。接下来,我们将详细介绍这三种循环,看看它们的语法、用法和一些示例代码。

1. for 循环

for 循环通常用于已知迭代次数的情况。其基本语法如下:

1
2
3
for (初始化; 条件; 增量) {
// 循环体
}

示例

下面是一个使用 for 循环打印从 15 的数字的示例:

1
2
3
4
5
6
7
8
9
#include <iostream>
using namespace std;

int main() {
for (int i = 1; i <= 5; i++) {
cout << i << endl;
}
return 0;
}

在这个示例中:

  • int i = 1 是初始化部分,设置循环变量。
  • i <= 5 是条件部分,当条件为真时,循环继续。
  • i++ 是增量部分,每次循环后 i 增加 1

2. while 循环

while 循环用于在条件为真时反复执行一段代码。其基本语法如下:

1
2
3
while (条件) {
// 循环体
}

示例

下面是一个使用 while 循环打印从 15 的数字的示例:

1
2
3
4
5
6
7
8
9
10
11
#include <iostream>
using namespace std;

int main() {
int i = 1;
while (i <= 5) {
cout << i << endl;
i++;
}
return 0;
}

在这个示例中:

  • while (i <= 5) 是检查条件的部分。
  • 如果条件为真,执行循环体中的代码。
  • 记得在循环中更新 i,否则会发生无限循环。

3. do-while 循环

do-while 循环与 while 循环类似,但是它保证至少执行一次循环体。其基本语法如下:

1
2
3
do {
// 循环体
} while (条件);

示例

下面是一个使用 do-while 循环打印从 15 的数字的示例:

1
2
3
4
5
6
7
8
9
10
11
#include <iostream>
using namespace std;

int main() {
int i = 1;
do {
cout << i << endl;
i++;
} while (i <= 5);
return 0;
}

在这个示例中:

  • do 部分的代码会在条件检查之前执行一次。
  • while (i <= 5) 进行条件检查,决定是否继续循环。

总结

  • for 循环适合已知次数的重复。
  • while 循环适合在条件为真时重复,但可能一次也不执行。
  • do-while 循环保证至少执行一次。

这三种循环语句都是处理重复任务的重要工具,掌握它们是学习 C++ 的基础。

线程的生命周期与控制

线程的生命周期与控制

在现代 C++ 中,线程支持是通过 <thread> 头文件提供的,它允许程序同时执行多个任务。了解线程的生命周期与控制对于编写高效、并发的程序至关重要。

1. 线程的生命周期

线程的生命周期主要包括以下几个阶段:

  1. 创建:线程对象的实例化。
  2. 运行:执行线程内的任务。
  3. 等待:等待线程完成,通常是通过 joindetach
  4. 销毁:线程结束后,资源被释放。

1.1 创建线程

在 C++ 中,创建线程的常见方法是使用 std::thread 类。以下是一个创建线程的简单示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <iostream>
#include <thread>

void threadFunction() {
std::cout << "线程正在运行..." << std::endl;
}

int main() {
// 创建一个线程
std::thread t(threadFunction);

// 检查线程是否可连接
if (t.joinable()) {
t.join(); // 等待线程结束
}

return 0;
}

在这个示例中,std::thread t(threadFunction); 创建了一个新的线程,该线程将执行 threadFunction 函数。

1.2 线程运行

一旦线程被创建,它将开始执行分配给它的任务。std::thread 将在其构造函数中启动线程。线程一旦开始运行,程序将继续执行主线程中的代码,直到调用 join()detach()

1.3 等待与控制

当一个线程完成其工作时,主线程可以通过以下两种方法之一来进行控制:

1.3.1 Join

join() 用于等待线程完成执行。调用 join() 的线程会阻塞,直到目标线程结束。

1
2
std::thread t(threadFunction);
t.join(); // 阻塞,直到线程 t 完成

1.3.2 Detach

detach() 将线程分离,使其在后台运行。分离后,主线程不会等待该线程完成,但要小心使用,因为一旦线程分离,无法再调用 join()

1
2
std::thread t(threadFunction);
t.detach(); // 分离线程 t,让其在后台运行

1.4 线程销毁

当线程结束时,它会自动释放其内部资源。如果线程是可连接的(通过 joinable()),调用 join() 后会回收线程资源。如果使用了 detach(),线程资源将由系统自动管理。

对线程的生命周期管理非常重要,不当的管理可能会导致资源泄漏或未定义行为。

2. 线程的状态控制

C++ 还提供了以下几种方法来控制线程的状态:

  1. 检查线程状态:使用 joinable() 方法检查线程是否可连接。
  2. 使用条件变量std::condition_variable 可以用于线程之间的同步,帮助控制线程的执行时机。
  3. 互斥量:使用 std::mutex 来保护共享数据,避免数据竞争。

2.1 示例:使用条件变量

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
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <chrono>

std::mutex mtx;
std::condition_variable cv;
bool ready = false;

void waitForWork() {
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, [] { return ready; }); // 等待条件变量
std::cout << "工作已完成!" << std::endl;
}

void doWork() {
std::this_thread::sleep_for(std::chrono::seconds(1)); // 模拟计算
{
std::lock_guard<std::mutex> lock(mtx);
ready = true; // 改变状态
}
cv.notify_one(); // 通知等待的线程
}

int main() {
std::thread worker(waitForWork);
std::thread producer(doWork);

worker.join();
producer.join();

return 0;
}

在这个示例中,我们使用条件变量 cv 来控制线程的执行。waitForWork 线程会等待 doWork 完成其工作并改变 ready 状态。

3. 小结

C++ 线程的生命周期与控制是高效并发编程的核心。掌握以下内容能够帮助你更好地利用线程:

  • 创建和销毁线程的过程。
  • 管理线程的执行状态(使用 joindetach)。
  • 使用 mutexcondition_variable 进行线程间的同步。

通过不断实践和完善你的代码,你可以更深入地理解并掌握 C++ 中的线程。