[C/C++] -- C++11相关内容

一:声明

  • auto:

auto 是 C++11 引入的一个关键字,用于自动推断变量的类型。通过使用 auto,编译器可以根据变量的初始化表达式推断其类型,从而减少代码中的重复冗长的类型声明。

简化模板声明:

for(auto p = vec.begin();p!=vec.end();p++)
  • decltype

decltype 是 C++11 引入的一个关键字,用于获取表达式的类型,而不进行实际的表达式求值。它的作用是让编译器在编译时推断表达式的类型,并将其作为编译器生成的类型声明的一部分。

获取变量的类型:

int x = 5;
decltype(x) y; // y 的类型为 int,与 x 的类型相同

获取表达式的类型:

int a = 10;
double b = 20.5;
decltype(a + b) c; // c 的类型为 double,因为 a + b 的类型为 double

结合 autodecltype

int x = 5;
auto y = x; // y 的类型为 int,auto 推断为 int
decltype(auto) z = x; // z 的类型为 int&,decltype(auto) 保留了 x 的引用类型

获取函数返回值类型:

int foo();
decltype(foo()) result; // result 的类型为 foo() 函数返回值的类型
  • auto

    • 简化模板代码,减少模板参数的复杂性。
    • 适用于需要简化类型声明,提高代码可读性的场合。
  • decltype

    • 在需要编写泛型代码时,用于捕获表达式的确切类型。
    • 与模板结合,用于处理复杂的类型推导,例如迭代器或容器中的类型。
    • 在需要保留变量引用类型的场合。
int arr[5] = {1, 2, 3, 4, 5};
decltype(arr) arr_copy; // arr_copy 的类型是 int[5]
auto arr_ref = arr; // arr_ref 的类型是 int*

当我们声明 int arr[5] = {1, 2, 3, 4, 5}; 时,我们创建了一个名为 arr 的数组,其中包含了5个整数元素。

  1. decltype(arr) arr_copy

    这行代码中,decltype(arr) 将返回 arr 的类型,即 int[5],表示一个包含5个整数元素的数组。因此,arr_copy 的类型也是 int[5],它是一个未初始化的包含5个整数元素的数组。
  2. auto arr_ref = arr

    这里使用 auto 推导 arr_ref 的类型。因为 arr 是一个数组,当数组名被用作表达式时,它会自动退化为指向数组首元素的指针(即 int* 类型)。因此,arr_ref 的类型被推导为 int*,表示一个指向整数的指针,指向数组 arr 的首元素。

所以,arr_copy 是一个未初始化的包含5个整数元素的数组,而 arr_ref 是一个指向数组 arr 的首元素的指针。

  • 返回类型后置

在C++11之前,返回类型通常在函数签名的前面指定,例如:

int add(int a, int b) {return a + b;
}

然而,随着C++11的引入,一种新的语法允许在函数签名之后指定返回类型,这被称为返回类型后置(Trailing Return Type)。这种语法对于需要从参数中推导返回类型的情况特别有用,并且在与 decltype 结合时非常方便。

#include <iostream>// 使用返回类型后置来定义一个函数,返回两个输入参数的和。
auto add(int a, int b) -> int {return a + b;
}// 使用返回类型后置和 decltype 来推导返回类型。
template <typename T, typename U>
auto multiply(T a, U b) -> decltype(a * b) {return a * b;
}int main() {std::cout << "Add: " << add(3, 4) << std::endl; // 返回类型是 intstd::cout << "Multiply: " << multiply(3, 4.5) << std::endl; // 返回类型由 decltype 推导return 0;
}
  • 模板别名:using=

在C++11中,引入了 using 关键字用于定义模板别名。using 关键字提供了一种更简洁、易读的方式来定义类型别名,特别是在使用模板时。

using alias_name = type;

alias_name 是你为类型定义的别名,而 type 是你要为其创建别名的类型。using 语句可以用于定义任何类型的别名,包括模板类型。

typedef 可以创建基本类型、指针类型、复合类型和函数指针类型的别名。

using 在 C++11 引入之后,成为了更通用的替代方案,可以用于创建类型别名、模板别名以及模板别名模板。

差别在于,新语法可用于模板部分具体化,但typedef不能

