【C++】C++11介绍

目录

C++11的由来

命名趣事

统一的列表初始化

统一的列表初始化的一些关键点和特性:

简单测试代码示例:

示例 1:初始化内置类型和数组

示例 2:初始化类和结构体

示例 3:初始化标准库容器

声明

auto关键字

auto 关键字的特点:

注意事项:

测试代码示例:

示例 1:基本类型推导

示例 2:复杂类型推导(如 STL 容器)

示例 3:与引用和指针的结合使用

decltype关键字

decltype 的基本用法:

decltype 的特点:

注意事项:

测试代码示例:

示例 1:获取变量的类型

示例 2:获取表达式的类型

示例 3:处理引用和指针

nullptr

nullptr 的特点:

测试代码示例:

范围for

范围for循环的基本语法:

关键点:

示例代码:

示例 1:遍历数组

示例 2:遍历向量(vector)

示例 3:遍历其他STL容器

注意事项:

修改容器元素的示例:

智能指针

1. std::unique_ptr

2. std::shared_ptr

3. std::weak_ptr

测试代码示例:

示例 1:使用 std::unique_ptr

示例 2:使用 std::shared_ptr

示例 3:使用 std::weak_ptr 解决循环引用问题

注意事项:

总结:

STL中一些变化

1. 容器初始化

2. 无序容器

3. range-based for loop

4. auto 类型推导

5. 新的算法

6. lambda 表达式

7. 右值引用和移动语义

右值引用和移动语义

右值引用

移动语义

移动构造函数

移动赋值运算符

std::move

示例代码

可变参数模板

可变参数模板的基本结构

递归分解模板参数包

完美转发

简单的测试代码

lambda表达式

示例 1:无捕获和无参数的 lambda

示例 2:捕获外部变量的 lambda

示例 3:按引用捕获外部变量的 lambda

示例 4:带有返回类型的 lambda

包装器

function包装器

std::function 的特点:

使用 std::function 的简单测试代码:

bind函数适配器

std::bind 的特点:

使用 std::bind 的简单测试代码:

线程库

C++11 线程库的主要特性:

线程创建

简单的测试代码:

注意事项:

线程同步

1. std::mutex(互斥锁)

2. std::condition_variable(条件变量)

3. std::atomic(原子操作)

注意事项:

线程安全的数据结构

std::lock_guard

std::unique_lock

简单的测试代码:

注意事项:


C++11的由来

在2003年C++标准委员会曾经提交了一份技术勘误表(简称TC1),使得C++03这个名字已经取代了C++98称为C++11之前的最新C++标准名称。不过由于C++03(TC1)主要是对C++98标准中的漏洞进行修复,语言的核心部分则没有改动,因此人们习惯性的把两个标准合并称为C++98/03标准。从C++0x到C++11,C++标准10年磨一剑,第二个真正意义上的标准珊珊来迟。相比于C++98/03,C++11则带来了数量可观的变化,其中包含了约140个新特性,以及对C++03标准中约600个缺陷的修正,这使得C++11更像是从C++98/03中孕育出的一种新语言。相比较而言,C++11能更好地用于系统开发和库开发、语法更加泛华和简单化、更加稳定和安全,不仅功能更强大,而且能提升程序员的开发效率,公司实际项目开发中也用得比较多,所以我们要作为一个重点去学习。C++11增加的语法特性非常篇幅非常多,我们这里没办法一 一讲解,所以本节主要讲解实际中比较实用的语法

  • C++11的全部参考文档

C++11详细内容

命名趣事

1998年是C++标准委员会成立的第一年,本来计划以后每5年视实际需要更新一次标准,C++国际 标准委员会在研究C++ 03的下一个版本的时候,一开始计划是2007年发布,所以最初这个标准叫 C++ 07。但是到06年的时候,官方觉得2007年肯定完不成C++ 07,而且官方觉得2008年可能也 完不成。最后干脆叫C++ 0x。x的意思是不知道到底能在07还是08还是09年完成。结果2010年的 时候也没完成,最后在2011年终于完成了C++标准。所以最终定名为C++11

统一的列表初始化

C++11 引入了统一的列表初始化(uniform initialization),这是一种新的初始化语法,它使用花括号 {} 来初始化对象。统一的列表初始化提供了一种更为一致和灵活的初始化方式,不仅适用于自定义类型,也适用于内置类型。

统一的列表初始化的一些关键点和特性:

  1. 语法一致性:不论对象类型如何,都可以使用 {} 来进行初始化。

  2. 灵活性:它可以用来初始化数组、类对象、聚合体(如结构体和联合体),甚至是标准库容器。

  3. 避免最窄匹配:在函数重载时,使用列表初始化可以避免最窄匹配的问题,因为列表初始化不会触发类型转换。

  4. 初始化器的嵌套:可以嵌套使用初始化器列表,例如,初始化多维数组或包含其他容器的容器。

简单测试代码示例:

示例 1:初始化内置类型和数组

#include <iostream>
#include <vector>int main() {// 初始化内置类型int x = {42}; // 与 int x = 42; 等价double y{3.14}; // 使用花括号进行初始化// 初始化数组int arr1[3] = {1, 2, 3}; // 使用花括号初始化数组int arr2[] = {4, 5, 6}; // 编译器根据初始化器列表的大小自动确定数组大小// 输出数组元素for (int i = 0; i < 3; ++i) {std::cout << "arr1[" << i << "]: " << arr1[i] << std::endl;std::cout << "arr2[" << i << "]: " << arr2[i] << std::endl;}return 0;
}

示例 2:初始化类和结构体

#include <iostream>struct Point {int x, y;
};class MyClass {
public:MyClass(int val) : value(val) {}int value;
};int main() {// 初始化结构体Point p1{1, 2}; // 结构体成员按照声明的顺序初始化Point p2 = {3, 4}; // 等价于上面的初始化方式// 初始化类对象MyClass obj1{10}; // 调用构造函数进行初始化// 输出值std::cout << "p1: (" << p1.x << ", " << p1.y << ")" << std::endl;std::cout << "p2: (" << p2.x << ", " << p2.y << ")" << std::endl;std::cout << "obj1.value: " << obj1.value << std::endl;return 0;
}

示例 3:初始化标准库容器

#include <iostream>
#include <vector>
#include <list>int main() {// 初始化 vectorstd::vector<int> vec{1, 2, 3, 4, 5};// 初始化 liststd::list<int> lst{5, 4, 3, 2, 1};// 输出容器元素for (int i : vec) {std::cout << i << " ";}std::cout << std::endl;for (int i : lst) {std::cout << i << " ";}std::cout << std::endl;return 0;
}

在以上示例中,你可以看到统一的列表初始化如何为不同的数据类型提供了一致的初始化语法。这种语法不仅提高了代码的可读性,而且减少了在初始化时可能发生的错误。注意,虽然统一的列表初始化在大多数情况下都很方便,但也有一些情况下它可能并不适用,例如初始化非聚合类对象时,如果该类没有合适的构造函数接受一个初始化器列表,那么就会编译失败。

声明

c++11提供了多种简化声明的方式

auto关键字

在C++98中auto是一个存储类型的说明符,表明变量是局部自动存储类型,但是局部域中定义局部的变量默认就是自动存储类型,所以auto就没什么价值了。C++11中废弃auto原来的用法,将其用于实现自动类型腿断。这样要求必须进行显示初始化,让编译器将定义对象的类型设置为初始化值的类型。

