C++11以及17部分特性

发布时间 2023-11-26 19:17:52作者: Labant
  1 //1、并发支持
  2 //1.1、C++11 内存模型:
  3 //      a.原子性(Atomicity):对于原子类型(std::atomic),其成员函数的操作是原子的,不会被其他线程中断。
  4 //      b.可见性(Visibility):对于非原子类型,通过使用互斥量或同步操作来确保共享数据的可见性,即在一个线程中对共享数据的修改会立即反映到其他线程中。
  5 //      c.有序性(Ordering):通过同步操作(如互斥量、原子操作的memory_order参数等)来定义操作的顺序性,从而在多线程环境中确定操作和事件的相对顺序
  6 //1.2、线程与锁
  7 //      C++ 对线程和锁级别编程的支持是 POSIX 和 Windows 所提供的线程和锁的类型安全变体
  8 //      a.thread——系统的执行线程,支持 join() 和 detach()
  9 //      b.mutex——系统的互斥锁,支持 lock()、unlock() 和保证 unlock() 的 RAII 方式
 10 //      c.condition_variable——系统中线程间进行事件通信的条件变量
 11 //      d.thread_local——线程本地存储
 12 //      说明:线程和锁模型需要使用某种形式的同步来避免竞争条件。C++11 为此提供了标准 的 mutex(互斥锁)
 13 // 1.3、期值(future)
 14 //      a.future——一个句柄,通过它你可以从一个共享的单对象缓冲区中 get() 一个值,可能需要等待某个 promise 将该值放入缓冲区
 15 //      b.promise——一个句柄,通过它你可以将一个值 put() 到一个共享的单对象缓冲区,可能会唤醒某个等待 future 的 thread
 16 //      c.packaged_task——一个类,它使得设置一个函数在线程上异步执行变得容易,由 future 来接受 promise 返回的结果
 17 //      d.async()——一个函数,可以启动一个任务并在另一个 thread 上执行
 18 // 
 19 // 2、简化使用
 20 // 2.1、auto 和 decltype
 21 //      a.auto:让编译器在编译器就推导出变量的类型,可以通过=右边的类型推导出变量的类型。(前提是定义一个变量时对其进行初始化)
 22         /*exp:*/auto a = 10.5;
 23 //      b.decltype:用于推导表达式类型,这里只用于编译器分析表达式的类型,表达式实际不会进行运算。(decltypde是不需要推导变量初始化的,根据的是表达式对变量的类型就可以推导。)
 24 #include <typeinfo>//decltype头文件,用法:decltype(exp) varname = value;
 25         auto varname = a;
 26         decltype(varname) x;  //x 被推导成了 double
 27 //2.2、范围for
 28 //        在C++11中,引入了范围for循环(Range-based for loop),它提供了一种简洁而直观的方式来遍历容器、数组、字符串和其他可迭代对象。
 29 //2.3、资源管理指针
 30 //         在<memory>t头文件中
 31 #include <memory>
 32 //        a.unique_ptr: 是一种独占式智能指针,用于管理唯一的对象,确保只有一个指针可以访问该对象。
 33 //            通过 std::make_unique 函数可以创建 std::unique_ptr 对象,如:std::unique_ptr<int> ptr = std::make_unique<int>(42);
 34 //         b.shared_ptr:是一种共享式智能指针,多个指针可以同时共享对同一对象的拥有权。
 35 //            使用引用计数技术追踪所管理对象的引用数量,当引用计数变为零时,自动销毁所管理的对象。
 36 //            通过 std::make_shared 函数可以创建 std::shared_ptr 对象,如:
 37             std::shared_ptr<int> ptr = std::make_shared<int>(42);
 38 //         c.weak_ptr:是一种弱引用智能指针,它可以解决 std::shared_ptr 的循环引用问题,导致引用计数不正确问题。
 39 //            td::weak_ptr 指向 std::shared_ptr 管理的对象,但不会增加引用计数。因此,当所有 std::shared_ptr 对象超出作用域后,
 40 //            即使还有 std::weak_ptr 对象存在,所管理的对象也会被销毁。
 41 //            通过 std::shared_ptr 的 std::weak_ptr 构造函数可以创建 std::weak_ptr 对象,如:
 42         std::weak_ptr<int> weakPtr = ptr;
 43 // 2.4、统一初始化
 44 //            a.直接初始化
 45             int init_a = 10;
 46 //            b.拷贝初始化
 47             int init_b = init_a;
 48 //            c.初始化列表
 49             int init_c{ 100 };
 50 //2.5、nullptr
 51 //2.6、constexpr
 52 //         constexpr 关键字的功能是使指定的常量表达式获得在程序编译阶段计算出结果的能力,
 53 //        而不必等到程序运行阶段。C++ 11 标准中,constexpr 可用于修饰普通变量、函数(包括模板函数)以及类的构造函数
 54 //            a.值不会改变
 55 //             b.在编译过程就能得到计算结果的表达式。
 56 //         constexpr与const区别:
 57 //            a.const只表示read only的语义,只保证了运行时不可以被修改,但它修饰的仍然有可能是个动态变量,
 58 //             b.constexpr修饰的才是真正的常量,它会在编译期间就会被计算出来,整个运行过程中都不可以被改变,constexpr可以用于修饰函数,
 59 //                这个函数的返回值会尽可能在编译期间被计算出来当作一个常量,但是如果编译期间此函数不能被计算出来,那它就会当作一个普通函数被处理
 60 //2.7、explicit
 61 //        explicit专用于修饰构造函数,表示只能显式构造,不可以被隐式转换,根据代码看explicit的作用
 62 //2.8、final 、 override
 63 //        a.final用于修饰一个类,表示禁止该类进一步派生和虚函数的进一步重载
 64 //         b.override用于修饰派生类中的成员函数,标明该函数重写了基类函数,如果一个函数声明了override但父类却没有这个虚函数,
 65 //            编译报错,使用override关键字可以避免开发者在重写基类函数时无意产生的错误
 66 // 2.9、右值引用
 67 //        a.右值引用(Rvalue reference)是一种引用类型,它用于绑定到临时对象或将要被移动的对象(右值)
 68 //         b.右值引用的语法是在类型后面加上 &&,例如 int&& 表示一个右值引用到 int 类型的对象。右值引用只能绑定到右值,不能绑定到左值
 69 //         c.右值引用主要有两个重要的应用场景:移动语义和完美转发:
 70 //                1)移动语义: 右值引用使得我们可以实现高效的资源管理,尤其是在处理动态分配的内存或大型对象时。通过移动语义,我们可以将资源从一个对象转移到另一个对象,避免了不必要的拷贝开销。
 71 //                通过定义移动构造函数和移动赋值运算符,并使用右值引用参数,可以实现对资源的高效转移。移动构造函数用于在构造对象时从临时或将要被销毁的对象中“窃取”资源,移动赋值运算符用于在对象已存在时将资源从右值赋值给对象。
 72 //                这样,在资源转移完成后,原始对象就不再拥有该资源,而新对象拥有该资源,避免了多余的内存分配和拷贝操作
 73 //                2)完美转发: 完美转发是指在函数模板中保持参数的值类别(左值或右值)并将其转发到其他函数,以实现泛型编程中的通用参数传递。通过使用右值引用和模板参数推导,可以实现参数类型的自动推导和类型保持。
 74 //                在函数模板中使用右值引用参数可以接收右值和左值,并保持参数的原始类型。结合 std::forward 可以实现完美转发,将参数以原始类型转发到其他函数。这样,在调用模板函数时,参数的值类别被保留,从而选择正确的函数进行处理。
 75 //        右值引用在 C++11 中引入,它的出现在很大程度上优化了资源管理和提升了代码的性能。它为移动语义和完美转发提供了重要的基础,并在现代 C++ 开发中广泛应用
 76 // 2.10、移动语义
 77 //        C++11 引入了移动语义(Move Semantics)的概念,旨在提高对象的性能和效率。移动语义通过转移资源所有权,避免不必要的拷贝操作,从而更高效地管理对象。
 78 //        a.在传统的拷贝语义中,当我们将一个对象赋值给另一个对象或者作为函数参数传递时,会进行对象的拷贝操作,这包括复制所有成员变量的值、分配内存等。在某些情况下,这种拷贝操作是非常昂贵的,特别是对于大型对象或者资源密集型的操作;
 79 //        b.移动语义通过引入右值引用(Rvalue Reference)来解决这个问题。右值引用使用 && 语法进行声明,表示一个临时对象或者即将销毁的对象。在移动语义中,我们可以将资源的所有权从一个对象转移到另一个对象,而不需要进行昂贵的拷贝操作。
 80 //        c.在使用移动语义时,可以借助 std::move 函数将左值转换为右值引用,以便进行移动操作
 81 //        d.通过移动语义,我们可以避免不必要的拷贝操作,提高代码的性能和效率。特别是对于容器类(如 std::vector、std::string)或动态分配的资源,利用移动语义可以显著降低内存分配和复制的开销
 82 //        注意:移动构造函数的实现通常是将源对象指针设置为 nullptr,以确保在析构时不会释放已经被转移的资源。此外,移动构造函数和拷贝构造函数应该遵循特定的语义规范,以确保正确、可预期的行为.
 83 #include <iostream>
 84 #include <vector>
 85 
 86             class MyObject {
 87             public:
 88                 MyObject() {
 89                     std::cout << "Default constructor" << std::endl;
 90                     // 假设需要分配大量内存或进行其他资源密集型操作
 91                 }
 92 
 93                 MyObject(const MyObject& other) {
 94                     std::cout << "Copy constructor" << std::endl;
 95                     // 实现对象的拷贝操作
 96                 }
 97 
 98                 MyObject(MyObject&& other) {
 99                     std::cout << "Move constructor" << std::endl;
100                     // 实现对象的移动操作
101                 }
102             };
103 
104             int main() {
105                 MyObject obj1;  // 调用默认构造函数
106                 MyObject obj2(obj1);  // 调用拷贝构造函数,拷贝 obj1 的值到 obj2
107                 MyObject obj3(std::move(obj1));  // 调用移动构造函数,将 obj1 的值转移到 obj3
108 
109                 return 0;
110             }
111 // 
112 //2.11、完美转发
113 //        a.完美转发(perfect forwarding)是 C++ 中用于保持传递参数类型和转发函数调用的机制。它通常与模板和右值引用一起使用,以实现泛型编程中的参数传递;
114 //        b.在传统的函数调用中,如果我们想要将一个函数的参数传递给另一个函数,通常可以直接通过值传递或引用传递来实现。但是问题在于,当我们希望将参数以原始类型(值类型或引用类型)传递给另一个函数时,
115 //            需要显式指定参数的类型,而无法自动推导,完美转发解决了这个问题,它允许我们在一层函数中接收参数,并将其转发到另一层函数,同时保持参数的原始类型。这样就可以实现泛型编程中的通用参数传递,不再需要手动指定参数类型
116 //        c.完美转发的核心是使用了两种类型:通用引用和 std::forward
117 //                1).通用引用(Universal Reference)是指使用了 auto&& 或模板参数推导结合引用折叠规则的引用类型。通用引用可以绑定到左值或右值,并保持参数的原始类型。
118 //                2).std::forward 是一个条件转发的工具函数,用于根据参数的原始类型,选择性地将参数转发为左值引用或右值引用。它的使用场景通常是在模板函数或模板类中,用于将参数转发到另一个函数。
119 //        d.完美转发示例:
120 #include <iostream>
121 #include <utility>
122 
123             void processValue(int& value)
124             {
125                 std::cout << "Lvalue: " << value << std::endl;
126                 value = 42;
127             }
128 
129             void processValue(int&& value) {
130                 std::cout << "Rvalue: " << value << std::endl;
131             }
132 
133             //forwardValue 是一个模板函数,它使用了通用引用来接收参数,并使用 std::forward 将参数转发给 processValue 函数。通过 std::forward,参数的原始类型可以被保持,并且传递给正确的版本进行处理。
134             template<typename T>
135             void forwardValue(T&& value)
136             {
137                 processValue(std::forward<T>(value));
138             }
139 
140             //在 main 函数中,我们先传递了一个左值 x 给 forwardValue 函数,然后传递了一个右值 20。通过完美转发,参数的类型被正确地保持,并且分别调用了 processValue 的左值版本和右值版本。
141             int main() {
142                 int x = 10;
143 
144                 forwardValue(x);  // 传递左值
145                 std::cout << "After forwarding, x = " << x << std::endl;
146 
147                 forwardValue(20);  // 传递右值
148 
149                 return 0;
150             }
151             //在上述示例中,我们定义了两个函数:processValue 和 forwardValue。processValue 函数重载了一个接收左值引用和右值引用参数的版本,分别对左值和右值做出不同的处理。
152 //        e.需要注意的是,在使用完美转发时,通常需要使用模板函数,并搭配通用引用的语法。这样可以保持参数的原始类型,并进行类型推导,从而实现泛型编程中的参数转发。
153 //3.改进对泛型编程的支持
154 //3.1、lambda表达式
155 //        a.Lambda 表达式是一种匿名函数,可以在需要函数的地方定义并使用它,而无需显式命名函数。
156 //         b.Lambda 表达式的基本语法如下:其中,“(parameters)”、“-> return_type”可以省略。
157 //            [capture list](parameters) -> return_type {
158                 // 函数体
159 //            }
160 //         c.具体使用:1.直接传值可读不可改;2、引用传值可读改;3、“=”,变量范围内都可访问可读改;
161             int main1() {
162                 int x = 10;
163                 // 使用 Lambda 表达式打印变量 x 的值
164                 auto printX = [x]() {
165                     std::cout << "x = " << x << std::endl;
166                 };
167                 printX();  // 调用 Lambda 函数
168 
169                 return 0;
170             }
171 //3.2、变参模版
172 //        C++11 引入了变参模板(Variadic Template),它允许函数或类模板接受任意数量的参数。这使得我们能够定义更加灵活的函数和类模板,支持可变数量的参数
173 //         a.在 C++11 中,使用 ... 表示变参模板
174 //        b.简单示例:
175             void print() {  //无参构造函数是必须的,在输出可变参的最后需要调用 这个无参构造函数
176                 std::cout << std::endl;
177             }
178 
179             template<typename T, typename... Args>
180             void print(T first, Args... args) {
181                 std::cout << first << " ";   //输出第一个参数
182                 print(args...);  //输出剩下的可变参
183             }
184 
185             int main2() {
186                 print(1, 2, 3, "Hello", 4.5);  // 调用变参模板函数 print
187 
188                 return 0;
189             }
190 //3.3、别名
191 //        在 C++11 中,引入了类型别名(Type Alias)功能,允许为已有的类型定义一个新的名称。这种类型别名可以提高代码的可读性、简化复杂类型的书写,
192 //        并且可以方便地修改类型定义而不需要改变使用该类型的代码。
193 //         a.C++11 提供了两种方式来创建类型别名:
194 //                1).typedef int myInt;  // 将 int 定义为 myInt 类型的别名
195 //                2).using myInt = int;  // 将 int 定义为 myInt 类型的别名
196 //3.4、tuple 元组
197 //        a.在 C++11 中引入了 std::tuple 类模板,它是一个通用的元组(Tuple)类,用于存储多个不同类型的值,
198 //            std::tuple 可以看作是一个固定大小的、类型安全的、不可修改的集合。
199 //         b.使用 std::tuple 可以方便地组合多个值,而无需定义新的结构体或类.
200 //        c.获取tuple值的方式有两种:
201 //                1).使用 std::get<0>(tupleObj)获取第一个对象值
202 #include <tuple>
203                 int main3() {
204                     // 创建一个包含 int、double 和字符串的 tuple
205                     std::tuple<int, double, std::string> myTuple(42, 3.14, "Hello");
206 
207                     // 使用 std::get 访问 tuple 中的元素(通过索引)
208                     int intValue = std::get<0>(myTuple);
209                     double doubleValue = std::get<1>(myTuple);
210                     std::string stringValue = std::get<2>(myTuple);
211 
212                     std::cout << "int value: " << intValue << std::endl;
213                     std::cout << "double value: " << doubleValue << std::endl;
214                     std::cout << "string value: " << stringValue << std::endl;
215 
216                     return 0;
  1 //1、并发支持
  2 //1.1、C++11 内存模型:
  3 //      a.原子性(Atomicity):对于原子类型(std::atomic),其成员函数的操作是原子的,不会被其他线程中断。
  4 //      b.可见性(Visibility):对于非原子类型,通过使用互斥量或同步操作来确保共享数据的可见性,即在一个线程中对共享数据的修改会立即反映到其他线程中。
  5 //      c.有序性(Ordering):通过同步操作(如互斥量、原子操作的memory_order参数等)来定义操作的顺序性,从而在多线程环境中确定操作和事件的相对顺序
  6 //1.2、线程与锁
  7 //      C++ 对线程和锁级别编程的支持是 POSIX 和 Windows 所提供的线程和锁的类型安全变体
  8 //      a.thread——系统的执行线程,支持 join() 和 detach()
  9 //      b.mutex——系统的互斥锁,支持 lock()、unlock() 和保证 unlock() 的 RAII 方式
 10 //      c.condition_variable——系统中线程间进行事件通信的条件变量
 11 //      d.thread_local——线程本地存储
 12 //      说明:线程和锁模型需要使用某种形式的同步来避免竞争条件。C++11 为此提供了标准 的 mutex(互斥锁)
 13 // 1.3、期值(future)
 14 //      a.future——一个句柄,通过它你可以从一个共享的单对象缓冲区中 get() 一个值,可能需要等待某个 promise 将该值放入缓冲区
 15 //      b.promise——一个句柄,通过它你可以将一个值 put() 到一个共享的单对象缓冲区,可能会唤醒某个等待 future 的 thread
 16 //      c.packaged_task——一个类,它使得设置一个函数在线程上异步执行变得容易,由 future 来接受 promise 返回的结果
 17 //      d.async()——一个函数,可以启动一个任务并在另一个 thread 上执行
 18 // 
 19 // 2、简化使用
 20 // 2.1、auto 和 decltype
 21 //      a.auto:让编译器在编译器就推导出变量的类型,可以通过=右边的类型推导出变量的类型。(前提是定义一个变量时对其进行初始化)
 22         /*exp:*/auto a = 10.5;
 23 //      b.decltype:用于推导表达式类型,这里只用于编译器分析表达式的类型,表达式实际不会进行运算。(decltypde是不需要推导变量初始化的,根据的是表达式对变量的类型就可以推导。)
 24 #include <typeinfo>//decltype头文件,用法:decltype(exp) varname = value;
 25         auto varname = a;
 26         decltype(varname) x;  //x 被推导成了 double
 27 //2.2、范围for
 28 //        在C++11中,引入了范围for循环(Range-based for loop),它提供了一种简洁而直观的方式来遍历容器、数组、字符串和其他可迭代对象。
 29 //2.3、资源管理指针
 30 //         在<memory>t头文件中
 31 #include <memory>
 32 //        a.unique_ptr: 是一种独占式智能指针,用于管理唯一的对象,确保只有一个指针可以访问该对象。
 33 //            通过 std::make_unique 函数可以创建 std::unique_ptr 对象,如:std::unique_ptr<int> ptr = std::make_unique<int>(42);
 34 //         b.shared_ptr:是一种共享式智能指针,多个指针可以同时共享对同一对象的拥有权。
 35 //            使用引用计数技术追踪所管理对象的引用数量,当引用计数变为零时,自动销毁所管理的对象。
 36 //            通过 std::make_shared 函数可以创建 std::shared_ptr 对象,如:
 37             std::shared_ptr<int> ptr = std::make_shared<int>(42);
 38 //         c.weak_ptr:是一种弱引用智能指针,它可以解决 std::shared_ptr 的循环引用问题,导致引用计数不正确问题。
 39 //            td::weak_ptr 指向 std::shared_ptr 管理的对象,但不会增加引用计数。因此,当所有 std::shared_ptr 对象超出作用域后,
 40 //            即使还有 std::weak_ptr 对象存在,所管理的对象也会被销毁。
 41 //            通过 std::shared_ptr 的 std::weak_ptr 构造函数可以创建 std::weak_ptr 对象,如:
 42         std::weak_ptr<int> weakPtr = ptr;
 43 // 2.4、统一初始化
 44 //            a.直接初始化
 45             int init_a = 10;
 46 //            b.拷贝初始化
 47             int init_b = init_a;
 48 //            c.初始化列表
 49             int init_c{ 100 };
 50 //2.5、nullptr
 51 //2.6、constexpr
 52 //         constexpr 关键字的功能是使指定的常量表达式获得在程序编译阶段计算出结果的能力,
 53 //        而不必等到程序运行阶段。C++ 11 标准中,constexpr 可用于修饰普通变量、函数(包括模板函数)以及类的构造函数
 54 //            a.值不会改变
 55 //             b.在编译过程就能得到计算结果的表达式。
 56 //         constexpr与const区别:
 57 //            a.const只表示read only的语义,只保证了运行时不可以被修改,但它修饰的仍然有可能是个动态变量,
 58 //             b.constexpr修饰的才是真正的常量,它会在编译期间就会被计算出来,整个运行过程中都不可以被改变,constexpr可以用于修饰函数,
 59 //                这个函数的返回值会尽可能在编译期间被计算出来当作一个常量,但是如果编译期间此函数不能被计算出来,那它就会当作一个普通函数被处理
 60 //2.7、explicit
 61 //        explicit专用于修饰构造函数,表示只能显式构造,不可以被隐式转换,根据代码看explicit的作用
 62 //2.8、final 、 override
 63 //        a.final用于修饰一个类,表示禁止该类进一步派生和虚函数的进一步重载
 64 //         b.override用于修饰派生类中的成员函数,标明该函数重写了基类函数,如果一个函数声明了override但父类却没有这个虚函数,
 65 //            编译报错,使用override关键字可以避免开发者在重写基类函数时无意产生的错误
 66 // 2.9、右值引用
 67 //        a.右值引用(Rvalue reference)是一种引用类型,它用于绑定到临时对象或将要被移动的对象(右值)
 68 //         b.右值引用的语法是在类型后面加上 &&,例如 int&& 表示一个右值引用到 int 类型的对象。右值引用只能绑定到右值,不能绑定到左值
 69 //         c.右值引用主要有两个重要的应用场景:移动语义和完美转发:
 70 //                1)移动语义: 右值引用使得我们可以实现高效的资源管理,尤其是在处理动态分配的内存或大型对象时。通过移动语义,我们可以将资源从一个对象转移到另一个对象,避免了不必要的拷贝开销。
 71 //                通过定义移动构造函数和移动赋值运算符,并使用右值引用参数,可以实现对资源的高效转移。移动构造函数用于在构造对象时从临时或将要被销毁的对象中“窃取”资源,移动赋值运算符用于在对象已存在时将资源从右值赋值给对象。
 72 //                这样,在资源转移完成后,原始对象就不再拥有该资源,而新对象拥有该资源,避免了多余的内存分配和拷贝操作
 73 //                2)完美转发: 完美转发是指在函数模板中保持参数的值类别(左值或右值)并将其转发到其他函数,以实现泛型编程中的通用参数传递。通过使用右值引用和模板参数推导,可以实现参数类型的自动推导和类型保持。
 74 //                在函数模板中使用右值引用参数可以接收右值和左值,并保持参数的原始类型。结合 std::forward 可以实现完美转发,将参数以原始类型转发到其他函数。这样,在调用模板函数时,参数的值类别被保留,从而选择正确的函数进行处理。
 75 //        右值引用在 C++11 中引入,它的出现在很大程度上优化了资源管理和提升了代码的性能。它为移动语义和完美转发提供了重要的基础,并在现代 C++ 开发中广泛应用
 76 // 2.10、移动语义
 77 //        C++11 引入了移动语义(Move Semantics)的概念,旨在提高对象的性能和效率。移动语义通过转移资源所有权,避免不必要的拷贝操作,从而更高效地管理对象。
 78 //        a.在传统的拷贝语义中,当我们将一个对象赋值给另一个对象或者作为函数参数传递时,会进行对象的拷贝操作,这包括复制所有成员变量的值、分配内存等。在某些情况下,这种拷贝操作是非常昂贵的,特别是对于大型对象或者资源密集型的操作;
 79 //        b.移动语义通过引入右值引用(Rvalue Reference)来解决这个问题。右值引用使用 && 语法进行声明,表示一个临时对象或者即将销毁的对象。在移动语义中,我们可以将资源的所有权从一个对象转移到另一个对象,而不需要进行昂贵的拷贝操作。
 80 //        c.在使用移动语义时,可以借助 std::move 函数将左值转换为右值引用,以便进行移动操作
 81 //        d.通过移动语义,我们可以避免不必要的拷贝操作,提高代码的性能和效率。特别是对于容器类(如 std::vector、std::string)或动态分配的资源,利用移动语义可以显著降低内存分配和复制的开销
 82 //        注意:移动构造函数的实现通常是将源对象指针设置为 nullptr,以确保在析构时不会释放已经被转移的资源。此外,移动构造函数和拷贝构造函数应该遵循特定的语义规范,以确保正确、可预期的行为.
 83 #include <iostream>
 84 #include <vector>
 85 
 86             class MyObject {
 87             public:
 88                 MyObject() {
 89                     std::cout << "Default constructor" << std::endl;
 90                     // 假设需要分配大量内存或进行其他资源密集型操作
 91                 }
 92 
 93                 MyObject(const MyObject& other) {
 94                     std::cout << "Copy constructor" << std::endl;
 95                     // 实现对象的拷贝操作
 96                 }
 97 
 98                 MyObject(MyObject&& other) {
 99                     std::cout << "Move constructor" << std::endl;
100                     // 实现对象的移动操作
101                 }
102             };
103 
104             int main() {
105                 MyObject obj1;  // 调用默认构造函数
106                 MyObject obj2(obj1);  // 调用拷贝构造函数,拷贝 obj1 的值到 obj2
107                 MyObject obj3(std::move(obj1));  // 调用移动构造函数,将 obj1 的值转移到 obj3
108 
109                 return 0;
110             }
111 // 
112 //2.11、完美转发
113 //        a.完美转发(perfect forwarding)是 C++ 中用于保持传递参数类型和转发函数调用的机制。它通常与模板和右值引用一起使用,以实现泛型编程中的参数传递;
114 //        b.在传统的函数调用中,如果我们想要将一个函数的参数传递给另一个函数,通常可以直接通过值传递或引用传递来实现。但是问题在于,当我们希望将参数以原始类型(值类型或引用类型)传递给另一个函数时,
115 //            需要显式指定参数的类型,而无法自动推导,完美转发解决了这个问题,它允许我们在一层函数中接收参数,并将其转发到另一层函数,同时保持参数的原始类型。这样就可以实现泛型编程中的通用参数传递,不再需要手动指定参数类型
116 //        c.完美转发的核心是使用了两种类型:通用引用和 std::forward
117 //                1).通用引用(Universal Reference)是指使用了 auto&& 或模板参数推导结合引用折叠规则的引用类型。通用引用可以绑定到左值或右值,并保持参数的原始类型。
118 //                2).std::forward 是一个条件转发的工具函数,用于根据参数的原始类型,选择性地将参数转发为左值引用或右值引用。它的使用场景通常是在模板函数或模板类中,用于将参数转发到另一个函数。
119 //        d.完美转发示例:
120 #include <iostream>
121 #include <utility>
122 
123             void processValue(int& value)
124             {
125                 std::cout << "Lvalue: " << value << std::endl;
126                 value = 42;
127             }
128 
129             void processValue(int&& value) {
130                 std::cout << "Rvalue: " << value << std::endl;
131             }
132 
133             //forwardValue 是一个模板函数,它使用了通用引用来接收参数,并使用 std::forward 将参数转发给 processValue 函数。通过 std::forward,参数的原始类型可以被保持,并且传递给正确的版本进行处理。
134             template<typename T>
135             void forwardValue(T&& value)
136             {
137                 processValue(std::forward<T>(value));
138             }
139 
140             //在 main 函数中,我们先传递了一个左值 x 给 forwardValue 函数,然后传递了一个右值 20。通过完美转发,参数的类型被正确地保持,并且分别调用了 processValue 的左值版本和右值版本。
141             int main() {
142                 int x = 10;
143 
144                 forwardValue(x);  // 传递左值
145                 std::cout << "After forwarding, x = " << x << std::endl;
146 
147                 forwardValue(20);  // 传递右值
148 
149                 return 0;
150             }
151             //在上述示例中,我们定义了两个函数:processValue 和 forwardValue。processValue 函数重载了一个接收左值引用和右值引用参数的版本,分别对左值和右值做出不同的处理。
152 //        e.需要注意的是,在使用完美转发时,通常需要使用模板函数,并搭配通用引用的语法。这样可以保持参数的原始类型,并进行类型推导,从而实现泛型编程中的参数转发。
153 //3.改进对泛型编程的支持
154 //3.1、lambda表达式
155 //        a.Lambda 表达式是一种匿名函数,可以在需要函数的地方定义并使用它,而无需显式命名函数。
156 //         b.Lambda 表达式的基本语法如下:其中,“(parameters)”、“-> return_type”可以省略。
157 //            [capture list](parameters) -> return_type {
158                 // 函数体
159 //            }
160 //         c.具体使用:1.直接传值可读不可改;2、引用传值可读改;3、“=”,变量范围内都可访问可读改;
161             int main1() {
162                 int x = 10;
163                 // 使用 Lambda 表达式打印变量 x 的值
164                 auto printX = [x]() {
165                     std::cout << "x = " << x << std::endl;
166                 };
167                 printX();  // 调用 Lambda 函数
168 
169                 return 0;
170             }
171 //3.2、变参模版
172 //        C++11 引入了变参模板(Variadic Template),它允许函数或类模板接受任意数量的参数。这使得我们能够定义更加灵活的函数和类模板,支持可变数量的参数
173 //         a.在 C++11 中,使用 ... 表示变参模板
174 //        b.简单示例:
175             void print() {  //无参构造函数是必须的,在输出可变参的最后需要调用 这个无参构造函数
176                 std::cout << std::endl;
177             }
178 
179             template<typename T, typename... Args>
180             void print(T first, Args... args) {
181                 std::cout << first << " ";   //输出第一个参数
182                 print(args...);  //输出剩下的可变参
183             }
184 
185             int main2() {
186                 print(1, 2, 3, "Hello", 4.5);  // 调用变参模板函数 print
187 
188                 return 0;
189             }
190 //3.3、别名
191 //        在 C++11 中,引入了类型别名(Type Alias)功能,允许为已有的类型定义一个新的名称。这种类型别名可以提高代码的可读性、简化复杂类型的书写,
192 //        并且可以方便地修改类型定义而不需要改变使用该类型的代码。
193 //         a.C++11 提供了两种方式来创建类型别名:
194 //                1).typedef int myInt;  // 将 int 定义为 myInt 类型的别名
195 //                2).using myInt = int;  // 将 int 定义为 myInt 类型的别名
196 //3.4、tuple 元组
197 //        a.在 C++11 中引入了 std::tuple 类模板,它是一个通用的元组(Tuple)类,用于存储多个不同类型的值,
198 //            std::tuple 可以看作是一个固定大小的、类型安全的、不可修改的集合。
199 //         b.使用 std::tuple 可以方便地组合多个值,而无需定义新的结构体或类.
200 //        c.获取tuple值的方式有两种:
201 //                1).使用 std::get<0>(tupleObj)获取第一个对象值
202 #include <tuple>
203                 int main3() {
204                     // 创建一个包含 int、double 和字符串的 tuple
205                     std::tuple<int, double, std::string> myTuple(42, 3.14, "Hello");
206 
207                     // 使用 std::get 访问 tuple 中的元素(通过索引)
208                     int intValue = std::get<0>(myTuple);
209                     double doubleValue = std::get<1>(myTuple);
210                     std::string stringValue = std::get<2>(myTuple);
211 
212                     std::cout << "int value: " << intValue << std::endl;
213                     std::cout << "double value: " << doubleValue << std::endl;
214                     std::cout << "string value: " << stringValue << std::endl;
215 
216                     return 0;
217                 }
218 //                2).使用结构化绑定(Structured Binding)来解包元组中的值(C++ 17新增)
219                 int main4() {
220                     std::tuple<int, double, std::string> myTuple(42, 3.14, "Hello");
221 
222                     // 结构化绑定解包 tuple 中的值
223                     auto [intValue, doubleValue, stringValue] = myTuple;
224 
225                     std::cout << "int value: " << intValue << std::endl;
226                     std::cout << "double value: " << doubleValue << std::endl;
227                     std::cout << "string value: " << stringValue << std::endl;
228 
229                     return 0;
230                 }

 

217                 }
218 //                2).使用结构化绑定(Structured Binding)来解包元组中的值(C++ 17新增)
219                 int main4() {
220                     std::tuple<int, double, std::string> myTuple(42, 3.14, "Hello");
221 
222                     // 结构化绑定解包 tuple 中的值
223                     auto [intValue, doubleValue, stringValue] = myTuple;
224 
225                     std::cout << "int value: " << intValue << std::endl;
226                     std::cout << "double value: " << doubleValue << std::endl;
227                     std::cout << "string value: " << stringValue << std::endl;
228 
229                     return 0;
230                 }