template<typename T>using arr12 = std::arry<T,12>

对于如下声明:

std::array<double,12> a1;
std::array<std::string,12> a2;

可以使用上述具体化模板声明:

arr12<double> a1;
arr12(std::string) a2;
  • nullptr

nullptr 是 C++11 中引入的空指针常量,用于代表空指针。它是一个特殊的字面值,可以被赋值给指针类型,而且不会与整数进行混淆。nullptr 用于替代传统的 NULL 宏,NULL 通常被定义为 0 或者 (void*)0。因为 NULL 的定义可能是 0 或者指针类型的零值,所以在某些情况下,使用 NULL 可能会引起歧义,特别是在函数重载时。

nullptr 的引入解决了这个问题,它是一个明确的指针值,可以用于初始化任何指针类型,而不会与整数进行混淆。

二:智能指针

智能指针是 C++ 中用于管理动态内存的一种工具,它们可以自动管理内存的生命周期,从而减少内存泄漏和悬挂指针等问题。智能指针在 C++11 引入标准库之后变得非常流行。

  1. std::unique_ptr

    • std::unique_ptr 是一种独占所有权的智能指针,它确保在其生命周期结束时自动释放所管理的对象。
    • 每个 std::unique_ptr 拥有对其所指向对象的唯一所有权,当 std::unique_ptr 被销毁时,它会自动释放其所管理的对象。
    • 不能进行拷贝操作,但可以进行移动操作。
  2. std::shared_ptr

    • std::shared_ptr 是一种共享所有权的智能指针,它允许多个指针共享对同一对象的所有权。
    • std::shared_ptr 使用引用计数来跟踪有多少个 std::shared_ptr 共享同一对象。当引用计数为零时,对象会被自动释放。
    • 拷贝 std::shared_ptr 会增加引用计数,而销毁或者重置 std::shared_ptr 则会减少引用计数。
  3. std::weak_ptr

    • std::weak_ptr 是一种弱引用智能指针,它不会增加对象的引用计数,也不会拥有对象的所有权。
    • 通常与 std::shared_ptr 一起使用,用于解决 std::shared_ptr 的循环引用问题。
    • 可以通过 std::weak_ptr 创建 std::shared_ptr,但需要检查 std::weak_ptr 是否过期(即底层对象是否已被释放)。

三:类的修改

  • explicit

explicit 关键字用于防止隐式转换和复制初始化。它可以应用于单参数构造函数和转换函数。

#include <iostream>class MyClass {
public:explicit MyClass(int x) : data(x) {}int getData() const { return data; }private:int data;
};void process(const MyClass& obj) {std::cout << "Data: " << obj.getData() << std::endl;
}int main() {MyClass obj1(42); // 直接初始化,正常process(obj1);    // 调用 process 函数,正常// MyClass obj2 = 42; // 错误!explicit 构造函数禁止复制初始化MyClass obj2 = MyClass(42); // 正确,使用直接初始化process(obj2);              // 调用 process 函数,正常return 0;
}

explicit 阻止了 MyClass 的单参数构造函数被用于隐式转换,从而增强了代码的清晰度和安全性。

  • 类内成员初始化

类内成员初始化是在 C++11 引入的特性,允许在类定义中直接初始化成员变量。这种方式可以确保成员变量在对象创建时就被初始化,提高了代码的可读性和可维护性。

#include <iostream>class MyClass {
public:// 类内初始化成员变量int data = 0;double value = 3.14;// 构造函数MyClass(int d) : data(d) {} // 对于没有在类内初始化的成员变量,可以在构造函数中进行初始化
};int main() {MyClass obj(42);std::cout << "Data: " << obj.data << std::endl;   // 输出:Data: 42std::cout << "Value: " << obj.value << std::endl; // 输出:Value: 3.14return 0;
}

四:模板和STL的修改

为改善模板和标准模板库的可用性。

  • for循环修改

对于内置数组以及包含方法begin()和end()的类(如std::string)和STL容器,基于范围的for循环可简化编写循环的工作。

如要修改可以使用引用类型。