auto 关键字是 C++11 引入的一个非常有用的特性,它允许编译器自动推断变量的类型。这大大简化了代码编写,特别是在处理复杂类型或模板时。使用 auto 可以让程序员不必显式地指定变量的类型,编译器会根据初始化的表达式自动确定变量的类型。

auto 关键字的特点:

  1. 自动类型推导:编译器根据初始化表达式自动推断 auto 变量的类型。

  2. 简化代码:特别是在处理复杂类型时,使用 auto 可以使代码更加简洁。

  3. 与引用和指针的结合auto 可以与引用(&)和指针(*)结合使用,自动推导引用或指针的类型。

  4. 初始化要求:使用 auto 声明的变量必须立即初始化,否则编译会报错。

注意事项:

  • 虽然 auto 可以简化代码,但过度使用可能会降低代码的可读性。因此,在使用 auto 时,应确保代码的可读性和可维护性。

  • auto 不能用于函数参数或返回类型的推导(C++14 引入了 auto 类型的函数返回类型,但那是另外的话题)。

测试代码示例:

示例 1:基本类型推导
#include <iostream>int main() {auto x = 42; // x 被推导为 int 类型auto y = 3.14; // y 被推导为 double 类型std::cout << "x: " << x << ", type: " << typeid(x).name() << std::endl;std::cout << "y: " << y << ", type: " << typeid(y).name() << std::endl;return 0;
}
示例 2:复杂类型推导(如 STL 容器)
#include <iostream>
#include <vector>int main() {std::vector<int> vec = {1, 2, 3, 4, 5};auto it = vec.begin(); // it 被推导为 std::vector<int>::iterator 类型std::cout << "First element: " << *it << std::endl;return 0;
}
示例 3:与引用和指针的结合使用
#include <iostream>
#include <vector>int main() {int a = 10;auto& ref = a; // ref 被推导为 int& 类型,即 a 的引用auto* ptr = &a; // ptr 被推导为 int* 类型,即指向 a 的指针ref = 20; // 修改 ref,实际上修改了 astd::cout << "a: " << a << std::endl; // 输出 20*ptr = 30; // 修改 ptr 指向的值,实际上也修改了 astd::cout << "a: " << a << std::endl; // 输出 30return 0;
}

在上述示例中,auto 关键字使得程序员不必显式声明变量 xyitrefptr 的类型,编译器会根据初始化的表达式自动推导出它们的类型。这不仅简化了代码,还提高了代码的可读性和可维护性。当然,在使用 auto 时,应确保初始化的表达式是明确的,以便编译器能够准确地推导出变量的类型。

decltype关键字

decltype 是 C++11 引入的一个关键字,它用于在编译时查询表达式的类型。decltype 提供了一种方式,使得程序员能够明确地获取一个表达式或变量的类型,并在需要的时候使用这个类型信息。

decltype 的基本用法:

  1. 获取变量的类型decltype 可以直接用于变量,以获取其类型。

  2. 获取表达式的类型decltype 也可以用于更复杂的表达式,以获取该表达式的类型。

  3. 引用和指针的处理:如果表达式的结果是一个左值(例如变量或数组元素),则 decltype 会产生对该类型的引用;如果表达式的结果是一个右值(例如字面量或临时对象),则 decltype 会产生该类型的非引用。

decltype 的特点:

  • 类型推导:编译器会根据 decltype 后面的表达式自动推导类型。

  • auto 的区别auto 用于变量声明时的类型推导,而 decltype 用于在编译时查询任意表达式的类型。

  • 灵活性decltype 可以处理复杂的表达式和类型,包括函数指针、成员函数指针等。

注意事项:
  • decltype 主要用于模板元编程和高级类型操作,对于一般的应用程序开发,可能并不常用。

  • 使用 decltype 时,应确保表达式的含义是明确的,以避免类型推导错误。

测试代码示例:

示例 1:获取变量的类型
#include <iostream>
#include <type_traits> // 用于 std::is_same 检查类型是否相同int main() {int x = 10;decltype(x) y = 20; // y 的类型与 x 相同,即 intstd::cout << typeid(y).name() << std::endl; // 输出 y 的类型return 0;
}
示例 2:获取表达式的类型
#include <iostream>
#include <type_traits>int main() {int a = 5, b = 10;decltype(a + b) sum = a + b; // sum 的类型与 a + b 的结果类型相同,即 intstd::cout << typeid(sum).name() << std::endl; // 输出 sum 的类型return 0;
}
示例 3:处理引用和指针
#include <iostream>
#include <type_traits>int main() {int a = 5;decltype((a)) ref = a; // ref 是 int&,因为 a 是左值decltype(10) val = 10; // val 是 int,因为 10 是右值std::cout << std::boolalpha;std::cout << "ref is a reference: " << std::is_reference<decltype(ref)>::value << std::endl; // 输出 truestd::cout << "val is a reference: " << std::is_reference<decltype(val)>::value << std::endl; // 输出 falsereturn 0;
}

在上面的示例中,decltype 用于推导变量的类型,以及表达式的类型。同时,通过使用 std::is_referencestd::is_same 等类型特性,我们可以检查推导出的类型是否符合预期。这些特性通常定义在 <type_traits> 头文件中。

nullptr

nullptr 是 C++11 中引入的一个新关键字,用于表示空指针常量。在 C++11 之前,通常使用 NULL0 来表示空指针,但是这两个选择都存在一些问题。NULL 通常是定义为 0(void*)0 的宏,这导致了类型的不一致性,而直接使用 0 作为指针类型虽然大多数情况下可以工作,但在模板编程中可能会引发问题。 nullptr 解决了这些问题,它是一个类型安全的空指针常量,其类型为 std::nullptr_t。它不能隐式转换为整数类型,只能转换为指针类型,这使得它在模板编程中更为安全和方便。

nullptr 的特点:

  1. 类型安全nullptr 的类型是 std::nullptr_t,这确保了它只能被赋给指针类型的变量,从而避免了与整数类型的混淆。

  2. 明确性nullptr 的使用比 NULL0 更明确,它清晰地表示一个空指针。

  3. NULL 的兼容性nullptr 可以与接受 NULL 的代码兼容,因为 nullptr 可以隐式转换为 void*

测试代码示例:

#include <iostream>int main() {int* ptr1 = nullptr; // 使用 nullptr 初始化一个空指针int* ptr2 = 0; // 使用 0 初始化一个空指针(在 C++11 中仍然有效,但不推荐)int* ptr3 = NULL; // 使用 NULL 初始化一个空指针(在 C++11 中仍然有效,但不推荐)if (ptr1 == nullptr) {std::cout << "ptr1 is nullptr" << std::endl;}if (ptr2 == nullptr) {std::cout << "ptr2 is nullptr" << std::endl;}if (ptr3 == nullptr) {std::cout << "ptr3 is nullptr" << std::endl;}// 尝试将 nullptr 赋给非指针类型,这将导致编译错误// int nonPointer = nullptr; // 错误:不能将 nullptr 赋给非指针类型return 0;
}

