使用 C++11 让程序更简洁、更现代

使用 C++11 让程序更简洁、更现代 

阅读 《深入应用C++11优化代码与工程应用》 学习笔记

初始化

C++ 11 对象初始化
// C++ 11 初始化
	int  i {1};
	int  i_arr[]{ 1, 2, 3, 4, 5 };
	int	 * pint   = new int{ 1 };
	int  * parr = new int[2]{ 1,2 };
	
	struct A {
		int x;
		int y;
	} A_a = { 1, 2 };

	struct B {
		int x;
		int y;
		B(int _x, int _y) :x(_x), y(_y) {}
	} B_b = { 1, 2 };
	/*
	 A_a 的初始化过程是C++98/03 中就有的聚合类型(Aggregates)的初始化。 它将以拷贝的形式,用初始化列表中的值来初始化Struct A 成员。
	 B_b 的初始化时自定义了一个构造函数, 因此, 实际上初始化时以构造函数进行的。
	 C++ 聚合类型的定义:
		1、类型是个普通数组 (如 int[10]、 char[2][3])
		2、类型是一个类(class、struct、union)
			* 无自定义的构造函数。
			* 无私有(private)或保护(protected)的非静态数据成员。
			* 无基类。
			* 无虚函数。
			* 不能有{} 和 = 直接初始化的非静态数据成员。
	*/

	// 实际上 stl 中的 容器是通过 std::initializer_list 这个轻量级的类模板来完成上述功能支持的
	vector <int> varr{ 1, 2, 3, 4, 5 };
	// initializer_list 是非常高效的, 它内部并不负责保持初始化列表中元素的拷贝,仅仅存储了列表中元素的引用而已。

std::vector 模板 构造函数定义:
	vector(_XSTD initializer_list<value_type> _Ilist,
		const _Alloc& _Al = allocator_type())
		: _Mybase(_Al)
		{	// construct from initializer_list
		_Construct(_Ilist.begin(), _Ilist.end());
		}

std::initializer_list模板定义:
template<class _Elem>
	class initializer_list
	{	// list of pointers to elements
public:
	typedef _Elem value_type;
	typedef const _Elem& reference;
	typedef const _Elem& const_reference;
	typedef size_t size_type;

	typedef const _Elem* iterator;
	typedef const _Elem* const_iterator;

	_CONST_FUN initializer_list() _NOEXCEPT
		: _First(0), _Last(0)
		{	// empty list
		}

	_CONST_FUN initializer_list(const _Elem *_First_arg,
		const _Elem *_Last_arg) _NOEXCEPT
		: _First(_First_arg), _Last(_Last_arg)
		{	// construct with pointers
		}

	_CONST_FUN const _Elem *begin() const _NOEXCEPT
		{	// get beginning of list
		return (_First);
		}

	_CONST_FUN const _Elem *end() const _NOEXCEPT
		{	// get end of list
		return (_Last);
		}

	_CONST_FUN size_t size() const _NOEXCEPT
		{	// get length of list
		return ((size_t)(_Last - _First));
		}

private:
	const _Elem *_First;
	const _Elem *_Last;
	};


类型推导 

auto 用于通过一个表达式在编译时正确待定义的变量类型, auto所修饰的变量必须被初始化, 编译器需要通过初始化来确定auto所代表的类型。
        // auto 类型推导
	auto x	= 1;				//OK: x 是 int 类型
	auto pi = new auto(1);		//OK: pi是 int * 类型
	const auto u = 1;			//OK: u 是 const int  类型
	const auto *pv = &x;		//OK: pv是 const int * 类型
	
	// auto 使用
	map<int, int> resultMap { { 1, 10 }, { 2, 20 }, };
	auto miter = resultMap.begin();	//map<int, int>::iterator  miter = resultMap.begin();
	for (; miter != resultMap.end(); ++ miter)
	{
		cout << miter->second << endl;
	}
       
decltype:
<span style="white-space:pre">	</span>// C++ 11 decltype
	int  y = 1;
	decltype(y) dv = 2;			// dv -> int

模板改进