#include <iostream>
#include <vector>
#include <string>int main() {// 修改内置数组int arr[] = {1, 2, 3, 4, 5};for (int &x : arr) {x *= 2; // 将数组中的每个元素乘以 2}// 修改 std::stringstd::string str = "hello";for (char &c : str) {c = toupper(c); // 将字符串中的每个字符转换为大写形式}// 修改 STL 容器(例如 std::vector)std::vector<int> vec = {1, 2, 3, 4, 5};for (auto &num : vec) {num *= 2; // 将容器中的每个元素乘以 2}// 输出修改后的结果for (const auto &x : arr) {std::cout << x << " ";}std::cout << std::endl;std::cout << str << std::endl;for (const auto &num : vec) {std::cout << num << " ";}std::cout << std::endl;return 0;
}
  • 新的STL容器

C++ 新的 STL(标准模板库)容器包括一些在 C++11 标准中引入的以及后续标准中新增的容器。

  1. std::array

    1. 固定大小的数组,与内置数组类似,但提供了更多的功能和安全性。
    2. 它具有固定的大小,在编译时就确定了,因此不支持动态大小调整。
  2. std::forward_list

    1. 单向链表,与 std::list 不同,它只能从头到尾进行迭代,无法逆向迭代。
    2. std::forward_list 在某些情况下比 std::list 更加高效,尤其是对于大量的插入和删除操作。
  3. std::unordered_setstd::unordered_map

    1. 哈希集合和哈希映射,分别对应于 std::set 和 std::map 的无序版本。
    2. 它们使用哈希表来实现,具有 O(1) 的平均插入、查找和删除时间复杂度,但不保证元素的顺序。
  4. std::unordered_multisetstd::unordered_multimap

    1. 允许重复键的哈希集合和哈希映射,分别对应于 std::multiset 和 std::multimap 的无序版本。
    2. 允许插入相同键的多个副本,不保证元素的顺序。
  5. std::tuple

    1. 元组,可以存储多个不同类型的值,并且可以在编译时或运行时访问这些值。
    2. 元组的大小和类型在编译时确定,提供了一种方便的方式来处理多个值。
  6. std::array_viewstd::span(C++20):

    1. 提供了对连续内存区域的非拥有式访问,允许安全地查看数组或容器的一部分,而不复制数据。
    2. std::span 在 C++20 中引入,提供了更多的功能和灵活性。
  • 新的STL方法

cbegin()cend() 返回的是常量迭代器,而 begin()end() 返回的是普通迭代器。主要区别在于:

  1. cbegin() 和 cend()

    • 返回常量迭代器。
    • 用于遍历容器中的元素,但不能修改这些元素。
    • cbegin() 返回指向容器第一个元素的常量迭代器。
    • cend() 返回指向容器尾后位置的常量迭代器。
  2. begin() 和 end()

    • 返回普通迭代器。
    • 可以用于遍历容器中的元素,并且可以修改这些元素。
    • begin() 返回指向容器第一个元素的迭代器。
    • end() 返回指向容器尾后位置的迭代器。
  3. crbegin() 和 crend()

    • 返回常量逆向迭代器。
    • 用于逆序遍历容器中的元素,并且不能修改这些元素。
    • crbegin() 返回指向容器最后一个元素的常量逆向迭代器。
    • crend() 返回指向容器起始位置的常量逆向迭代器。
  4. rbegin() 和 rend()

    • 返回普通逆向迭代器。
    • 用于逆序遍历容器中的元素,并且可以修改这些元素。
    • rbegin() 返回指向容器最后一个元素的逆向迭代器。
    • rend() 返回指向容器起始位置的逆向迭代器。
#include <iostream>
#include <vector>int main() {// 创建一个vectorstd::vector<int> vec = {1, 2, 3, 4, 5};// 使用 cbegin() 和 cend() 遍历容器中的元素(不修改元素)std::cout << "Using cbegin() and cend(): ";for (auto it = vec.cbegin(); it != vec.cend(); ++it) {std::cout << *it << " ";}std::cout << std::endl;// 使用 begin() 和 end() 遍历容器中的元素(可以修改元素)std::cout << "Using begin() and end(): ";for (auto it = vec.begin(); it != vec.end(); ++it) {*it *= 2; // 修改元素的值std::cout << *it << " ";}std::cout << std::endl;// 使用 crbegin() 和 crend() 逆序遍历容器中的元素(不修改元素)std::cout << "Using crbegin() and crend(): ";for (auto it = vec.crbegin(); it != vec.crend(); ++it) {std::cout << *it << " ";}std::cout << std::endl;// 使用 rbegin() 和 rend() 逆序遍历容器中的元素(可以修改元素)std::cout << "Using rbegin() and rend(): ";for (auto it = vec.rbegin(); it != vec.rend(); ++it) {*it *= 2; // 修改元素的值std::cout << *it << " ";}std::cout << std::endl;return 0;
}
  • >>