在这个示例中,我们创建了三个指针 ptr1ptr2ptr3,并分别使用 nullptr0NULL 来初始化它们。然后,我们检查这些指针是否等于 nullptr,并打印相应的消息。注意,尝试将 nullptr 赋给非指针类型的变量会导致编译错误,这展示了 nullptr 的类型安全性。

范围for

C++11 引入的范围for循环(Range-based for loop)是一种简化遍历容器(如数组、向量、列表等)或任何支持迭代器的序列的语法。范围for循环通过隐藏迭代器细节,使得遍历容器变得更加直观和简洁。

范围for循环的基本语法:

for (declaration : expression) {// 循环体
}
  • declaration:定义了循环变量,该变量会在每次迭代时接收容器中的一个元素。

  • expression:表示一个容器或序列,它必须支持 begin()end() 成员函数或类似的全局函数,用于获取迭代器。

关键点:

  1. 迭代器隐藏:范围for循环内部处理了迭代器的细节,程序员无需显式使用迭代器。

  2. 自动类型推导:循环变量 declaration 的类型通常由 expression 中的元素类型自动推导得出。

  3. 效率:范围for循环在性能上与使用迭代器的手动循环相当,没有额外的开销。

示例代码:

示例 1:遍历数组
#include <iostream>int main() {int arr[] = {1, 2, 3, 4, 5};// 使用范围for循环遍历数组for (int num : arr) {std::cout << num << " ";}std::cout << std::endl;return 0;
}
示例 2:遍历向量(vector)
#include <iostream>
#include <vector>int main() {std::vector<int> vec = {1, 2, 3, 4, 5};// 使用范围for循环遍历向量for (int num : vec) {std::cout << num << " ";}std::cout << std::endl;return 0;
}
示例 3:遍历其他STL容器
#include <iostream>
#include <list>
#include <set>int main() {std::list<int> lst = {1, 2, 3, 4, 5};std::set<int> st = {5, 4, 3, 2, 1};// 遍历链表for (int num : lst) {std::cout << num << " ";}std::cout << std::endl;// 遍历集合for (int num : st) {std::cout << num << " ";}std::cout << std::endl;return 0;
}
注意事项:
  • 如果 expression 不支持 begin()end() 函数,范围for循环将无法使用。

  • 在循环体内,循环变量 declaration 是只读的(对于常量表达式),并且每个迭代都会得到一个新的元素副本(对于非引用类型)。如果需要修改容器中的元素,应使用引用类型。

修改容器元素的示例:

#include <iostream>
#include <vector>int main() {std::vector<int> vec = {1, 2, 3, 4, 5};// 使用引用遍历向量并修改元素for (int& num : vec) {num *= 2; // 修改容器中的元素}// 输出修改后的向量for (int num : vec) {std::cout << num << " ";}std::cout << std::endl;return 0;
}

在这个例子中,我们使用 int&(整数引用)作为循环变量的类型,以便在循环体内直接修改向量 vec 中的元素。每个 num 都是向量中元素的引用,所以当我们改变 num 的值时,实际上是改变了向量中对应元素的值。

智能指针

C++11 引入了三种智能指针:std::unique_ptrstd::shared_ptrstd::weak_ptr,它们旨在自动管理动态分配的内存,以避免常见的内存泄漏和悬挂指针问题。这些智能指针通过自动释放它们所指向的对象,简化了内存管理。

1. std::unique_ptr

std::unique_ptr 表示对动态分配对象的独占所有权。同一时间只能有一个 unique_ptr 指向一个对象。当 unique_ptr 被销毁时(例如超出作用域),它所指向的对象也会被自动删除。

2. std::shared_ptr

std::shared_ptr 实现共享所有权语义。多个 shared_ptr 可以指向同一个对象,并且当最后一个指向该对象的 shared_ptr 被销毁时,对象才会被删除。这通过引用计数来实现,每个 shared_ptr 都持有一个指向控制块的指针,该控制块包含对对象的引用计数。

3. std::weak_ptr

std::weak_ptr 是对 shared_ptr 所管理对象的弱引用,不会影响对象的生命周期。它主要是为了解决 shared_ptr 之间的循环引用问题。

测试代码示例:

示例 1:使用 std::unique_ptr
#include <iostream>
#include <memory>class MyClass {
public:MyClass(int value) : value_(value) {}~MyClass() { std::cout << "Destroying MyClass with value " << value_ << std::endl; }void printValue() const { std::cout << "Value: " << value_ << std::endl; }private:int value_;
};int main() {std::unique_ptr<MyClass> ptr(new MyClass(42)); // 使用 new 创建对象并用 unique_ptr 管理ptr->printValue(); // 输出对象的值// 当 ptr 离开作用域时,它所指向的对象也会被自动删除return 0;
}
示例 2:使用 std::shared_ptr
#include <iostream>
#include <memory>class MyClass {
public:MyClass(int value) : value_(value) {std::cout << "Creating MyClass with value " << value_ << std::endl;}~MyClass() {std::cout << "Destroying MyClass with value " << value_ << std::endl;}void printValue() const { std::cout << "Value: " << value_ << std::endl; }private:int value_;
};int main() {std::shared_ptr<MyClass> ptr1 = std::make_shared<MyClass>(42); // 使用 make_shared 创建对象并用 shared_ptr 管理std::shared_ptr<MyClass> ptr2 = ptr1; // ptr2 现在也指向同一个对象ptr1->printValue(); // 输出对象的值ptr2->printValue(); // 再次输出对象的值// 当 ptr1 和 ptr2 都离开作用域时,对象才会被删除return 0;
}
示例 3:使用 std::weak_ptr 解决循环引用问题
#include <iostream>
#include <memory>class B;class A {
public:std::shared_ptr<B> b_ptr;~A() { std::cout << "Destroying A\n"; }
};class B {
public:std::weak_ptr<A> a_ptr; // 使用 weak_ptr 避免循环引用~B() { std::cout << "Destroying B\n"; }
};int main() {{std::shared_ptr<A> a = std::make_shared<A>();std::shared_ptr<B> b = std::make_shared<B>();a->b_ptr = b;b->a_ptr = a;// 此时,即使 A 和 B 相互引用,也不会导致循环引用问题,因为 B 使用的是 weak_ptr} // a 和 b 离开作用域,它们指向的对象会被正确销毁return 0;
}

在上述示例中,std::unique_ptr 用于管理单个对象的生命周期,确保对象在 unique_ptr 销毁时也被销毁。std::shared_ptr 用于共享对象的所有权,当最后一个指向对象的 shared_ptr 被销毁时,对象才会被删除。而 std::weak_ptr 解决了 shared_ptr 之间的循环引用问题,它不会延长对象的生命周期,只是提供了一个观察 shared_ptr 所管理对象的手段。

注意事项:
  • 使用智能指针时,通常应避免直接使用 newdelete 来管理动态内存,因为智能指针会自动处理这些操作。

  • std::make_shared 是创建 shared_ptr 的推荐方式,因为它比直接使用 newshared_ptr 构造函数更高效,因为它只分配一次内存(同时分配控制块和对象本身)。

  • 小心使用 std::shared_ptr,以避免不必要的共享所有权和潜在的性能开销。在确实需要共享所有权的情况下才使用它。

  • 当处理智能指针时,注意避免野指针(dangling pointers)和悬挂指针(dangling references),即不要保留指向已删除对象的智能指针或引用。

总结:

C++11 的智能指针通过自动管理内存,极大地简化了动态内存管理,并减少了内存泄漏和悬挂指针的风险。在实际编程中,应优先使用智能指针来管理动态分配的内存,并谨慎处理智能指针之间的关系,以避免循环引用等问题。

STL中一些变化

C++11 为 STL(Standard Template Library,标准模板库)带来了许多重要的改进和新增功能,这些变化使得 STL 更加灵活、高效和易于使用。下面是一些 C++11 中 STL 的主要变化,以及相应的简单测试代码:

1. 容器初始化

C++11 提供了列表初始化(List Initialization)的方式初始化容器,这使得代码更加简洁。

#include <iostream>
#include <vector>
#include <list>
#include <initializer_list>int main() {// 使用初始化列表初始化 vectorstd::vector<int> vec = {1, 2, 3, 4, 5};for (int i : vec) {std::cout << i << " ";}std::cout << std::endl;// 使用初始化列表初始化 liststd::list<std::string> lst = {"apple", "banana", "cherry"};for (const std::string& s : lst) {std::cout << s << " ";}std::cout << std::endl;return 0;
}