// C++ 11 auto 和 decltype 的结合使用
// 返回类型后置语法
// C++ 11 中添加了返回类型后置(trailing-return-type, 又称跟踪返回类型)语法,将decltype 和 auto 结合起来完成返回值类型推导。
template <typename T, typename U>
auto test_add(T t, U u) -> decltype(t + u)
{
	return t + u;
}
// 模板使用
// eg:	auto res = test_add<int, double>(1, 1.2);


// C++ 11 模板的改进 (using 别名语法)
// 通过using 定义模板别名的语法, 只是在普通类型别名语法的基础上添加template的参数列表, 
// 使用using 可以轻松的创建一个新的模板别名,而不需要像C++98/03那样使用繁琐的外敷模板。
// C++ 98/03
template <typename T>
struct map_t1
{
	typedef std::map<int, T> mapT;
};
// eg: map_t1<int>::mapT map_1;

// C++ 11
template <typename T>
using map_t2 = std::map<int, T>;
// eg: map_t2<int>  map_2;

// 函数模板的默认模板参数
// 当模板有默认参数时,函数模板的调用如同一个普通函数
template <typename T = int>
T func(T value)
{
	return value;
}

基于范围的 for 循环 

<span style="white-space:pre">	</span>map<int, int> resultMap { { 1, 10 }, { 2, 20 }, };
	// C++ 11 for 循环
	for(const auto &v : resultMap)
	{
		cout << v.second << endl;
	}


std::function 和 bind 绑定器

添加头文件 #include <functional>

在 C++ 98/03中 , 存在“可调用对象” 这个概念; 调用对象有如下几种定义:
  •  函数指针
  •  具有 operator()成员函数的类对象(仿函数)
  •  可被转换为函数指针的类对象
  •  类成员(函数)指针
void func_t(void)
{
	//......
}

struct Foo1 {
	void operator()(int x) {
		cout << x<< endl;
	}
};

struct Foo2 {
	using foo_t = void(*) (void);
	static void func(void) {
		//......
	}

	operator foo_t(void) {
		return func;
	}
};

struct Foo3 {
	int m_a;
	void func(void) {
		//......
	}
};


C++ 98/03 使用:
// 函数指针
void (* p_func)(void) = &func_t;

// 仿函数
vector<int> arr{ 1,2,3 };
std::for_each(arr.begin(), arr.end(), Foo1());

// 可被转换为函数指针的类对象
Foo2 foo_2;
foo_2()

// 成员函数指针
void (Foo3::*p_func)(void) = &Foo3::func;
Foo3 foo3;
(foo3.*p_func)();

C++ 11 可调用对象包装器 std::function
std::function<void(void)> fr1 = func_t; // 可以取代函数指针的作用
Foo2 foo2;
fr1 = foo2;

C++ 11 绑定器 std:bind
std:bind 返回类型是stl 内部定义的仿函数类型
std::placeholders::_1 是一个占位符, 代表这个位置将在函数调用时,被传入的第一个参数所替代
void func_bind(int x, int y) {
	cout << "x = " << x << endl;
	cout << "y = " << y << endl;
}

int main(){
	auto fb1 = std::bind(func_bind, std::placeholders::_1, 100);
	fb1(101, 200); // out:  x= 101 y =100
	auto fb2 = std::bind(func_bind, std::placeholders::_2, 200);
	fb2(101, 301); // out:  x= 301 y =200
	auto fb3 = std::bind(func_bind, std::placeholders::_1, std::placeholders::_2);
	fb3(100, 200); // out:  x= 100 y =200
	auto fb4 = std::bind(func_bind, std::placeholders::_2, std::placeholders::_1);
	fb4(100, 200); // out:  x= 200 y =100
<span style="white-space:pre">	</span>return 0
}


lambda 表达式 

lambda 表达式有如下优点:
  • 声明式编程风格:就地匿名定义目标函数或函数对象,不需要额外写一个命名函数或者函数对象。以更直接的方式去写程序,好的可读性和可维护性。
  • 简洁:不需要额外再写一个函数或者函数对象,避免了代码膨胀和功能分散,让开发者更加集中精力在手边的问题,同时也获取了更高的生产率。
  • 在需要的时间和地点实现功能闭包,使程序更灵活。


