C++ 11 Shepard-Wang

一 初始化列表

C++ 11 支持 内置类型和自定义类型 的列表初始化

	// 两种形式
	vector<int> v1 = { 1, 2, 3 };
	vector<int> v2{ 1, 2, 3 };

	int i1 = { 1 };
	int i2{ 1 };

	pair<int, int> p = { 1, 1 };
	map<int, int> m = { {1, 1}, {2, 2}, };

自定义类型

class A
{
public:
	A(int a, int b)
		:_a(a)
		,_b(b)
	{}

	A(int a)
		:_a(a)
		, _b(0)
	{}
private:
	int _a;
	int _b;
};

template<class T>
class Vector
{
public:
	Vector(size_t n)
		:_array(new T[n])
		,_size(0)
		,_capacity(n)
	{}

	Vector(const initializer_list<T>& ilst)
		:_array(new T[ilst.size()])
		,_size(0)
		,_capacity(ilst.size())
	{
		for (const auto& e : ilst)
		{
			_array[_size++] = e;
		}
	}

private:
	T* _array;
	size_t _size;
	size_t _capacity;
};

	// 下面这两种意思是等价的
	A a1{ 1, 2 };
	A a2(1, 2);

	// 单参构造函数的隐式类型转换
	// 5 先构造成匿名对象,然后 a3 调用拷贝构造函数
	A a3 = 5; 

	// 需要注意的是,自己实现的序列式容器(比如你自己实现一个 vector)是无法天然支持列表初始化的
	// 需要提供参数为 initializer_list 的构造函数
	Vector<int> V{ 1, 2, 3, 4, 5 };

二 类型推导

auto

auto 编译时根据初始化表达式进行类型推导

	map<string, string> m;
	auto it = m.begin();

decltype

decltype 运行时类型推导

	decltype(2 - 1) d1;

	// 根据函数返回值推导类型
	decltype(func()) d2;

	cout << "d2 type: " << typeid(d2).name() << endl; // int

三 显式缺省函数 & 删除默认函数

default:让编译器显示生成一个默认函数

delete:把一个函数声明成删除函数,不能再被使用

class B
{
public:
	B() = default;

	B(const B& b) = delete; //防拷贝
    
    B& operator=(const B& b) = delete;
};

四 右值

1. 左值和右值

左值:可以放在 = 左侧,可以取地址 右值:不能放在 = 左侧,不能取地址

常量,临时变量,匿名变量,将亡值 都是右值

2. 左值引用和右值引用

左值引用:既可以引用左值,也可以引用右值

右值引用:只能引用右值

	/*
	 * 左值引用:既可以引用左值,也可以引用右值
	 */
	
	int a = 3;

	int& ra = a;
	// 左值引用可以引用右值,需要加上 const
	const int& rc = 10;

	/*
	 * 左值引用:只能引用右值
	 */

	int&& r = 10;

	// 匿名对象的引用
	A&& a1 = A();

	const A& a2 = A();

3. 编译器的优化

对于下面这个类:

class A
{
public:
	// A() = default;
	A()
	{
		cout << "A()" << endl;
	}

	// A(const A& a) = default;
	A(const A& a) 
	{
		cout << "A(const A& a)" << endl;
	}
private:
};

如果我们试图用匿名对象来创建一个对象时:

void test4()
{
	A&& a1 = A();

	const A& a2 = A();

	/*
	 * 输出:
	 * A()
	 * A()
	 *
	 * 说明编译器进行了优化,只调用了 a1,a2 的构造函数
	 * 如果没有进行优化,编译器应该调用的是:匿名对象的构造函数 + a1,a2 的拷贝构造
	 */
}

4. 移动构造 & 移动赋值

class String
{
public:
	String(const char* str = "")
		:_str(new char[strlen(str) + 1])
		,_size(strlen(str))
	{
		strcpy(_str, str);
		cout << "String(const char* str = "")" << endl;
	}

	String(const String& str)
		: _str(new char[strlen(str._str) + 1])
		, _size(str._size)
	{
		strcpy(_str, str._str);
		cout << "String(const String& str)" << endl;
	}
	
	~String()
	{
		if (_str)
		{
			delete[] _str;
		}
	}
private:
	char* _str;
	int _size;
};

String getStr(const char* s)
{
	String str(s);
	return str;
}