为避免与运算符>>混淆,C++11要求在声明嵌套模板时使用空格将尖括号分开

std::vector<std::pair<int, std::string> > myVector; // 正确
  • 左值引用

传统C++引用(现称左值引用)使得标识符关联到左值,

左值引用的声明

int x = 5;
int& refX = x;  // refX 是对 x 的左值引用

左值引用作为函数参数

左值引用经常用作函数的参数,可以实现按引用传递,避免不必要的复制。

void increment(int& num) {num++;  // 修改传入的参数
}int main() {int value = 10;increment(value);  // 传入 value 的引用std::cout << value << std::endl;  // 输出 11,因为 value 已被增加return 0;
}

左值引用和赋值

int x = 5;
int y = 10;
int& ref = x;  // ref 引用 x
ref = y;       // 将 x 的值改为 y 的值,即 x 变为 10

虽然引用和取地址都可以用于访问对象,但它们的实现方式和用途不同:

  • 引用是对象的别名,提供了对对象的另一种访问方式,更方便、更安全地操作对象。
  • 取地址是获取对象在内存中的地址,返回的是指向目标对象的指针,可以通过指针来直接访问或修改对象的值,但需要注意指针的正确使用和管理。

引用示例

#include <iostream>int main() {int x = 10;int& ref = x;  // 定义引用 ref,绑定到变量 xstd::cout << "x 的值为:" << x << std::endl;std::cout << "ref 的值为:" << ref << std::endl;ref = 20;  // 通过引用修改 x 的值std::cout << "修改后,x 的值为:" << x << std::endl;std::cout << "修改后,ref 的值为:" << ref << std::endl;return 0;
}

取地址示例

#include <iostream>int main() {int x = 10;int* ptr = &x;  // 取得变量 x 的地址,并将其保存到指针 ptr 中std::cout << "x 的值为:" << x << std::endl;std::cout << "ptr 指向的值为:" << *ptr << std::endl;*ptr = 20;  // 通过指针修改 x 的值std::cout << "修改后,x 的值为:" << x << std::endl;std::cout << "修改后,ptr 指向的值为:" << *ptr << std::endl;return 0;
}
  • 左值引用必须绑定到一个具有持久性的对象,因此不能绑定到临时对象(右值)。
  • 左值引用一般用于实现按引用传递,可以在函数内部修改传入的参数,而不是复制参数的副本。
  • 右值引用

右值引用是 C++11 引入的新特性,用于实现移动语义和完美转发,主要用于优化对象的拷贝和移动操作。

右值引用的声明方式是在类型后面加上 &&,例如 int&& 表示一个右值引用类型。

int&& rvalue_ref = 5; // 右值引用绑定到临时对象 5

右值引用主要用于绑定到临时对象(右值),通常用于移动语义和完美转发。

int&& rvalue_ref = 5; // 右值引用绑定到临时对象 5int x = 10;
int&& rvalue_ref2 = std::move(x); // std::move 将左值转换为右值引用

std::move 是一个 C++ 中的函数模板,主要用于将对象转换为右值引用,从而支持移动语义。它的主要作用有两个:

  1. 标识对象为右值引用: std::move 将对象转换为右值引用,即使原本是左值,这样就可以在移动构造函数和移动赋值运算符中使用。这使得我们可以使用移动语义,将资源从一个对象转移到另一个对象,而不是进行深拷贝。这样可以提高程序的性能和效率。

  2. 避免不必要的拷贝: 通过将对象转换为右值引用,std::move 告诉编译器该对象可以被移动而不是复制,从而避免不必要的拷贝操作。这对于大型对象或者资源管理类(如动态分配的内存、文件句柄等)尤其有用,因为移动操作通常比复制操作更加高效。