2. 无序容器

C++11 引入了新的无序容器,包括 unordered_mapunordered_multimapunordered_setunordered_multiset,它们基于哈希表实现,提供了常数平均时间复杂度的查找操作。

#include <iostream>
#include <unordered_map>int main() {std::unordered_map<std::string, int> umap;umap["apple"] = 1;umap["banana"] = 2;umap["cherry"] = 3;for (const auto& pair : umap) {std::cout << pair.first << ": " << pair.second << std::endl;}return 0;
}

3. range-based for loop

基于范围的 for循环(range-based for loop)使得遍历容器变得更加简单。

#include <iostream>
#include <vector>int main() {std::vector<int> vec = {1, 2, 3, 4, 5};// 使用基于范围的for循环遍历 vectorfor (const auto& elem : vec) {std::cout << elem << " ";}std::cout << std::endl;return 0;
}

4. auto 类型推导

在 C++11 中,auto 关键字用于自动推导变量类型,这特别适用于迭代器和复杂的 STL 类型。

#include <iostream>
#include <vector>int main() {std::vector<int> vec = {1, 2, 3, 4, 5};// 使用 auto 推导迭代器类型for (auto it = vec.begin(); it != vec.end(); ++it) {std::cout << *it << " ";}std::cout << std::endl;return 0;
}

5. 新的算法

C++11 为 STL 算法库增加了一些新算法,例如 std::copy_nstd::all_ofstd::any_ofstd::none_of 等。

#include <iostream>
#include <vector>
#include <algorithm>int main() {std::vector<int> vec = {1, 2, 3, 4, 5};// 使用 std::all_of 检查所有元素是否都大于 0bool all_positive = std::all_of(vec.begin(), vec.end(), [](int i) { return i > 0; });std::cout << std::boolalpha << "All elements are positive: " << all_positive << std::endl;return 0;
}

6. lambda 表达式

C++11 引入了 lambda 表达式,这使得在 STL 算法中使用匿名函数变得更加容易。

#include <iostream>
#include <vector>
#include <algorithm>int main() {std::vector<int> vec = {1, 2, 3, 4, 5};// 使用 lambda表达式与 `std::for_each` 算法遍历并打印 vector 中的每个元素std::for_each(vec.begin(), vec.end(), [](int i) {std::cout << i << " ";});std::cout << std::endl;// 使用 lambda 表达式与 `std::remove_if` 算法移除 vector 中所有的偶数元素vec.erase(std::remove_if(vec.begin(), vec.end(), [](int i) {return i % 2 == 0;}), vec.end());// 打印修改后的 vectorfor (int i : vec) {std::cout << i << " ";}std::cout << std::endl;return 0;
}

7. 右值引用和移动语义

C++11 引入了右值引用和移动语义,允许资源在对象之间高效地转移,这显著提高了 STL 容器的性能,特别是在插入和删除元素时。

#include <iostream>
#include <vector>
#include <string>int main() {std::string str = "Hello, World!";std::vector<std::string> vec;// 使用 push_back 和移动语义添加元素到 vectorvec.push_back(std::move(str)); // str 的资源被移动到 vector 中的元素// 打印 vector 中的元素for (const auto& s : vec) {std::cout << s << std::endl;}return 0;
}

以上只是 C++11 中 STL 变化的一部分。实际上,C++11 对 STL 进行了许多改进和扩展,包括性能优化、错误处理的改进、新算法和容器的增加等。这些变化使得 C++ 的标准库更加现代、高效和易用。

右值引用和移动语义

C++11 引入了右值引用和移动语义,这两个特性极大地提高了 C++ 的性能,特别是在处理资源密集型对象时,如大数组、大字符串或自定义的复杂数据结构。

右值引用

右值引用是对一个将要被销毁的对象的引用。它使用 && 符号表示,并且只能绑定到右值。右值通常是临时对象或不再使用的对象。

移动语义

移动语义允许我们从一个对象“窃取”资源(如内存、文件句柄等),而不是复制它们。这通常通过重载移动构造函数和移动赋值运算符来实现。移动操作通常比复制操作更快,因为它避免了资源的复制,只是简单地重新指向原有资源。

移动构造函数

移动构造函数接受一个右值引用参数,并用于初始化对象,同时确保源对象在移动操作后处于有效但未定义的状态。

移动赋值运算符

移动赋值运算符也接受一个右值引用参数,并用于将资源从一个对象移动到另一个已存在的对象。

std::move

std::move 是一个标准库函数,它将其参数转换为右值引用。它实际上并不移动任何东西,但它允许我们将左值当作右值来处理,从而可以调用移动构造函数或移动赋值运算符。

示例代码