void test5()
{
	String str = getStr("Shepard");

	/*
	 * 输出: 
	 *  String(const char* str = )
	 *	String(const String& str)
	 * 
	 * 说明:
	 * 走了 getStr() 中 str 的构造函数 和 test5() 中 str 的拷贝构造
	 * getStr() 中返回的 str 是一个将亡值,是一个右值。
	 * 编译器进行了优化,没有构造返回的临时变量
	 * 
	 * 问题:
	 * 尽管编译器进行了优化,这仍然存在一个问题:
	 * getStr 中返回的 str 并没有真正使用,它的存在只是为了构造 test5 中的 str 对象
	 * 那么先为其开资源,然后再删除似乎是没有什么意义的
	 */
}

移动构造

class String2
{
public:
	String2(const char* str = "")
		:_str(new char[strlen(str) + 1])
		, _size(strlen(str))
	{
		strcpy(_str, str);
		cout << "String2(const char* str = "")" << endl;
	}

	String2(const String2& str)
		: _str(new char[strlen(str._str) + 1])
		, _size(str._size)
	{
		strcpy(_str, str._str);
		cout << "String2(const String& str)" << endl;
	}

    // 移动构造
	String2(String2&& str)
		: _str(str._str) // 将 str 的资源给 _str
		, _size(str._size)
	{
		str._str = nullptr; // str 的资源将由 _str 清理
		cout << "String2(String2&& str)" << endl;
	}

    ~String2()
	{
		if (_str)
		{
			delete[] _str;
		}
	}

private:
	char* _str;
	int _size;
};

String2 getStr2(const char* s)
{
	String2 str(s);
	return str;
}

void test6()
{
	String2 str = getStr2("Shepard");

	/*
	 * 输出:
	 * String2(const char* str = )
	 * String2(String2&& str)
	 * 
	 * 说明:
	 * 在构造 test6 中的 str 时,调用的是 String2 的参数为右值引用拷贝构造函数
	 * 
	 * 这个拷贝构造函数的功能:
	 * 它直接将右值(将亡值)的资源给了 test6 中的 str,从而避免了test6 中的 str 再次
	 * 申请资源,进而提高了效率
	 */
    
    // 调用深拷贝构造,因为 str 并不是“没有用”的值
	String2 str2(str);
    
    /*
	 * String2("Wang") 是一个临时的匿名对象,它的生命周期只有 1 行
	 * 所以如果用其拷贝构造 str3 ,将调用 移动构造
	 * 但是这里编译器会做优化,所以调用的是 str3 的拷贝构造
	 */
	String2 str3 = String2("Wang");
}

移动赋值

	String2& operator=(const String2& str)
	{
		if (&str != this)
		{
			delete[] _str;
			_str = new char[strlen(str._str) + 1];
			_size = str._size;
			strcpy(_str, str._str);
		}

		cout << "String2& operator=(const String2& str)" << endl;
		return *this;
	}

	// 移动赋值
	String2& operator=(String2&& str)
	{
		if (&str != this)
		{
			swap(str._str, _str);
			_size = str._size;
		}
		cout << "String2& operator=(String2&& str)" << endl;
		return *this;
	}

	cout << "============ 赋值运算符重载函数1 ============" << endl;

	// 创建了一个匿名对象来给 str3 赋值。一共创建 3 个对象
	str3 = getStr2("CET-6");

	cout << "============ 赋值运算符重载函数2 ============" << endl;
	
	// 直接用将亡对象给 str3 赋值
	str3 = String2("CET-4");

	/*
	 * 输出:
	 *
	 * ============ 赋值运算符重载函数1 ============
	 *	String2(const char* str = )
	 * 	String2(String2&& str)
	 *	String2& operator=(String2&& str)
	 *	============ 赋值运算符重载函数2 ============
	 *	String2(const char* str = )
	 *	String2& operator=(String2&& str) 
	 */

5. 右值引用引用左值

函数:std::move

void test1()
{
	int a = 10;

	// std::move 把变量的属性变为右值
	int&& ra = std::move(a);
}

.

class String2
{
public:
	String2(const char* str = "")
		:_str(new char[strlen(str) + 1])
		, _size(strlen(str))
	{
		strcpy(_str, str);
		cout << "String2(const char* str = "")" << endl;
	}

	String2(const String2& str)
		: _str(new char[strlen(str._str) + 1])
		, _size(str._size)
	{
		strcpy(_str, str._str);
		cout << "String2(const String& str)" << endl;
	}