右值引用可以用于传递临时对象或者转移对象的所有权。

void process(int&& data) {// 处理右值引用绑定的临时对象
}int main() {process(10); // 传递临时对象int x = 20;process(std::move(x)); // 转移对象的所有权return 0;
}

移动语义

传统的拷贝操作会导致对象的深拷贝,即复制对象的所有内容,包括动态分配的内存资源,这在处理大型对象时可能效率低下。而移动语义允许在资源管理类中,将资源的所有权从一个对象转移到另一个对象,而不需要进行深拷贝,从而提高了效率。

移动语义的关键在于利用右值引用来识别临时对象(右值),然后通过移动构造函数或者移动赋值运算符来“窃取”这些临时对象的资源,而不是像拷贝构造函数那样创建新的资源副本。

移动语义的实现通常使用 std::move 来将对象转换为右值引用,以便移动构造函数和移动赋值运算符能够正确地被调用。通过移动语义,可以有效地避免不必要的资源复制,提高程序的性能

完美转发

完美转发是一种技术,允许我们在函数中将参数以相同的方式传递给其他函数,保持参数的值类型不变。它通过右值引用和模板来实现。std::forward 是实现完美转发的关键,它能够在保持参数类型不变的同时,将参数转发给其他函数。

 简单的参数传递:如果你只是简单地将参数传递给其他函数,而不需要保留其值类别(即不需要完美转发),那么你可以直接使用传递给你的参数。在这种情况下,不使用 std::forward 是可以的。

template<typename T>
void foo(T arg) {bar(arg);  // 没有保留参数的值类别的必要性
}

 复杂的参数传递:但是,在需要将参数完美转发给其他函数的情况下,特别是在涉及到重载或泛型编程时,使用 std::forward 更为安全。这样可以确保参数的原始值类别被保留,从而正确地调用相关函数。使用 std::forward 可以避免意外地触发拷贝构造函数或移动构造函数,从而提高了代码的效率和安全性。

template<typename T>
void foo(T&& arg) {bar(std::forward<T>(arg));  // 保留参数的值类别
}
#include <iostream>
#include <utility> // for std::move, std::forwardtemplate<typename T>
class MoveOnlyVector {
private:T* data;size_t capacity;size_t size;public:// 默认构造函数MoveOnlyVector() : data(nullptr), capacity(0), size(0) {}// 析构函数~MoveOnlyVector() {delete[] data;}// 移动构造函数MoveOnlyVector(MoveOnlyVector&& other) noexcept : data(std::exchange(other.data, nullptr)), capacity(std::exchange(other.capacity, 0)), size(std::exchange(other.size, 0)) {}// 移动赋值运算符MoveOnlyVector& operator=(MoveOnlyVector&& other) noexcept {if (this != &other) {delete[] data;data = std::exchange(other.data, nullptr);capacity = std::exchange(other.capacity, 0);size = std::exchange(other.size, 0);}return *this;}// 添加元素template<typename U>void push_back(U&& value) {if (size >= capacity) {// 扩展容量size_t new_capacity = (capacity == 0) ? 1 : 2 * capacity;T* new_data = new T[new_capacity];for (size_t i = 0; i < size; ++i) {new_data[i] = std::move(data[i]);}delete[] data;data = new_data;capacity = new_capacity;}data[size++] = std::forward<U>(value);}
};int main() {MoveOnlyVector<std::string> vec;// 添加元素vec.push_back("hello");vec.push_back(std::string("world"));return 0;
}

五:绑定器和函数对象

  • function

std::function 是C++11中的一个模板类,用于封装任意可调用对象,包括函数指针、函数对象、成员函数指针、Lambda表达式等。它提供了一种统一的方式来处理不同类型的可调用对象,并可以在运行时确定其类型。

#include <functional>// 定义一个函数
int add(int x, int y) {return x + y;
}// 使用 std::function 封装一个可调用对象
std::function<int(int, int)> func = add;// 调用封装的函数
int result = func(3, 4);  // result = 7

 std::function 的模板参数是函数的签名,它可以用来存储具有相同参数和返回类型的任意可调用对象。

  • bind