#include <iostream>
#include <cstring>class ArrayWrapper {
private:int* data;size_t size;public:// 构造函数ArrayWrapper(size_t s) : size(s) {data = new int[size];std::cout << "Allocated " << size << " integers at " << (void*)data << std::endl;}// 移动构造函数ArrayWrapper(ArrayWrapper&& other) noexcept : data(other.data), size(other.size) {other.data = nullptr;other.size = 0;std::cout << "Moved " << size << " integers from " << (void*)other.data << " to " << (void*)data << std::endl;}// 析构函数~ArrayWrapper() {if (data) {delete[] data;std::cout << "Deallocated " << size << " integers at " << (void*)data << std::endl;}}// 移动赋值运算符ArrayWrapper& operator=(ArrayWrapper&& other) noexcept {if (this != &other) {delete[] data;data = other.data;size = other.size;other.data = nullptr;other.size = 0;std::cout << "Moved " << size << " integers from " << (void*)other.data << " to " << (void*)data << std::endl;}return *this;}// 禁用复制构造函数和复制赋值运算符ArrayWrapper(const ArrayWrapper&) = delete;ArrayWrapper& operator=(const ArrayWrapper&) = delete;
};int main() {{ArrayWrapper a(5); // 使用构造函数创建一个 ArrayWrapper 对象ArrayWrapper b = std::move(a); // 使用移动构造函数创建另一个对象,资源从 a移动到b// 此时a的data指针被置为nullptr,不应该再访问a} // a和b的析构函数被调用,但由于a的资源已经被移动,它不会尝试删除空指针ArrayWrapper c(10);ArrayWrapper d(5);d = std::move(c); // 使用移动赋值运算符将c的资源移动到d// 此时c的data指针被置为nullptr,不应该再访问creturn 0;
}

在这个示例中,ArrayWrapper 类管理一个动态分配的整数数组。它有一个移动构造函数和一个移动赋值运算符,这两个函数都允许我们从一个 ArrayWrapper 对象窃取资源并“移动”到另一个对象,而不是复制它们。注意,我们禁用了复制构造函数和复制赋值运算符,以防止不小心进行深拷贝。

std::move 函数用于将左值转换为右值引用,从而允许我们调用移动构造函数或移动赋值运算符。

请注意,在移动操作后,源对象(在这个例子中是 ac)处于有效但未定义的状态,这意味着它们仍然是一个有效的 ArrayWrapper 对象,但其内部数据(在这个例子中是 data 指针)不再指向有效的资源

可变参数模板

C++11 引入了可变参数模板(variadic templates),它允许模板接受任意数量和类型的参数。这大大增强了模板的灵活性,使得我们可以编写更通用和可复用的代码。可变参数模板使用模板参数包(template parameter packs)来实现,它们是以省略号(...)结尾的模板参数。

可变参数模板的基本结构

template <typename... Args>
class MyClass {// ...
};template <typename... Args>
void myFunction(Args... args) {// ...
}

在上面的代码中,Args... 是一个模板参数包,它可以代表任意类型和数量的参数。

递归分解模板参数包

处理模板参数包时,通常需要使用递归模板特化或者递归函数来“解开”这个包,对每一个参数进行处理。C++11 提供了一种更简单的方式,即使用 std::initializer_liststd::forward 与完美转发结合来展开参数包。

完美转发

完美转发(Perfect Forwarding)是 C++11 引入的一个特性,它允许我们将参数以原有的形式(包括左值或右值)转发给另一个函数。这通常与可变参数模板一起使用,以实现对任意数量和类型的参数进行转发。

简单的测试代码

以下是一个简单的示例,演示了如何使用可变参数模板和完美转发来创建一个通用的打印函数,该函数可以接受任意数量和类型的参数,并将它们打印到标准输出。

#include <iostream>
#include <utility> // for std::forward// 辅助函数,用于递归地打印参数
template <typename Arg, typename... Args>
void print(Arg&& arg, Args&&... args) {std::cout << std::forward<Arg>(arg) << " ";print(std::forward<Args>(args)...); // 递归调用
}// 终止递归的基例
void print() {std::cout << std::endl;
}// 封装函数,接受任意数量和类型的参数
template <typename... Args>
void print_all(Args&&... args) {print(std::forward<Args>(args)...); // 展开参数包
}int main() {print_all("Hello", 42, 3.14, 'c'); // 输出: Hello 42 3.14 cprint_all(1, "world", 2.718);      // 输出: 1 world 2.718return 0;
}

在这个例子中,print 函数是一个递归模板函数,它接受一个参数 arg 和任意数量的额外参数 args...。它首先打印 arg,然后递归地调用自身来打印剩余的参数。print_all 函数是一个封装函数,它接受任意数量和类型的参数,并使用 std::forward 将它们转发给 print 函数。

std::forward 用于完美转发参数,确保左值保持为左值,右值保持为右值,这样我们可以保持原始参数的类别(lvalue 或 rvalue),这对于性能优化(如移动语义)至关重要。

通过可变参数模板和完美转发,我们可以编写非常通用和灵活的代码,以适应各种不同的使用场景。

lambda表达式

C++11 中的 lambda 表达式是一种创建匿名函数对象的方式,它可以捕获其所在作用域的变量,并且可以在需要函数对象的地方使用。Lambda 表达式提供了一种简洁的方式来定义小型函数,它们经常与 STL 算法一同使用,以提供自定义的行为。

  • Lambda 表达式的基本语法如下:

[capture](parameters) -> return_type {body_of_lambda}
  • capture:捕获子句,用于指定哪些外部变量可以在 lambda 体内被访问。捕获可以是按值(=)或按引用(&)。

  • parameters:参数列表,与常规函数参数列表相似。

  • return_type:返回类型。如果 lambda 体中的代码不包含 return 语句,或者所有路径都返回同一类型,则编译器可以推导出返回类型,此时 -> return_type 是可选的。

  • body_of_lambda:lambda 函数的主体,包含要执行的代码。

下面是一些使用 lambda 表达式的简单示例:

示例 1:无捕获和无参数的 lambda

#include <iostream>
#include <vector>
#include <algorithm>int main() {std::vector<int> numbers = {1, 2, 3, 4, 5};// 使用无捕获和无参数的 lambda 打印每个元素std::for_each(numbers.begin(), numbers.end(), [](int n) {std::cout << n << ' ';});std::cout << std::endl;return 0;
}

示例 2:捕获外部变量的 lambda

#include <iostream>
#include <vector>
#include <algorithm>int main() {int threshold = 3;std::vector<int> numbers = {1, 2, 3, 4, 5};// 使用按值捕获外部变量 threshold 的 lambda 过滤出大于 threshold 的元素auto filtered = std::copy_if(numbers.begin(), numbers.end(),std::ostream_iterator<int>(std::cout, " "),[threshold](int n) { return n > threshold; });std::cout << std::endl;return 0;
}

示例 3:按引用捕获外部变量的 lambda

#include <iostream>
#include <vector>
#include <algorithm>int main() {int sum = 0;std::vector<int> numbers = {1, 2, 3, 4, 5};// 使用按引用捕获外部变量 sum 的 lambda 累加 vector 中的所有元素std::for_each(numbers.begin(), numbers.end(), [&sum](int n) {sum += n;});std::cout << "Sum: " << sum << std::endl;return 0;
}

示例 4:带有返回类型的 lambda

#include <iostream>
#include <vector>
#include <algorithm>int main() {std::vector<int> numbers = {1, 2, 3, 4, 5};int max_value = 0;// 使用带有返回类型的 lambda 找出 vector 中的最大值auto max_finder = [](int a, int b) -> int { return (a > b) ? a : b; };max_value = std::accumulate(numbers.begin(), numbers.end(), 0, max_finder);std::cout << "Max value: " << max_value << std::endl;return 0;
}

在这些示例中,lambda 表达式被用作算法(如 std::for_eachstd::copy_ifstd::accumulate)的参数,以提供自定义的行为。Lambda 表达式让代码更加简洁和灵活,因为它们允许在需要的地方直接定义小型函数,而无需创建单独的函数或函数对象。

包装器

function包装器

C++11 引入了 std::function,它是一个通用的、多态的函数包装器。std::function 可以将任何可调用的目标(函数、lambda 表达式、bind 表达式或其他函数对象)封装成统一的接口。这使得函数可以作为参数传递,也可以赋值给变量,甚至存储在容器中。

std::function 的特点:

  1. 类型擦除std::function 内部使用类型擦除技术,使得你可以存储不同类型的可调用对象,而无需关心其具体的类型。

  1. 通用性:它可以封装任何可调用的对象,包括普通函数、成员函数、lambda 表达式、bind 表达式等。

  1. 可调用性:你可以像调用普通函数一样调用 std::function 对象。

使用 std::function 的简单测试代码:

#include <iostream>
#include <functional> // 引入 std::function// 普通函数
void normal_function(int x) {std::cout << "Normal function called with: " << x << std::endl;
}// Lambda 表达式
auto lambda_function = [](int x) {std::cout << "Lambda function called with: " << x << std::endl;
};// 使用 std::function 的例子
int main() {// 创建一个 std::function 对象,它可以存储接受 int 参数且没有返回值的任何可调用对象std::function<void(int)> func;// 将普通函数赋值给 std::function 对象func = normal_function;func(42); // 输出: Normal function called with: 42// 将 lambda 表达式赋值给 std::function 对象func = lambda_function;func(100); // 输出: Lambda function called with: 100// 也可以直接将 lambda 表达式构造 std::function 对象std::function<void(int)> another_func = [](int x) {std::cout << "Another lambda function called with: " << x << std::endl;};another_func(200); // 输出: Another lambda function called with: 200return 0;
}

在上面的代码中,我们首先定义了一个普通函数 normal_function 和一个 lambda 表达式 lambda_function。然后我们创建了一个 std::function 对象 func,它可以接受一个 int 类型的参数并且没有返回值。我们首先将 normal_function 赋值给 func 并调用它,然后将 lambda_function 赋值给 func 并调用它。最后,我们还展示了如何直接用一个 lambda 表达式来构造 std::function 对象。

std::function 的一个常见用途是作为回调函数,因为它允许你传递任何可调用的对象作为参数,增加了代码的灵活性和可复用性。此外,std::function 还可以与 std::bind 或 lambda 表达式结合使用,以实现更复杂的函数调用逻辑。

bind函数适配器

C++11 的 std::bind 是一个功能强大的函数适配器,它可以将一个可调用对象(如函数、成员函数、函数对象或 lambda 表达式)与一组参数绑定在一起,生成一个新的可调用对象。这个新的可调用对象可以像普通函数一样被调用,且会调用原始的可调用对象,并传递给它绑定的参数。

std::bind 的特点:

  1. 参数绑定:你可以使用 std::bind 来绑定可调用对象的参数,从而在后续调用时无需再次提供这些参数。

  1. 占位符std::bind 提供了占位符 _1_2 等,用于表示绑定后的新函数对象参数的位置。

  1. 成员函数绑定:你可以使用 std::bind 来绑定类的成员函数,并指定要操作的对象实例。

使用 std::bind 的简单测试代码:

#include <iostream>
#include <functional> // 引入 std::bind// 普通函数
void print_sum(int a, int b) {std::cout << "Sum: " << a + b << std::endl;
}// 类的成员函数
class MyClass {
public:void print_hello(const std::string& name) {std::cout << "Hello, " << name << "!" << std::endl;}
};int main() {// 使用 std::bind 绑定普通函数的参数auto bound_print_sum = std::bind(print_sum, 10, std::placeholders::_1);bound_print_sum(20); // 输出: Sum: 30// 使用 std::bind 绑定成员函数的实例和参数MyClass obj;auto bound_member_func = std::bind(&MyClass::print_hello, &obj, std::placeholders::_1);bound_member_func("World"); // 输出: Hello, World!// 使用 std::bind 绑定 lambda 表达式auto lambda = [](int x, int y) { return x * y; };auto bound_lambda = std::bind(lambda, 5, std::placeholders::_1);int product = bound_lambda(6); // product 现在是 30std::cout << "Product: " << product << std::endl; // 输出: Product: 30return 0;
}

在上面的代码中,我们首先定义了一个普通函数 print_sum 和一个包含成员函数 print_hello 的类 MyClass

然后,我们使用 std::bind 来绑定 print_sum 函数的第一个参数为 10,生成一个新的可调用对象 bound_print_sum。当我们调用 bound_print_sum(20) 时,它实际上会调用 print_sum(10, 20)

接着,我们使用 std::bind 来绑定 MyClass 的实例 objprint_hello 成员函数,生成一个新的可调用对象 bound_member_func。当我们调用 bound_member_func("World") 时,它实际上会调用 obj.print_hello("World")

最后,我们还展示了如何使用 std::bind 来绑定一个 lambda 表达式,并调用它。

std::bind 的一个常见用途是生成回调函数或者将函数与特定参数绑定以生成新的行为。然而,由于 lambda 表达式的灵活性和易用性,在很多情况下,lambda 表达式已经成为了 std::bind 的替代品。不过,std::bind 仍然在某些特定场景下(如绑定成员函数)有其独特的用途。

线程库

C++11 引入了对线程的原生支持,通过 <thread> 头文件提供了一组用于创建和管理线程的类和函数。这使得 C++ 程序员能够更方便地在多核处理器上编写并发程序。

C++11 线程库的主要特性:

  1. 线程创建:使用 std::thread 类创建新线程。

  2. 线程同步:通过互斥锁(std::mutex)、条件变量(std::condition_variable)、原子操作(std::atomic)等实现线程同步。 C++11 提供了多种工具来实现线程同步,这些工具对于确保多线程环境下的数据完整性和程序正确性至关重要。

  3. 线程局部存储:使用 thread_local 存储类,为线程提供局部存储。

  4. 线程安全的数据结构:例如 std::lock_guardstd::unique_lockstd::shared_mutex 等,这些工具帮助程序员更安全地管理线程间的共享资源。

线程创建

简单的测试代码:

下面是一个简单的示例,展示了如何使用 C++11 的线程库来创建两个线程,并分别输出不同的信息。

#include <iostream>
#include <thread>
#include <chrono> // 用于休眠// 线程执行的函数
void thread_function(const std::string& message) {for (int i = 0; i < 5; ++i) {std::cout << message << " " << i << std::endl;// 休眠一段时间,以便观察线程交替执行的情况std::this_thread::sleep_for(std::chrono::seconds(1));}
}int main() {// 创建两个线程std::thread thread1(thread_function, "Thread 1");std::thread thread2(thread_function, "Thread 2");// 等待两个线程完成thread1.join();thread2.join();return 0;
}

在上面的代码中:

  • std::thread 类用于创建新线程。构造函数接受一个可调用的对象(例>如函数、函数对象、lambda 表达式等)以及任何所需的参数。

  • thread_function 是一个简单的函数,它将一个字符串和一个整数作为参数,并输出信息。

  • std::this_thread::sleep_for 用于使当前线程休眠一段时间。

  • join 成员函数用于等待线程完成执行。在 main 函数中,我们调用 join 来确保 main 线程会等待 thread1thread2 完成后才继续执行。

注意事项:
  • 当创建线程时,请确保传递给 std::thread 构造函数的函数或 lambda 表达式是线程安全的,即它们不会访问或修改没有适当同步的共享数据。

  • 线程局部存储和原子操作是管理共享数据的重要工具,它们可以确保数据的正确性和线程安全。

  • 在多线程环境中,应当特别注意资源的管理,避免资源泄漏和竞争条件。

C++11 的线程库为 C++ 程序员提供了一个强大的工具集,使得编写高效且安全的并发程序变得更加容易。然而,编写复杂的并发程序仍然需要深入理解和小心处理同步和通信问题。

线程同步

1. std::mutex(互斥锁)

std::mutex 是一个简单的互斥锁,用于保护共享资源不被多个线程同时访问。当一个线程拥有互斥锁时,其他尝试获取该互斥锁的线程将会被阻塞,直到锁被释放。

测试代码:

#include <iostream>
#include <thread>
#include <mutex>
#include <chrono>std::mutex mtx; // 全局互斥锁
int counter = 0; // 共享计数器void increment() {for (int i = 0; i < 1000; ++i) {std::lock_guard<std::mutex> lock(mtx); // 使用 lock_guard 自动管理锁++counter;}
}int main() {std::thread t1(increment);std::thread t2(increment);t1.join();t2.join();std::cout << "Final counter value is " << counter << std::endl;return 0;
}

