简介:C++作为一种广泛使用的编程语言,特别适用于系统软件、游戏开发、高性能计算和嵌入式系统。'Comp345'课程旨在教授学生C++的基础语法、面向对象编程原则、标准模板库(STL)的使用以及高级主题如模板元编程和并发编程。学生将通过实践学习,掌握变量声明、控制流结构、函数、OOP概念(类、对象、封装、继承、多态)、STL容器、算法和函数对象。同时,课程也涉及到C++模板的使用、模板特化,以及C++11及后续版本的并发编程特性。该课程通过实践项目如'Comp345-main'源代码文件,帮助学生将所学知识综合应用,以提高编程技能和解决复杂问题的能力。
1. C++基础语法讲解
在探索C++的丰富特性和强大功能之前,掌握基础语法是必不可少的步骤。本章将从最基础的元素出发,逐步介绍C++的语法结构,确保每位读者都能够打下坚实的编程基础。
1.1 基本数据类型和运算符
C++拥有丰富的数据类型,包括整型、浮点型、字符型、布尔型等。理解这些基本数据类型是学习C++的第一步。运算符是编程语言中用于执行数值或逻辑运算的符号,例如加减乘除等。掌握各种运算符的用法,能够帮助我们构建出更加复杂的表达式和语句。
int main() {
int a = 10; // 整型变量
double b = 3.14; // 浮点型变量
char c = 'A'; // 字符型变量
bool d = true; // 布尔型变量
// 算术运算符示例
int sum = a + b; // 加法运算
int diff = a - b; // 减法运算
int prod = a * b; // 乘法运算
double div = a / b; // 除法运算
return 0;
}
1.2 控制结构与函数
控制结构是编程中实现逻辑判断和循环重复操作的语法基础。C++中的条件语句(如if和switch)以及循环语句(如for和while)构成了控制结构的核心。函数则允许我们将代码封装成可重复调用的模块,是组织代码和实现复用的基础。
// 示例:使用if条件语句进行逻辑判断
int value = 10;
if (value > 0) {
std::cout << "The value is positive." << std::endl;
}
// 示例:定义并调用函数
void printValue(int num) {
std::cout << "The number is: " << num << std::endl;
}
int main() {
printValue(value);
return 0;
}
通过上述基础的讲解,读者将能够对C++的基本语法有一个直观的认识,并能够编写出简单的程序。下一章将深入探讨面向对象编程的概念,这是C++中一个非常重要的编程范式。
2. 面向对象编程(OOP)教学
2.1 面向对象编程基础
2.1.1 类和对象
在面向对象编程的世界中,类和对象是构建程序的基石。一个类可以被看作是一个蓝图,定义了一类对象共有的属性和方法。而对象则是根据这个蓝图创建出来的具体实例。从C++的视角来审视类和对象,我们可以发现它们是如何支撑起整个OOP框架的。
在C++中,定义一个类的语法如下:
class MyClass {
// 成员变量
int myAttribute;
public:
// 构造函数
MyClass(int attr) : myAttribute(attr) {}
// 成员函数(方法)
void myMethod() {
// 实现细节
}
};
2.1.2 封装、继承与多态
封装是面向对象编程的三大特性之一。它是指将数据(属性)和操作数据的方法(行为)绑定在一起,形成一个独立的单元。在C++中,使用public、protected和private关键字来控制类成员的访问权限,从而实现封装。
继承是面向对象编程的另一个关键特性,它允许创建类的层次结构。通过继承,子类可以继承父类的属性和方法,同时还能添加自己的特性和行为或重写继承来的方法。C++支持单一继承和多继承。
class Base {
public:
virtual void myVirtualMethod() {
std::cout << "Base class method" << std::endl;
}
};
class Derived : public Base {
public:
void myVirtualMethod() override {
std::cout << "Derived class method" << std::endl;
}
};
多态性是指能够使用基类的指针或引用来指向派生类的对象,并通过基类的指针或引用调用在派生类中重写的方法。在C++中,多态是通过虚函数实现的。虚函数允许在派生类中重写,使得基类指针或引用能够动态绑定到派生类的实现上。
. . . 封装的代码示例及逻辑分析
下面是一个封装的代码示例:
class Account {
private:
double balance; // 私有成员变量
public:
void deposit(double amount) {
balance += amount; // 只允许通过成员函数修改balance
}
double getBalance() const {
return balance; // 允许外部查询,但不允许修改
}
};
在这个例子中, balance
是 Account
类的一个私有成员变量,它不能被类的外部直接访问。通过封装,我们只能通过公共成员函数 deposit
和 getBalance
来修改和访问 balance
。这样的设计可以确保 balance
的一致性和安全性,防止外部代码直接修改 balance
导致的数据不一致问题。
. . . 继承的代码示例及逻辑分析
这里是一个简单的继承代码示例:
class Vehicle {
protected:
int wheels;
public:
Vehicle(int w) : wheels(w) {}
};
class Car : public Vehicle {
public:
Car(int w) : Vehicle(w) {}
};
在这个例子中, Car
类继承自 Vehicle
类,并通过继承获得了 Vehicle
类中的 wheels
成员变量和构造函数。 Car
类将继承得到的 wheels
变量作为其属性,并在自己的构造函数中初始化它。
. . . 多态的代码示例及逻辑分析
接下来是一个多态的例子:
class Shape {
public:
virtual void draw() = 0;
virtual ~Shape() {}
};
class Circle : public Shape {
public:
void draw() override {
std::cout << "Drawing Circle" << std::endl;
}
};
class Rectangle : public Shape {
public:
void draw() override {
std::cout << "Drawing Rectangle" << std::endl;
}
};
void drawShapes(std::vector<Shape*> shapes) {
for (Shape* shape : shapes) {
shape->draw(); // 动态绑定调用实际类型的draw方法
}
}
在这个例子中, Shape
是一个抽象基类,它包含了一个纯虚函数 draw
。 Circle
和 Rectangle
类继承自 Shape
类,并重写了 draw
方法。在 drawShapes
函数中,我们接受了一个 Shape
指针的向量,这个向量可以包含 Shape
的任何派生类对象。通过调用 draw
方法,我们能够利用多态性,根据对象的实际类型来调用相应的 draw
方法。
通过这些示例,我们可以看到类和对象在面向对象编程中的核心作用,以及如何通过封装、继承和多态这些基本特性,构建出强大且灵活的C++程序。
3. 标准模板库(STL)的使用
3.1 STL容器和迭代器
3.1.1 序列式容器:vector、list、deque
序列式容器是STL中最基本也是最常用的容器类型之一,它们以线性方式存储元素。在这类容器中,元素的访问和操作主要基于元素在序列中的位置。常见的序列式容器包括vector、list和deque。
vector vector
是一个动态数组,能够高效地进行随机访问操作。它支持在序列尾部快速地插入和删除元素,但在序列中间进行插入和删除操作时效率较低。 vector
适用于元素数量未知,但需要频繁访问任何位置元素的场景。
#include <iostream>
#include <vector>
int main() {
std::vector<int> vec; // 创建一个空的vector
for(int i = 0; i < 10; ++i) {
vec.push_back(i); // 动态添加元素
}
for(int i = 0; i < vec.size(); ++i) {
std::cout << vec[i] << ' '; // 通过索引访问元素
}
std::cout << std::endl;
// 输出: ***
return 0;
}
代码分析: - #include <vector>
:包含了 vector
类的定义。 - std::vector<int> vec;
:声明了一个 int
类型的 vector
容器。 - vec.push_back(i);
:向 vector
的末尾添加元素。 - std::cout << vec[i];
:通过索引访问 vector
中的元素。
vector
的关键优势在于随机访问的效率,这得益于其内部连续的内存存储方式。但是,需要注意的是, vector
不支持在任意位置高效地进行插入和删除操作。
list list
是一个双向链表结构,提供了在任何位置进行高效插入和删除操作的能力。不同于 vector
, list
不能进行随机访问,因为其元素不是存储在连续的内存空间中。
#include <iostream>
#include <list>
int main() {
std::list<int> lst;
for(int i = 0; i < 10; ++i) {
lst.push_back(i); // 从尾部添加元素
}
for(auto it = lst.begin(); it != lst.end(); ++it) {
std::cout << *it << ' '; // 通过迭代器遍历list
}
std::cout << std::endl;
// 输出: ***
return 0;
}
代码分析: - #include <list>
:包含了 list
类的定义。 - std::list<int> lst;
:声明了一个 int
类型的 list
容器。 - lst.push_back(i);
:向 list
的末尾添加元素。 - for(auto it = lst.begin(); it != lst.end(); ++it)
:通过迭代器遍历 list
中的元素。
list
容器在进行插入和删除操作时的效率非常高,因此它适合需要在任意位置频繁修改元素的场景。
deque deque
(double-ended queue)是一种双端队列,它允许在容器的前端和尾端都能高效地插入和删除元素。 deque
在内部实现了多个动态分配的数组,每个数组存储一部分元素,且元素可以连续存储也可以不连续。这就使得 deque
既具有 list
的灵活性,又能在两端快速访问元素,类似于 vector
。
#include <iostream>
#include <deque>
int main() {
std::deque<int> dq;
for(int i = 0; i < 10; ++i) {
dq.push_front(i); // 在前端添加元素
}
for(int i = 0; i < 10; ++i) {
dq.push_back(i); // 在尾端添加元素
}
for(auto it = dq.begin(); it != dq.end(); ++it) {
std::cout << *it << ' ';
}
std::cout << std::endl;
// 输出: ***
return 0;
}
代码分析: - #include <deque>
:包含了 deque
类的定义。 - std::deque<int> dq;
:声明了一个 int
类型的 deque
容器。 - dq.push_front(i);
:在 deque
的前端添加元素。 - dq.push_back(i);
:在 deque
的尾端添加元素。 - for(auto it = dq.begin(); it != dq.end(); ++it)
:通过迭代器遍历 deque
中的元素。
在实际应用中,选择 vector
、 list
还是 deque
,需要根据具体需求和操作特点来决定。例如,如果需要频繁随机访问元素,应该选择 vector
。如果需要频繁在容器中间进行插入和删除操作, list
会是一个更好的选择。而如果需要在两端频繁进行操作,但同时又需要一定的随机访问能力,则 deque
可能是最佳选择。
3.1.2 关联式容器:set、map
关联式容器主要用于存储键值对(key-value pairs),它提供了高效的查找、插入和删除操作,基于红黑树等平衡二叉树实现。它们通常以对数时间复杂度完成这些操作。常见的关联式容器包括set和map。
set set
是一个集合,其内部元素不会重复,且元素是有序排列的。每个元素本身即是键值,又作为值。 set
通常使用红黑树实现,因此其元素总是有序的,且 set
不支持随机访问操作。
#include <iostream>
#include <set>
int main() {
std::set<int> mySet;
mySet.insert(5);
mySet.insert(3);
mySet.insert(8);
mySet.insert(8); // 尝试插入重复值,set不存储重复元素
for (auto it = mySet.begin(); it != mySet.end(); ++it) {
std::cout << *it << ' ';
}
std::cout << std::endl;
// 输出: 3 5 8
return 0;
}
代码分析: - #include <set>
:包含了 set
类的定义。 - std::set<int> mySet;
:声明了一个 int
类型的 set
容器。 - mySet.insert(...);
:向 set
中插入元素。 - for(auto it = mySet.begin(); it != mySet.end(); ++it)
:通过迭代器遍历 set
中的元素。
set
容器中的元素总是有序的,而且不允许重复。它提供了一些成员函数,比如 lower_bound
和 upper_bound
,用于在对数时间复杂度内查找元素。由于其基于平衡树实现, set
的操作效率在众多数据结构中是非常高效的。
map map
是一个字典容器,它以键值对形式存储数据,每个键值对由键(key)和值(value)组成,且每个键是唯一的。键通常用于快速查找对应的值。 map
同样基于红黑树实现,因此它也是有序的。
#include <iostream>
#include <map>
int main() {
std::map<std::string, int> myMap;
myMap["apple"] = 1;
myMap["banana"] = 2;
myMap["orange"] = 3;
for(auto it = myMap.begin(); it != myMap.end(); ++it) {
std::cout << it->first << " : " << it->second << std::endl;
}
// 输出:
// apple : 1
// banana : 2
// orange : 3
return 0;
}
代码分析: - #include <map>
:包含了 map
类的定义。 - std::map<std::string, int> myMap;
:声明了一个键为 string
类型,值为 int
类型的 map
容器。 - myMap["apple"] = 1;
:向 map
中插入键值对。 - for(auto it = myMap.begin(); it != myMap.end(); ++it)
:通过迭代器遍历 map
中的元素。
map
容器提供了一种机制,能够将键与值关联起来,让我们可以通过键来快速查找值。它特别适合那些需要查找操作的场景,如数据库系统中的索引实现等。
关联式容器如 set
和 map
在需要高效查找、插入和删除元素的场景中非常有用,但它们通常比序列式容器消耗更多的内存,因为每个元素除了要存储键值对之外,还需要存储额外的平衡树相关数据。
3.2 STL算法与函数对象
3.2.1 常用算法:排序、搜索、变换
STL提供了丰富的算法,这些算法可以在不同的容器上执行操作,而无需关心容器的具体实现细节。这些算法被分类为非修改性序列操作、修改性序列操作、排序和通用数字算法等。以下是一些常用的STL算法。
排序算法 - sort
:对容器中的元素进行排序。默认情况下, sort
使用 <
操作符比较元素。 - stable_sort
:类似于 sort
,但是它会保持相等元素的相对顺序。 - partial_sort
:仅对前 n
个元素进行部分排序。
#include <iostream>
#include <vector>
#include <algorithm> // STL算法库
int main() {
std::vector<int> vec = {5, 3, 8, 1, 2, 9};
std::sort(vec.begin(), vec.end()); // 对vector进行排序
for (int v : vec) {
std::cout << v << ' ';
}
std::cout << std::endl;
// 输出: 1 2 3 5 8 9
return 0;
}
搜索算法 - find
:在容器中查找是否存在指定的值。 - count
:计数容器中与指定值相等的元素的数量。
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
std::vector<int> vec = {1, 2, 3, 4, 5};
int target = 3;
auto it = std::find(vec.begin(), vec.end(), target);
if (it != vec.end()) {
std::cout << "找到目标值: " << target << std::endl;
} else {
std::cout << "未找到目标值: " << target << std::endl;
}
// 输出: 找到目标值: 3
return 0;
}
变换算法 - transform
:对容器中的元素应用一个操作,并存储结果到另一个容器中。 - for_each
:对容器中的每个元素执行一个操作。
#include <iostream>
#include <vector>
#include <algorithm>
#include <cmath> // 为了使用 sqrt 函数
int main() {
std::vector<double> vec = {1.0, 2.0, 3.0, 4.0, 5.0};
std::vector<double> result(vec.size()); // 创建一个相同大小的vector存储结果
std::transform(vec.begin(), vec.end(), result.begin(), [](double x) {
return std::sqrt(x); // 对每个元素求平方根
});
for (double r : result) {
std::cout << r << " ";
}
std::cout << std::endl;
// 输出: 1 1.41421 1.73205 2 2.23607
return 0;
}
STL算法库提供了高度的灵活性和复用性,使得开发者可以不必重写基础代码,而是专注于问题的解决。对于每种算法,STL都提供了大量的重载版本,以适应不同的需求。例如, sort
算法可以通过自定义比较函数来进行复杂类型的排序。
3.2.2 函数对象和仿函数
仿函数(Functors),又称为函数对象,是重载了 operator()
的一种对象类型。它们在某些方面表现得就像函数一样,但它们是类的实例,并且可以持有状态。
#include <iostream>
#include <vector>
#include <algorithm>
class Square {
public:
int operator()(int x) {
return x * x;
}
};
int main() {
std::vector<int> vec = {1, 2, 3, 4, 5};
std::transform(vec.begin(), vec.end(), vec.begin(), Square());
for (int v : vec) {
std::cout << v << ' ';
}
std::cout << std::endl;
// 输出: 1 4 9 16 25
return 0;
}
代码分析: - class Square
:定义了一个仿函数类。 - Square()
:重载了 operator()
,使得该类的对象可以像函数一样被调用。 - std::transform(..., Square())
:使用 Square
对象作为转换函数。
仿函数的使用非常广泛,特别是在需要算法操作时需要保持状态的情况下,比如当一个操作依赖于之前的状态或者需要保存状态供以后使用时。STL提供了一些预定义的仿函数,例如 std::plus
、 std::minus
、 std::multiplies
等,用于简化常用的数学运算。此外,自定义仿函数可以像任何其他对象一样被复制和传递,这使得它们在STL算法中非常灵活和强大。
STL算法和仿函数的结合,为编程提供了一种强大的抽象机制,它允许开发者以更抽象的方式编写通用代码,从而提高代码的复用性和效率。通过使用STL算法和仿函数,开发者可以快速实现复杂的数据处理逻辑,而且代码更加简洁和易于维护。
4. 模板元编程原理
4.1 模板基础与特化
4.1.1 函数模板和类模板
模板是 C++ 中的强大特性,它允许编写与数据类型无关的代码。函数模板允许我们创建一个可以与多种数据类型一起工作的函数,而类模板则允许我们创建一个可以与多种数据类型一起工作的类。
函数模板的基本语法:
template <typename T>
T max(T a, T b) {
return a > b ? a : b;
}
在上面的代码中, typename T
是一个模板参数,它将在函数调用时被实际的数据类型所替代。函数 max
可以用于比较两个相同类型的值并返回较大的一个。
类模板的基本语法:
template <typename T>
class Stack {
public:
void push(const T& element);
void pop();
T top() const;
bool isEmpty() const;
private:
std::vector<T> elems; // 内部使用 STL vector 存储元素
};
在这个例子中, Stack
是一个类模板,它使用了 std::vector<T>
来存储元素。 T
是模板参数,用户在创建 Stack
对象时需要指定具体的数据类型。
4.1.2 模板特化和偏特化
模板特化是模板编程中的一个高级特性,它允许我们为特定的类型或类型组合提供定制的模板实现。模板特化有两种形式:完全特化和偏特化。
完全特化:
template <>
int max<int>(int a, int b) {
return a > b ? a : b;
}
在这个例子中,我们为 int
类型完全特化了 max
函数模板。这意味着当 max
函数被 int
类型调用时,将会使用这个特化版本。
偏特化:
template <typename T1, typename T2>
class Pair {
public:
Pair(const T1& a, const T2& b) : first(a), second(b) {}
T1 first;
T2 second;
};
// 偏特化版本,当 T1 和 T2 相同时使用
template <typename T>
class Pair<T, T> {
public:
Pair(const T& a, const T& b) : first(a), second(b) {}
T first;
T second;
};
偏特化允许我们针对模板参数的某些特定组合提供不同的实现。在这个例子中,我们为 Pair
类模板提供了一个偏特化版本,它要求两个模板参数必须是相同的类型。
4.2 模板元编程技术
4.2.1 编译时计算
模板元编程(TMP)是 C++ 中利用模板进行编译时计算的一种编程范式。它允许在编译阶段执行复杂的计算,从而生成高效的运行时代码。这使得模板元编程成为了性能优化的强大工具。
编译时计算示例:
template<int N>
struct Factorial {
static const int value = N * Factorial<N - 1>::value;
};
template<>
struct Factorial<0> {
static const int value = 1;
};
int main() {
constexpr int fact5 = Factorial<5>::value; // 编译时计算得到 120
return 0;
}
在这个例子中, Factorial
是一个模板结构体,它递归地计算阶乘。当编译器处理到 Factorial<5>::value
时,它会展开为 5 * 4 * 3 * 2 * 1
的计算过程,最终结果在编译时就已经确定。
4.2.2 静态断言和类型萃取
静态断言( static_assert
)是 C++11 引入的一个特性,它允许在编译时检查某些条件是否满足,如果不满足则编译会失败并输出相应的错误信息。
静态断言示例:
template<typename T>
void process(const T& value) {
static_assert(std::is_integral<T>::value, "T must be an integral type");
// ... 处理逻辑 ...
}
int main() {
process(42); // 正确
// process(3.14); // 编译错误,因为这不是整型
}
在这个例子中, process
函数模板使用了 static_assert
来确保传入的类型 T
是一个整型。如果传入的类型不是整型,编译时会失败,并显示一条错误信息。
类型萃取是一种编程技术,它允许我们根据类型属性提取出相关的类型或常量。 std::is_integral
是标准库提供的一个类型萃取模板,它用于检测一个类型是否为整型。
类型萃取示例:
#include <type_traits>
template<typename T>
void doSomething(T& value) {
using ValueType = typename std::remove_reference<T>::type;
// ... 根据 ValueType 进行特定操作 ...
}
int main() {
int a = 10;
doSomething(a); // ValueType 是 int
return 0;
}
在这个例子中, doSomething
函数模板利用 std::remove_reference
来移除 T
的引用属性,并将其作为 ValueType
使用。这是一种常见的类型萃取技术,用于获取原始数据类型。
模板元编程是一个深奥而强大的主题,它在C++中开辟了一条既快速又高效的代码执行路径,是每个C++高级开发者都应该掌握的技巧。通过编译时计算和类型萃取等技术,可以大幅优化程序的性能,并在编译阶段就解决潜在的类型问题。
5. 并发编程与多线程
并发编程是现代编程语言中的一个重要特性,它使得我们可以编写能够同时执行多个任务的程序。C++在C++11标准中引入了对并发编程的原生支持,大大简化了多线程程序的编写,提升了程序执行的效率和吞吐量。在本章中,我们将探讨C++中的多线程基础以及C++11并发特性的应用。
5.1 多线程基础
多线程允许程序将一个大的任务切分成多个小任务,并在不同的处理器核心上同时运行这些任务。这样可以有效地提高程序的性能,特别是在CPU密集型任务中。
5.1.1 线程的创建与管理
C++11中引入了 <thread>
头文件,为线程的操作提供了简单的API。
#include <iostream>
#include <thread>
void thread_function() {
// 线程将要执行的代码
std::cout << "Hello from thread!\n";
}
int main() {
std::thread t(thread_function); // 创建一个线程对象
std::cout << "Hello from main!\n";
t.join(); // 等待线程完成
return 0;
}
上面的示例中,我们创建了一个线程 t
,并且让它执行 thread_function
函数。 join
方法是让主线程等待子线程完成工作后再继续执行。
5.1.2 线程同步机制
由于多个线程可能同时访问同一数据,线程同步机制就显得尤为重要。C++11提供了多种同步工具,例如互斥锁( std::mutex
)、条件变量( std::condition_variable
)等。
#include <iostream>
#include <thread>
#include <mutex>
std::mutex mtx; // 全局互斥锁
int shared_data = 0;
void increment(int n) {
for (int i = 0; i < n; ++i) {
mtx.lock(); // 上锁
++shared_data;
mtx.unlock(); // 解锁
}
}
int main() {
std::thread t1(increment, 10000);
std::thread t2(increment, 10000);
t1.join();
t2.join();
std::cout << "Shared data: " << shared_data << '\n';
return 0;
}
在上面的示例中,我们使用 std::mutex
来保护对共享资源 shared_data
的访问,确保在任何时刻只有一个线程可以修改它。
5.2 C++11并发特性的应用
C++11中的并发支持不仅包括了线程的创建和管理,还引入了原子操作、内存模型和线程局部存储等高级特性。
5.2.1 原子操作与内存模型
原子操作保证了对变量的操作是不可分割的,从而避免了并发执行时的数据竞争问题。
#include <iostream>
#include <atomic>
std::atomic<int> atomic_data(0);
void atomic_increment(int n) {
for (int i = 0; i < n; ++i) {
atomic_data.fetch_add(1, std::memory_order_relaxed); // 原子加操作
}
}
int main() {
std::thread t1(atomic_increment, 10000);
std::thread t2(atomic_increment, 10000);
t1.join();
t2.join();
std::cout << "Atomic data: " << atomic_data << '\n';
return 0;
}
在上述代码中, std::atomic
用于创建一个原子类型, fetch_add
执行一个原子增加操作, std::memory_order_relaxed
指定了内存模型。
5.2.2 并发算法和线程局部存储
C++11中的 <algorithm>
提供了针对迭代器范围的并发算法。 <thread>
头文件还引入了线程局部存储,允许每个线程拥有自己的数据副本。
#include <iostream>
#include <thread>
#include <vector>
#include <thread>
std::vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
std::vector<int> results(numbers.size());
std::thread_specific_ptr<int> t_result(new int);
void process_numbers(int start, int end) {
for (int i = start; i < end; ++i) {
t_result.reset(new int); // 为当前线程设置新的数据
*t_result = numbers[i] * numbers[i]; // 计算平方
results[i] = *t_result; // 将结果存储在共享数组中
}
}
int main() {
std::thread t1(process_numbers, 0, 5);
std::thread t2(process_numbers, 5, 10);
t1.join();
t2.join();
for (const auto& result : results) {
std::cout << result << ' ';
}
return 0;
}
在此例中,我们利用 std::thread_specific_ptr
创建了线程局部存储,并在多线程环境下安全地存储了计算结果。
通过本章的学习,您已经掌握了C++中多线程编程的基础知识和C++11并发特性的高级应用。接下来的章节将探讨如何将这些知识应用到实际的项目实战中,以及如何提升您的编程综合能力。
简介:C++作为一种广泛使用的编程语言,特别适用于系统软件、游戏开发、高性能计算和嵌入式系统。'Comp345'课程旨在教授学生C++的基础语法、面向对象编程原则、标准模板库(STL)的使用以及高级主题如模板元编程和并发编程。学生将通过实践学习,掌握变量声明、控制流结构、函数、OOP概念(类、对象、封装、继承、多态)、STL容器、算法和函数对象。同时,课程也涉及到C++模板的使用、模板特化,以及C++11及后续版本的并发编程特性。该课程通过实践项目如'Comp345-main'源代码文件,帮助学生将所学知识综合应用,以提高编程技能和解决复杂问题的能力。