// 添加移动构造函数
Test(Test&& a) : m\_num(a.m_num)
{
a.m_num = nullptr;
cout << "move construct: my name is sunny" << endl;
}
~Test()
{
delete m_num;
cout << "destruct Test class ..." << endl;
}
int\* m_num;
};
Test getObj() { Test t; return t; }
int main() { Test t = getObj(); cout << "t.m_num: " << *t.m_num << endl; return 0; };
construct: my name is jerry move construct: my name is sunny destruct Test class ... t.m_num: 100 destruct Test class ...
通过修改,在上面的代码给 Test 类添加了移动构造函数(参数为右值引用类型),这样在进行 Test t = getObj(); 操作的时候并没有调用拷贝构造函数进行深拷贝,而是调用了移动构造函数,在这个函数中只是进行了浅拷贝,没有对临时对象进行深拷贝,提高了性能。
如果不使用移动构造,在执行 Test t = getObj() 的时候也是进行了浅拷贝,但是当临时对象被析构的时候,类成员指针 int\* m\_num; 指向的内存也就被析构了,对象 t 也就无法访问这块内存地址了。
在测试程序中 getObj() 的返回值就是一个将亡值,也就是说是一个右值,在进行赋值操作的时候如果 = 右边是一个右值,那么移动构造函数就会被调用。移动构造中使用了右值引用,会将临时对象中的堆内存地址的所有权转移给对象t,这块内存被成功续命,因此在t对象中还可以继续使用这块内存。
## move 转移
在 C++11 添加了右值引用,并且不能使用左值初始化右值引用,如果想要使用左值初始化一个右值引用需要借助 std::move () 函数,使用std::move方法可以将左值转换为右值。使用这个函数并不能移动任何东西,而是和移动构造函数一样都具有移动语义,将对象的状态或者所有权从一个对象转移到另一个对象,只是转移,没有内存拷贝。
从实现上讲,std::move 基本等同于一个类型转换:static\_cast<T&&>(lvalue);,函数原型如下:
template<class _Ty> _NODISCARD constexpr remove_reference_t<_Ty>&& move(_Ty&& _Arg) _NOEXCEPT { // forward _Arg as movable return (static_cast<remove_reference_t<_Ty>&&>(_Arg)); }
**使用方法如下:**
class Test { public: Test(){} ...... } int main() { Test t; Test && v1 = t; // error Test && v2 = move(t); // ok return 0; }
在第 4 行中,使用左值初始化右值引用,因此语法是错误的。
在第 5 行中,使用 move() 函数将左值转换为了右值,这样就可以初始化右值引用了。
list ls; ls.push_back("hello"); ls.push_back("world"); ...... list ls1 = ls; // 需要拷贝, 效率低 list ls2 = move(ls);
如果不使用 std::move,拷贝的代价很大,性能较低。使用 move 几乎没有任何代价,只是转换了资源的所有权。如果一个对象内部有较大的堆内存或者动态数组时,使用 move () 就可以非常方便的进行数据所有权的转移。另外,我们也可以给类编写相应的移动构造函数(T::T(T&& another))和和具有移动语义的赋值函数(T&& T::operator=(T&& rhs)),在构造对象和赋值的时候尽可能的进行资源的重复利用,因为它们都是接收一个右值引用参数。
### forward 转发
右值引用类型是独立于值的,一个右值引用作为函数参数的形参时,在函数内部转发该参数给内部其他函数时,它就变成一个左值,并不是原来的类型了。如果需要按照参数原来的类型转发到另一个函数,可以使用 C++11 提供的 std::forward () 函数,该函数实现的功能称之为完美转发。
// 函数原型 template T&& forward (typename remove_reference::type& t) noexcept; template T&& forward (typename remove_reference::type&& t) noexcept;
// 精简之后的样子 std::forward(t);
下面通过一个例子演示一下关于 forward 的使用:
#include using namespace std;
template void printValue(T& t) { cout << "l-value: " << t << endl; }
template void printValue(T&& t) { cout << "r-value: " << t << endl; }
template void testForward(T && v) { printValue(v); printValue(move(v)); printValue(forward(v)); cout << endl; }
int main() { testForward(520); int num = 1314; testForward(num); testForward(forward(num)); testForward(forward<int&>(num)); testForward(forward<int&&>(num));
return 0;
}
l-value: 520 r-value: 520 r-value: 520
l-value: 1314 r-value: 1314 l-value: 1314
l-value: 1314 r-value: 1314 r-value: 1314
l-value: 1314 r-value: 1314 l-value: 1314
l-value: 1314 r-value: 1314 r-value: 1314
### 列表初始化
关于 C++ 中的变量,数组,对象等都有不同的初始化方法,在这些繁琐的初始化方法中没有任何一种方式适用于所有的情况。为了统一初始化方式,并且让初始化行为具有确定的效果,在 C++11 中提出了列表初始化的概念。
**统一的初始化:**
在 C++11 中,列表初始化变得更加灵活了,来看一下下面这段初始化类对象的代码
#include using namespace std;
class Test { public: Test(int) {} private: Test(const Test &); };
int main(void) { Test t1(520); Test t2 = 520; Test t3 = { 520 }; Test t4{ 520 }; int a1 = { 1314 }; int a2{ 1314 }; int arr1[] = { 1, 2, 3 }; int arr2[]{ 1, 2, 3 }; return 0; }
**聚合体**
在 C++11 中,列表初始化的使用范围被大大增强了,但是一些模糊的概念也随之而来,在前面的例子可以得知,列表初始化可以用于自定义类型的初始化,但是对于一个自定义类型,列表初始化可能有两种执行结果:
#include #include using namespace std;
struct T1 { int x; int y; }a = { 123, 321 };
struct T2 { int x; int y; T2(int, int) : x(10), y(20) {} }b = { 123, 321 };
int main(void) { cout << "a.x: " << a.x << ", a.y: " << a.y << endl; cout << "b.x: " << b.x << ", b.y: " << b.y << endl; return 0; }
a.x: 123, a.y: 321 b.x: 10, b.y: 20
**非聚合体**
对于聚合类型的类可以直接使用列表初始化进行对象的初始化,如果不满足聚合条件还想使用列表初始化其实也是可以的,需要在类的内部自定义一个构造函数, 在构造函数中使用初始化列表对类成员变量进行初始化:
#include #include using namespace std;
struct T1 { int x; double y; // 在构造函数中使用初始化列表初始化类成员 T1(int a, double b, int c) : x(a), y(b), z(c){} virtual void print() { cout << "x: " << x << ", y: " << y << ", z: " << z << endl; } private: int z; };
int main(void) { T1 t{ 520, 13.14, 1314 }; // ok, 基于构造函数使用初始化列表初始化类成员 t.print(); return 0; }
另外,需要额外注意的是聚合类型的定义并非递归的,也就是说当一个类的非静态成员是非聚合类型时,这个类也可能是聚合类型,比如下面的这个例子:
#include #include using namespace std;
struct T1 { int x; double y; private: int z; };
struct T2 { T1 t1; long x1; double y1; };
int main(void) { T2 t2{ {}, 520, 13.14 }; return 0; }
可以看到,T1 并非一个聚合类型,因为它有一个 Private 的非静态成员。但是尽管 T2 有一个非聚合类型的非静态成员 t1,T2 依然是一个聚合类型,可以直接使用列表初始化的方式进行初始化。
最后强调一下 t2 对象的初始化过程,对于非聚合类型的成员 t1 做初始化的时候,可以直接写一对空的大括号 {},这相当于调用是 T1 的无参构造函数。
**std::initializer\_list**
在 C++ 的 STL 容器中,可以进行任意长度的数据的初始化,使用初始化列表也只能进行固定参数的初始化,如果想要做到和 STL 一样有任意长度初始化的能力,可以使用 std::initializer\_list 这个轻量级的类模板来实现。
先来介绍一下这个类模板的一些特点:
* 它是一个轻量级的容器类型,内部定义了迭代器 iterator 等容器必须的概念,遍历时得到的迭代器是只读的。
* 对于 std::initializer\_list 而言,它可以接收任意长度的初始化列表,但是要求元素必须是同种类型 T
* 在 std::initializer\_list 内部有三个成员接口:size(), begin(), end()。
* std::initializer\_list 对象只能被整体初始化或者赋值。
std::initializer\_list,使用初始化列表 { } 作为实参进行数据传递即可。
#include #include using namespace std;
void traversal(std::initializer_list a) { for (auto it = a.begin(); it != a.end(); ++it) { cout << *it << " "; } cout << endl; }
int main(void) { initializer_list list; cout << "current list size: " << list.size() << endl; traversal(list);
list = { 1,2,3,4,5,6,7,8,9,0 };
cout << "current list size: " << list.size() << endl;
traversal(list);
cout << endl;
list = { 1,3,5,7,9 };
cout << "current list size: " << list.size() << endl;
traversal(list);
cout << endl;
// 直接通过初始化列表传递数据 //
traversal({ 2, 4, 6, 8, 0 });
cout << endl;
traversal({ 11,12,13,14,15,16 });
cout << endl;
return 0;
}
current list size: 0
current list size: 10 1 2 3 4 5 6 7 8 9 0
current list size: 5 1 3 5 7 9
2 4 6 8 0
11 12 13 14 15 16
* std::initializer\_list拥有一个无参构造函数,因此,它可以直接定义实例,此时将得到一个空的
* std::initializer\_list,因为在遍历这种类型的容器的时候得到的是一个只读的迭代器,因此我们不能修改里边的数据,只能通过值覆盖的方式进行容器内部数据的修改。虽然如此,在效率方面也无需担心,
* std::initializer\_list的效率是非常高的,它的内部并不负责保存初始化列表中元素的拷贝,仅仅存储了初始化列表中元素的引用。
**作为构造函数参数 std::initializer\_list**
自定义的类如果在构造对象的时候想要接收任意个数的实参,可以给构造函数指定为 std::initializer\_list 类型,在自定义类的内部还是使用容器来存储接收的多个实参。
#include #include #include using namespace std;
class Test { public: Test(std::initializer_list list) { for (auto it = list.begin(); it != list.end(); ++it) { cout << *it << " "; m_names.push_back(*it); } cout << endl; } private: vector m_names; };
int main(void) { Test t({ "jack", "lucy", "tom" }); Test t1({ "hello", "world", "nihao", "shijie" }); return 0; } jack lucy tom hello world nihao shijie
### using关键字
一般的using关键子我们都是用来声明当前文件的命名空间,比如标准库的命名空间std-> using namespace std;
但在c++11中,它的用处还有几个
1:取代typedef 。
//---------------------------------------test2 可以取代typedef了,而且更加灵活
using myIntVec = std::vector;
void testUsing2()
{
myIntVec mvec = { 1, 2, 3, 4, 5 };
mvec.push_back(123);
for (int num : mvec)
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上物联网嵌入式知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、电子书籍、讲解视频,并且后续会持续更新