表达式
[ capture ] ( params ) opt -> ret { body; };
capture 是捕获列表;params 是参数表;opt 是函数选项;ret 是返回值类型;body 是函数体。
<span style="white-space:pre">	</span>auto lambad_fun1 = [](int i) { return i; };
	//lambda 表达式在没有参数列表时,参数列表是可以省略的。
	auto lambad_fun2 = [] { return 1; };

lambda 表达式可以通过捕获列表捕获一定范围内的变量:
  • [] 不捕获任何变量。
  • [&] 捕获外部作用域中所有变量,并作为引用在函数体中使用(按引用捕获)。
  • [=] 捕获外部作用域中所有变量,并作为副本在函数体中使用(按值捕获)。
  • [=, &foo] 按值捕获外部作用域中所有变量,并按引用捕获 foo 变量。
  • [bar] 按值捕获 bar 变量,同时不捕获其他变量。
  • [this] 捕获当前类中的 this 指针,让 lambda 表达式拥有和当前类成员函数同样的访问权限。如果已经使用了 & 或者 = ,就默认添加此选项。捕获 this 的目的是可以在lamda 中使用当前类的成员函数和成员变量。
lambda 表达式的捕获列表精细地控制了 lambda 表达式能够访问的外部变量,以及如何访问这些变量。
class Lambda_Test
{
public:
	int i_ = 0;
	void func(int x, int y)
	{
		//auto x1 = []		{ return i_; };				// error,没有捕获外部变量
		auto x2 = [=]		{ return i_ + x + y; };		// OK,捕获所有外部变量
		auto x3 = [&]		{ return i_ + x + y; };		// OK,捕获所有外部变量
		auto x4 = [this]	{ return i_; };				// OK,捕获 this 指针
		//auto x5 = [this]	{ return i_ + x + y; };		// error,没有捕获 x、y
		auto x6 = [this, x, y] { return i_ + x + y; };	// OK,捕获 this 指针、x、y
		auto x7 = [this]	{ return i_++; };			// OK,捕获 this 指针,并修改成员的值
	}
};

int main(){
	int a = 0, b = 1;
	auto f1 = [] { return a; };			// error,没有捕获外部变量
	auto f2 = [&] { return a++; };			// OK,捕获所有外部变量,并对 a 执行自加运算
	auto f3 = [=] { return a; };			// OK,捕获所有外部变量,并返回 a
	auto f4 = [=] { return a++; };			// error,a 是以复制方式捕获的,无法修改
	auto f5 = [a] { return a + b; };		// error,没有捕获变量 b
	auto f6 = [a, &b] { return a + (b++); };	// OK,捕获 a 和 b 的引用,并对 b 做自加运算
	auto f7 = [=, &b] { return a + (b++); };	// OK,捕获所有外部变量和 b 的引用,并对 b 做自加运算


	auto f = [=] { return a; };		<span style="white-space:pre">	</span>// 按值捕获外部变量
	a += 1;						// a 被修改了
	std::cout << f() << std::endl;	<span style="white-space:pre">		</span>// 输出?
	//lambda 表达式按值捕获了所有外部变量。在捕获的一瞬间,a 的值就已经被复制到 f 中了。之后 a 被修改,但此时 f 中存储的 a 仍然还是捕获时的值,因此,最终输出结果是 0。

	//如果希望去修改按值捕获的外部变量应当怎么办呢?这时,需要显式指明 lambda 表达式为 mutable:
	//需要注意的一点是,被 mutable 修饰的 lambda 表达式就算没有参数也要写明参数列表。
	int a = 0;
	auto f2 = [=]() mutable { return a++; }; // OK, mutable
<span style="white-space:pre">	</span>return 0;
}

示例:
#include <iostream>
#include <algorithm>
#include <functional>
#include <map>
#include <vector>
using namespace std;

int main(){
<span style="white-space:pre">	</span>std::vector<int> v = { 1, 2, 3, 4, 5, 6 };
	int even_count = 0;
	for_each(v.begin(), v.end(), [&even_count](int val)
	{
		if (!(val & 1))  // val % 2 == 0
		{
			++even_count;
		}
	});
	std::cout << "The number of even is " << even_count << std::endl;

	getchar();
<span style="white-space:pre">	</span>return 0;
}




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值