	// 移动构造
	// 右值引用只能匹配右值
	String2(String2&& str)
		: _str(str._str) // 将 str 的资源给 _str
		, _size(str._size)
	{
		str._str = nullptr; // str 的资源将由 _str 清理
		cout << "String2(String2&& str)" << endl;
	}

	~String2()
	{
		if (_str)
		{
			delete[] _str;
		}
	}

private:
	char* _str;
	int _size;
};

class Person
{
public:
	Person(const char* s)
		:_str(s)
	{
		cout << "Person(const char* s)" << endl;
	}

	Person(const Person& p)
		:_str(p._str)
	{
		cout << "Person(const Person& p)" << endl;
	}

	Person(Person&& p)
        // 将 p._str 变成右值,然后进行 _str 的移动构造
		:_str(move(p._str))
	{
		cout << "Person(Person&& p)" << endl;
	}

private:
	String2 _str;
};

Person getPerson()
{
	Person p("Shepard");

	return p;
}

void test1()
{
	int a = 10;

	// std::move 把变量的属性变为右值
	int&& ra = std::move(a);

	//cout << "============== 使用 move 前 ==============" << endl;

	// Person p(getPerson());

	/* 
	 * 输出:
	 *	String2(const char* str = )
	 *	Person(const char* s)
	 *	String2(const String& str)
	 *	Person(Person&& p)
	 * 
	 * 注意到:
	 * Person 调用的是移动拷贝构造但是 String2 调用的却不是移动拷贝构造
	 * 对于 String2 来说,并不能识别 Person._str 为将亡值
	 * 这时候就需要使用使用 move 函数将其转为右值
	 */

	cout << "============== 使用 move 后 ==============" << endl;
	
	Person p(getPerson());
	
	/*
	* ============== 使用 move 后 ==============
	*	String2(const char* str = )
	*	Person(const char* s)
	*	String2(String2&& str)
	*	Person(Person&& p)
	**/
}

移动语义不能随意使用 需要保证属性被修改的左值后面不会再被使用。

6. 完美转发

完美转发是指在函数模板中,完全依照模板的参数的类型,将参数传递给函数模板中调用的另外一个函数

void Fun(int& x) { cout << "lvalue ref" << endl; }
void Fun(int&& x) { cout << "rvalue ref" << endl; } 
void Fun(const int& x) { cout << "const lvalue ref" << endl; } 
void Fun(const int&& x) { cout << "const rvalue ref" << endl; }

// PerfectForward 函数模板

void testPerfectForward() {
	PerfectForward(10); // rvalue ref 
	int a = 1;
	PerfectForward(a); // lvalue ref 
	PerfectForward(std::move(a)); // rvalue ref 
	const int b = 8; 
	PerfectForward(b); // const lvalue ref 
	PerfectForward(std::move(b)); // const rvalue ref return 0; 
}

情况1

如果,函数模板的形式如下,直接将值作为参数:

template<typename T>
void PerfectForward(T t) { Fun(t); }

输出为:

lvalue ref
lvalue ref
lvalue ref
lvalue ref
lvalue ref

PerfectForward() 函数按值传递,t 为左值

情况2

template<typename T>
void PerfectForward(const T& t) { Fun(t); }

函数参数按照 const 引用传递,输出:

const lvalue ref
const lvalue ref
const lvalue ref
const lvalue ref
const lvalue ref

完美转发

// 模板参数:T&& -> 表示未定类型
template<typename T> 
void PerfectForward(T&& t) { Fun(std::forward<T>(t)); } 

.

rvalue ref
lvalue ref
rvalue ref
const lvalue ref
const rvalue ref

7. 右值引用的作用

  1. 最主要的作用: 实现移动语义(移动构造和移动赋值)

  2. 可以给中间变量起别名

    int main() {
    	string s1("hello"); 
    	string s2(" world"); 
    	string s3 = s1 + s2; // s3是用s1和s2拼接完成之后的结果拷贝构造的新对象
    	string&& s4 = s1 + s2; // s4就是s1和s2拼接完成之后结果的别名
    	return 0; 
    }
    

完美转发应用的并不是右值引用,而是模板中的 T&& 表示未定义类型

五 lambda 表达式

1. 引入

内置类型的比较:

#include<algorithm>