std::bind 是C++11中的一个函数模板,用于部分应用函数参数或重新排序函数参数。它允许您在调用函数时固定某些参数的值,从而创建一个新的可调用对象。绑定器(对STL中bind1st和bind2nd的升级,结合二元函数对象-》一元函数对象)

#include <functional>// 定义一个函数
int add(int x, int y) {return x + y;
}auto add_five = std::bind(add, std::placeholders::_1, 5);
int result = add_five(3);  // result = 3 + 5 = 8

在这个例子中,std::bindadd 函数的第一个参数绑定为占位符 std::placeholders::_1,并将第二个参数固定为 5。返回的 add_five 可调用对象只有一个参数,当调用它时,它会将传递给它的参数与之前绑定的参数一起传递给 add 函数。

  •  Lambda表达式

Lambda表达式是C++11中引入的一种匿名函数语法,它允许您在需要函数对象的地方内联定义函数。Lambda表达式可以捕获外部变量,并具有非常灵活的语法。

#include <iostream>int main() {int x = 3;int y = 4;// 使用Lambda表达式定义一个函数对象auto func = [x, y](int a, int b) {return a * x + b * y;};int result = func(1, 2);  // result = 1 * 3 + 2 * 4 = 11std::cout << "Result: " << result << std::endl;return 0;
}

六:C++语言级别支持的多线程编程

C++的标准库在C++11版本中引入了多线程支持,这为跨平台多线程编程提供了一个统一的接口。这意味着你可以使用C++标准库中的多线程API来编写跨平台的多线程应用程序,而无需依赖于特定的操作系统API(如Windows的CreateThread、Linux的pthread_createclone)。

1. std::thread

std::thread是C++11中提供的类,它封装了操作系统特定的线程创建和管理方式。这使得你可以在不同的操作系统上使用相同的代码创建和管理线程。

#include <iostream>
#include <thread>// 一个简单的线程函数
void threadFunction() {std::cout << "Thread is running" << std::endl;
}int main() {// 创建一个新线程并运行 threadFunctionstd::thread myThread(threadFunction);// 等待线程结束myThread.join();return 0;
}

在这个例子中,创建了一个线程,执行threadFunction函数,然后等待该线程结束。std::thread类允许你创建、启动、加入、分离线程等。

2. std::mutex 和 线程同步:

多线程编程通常需要考虑同步和共享资源的访问。C++标准库提供了各种同步机制,如互斥锁、条件变量等。

#include <iostream>
#include <thread>
#include <mutex>// 共享资源
int counter = 0;
std::mutex mtx;void incrementCounter() {std::lock_guard<std::mutex> lock(mtx);counter++;
}int main() {std::thread t1(incrementCounter);std::thread t2(incrementCounter);t1.join();t2.join();std::cout << "Counter: " << counter << std::endl;return 0;
}

使用std::mutex来保护共享资源counter,确保在多线程环境下不会发生数据竞争

3. 其他同步机制:

除了std::mutex外,C++标准库还提供了std::condition_variablestd::futurestd::promise等,用于实现更复杂的线程同步和通信。

假设正在开发一个网络服务器,需要处理来自多个客户端的请求。我们将使用多线程来同时处理这些请求,以提高服务器的性能和并发能力。