在这个例子中,我们使用 std::lock_guard 来自动管理锁的生命周期,确保在离开作用域时锁被释放。increment 函数由两个线程 t1t2 同时执行,它们共同增加 counter 的值。如果没有互斥锁,counter 的值可能小于 2000,因为两个线程可能同时读取和写入 counter

2. std::condition_variable(条件变量)

std::condition_variable 用于让线程等待某个条件成立。一个或多个线程可以在条件变量上等待,而另一个线程可以在条件成立时通知这些等待的线程。

测试代码:

#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>std::mutex mtx;
std::condition_variable cv;
bool ready = false;void print_id(int id) {std::unique_lock<std::mutex> lock(mtx);while (!ready) { // 等待条件成立cv.wait(lock); // 释放锁并等待通知}// ... 执行其他任务std::cout << "thread " << id << '\n';
}void go() {std::unique_lock<std::mutex> lock(mtx);ready = true; // 设置条件为 truecv.notify_all(); // 通知所有等待的线程
}int main() {std::thread threads[10];// 创建并启动 10 个线程for (int i = 0; i < 10; ++i)threads[i] = std::thread(print_id, i);std::cout << "10 threads ready to race...\n";go(); // 发送通知for (auto& th : threads) th.join();return 0;
}

在这个例子中,我们创建了 10 个线程,它们都等待 ready 变量变为 true。主线程调用 go 函数,该函数设置 readytrue 并使用 cv.notify_all() 通知所有等待的线程。

3. std::atomic(原子操作)

std::atomic 提供了对基本数据类型的原子操作,这些操作在多线程环境中是安全的。原子操作不可分割,即它们不会在执行过程中被其他线程打断。

测试代码:

#include <iostream>
#include <thread>
#include <atomic>std::atomic<int> counter(0); // 原子整数void increment() {for (int i = 0; i < 1000; ++i) {++counter; // 原子增加}
}int main() {std::thread t1(increment);std::thread t2(increment);t1.join();t2.join();std::cout << "Final counter value is " << counter<< std::endl;
return 0;
}

在这个例子中,我们使用 std::atomic<int> 来定义 counter,它是一个线程安全的整数。两个线程 t1t2 同时增加 counter 的值。由于使用了原子操作,最终 counter 的值将准确为 2000,不会出现数据竞争或不一致的情况。

注意事项:
  • std::mutex 提供了互斥访问共享资源的能力,但过度使用可能导致性能下降和死锁。

  • std::condition_variable 应与互斥锁一起使用,以安全地等待和通知条件变化。

  • std::atomic 是轻量级的,适用于简单的原子操作,但可能不支持所有类型的原子操作或复杂的复合操作。

在使用这些同步工具时,应谨慎处理死锁和活锁问题,确保线程能够正确、高效地协作。此外,根据具体的应用场景和性能要求,可能需要结合使用多种同步机制,以达到最佳效果。

线程安全的数据结构

C++11 引入了两种锁保护的数据结构:std::lock_guardstd::unique_lock,它们用于简化互斥量(std::mutex)的管理,确保资源的正确同步访问。这两种锁类型都提供了 RAII(Resource Acquisition Is Initialization)风格的锁管理,即锁的获取在构造时自动完成,锁的释放则在对象销毁时自动完成。

std::lock_guard

std::lock_guard 是一个简单的锁类型,它在构造时自动锁定互斥量,并在析构时自动解锁。它适用于简单的锁定/解锁场景,其中不需要手动控制锁的获取和释放。

std::unique_lock

std::unique_lock 是一个更灵活的锁类型,它提供了比 std::lock_guard 更多的控制选项。你可以延迟锁定、尝试锁定、手动解锁等。它还允许与 std::condition_variable 一起使用,以在特定条件下等待。

简单的测试代码:

下面是一个简单的示例,展示了如何使用 std::lock_guardstd::unique_lock 来保护共享资源。