void testSort()
{
	int array[] = { 9, 5, 2, 7 };

	// 默认从小到大排序
	sort(array, array + sizeof(array) / sizeof(array[0]));
	for (auto& e : array)
	{
		cout << e << " ";
	}
	cout << endl;

	// 从大到小排序需要提供一个仿函数对象
	sort(array, array + sizeof(array) / sizeof(array[0]), greater<int>());
	for (auto& e : array)
	{
		cout << e << " ";
	}
	cout << endl;
}

自定义类型的比较

方法 1:重载 >, < 运算符:

class A
{
private:
	int _a;
public:
	A(int a)
		:_a(a)
	{}

	// 这里需要提供 const 成员函数,因为是 const 对象调用此函数
	bool operator<(const A& ref) const 
	{
		return _a < ref._a;
	}

	bool operator>(const A& ref) const 
	{
		return _a > ref._a;
	}
};

void testSort2()
{
	A array[] = { {9}, {5}, {2}, {7} };

	// 自定义类型的比较需要提供仿函数对象
	// 方法 1:重载 > < 运算符
	sort(array, array + sizeof(array) / sizeof(array[0]));
	
	// 从大到小排序
	sort(array, array + sizeof(array) / sizeof(array[0]), greater<A>());
}

方法 2:提供仿函数类(也需要重载 >, < 运算符)

class A
{
private:
	int _a;
public:
	A(int a)
		:_a(a)
	{}

	// 这里需要提供 const 成员函数,因为是 const 对象调用此函数
	bool operator<(const A& ref) const 
	{
		return _a < ref._a;
	}

	bool operator>(const A& ref) const 
	{
		return _a > ref._a;
	}
};

struct LessA
{
	bool operator()(const A& a1, const A& a2)
	{
		return a1 < a2;
	}
};

struct GreaterA
{
	bool operator()(const A& a1, const A& a2)
	{
		return a1 > a2;
	}
};

void testSort2()
{
	A array[] = { {9}, {5}, {2}, {7} };

	// 方法 2:提供一个仿函数类(同样也需要重载 > < 运算符)
	sort(array, array + sizeof(array) / sizeof(array[0]), LessA());

	sort(array, array + sizeof(array) / sizeof(array[0]), GreaterA());
}

上面的几种写法都过于麻烦,我们需要使用 lambda 表达式来简化。

2. lambda 表达式语法

[capture-list] (parameters) mutable -> return-type { statement }

[] {} // 最简单的 lambda 表达式
// mutable 将捕捉列表中的变量属性改为非 const ,默认情况下是 const
	
	// 使用 mutable 将参数列表给出
	// 返回值可以省略,由编译器自动推导
	[a, b] () mutable
	{
        a = 100; // 可修改
		return a + b;
	};

	// func 类型为函数指针
	auto func = [](int a, int b) ->int {a = 1; b = 2; return a + b; };
	// 可以直接使用
	func(a, b);

捕捉列表说明

  • [var]:表示值传递方式捕捉变量var

  • [=]:表示值传递方式捕获所有父作用域中的变量(包括this)

  • [&var]:表示引用传递捕捉变量var

  • [&]:表示引用传递捕捉所有父作用域中的变量(包括this)

  • [this]:表示值传递方式捕捉当前的this指针

例 1:

void testLambda1()
{
	int a = 1, b = 2;
	
	auto func = [=](int num)mutable->int {
		a = 10;
		b = 20;
		// c = 30; // 不能捕捉到 c
		return a + b + num;
	};
	int c = 3;
	
	func(10);
	// a, b 的值没有发生变化
}

例 2

void testLambda2()
{
	int a = 1, b = 2;

	// 不需要加 mutable 也可以修改捕捉列表中的变量
	auto func = [&](int num)->int {
		a = 10;
		b = 20;	
		return a + b + num;
	};
	int c = 3;

	func(50);
	cout << a << b << endl;
	// a, b 的值被改变
}

例 3

void testLambda3()
{
	int a = 1, b = 2;

	// [=, &a] 其他变量以值的形式捕捉,a 以引用的形式捕捉
	// [=, a] 这种写法是错误的,会造成重复
	auto func = [=, &a](int num)mutable->int {
		a = 10;
		// b = 20; // b 不能被改变
		return a + b + num;
	};

	int c = 3;
	
	func(30);
	cout << a << b << endl;
}

例 4

int global = 10;

auto _func = [=] {cout << global << endl; };