#include <iostream>
#include <thread>
#include <vector>
#include <mutex>
#include <queue>
#include <chrono>// 定义一个任务结构体,用于模拟客户端请求
struct Task {int id;Task(int _id) : id(_id) {}
};// 定义一个线程安全的队列,用于存储待处理的任务
template<typename T>
class SafeQueue {
private:std::queue<T> queue_;std::mutex mutex_;
public:void push(const T& item) {std::lock_guard<std::mutex> lock(mutex_);queue_.push(item);}bool try_pop(T& item) {std::lock_guard<std::mutex> lock(mutex_);if (queue_.empty()) {return false;}item = queue_.front();queue_.pop();return true;}
};// 定义一个处理请求的函数
void processRequest(SafeQueue<Task>& tasks, int threadId) {while (true) {Task task(0);if (tasks.try_pop(task)) {// 模拟处理请求的过程std::cout << "Thread " << threadId << " processing task " << task.id << std::endl;// 模拟请求处理时间std::this_thread::sleep_for(std::chrono::seconds(1));} else {// 如果队列为空,则线程等待新的任务到来std::this_thread::yield();}}
}int main() {const int numThreads = 4;SafeQueue<Task> taskQueue;// 创建多个线程来处理请求std::vector<std::thread> threads;for (int i = 0; i < numThreads; ++i) {threads.emplace_back(processRequest, std::ref(taskQueue), i);}// 模拟生成一些任务并将其放入队列中for (int i = 1; i <= 10; ++i) {taskQueue.push(Task(i));std::this_thread::sleep_for(std::chrono::milliseconds(200));}// 等待所有线程完成任务for (auto& thread : threads) {thread.join();}return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/3382.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Pandas 2.2 中文官方教程和指南(二十五·二)

新列 使用 DataFrame.map&#xff08;以前称为 applymap&#xff09;高效动态创建新列 In [53]: df pd.DataFrame({"AAA": [1, 2, 1, 3], "BBB": [1, 1, 2, 2], "CCC": [2, 1, 3, 1]})In [54]: df Out[54]: AAA BBB CCC 0 1 1 2 1…

用html写一个旋转菜单

<!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>旋转菜单</title><link relstylesheet href"https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.css"&…

【Nginx】(五) Nginx作为微服务API网关的配置与应用

在微服务架构中&#xff0c;API网关是一个至关重要的组件&#xff0c;它不仅负责路由请求到正确的服务&#xff0c;还提供负载均衡、认证授权、限流、监控和日志记录等功能。在本博客中&#xff0c;我们将探讨一个在线教育平台如何使用Nginx作为API网关来解决流量管理和安全问题…

Linux操作

一&#xff1a;各类操作快捷键 Ctrl c强制停止程序运行用于删除命令&#xff0c;执行出错时使用Ctrl d退出或者登出用于退出Linux账户 | 退出Linux自带的Python模式(命令框输入&#xff1a;python进入)history查看历史命令用于查看历史命令Ctrl r历史命令搜索进入后面在 内…

树莓派学习笔记--串口通信(配置硬件串口进行通信)

树莓派串口知识点 树莓派4b的外设一共包含两个串口&#xff1a;硬件串口&#xff08;/dev/ttyAMA0&#xff09;,mini串口&#xff08;/dev/ttyS0&#xff09; 硬件串口由硬件实现&#xff0c;有单独的波特率时钟源&#xff0c;性能高&#xff0c;可靠&#xff1b;而mini串口性能…

分享6款嵌入式常见的GUI,不要错过

大家好&#xff0c;我是知微&#xff01; 先来聊聊什么是GUI。图形用户界面&#xff08;Graphical User Interface&#xff0c;简称 GUI&#xff09;采用直观的图形方式展示&#xff0c;让用户与计算机或设备交流变得简单直观。不仅工作效率得到极大提升&#xff0c;用户体验也…

普通人也可以在抖音上开店卖货了,还有多少人不知道!

大家好&#xff0c;我是电商糖果 在抖音上开网店卖货最近几年特别火&#xff0c;我想只要是稍微了解电商创业的朋友都知道。 就当你没有创业做电商的想法&#xff0c;你只要有网购的习惯&#xff0c;就会发现抖音上购买商品非常的方便。 而且身边的同事&#xff0c;朋友也都…

全新G级越野车家族领衔 梅赛德斯-奔驰携强劲实力阵容和前瞻数字科技亮相2024北京车展

全新纯电G级越野车、G 500和AMG G 63亮相&#xff0c;油电双雄带来G级越野车的多元选择CLA级概念车携MMA平台及MB.OS构建奔驰电动化、数字化的未来&#xff0c;定义新生代电动风潮全新AMG GT 63 S E PERFORMANCE车展亮相&#xff0c;以F1技术打造“史上最快AMG量产车”“迈巴赫…

Vue2 —— 学习(十)

目录 一、vue-resource 库 二、插槽 &#xff08;一&#xff09;默认插槽 &#xff08;二&#xff09;具名插槽 &#xff08;三&#xff09;作用域插槽 三、vuex &#xff08;一&#xff09;介绍 &#xff08;二&#xff09;多组件共享数据 1.通过全局事件总线实现 2…

【virtuoso】 PDK

什么是PDK&#xff1f; PDK( Process Design Kit )&#xff0c;工程设计数据包&#xff0c;是芯片厂家foundary提供给IC设计公司的有关制造工艺的模型和EDA工具支持。是连接IC制造公司&#xff0c;IC设计公司的桥梁。 PDK包含内容&#xff1a; 器件模型 SPICE模型模型 与 测量误…

Nodejs--异步编程

异步编程 函数式编程 高阶函数 在通常的语言中&#xff0c;函数的参数只接受基本的数据类型或者是对象引用&#xff0c;返回值只能是基本数据类型和对象引用。 function foo(x) {return x }高阶函数是把函数作为参数&#xff0c;将函数作为返回值的函数 function foo(x) {…

最好的电脑监控软件,监控员工的软件哪个好用

经过对百度AI的询问&#xff0c;最好的电脑监控软件是安企神&#xff0c;因为它排名第一位。 经过对AI的详细询问&#xff0c;它给出了选择安企神作为电脑监控软件的十个理&#xff1a; 获取试用版找客服 1.全面的监控功能 安企神软件提供了屏幕监控、键盘记录、文件操作记录…

【Harmony3.1/4.0】笔记二-列表

概述 列表是一种复杂的容器&#xff0c;当列表项达到一定数量&#xff0c;内容超过屏幕大小时&#xff0c;可以自动提供滚动功能。它适合用于呈现同类数据类型或数据类型集&#xff0c;例如图片和文本。在列表中显示数据集合是许多应用程序中的常见要求&#xff08;如通讯录、…

抗D盾是什么,为什么游戏被攻击了需要抗D盾

游戏行业DDoS攻击的主要原因是因为游戏产品生命周期偏短&#xff0c;而DDoS供给成本又不高&#xff0c;只要发起攻击&#xff0c;企业为确保游戏稳定运营而不得不快速做出让步&#xff0c;致使敲诈勒索的成功率相对更高。在遭受DDoS攻击后&#xff0c;游戏公司的日损失甚至多达…

git远程分支强制覆盖本地分支

目录 第一章、问题1.1&#xff09;报错提示&#xff1a;没有为分支主机或分支配置被跟踪的分支1.2&#xff09;报错分析与解决 第二章、2.1&#xff09;本地误删代码后想要git pull拉取覆盖&#xff1a;失败2.2&#xff09;报错分析和解决 友情提醒: 先看文章目录&#xff0c;…

4.24总结

对部分代码进行了修改&#xff0c;将一些代码封装成方法&#xff0c;实现了头像功能&#xff0c;通过FileInputStream将本地的图片写入&#xff0c;再通过FileOutputStream拷贝到服务端的文件夹中&#xff0c;并将服务端的文件路径存入数据库中

codeforces round 151 div2(a,b,c)

中规中矩的前三题 题目链接 A 分类讨论就行 #include<bits/stdc.h>using namespace std;#define int long long #define PII pair<int,int>void solve() {int n, k, x;cin >> n >> k >> x;if (x ! 1) {cout << "YES" <<…

Spark-core面试知识点

Spark课程(web&#xff1a;默认值是8080&#xff0c;但是这个端口号容易被占用&#xff0c;顺势1&#xff1b;提交任务端口号&#xff1a;7077) 一、RDD RDD是spark最底层的核心抽象&#xff0c;叫做弹性分布式数据集。 特点&#xff1a;不可变&#xff0c;可分区&#xff0…

Java——接口

目录 一.接口的概念 二.语法规则 三.接口使用 四.接口特性 1.接口类型是一种引用类型&#xff0c;但是不能直接new接口的对象 2.接口中每一个方法都是public的抽象方法, 即接口中的方法会被隐式的指定为 public abstract&#xff08;只能是 public abstract&#xff0c…

君正X2100 RTOS 固件升级

使用cloner工具烧写固件需要在上电之前让boot_sel[2:0]处于boot from USB模式&#xff0c;但是电路板装在机壳内部后不方便改变boot_sel[2:0]的状态&#xff0c;如果要升级固件&#xff0c;需要通过机壳留出的USB口、网口、或者无线网络进行固件更新。 一、升级方案 1、固件分…