#include <iostream>
#include <thread>
#include <mutex>
#include <chrono> // 用于休眠// 共享资源
int shared_data = 0;
// 互斥量,用于保护 shared_data
std::mutex mtx;// 使用 std::lock_guard 的线程函数
void increment_with_lock_guard() {for (int i = 0; i < 5; ++i) {std::lock_guard<std::mutex> lock(mtx); // 在构造时自动锁定 mtx++shared_data;std::cout << "shared_data incremented to " << shared_data << " by lock_guard\n";std::this_thread::sleep_for(std::chrono::milliseconds(100)); // 休眠一段时间}
}// 使用 std::unique_lock 的线程函数
void increment_with_unique_lock() {for (int i = 0; i < 5; ++i) {std::unique_lock<std::mutex> lock(mtx); // 在构造时自动锁定 mtx++shared_data;std::cout << "shared_data incremented to " << shared_data << " by unique_lock\n";lock.unlock(); // 手动解锁std::this_thread::sleep_for(std::chrono::milliseconds(100)); // 休眠一段时间lock.lock(); // 手动重新锁定}
}int main() {// 创建两个线程,分别使用 lock_guard 和 unique_lockstd::thread t1(increment_with_lock_guard);std::thread t2(increment_with_unique_lock);// 等待两个线程完成t1.join();t2.join();std::cout << "Final shared_data value: " << shared_data << std::endl;return 0;
}

在这个例子中:

  • shared_data 是被多个线程访问的共享资源。

  • mtx 是一个互斥量,用于保护 shared_data 的访问。

  • increment_with_lock_guard 函数使用 std::lock_guard 来自动管理锁。在函数体内部,lock_guard 对象在构造时自动锁定 mtx,并在函数返回时(即 lock_guard 对象销毁时)自动解锁。

  • increment_with_unique_lock 函数使用 std::unique_lock,它提供了更多的灵活性。在这个例子中,我们展示了如何手动解锁和重新锁定互斥量。

注意事项:
  • 当你使用 std::lock_guardstd::unique_lock 时,你不需要显式调用 lock()unlock() 方法,除非你有特定的需求。

  • std::lock_guard 更适合简单的同步需求,而 std::unique_lock 更适合需要更精细控制同步的场景。

  • 使用锁时,要特别注意避免死锁。死锁通常发生在两个或更多的线程无限期地等待一个资源,而该资源又被另一个线程持有,后者也在等待其他线程释放资源。

  • 在多线程环境中,使用锁来保护共享资源是一种常见的做法,但也要注意锁的使用可能会引入性能瓶颈,因此应该谨慎使用,并考虑其他同步机制,如条件变量、原子操作等。

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

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

相关文章

基于双向长短期神经网络LSTM的飞行轨迹预测,基于GRU神经网络的飞行轨迹预测

目录 背影 摘要 LSTM的基本定义 LSTM实现的步骤 BILSTM神经网络 基于双向长短期神经网络LSTM的飞行轨迹预测,基于GRU神经网络的飞行轨迹预测 完整代码: 基于双向长短期神经网络LSTM的飞行轨迹预测,基于GRU神经网络的飞行轨迹预测资源-CSDN文库 https://download.csdn.net/do…

苹果全力升级:用专注AI的M4芯片彻底改造Mac系列

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

设计符合autosar架构的simulink模型框架

设计符合AUTOSAR架构的Simulink模型框架需要对AUTOSAR标准有深入的理解,同时也需要熟悉Simulink建模工具。以下是设计这样一个模型框架的步骤: 1. 理解AUTOSAR架构要求 研究AUTOSAR标准:首先,需要详细阅读并理解AUTOSAR标准文档,特别是与软件架构和模块定义相关的内容。确…

Nginx实现反向代理、负载均衡、动静分离

1. 什么是Nginx的反向代理&#xff1f; Nginx的反向代理是指Nginx作为服务器的前端&#xff0c;接收客户端的请求&#xff0c;然后将请求转发给后端的真实服务器&#xff0c;并将真实服务器的响应返回给客户端。这种代理方式使得客户端并不知道真实服务器的存在&#xff0c;它…

通过Transform与Animation,来探索CSS中的动态视觉效果

在 transform 和 animation 出现之前&#xff0c;前端开发者通常需要编写大量的 JavaScript 代码来实现动态效果。然而&#xff0c;这两个 CSS 属性的引入极大地简化了丰富动效和过渡效果的实现&#xff0c;从而让用户界面更加引人入胜&#xff0c;交互体验更为流畅。本文将深入…

最优算法100例之44-不用加减乘除做加法

专栏主页:计算机专业基础知识总结(适用于期末复习考研刷题求职面试)系列文章https://blog.csdn.net/seeker1994/category_12585732.html 题目描述 不用加减乘除做加法 题解报告 最优解法:使用异或 1)异或是查看两个数哪些二进制位只有一个为1,这些是非进位位,可以直接…

小程序地理位置权限申请+uniapp调用uni.getLocation

文章目录 一、小程序地理位置权限申请二、uniapp调用uni.getLocation 一、小程序地理位置权限申请 需要确保小程序类目已经填写 点击左侧导航栏找到最后的“设置”——“基本设置”——“前往填写” 在开发管理——接口设置——地理位置中可以看到&#xff1a; 即可点击想要申…

数据库操作sql循环和sql递归

1.sql循环 在SQL中&#xff0c;通常情况下并不直接支持循环&#xff0c;因为SQL是一种集合操作语言&#xff0c;更适合于对数据集进行操作和处理。但是&#xff0c;可以通过递归查询和循环结构实现类似循环的功能&#xff0c;具体取决于数据库系统的支持和实现。 一种常见的方…

智能物联网远传冷水表管理系统

智能物联网远传冷水表管理系统是一种基于物联网技术的先进系统&#xff0c;旨在实现对冷水表的远程监测、数据传输和智能化管理。本文将从系统特点、构成以及带来的效益三个方面展开介绍。 系统特点 1.远程监测&#xff1a;系统可以实现对冷水表数据的远程监测&#xff0c;无…

uni-app实现下拉刷新

业务逻辑如下&#xff1a; 1.在滚动容器中加入refresher-enabled属性&#xff0c;表示为开启下拉刷新 2.监听事件&#xff0c;添加refresherrefresh事件 3.在事件监听函数中加载数据 4.关闭动画&#xff0c;添加refresher-triggered属性&#xff0c;在数据请求前开启刷新动画…

单片机之蓝牙通信

目录 蓝牙介绍 HC05蓝牙模块 HC05参数 HC05引脚 各个引脚功能 HC05模块的作用 工作模式 配置模式 引脚接线 用AT指令进行配置 常用的AT指令 正常模式 测试步骤 烧录的程序 前言&#xff1a; keil文件 蓝牙介绍 蓝牙&#xff1a;Bluetooth&#xff0c;其是低成…

企业航拍VR全景视频展示仿如上门参观

360度VR全景视频因其广阔的视野和身临其境的体验&#xff0c;无论再房产楼盘的精致呈现&#xff0c;旅游景点的全景漫游&#xff0c;还是校园风光的生动展示&#xff0c;都成为企业商户的首选。 360度vr全景视频编辑软件是深圳VR公司华锐视点提供多种常见的三维仿真场景供选择&…

【Python细类】全局日志调试模式

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

goproxy 简单介绍 及一键安装脚本

goproxy 官网 https://goproxy.cn/ GoProxy 是一项用于 Go 模块的高性能代理服务&#xff0c;旨在为 Go 开发人员提供更快速、更可靠的模块下载体验。它提供以下主要功能&#xff1a; 全球分布式代理服务器: GoProxy 在全球多个地区部署了代理服务器&#xff0c;例如拉斯维加…

MBD(Model-Based Design)在汽车软件开发中的应用

MBD(Model-Based Design)在汽车软件开发中的应用已经成为提高开发效率、降低错误率、加快上市时间的重要方法。以下是关于MBD开发汽车软件的详细介绍和经验分享。 概述 MBD是一种系统开发的范式,它侧重于使用图形化模型来表示系统的各个方面,从而实现对复杂系统的高效设计…

【电控笔记6】电流回路+延迟效应

问题提出 数字控制系统的delay: 5.4节有介绍T0=0.5TS 低通滤波器的时间常数? 可用示例程序 m2 2 1b 如下图画出开环系统的伯德图进行比较,如图 2-2-4 所示,由于延迟组件会侵蚀系统的相位,因此从图可以看出,加入延迟效应后,q轴电流回路的相位裕度(Phase Margin) 从…

性能优化---CDN

1、CDN概念 CDN是指一种通过互联网互相连接的电脑网络系统&#xff0c;利用最靠近每位用户的服务器&#xff0c;更快、更可靠地将音乐、图片等文件发送给用户&#xff0c;来提高性能、可扩展性及低成本地网络内容传递给用户。 典型的CDN可分为&#xff1a; a、分发服务系统&…

芒果YOLOv7改进96:检测头篇DynamicHead动态检测头:即插即用|DynamicHead检测头,尺度感知、空间感知、任务感知

该专栏完整目录链接: 芒果YOLOv7深度改进教程 该创新点:在原始的Dynamic Head的基础上,对核心部位进行了二次的改进,在 原论文 《尺度感知、空间感知、任务感知》 的基础上,在 通道感知 的层级上进行了增强,关注每个像素点的比重。 在自己的数据集上改进,有效涨点就可以…

2024.4.12清空Google剩余的几个网址

Spring高频面试题-CSDN博客 从上面这个链接学到了Spring Bean是否是线程安全的。 013-第三讲-bean生命周期_哔哩哔哩_bilibili Java IO| ProcessOn免费在线作图,在线流程图,在线思维导图 上面这个是马士兵金九银十的一张IO思维导图 下面这个是一个学习项目&#xff0c;收费…

CSS3 平面 2D 变换+CSS3 过渡

个人主页&#xff1a;学习前端的小z 个人专栏&#xff1a;HTML5和CSS3悦读 本专栏旨在分享记录每日学习的前端知识和学习笔记的归纳总结&#xff0c;欢迎大家在评论区交流讨论&#xff01; 文章目录 ✍一、CSS3 平面 2D 变换&#x1f48e;1 坐标轴&#x1f48e;2 transform 语法…