void testLambda4()
{
	int a = 1, b = 2;

	auto func = [=, &a](int num)mutable->int {
		a = 10;
		return a + b + global +  num;
	};

	int c = 3;

	cout << func(30) << a << b << endl;
	_func();
}

但是,如果我们显式的在捕捉列表中指明 global 全局变量,则会报错:

int global = 10;

// Error 不能显示的指明带有静态存储期限的变量
auto _func = [global] {cout << global << endl; };

void testLambda4()
{
	int a = 1, b = 2;

	// Error 不能显示的指明带有静态存储期限的变量
	auto func = [b, &a, global](int num)mutable->int {
		a = 10;
		return a + b + global +  num;
	};

	int c = 3;

	cout << func(30) << a << b << endl;
	_func();
}

又出现了一个问题:

	// 即使不显示指明 global ,依然自动捕捉  
	auto func = [b, &a](int num)mutable->int {
		a = 10;
		return a + b + global +  num;
	};

lambda 表达式之间不能相互赋值,但是可以拷贝

	auto func = [b, &a](int num)mutable->int {
		a = 10;
		return a + b  +  num;
	};

	auto func2 = [b, &a](int num)mutable->int {
		a = 10;
		return a + b + num;
	};

	// lambda 表达式之间不能相互赋值
	func = func2;
	
	// 可以拷贝
	auto func3 = func;
	auto func4(func);

3. lambda 表达式实现原理

C++ 实现 lambda 表达式:如果定义了一个lambda表达式,编译器会自动生成一个类,在该类中重载了operator()

4. 简化引入中的例子

现在,我们使用 lambda 表达式完成对引入中代码的简化:

void UseLambda()
{
	A array[] = { {9}, {5}, {2}, {7} };
	
    // 从小到大
	sort(array, array + sizeof(array) / sizeof(array[0]),
		[](const A& a1, const A& a2)->bool 
		{
			return a1 < a2;
		});

    // 从大到小
	sort(array, array + sizeof(array) / sizeof(array[0]),
		[](const A& a1, const A& a2)->bool
		{
			return a1 > a2;
		});
}

六 线程库

RAII

// RAII:
//      在构造函数中初始化
//		在析构函数中释放资源

class ThreadManage
{
private:
	thread& _thread;

public:
	ThreadManage(thread& t)
		:_thread(t)
	{}

	~ThreadManage()
	{
		// 如果线程还存在,则等待其结束
		if (_thread.joinable())
			_thread.join();
	}
};

void func1()
{
	cout << "func1" << endl;
}

void func2(int a)
{
	cout << "func2" << a << endl;
}

void func3()
{
	cout << "func3" << endl;
}

void testThread()
{
	thread t1(func1);
	thread t2(func2, 5);
	thread t3(func3);

	// 三个局部变量,函数结束时,自动调用其析构函数
	// 析构函数中调用 join 函数回收资源
	ThreadManage tm1(t1);
	ThreadManage tm2(t2);
	ThreadManage tm3(t3);
}

当线程函数为类成员函数时

class ThreadClass
{
public:
	void func(int a)
	{
		cout << a << endl;
	}
};

void testThread2()
{
	ThreadClass tc;

	// 如果函数为成员函数,则要写完整的作用域 + this 指向的对象 + 类函数参数 
	thread t1(&ThreadClass::func, &tc, 10);
	
	t1.join();
}

当线程函数的参数为引用类型时

void refadd(int& a)
{
	a += 10;
}

void testThread3()
{
	int a = 0;
	// 引用传参时,因为线程有自己独立的栈,如果直接传入 a ,修改的时线程栈中拷贝的 a 。而不是 testThread3 中的 a
	// 如果要修改, 则应该将参数的格式改为ref(a) 
	thread t(refadd, ref(a));

	t.join();
	cout << a << endl;
}

线程函数的参数为指针类型时

可以直接作为参数

void pointeradd(int* a)
{
	*a += 10;
}

void testThread3()
{
	int a = 0;

	// 指针类型可以直接作为参数
	thread t2(pointeradd, &a);
	t2.join();

	cout << a << endl;
}

线程函数的提供:

  • 函数指针
  • lambda 表达式
  • 仿函数对象

jionable() 函数判断线程是否是有效的,如果是以下任意情况,则线程无效:

  • 采用无参构造函数构造的线程对象

  • 线程对象的状态已经转移给其他线程对象

  • 线程已经调用 jion 或者 detach 结束

练习代码