C++11:让程序更简洁

C++11引入了诸如auto类型推导、decltype表达式、模板别名、函数模板默认参数、列表初始化、基于范围的for循环、std::function和std::bind等功能,提高了代码的简洁性和效率。文章详细介绍了这些特性的用法和示例,展示了它们在简化类型推导、模板使用和函数调用等方面的优势。
摘要由CSDN通过智能技术生成

1 类型推导

1.1 auto类型推导

auto x= 5;					//OK:x是int类型
auto pi = new auto(1);		//OK: pi被推导为int*
const auto *V = &x, u=6;	//OK:v是const int*类型,u是const int类型
static auto y = 0.0;		//OK:y是double类型
auto int r;					//error: auto不再表示存储类型指示符
auto s;						//error: auto无法推导出s 的类型

看一个例子,在一个unordered_multimap中查找一个范围,代码如下:

#include <map>

int main (void)
{
	std: :unordered_multimap<int, int>resultMap;
	//...
	std::pair<std::unordered_multimap<int,int>::iterator, std:: unordered_multimap<int, int>::iterator>
	range = resultMap.equal_range(key);
	return 0;
)

这个equal_range返回的类型声明显得烦琐而冗长,而且实际上并不关心这里的具体类型(大概知道是一个std::pair就够了)。这时,通过auto就能极大的简化书写,省去推导具体类型的过程:

#include <map>

int main (void)
(
	std::unordered_multimap<int, int>map;
	//...
	auto range = map.equal_range(key);
	return 0;
}

1.2 decltype类型推导

int x = 0;
decltype(x) y = 1;			//y -> int
decltype (x + y) z = 0;		//z -> int
const int& i = x ;
decltype(i) j = y;			//j-> const int &
const decltype(z) * p = &z;	//*p -> const int, p -> const int *
decltype (z) * pi = &z;		//*pi -> int, pi -> int *
decltype(pi)* pp = &pi;		//*pp -> int *, pp -> int **

2 模板的改进

2.1 模板的别名

//重定义unsigned int
typedef unsigned int uint_t;
using uint_t = unsigned int;
//重定义std::map
typedef std::map<std::string, int> map_int_t;
using map_int_t = std::map<std::string, int>;

2.2 函数模板的默认参数

在C++98/03中,类模板可以有默认的模板参数,如下:

template <typename T, typename U = int, U N = 0>
struct Foo
{
	//...
};

但是却不支持函数的默认模板参数:

template <typename T = int>	//error in C++98/03: default template arguments
void func (void)
{
	//...
}

现在这一限制在C++11中被解除了,上面的func函数在C++11中可以直接使用.

3 列表初始化

我们知道,在C++98/03中的对象初始化方法有很多种:

int arr[3] = { 1, 2, 3 };  //普通数组

struct A
{
	int x;
	struct B
	{
		int i;
		int j;
	} b;
} a = { 1, {2, 3} };  //POD类型

//拷贝初始化
int i = 0;

class Foo
{
public:
	Foo(int){}
} foo = 123;

//直接初始化
int j(0);

Foo bar(123);

这些不同的初始化方法,都有各自的适用范围和作用。最关键的是,这些种类繁多的初始化方法,没有一种可以通用所有情况。
为了统一初始化方式,并且让初始化行为具有确定的效果,C++11中提出了列表初始化(List-initialization)的概念。
在C++11中,可以直接在变量名后面跟上初始化列表,来进行对象的初始化。
这种变量名后面跟上初始化列表方法同样适用于普通数组和POD类型的初始化:

int arr[3] { 1, 2, 3 } ;	//普通数组

struct A
{
	int x;
	struct B
	{
		int i;
		int j;
	} b;
} a { 1, { 2, 3 } };	//POD类型

在初始化时,{}前面的等于号是否书写对初始化行为没有影响。
另外,如同读者所想的那样,new操作符等可以用圆括号进行初始化的地方,也可以使用初始化列表:

int* a = new int { 123 };
double b = double { 12.12 };
int* arr = new int [3]{ 1, 2, 3 };

指针a指向了一个new操作符返回的内存,通过初始化列表方式在内存初始化时指定了值为123。
b则是对匿名对象使用列表初始化后,再进行拷贝初始化。
这里让人眼前一亮的是arr的初始化方式。堆上动态分配的数组终于也可以使用初始化列表进行初始化了。
除了上面所述的内容之外,列表初始化还可以直接使用在函数的返回值上:

struct Foo
{
	Foo (int,. double){ }
};

Foo func(void)
{
	return { 123, 321.0 };
}

这里的return语句就如同返回了一个Foo(123, 321.0)。
由上面的这些例子可以看到,在C++11中使用初始化列表是非常便利的。它不仅统一了各种对象的初始化方式,而且还使代码的书写更加简单清晰。
下面重点介绍当类型是一个类时的情况。首先是存在用户自定义构造函数时的例子,代码如下:

struct Foo
{
	int x;
	double y;
	int z;
	Foo (int, int) { }
};
Foo foo { 1, 2.5, 1 };	//error

这时无法将Foo看做一个聚合类型,因此,必须以自定义的构造函数来构造对象。私有(Private)或保护(Protected)的非静态数据成员的情况如下:

struct ST
{
	int x;
	double y;
protected:
	int z;
};
ST s { 1, 2.5, 1 };	//error 
struct Foo
{
	int x;
	double y;
protected:
	static int z;
};
Foo foo { 1, 2.5 };	//ok

在上面的示例中,ST的初始化是非法的。因为ST的成员z是一个受保护的非静态数据成员。
而Foo的初始化则是成功的,因为它的受保护成员是一个静态数据成员。
这里需要注意,Foo中的静态成员是不能通过实例foo的初始化列表进行初始化的,它的初始化遵循静态成员的初始化方式。
对于有基类和虚函数的情况:
在上面的示例中,ST的初始化是非法的。因为ST的成员z是一个受保护的非静态数据成员。
而Foo的初始化则是成功的,因为它的受保护成员是一个静态数据成员。
这里需要注意,Foo中的静态成员是不能通过实例foo的初始化列表进行初始化的,它的初始化遵循静态成员的初始化方式。
对于有基类和虚函数的情况:

struct ST
{
	int x;
	double y;
	virtual void F(){}
};
ST s { 1, 2.5 };	//error

struct Base {};
struct Foo : public Base
{
	int x;
	double y;
};
Foo foo { 1, 2.5 };	//error

ST和Foo的初始化都会编译失败。因为ST中存在一个虚函数F,而Foo则有一个基类Base。
最后,介绍“不能有{}和=直接初始化 (brace-or-equal-initializer)的非静态数据成员”这条规则,代码如下:

struct ST
{
	int x;
	double y = 0.0 ;
};
ST s { 12.5 };	//error

在ST中,y在声明时即被=直接初始化为0.0,因此,ST并不是一个聚合类型,不能直接使用初始化列表。
对于上述非聚合类型的情形,想要使用初始化列表的方法就是自定义一个构造函数,比如:

struct ST
{
	int x;
	double y;
	virtual void F() { }
private:
	int z;
public:
	ST(int i, double j, int k) : x(i), y(j), z(k){ }
} ;
ST s { 1, 2.5, 2 };

需要注意的是,聚合类型的定义并非递归的。简单来说,当一个类的非静态成员是非聚合类型时,这个类也有可能是聚合类型。比如下面这个例子:

struct ST
{
	int x;
	double y;
private:
	int z;
};
ST s { 1, 2.5, 1 };	//error

struct Foo
{
	ST st;
	int x;
	double y;
};
Foo foo { {}, 1, 2.5 };	 //oK

可以看到,ST并非一个聚合类型,因为它有一个 Private的非静态成员。
但是尽管Foo含有这个非聚合类型的非静态成员st,它仍然是一个聚合类型,可以直接使用初始化列表。
任意长度的初始化列表:

class Foo
{
public:
	Foo (std::initializer_list<int>) {}
};
Foo foo = {1, 2, 3, 4, 5 }; 	//oK!

std::initializer_list不仅可以用来对自定义类型做初始化,还可以用来传递同类型的数据集合,代码如下:

void func(std::initializer_list<int> l)
{
	for (auto it = l.begin(); it !=l.end (); ++it)
	{
		std::cout << *it << std::endl;
	}
}

int main (void)
{
	func({ });			//一个空集合
	func ({ 1, 2, 3 });	//传递{ 1, 2, 3 }
	return 0;
}

了解了std::initializer_list之后,再来看看它的一些特点,如下:

  1. 它是一个轻量级的容器类型,内部定义了iterator等容器必需的概念。
  2. 对于std:.initializer_list而言,它可以接收任意长度的初始化列表,但要求元素必须是同种类型T(或可转换为T)。
  3. 它有3个成员接口: size()、begin()、end()。它只能被整体初始化或赋值。

通过前面的例子,已经知道了std:;initializer_list的前几个特点。其中没有涉及的接口size()是用来获得std::initializer_list的长度的,比如:

std::initializer_list<int> list = { 1, 2, 3 };
size_t n = list.size() ;	//n = 3

最后,对std::initializer_list的访问只能通过begin()和 end()进行循环遍历,遍历时取得的迭代器是只读的。因此,无法修改std:;initializer_list中某一个元素的值,但是可以通过初始化列表的赋值对std::initializer_list做整体修改,代码如下:

std::initializer_list<int> list;
size_t n = list.size() ; 	//n = 0
list = { 1, 2, 3, 4, 5 };
n = list.size();			//n = 5
list = {3, 1, 2, 4 };
n = list.size() ;			//n = 4

std::initializer_list拥有一个无参数的构造函数,因此,它可以直接定义实例,此时将得到一个空的std::.initializer_list。
之后,我们对std::initializer_list进行赋值操作(注意,它只能通过初始化列表赋值),可以发现std::initializer_list被改写成了{1, 2, 3, 4, 5}。
然后,还可以对它再次赋值,std::initializer_list被修改成了{3, 1, 2, 4}。
std:.initializer_list的内部并不负责保存初始化列表中元素的拷贝,仅仅存储了列表中元素的引用而已。
因此,我们不应该像这样使用:

std::initializer_list<int> func (void)
{
	int a = 1, b = 2 ;
	return { a, b }; 	//a、b在返回时并没有被拷贝
}

虽然这能够正常通过编译,但却无法传递出我们希望的结果(a、b在函数结束时,生存期也结束了,因此,返回的将是不确定的内容)。
这种情况下最好的做法应该是这样:

std::vector<int> func (void)
{
	int a = 1, b = 2;
	return { a, b };
}

使用真正的容器,或具有转移/拷贝语义的物件来替代std::initializer_list返回需要的结果。

列表初始化防止类型收窄:

int a = 1.1;							//OK
int b = { 1.1 };						//error

float fa = 1e40;						//OK
float fb = { 1e40 } ;					//error

float fc = (unsigned long long)-1;		//OK
float fd = { (unsigned long long)-1 };	//error
float fe = (unsigned long long)1;		//OK
float ff = { (unsigned long long)1 };	//OK

const int x = 1024, y = 1;
char c = x ;							//OK
char d = { x }							//error
char e = y;								//OK
char f = { y };							//OK

4 基于范围的for循环

#include <iostream>
#include <vector>

int main (void)
{
	std::vector<int> arr = { 1,23 };
	
    //...
    
	for(auto n : arr)	//使用基于范围的for循环
		std: :cout<<n << std::endl;
		
	return 0;
}

接下来,看看基于范围的 for循环对容器的访问频率。看下面这段代码:

#include <iostream>
#include <vector>

std::vector<int> arr = { 1, 2, 3, 4, 5 };

std::vector<int>& get_range (void)
{
	std: :cout << "get_range ->: "<< std::endl;
	return arr;
}

int main (void)
{
	for (auto val : get_range ())
		std::cout << val << std::endl;
		
	return 0;
}

输出结果:
get_range ->:
1
2
3
4
5
从上面的结果中可以看到,不论基于范围的 for循环迭代了多少次,get_range()只在第一次迭代之前被调用。
因此,对于基于范围的 for循环而言,冒号后面的表达式只会被执行一次。
最后,让我们看看在基于范围的for循环迭代时修改容器会出现什么情况。比如,下面这段代码:

#include <iostream>
#include <vector>

int main (void)
{
	std::vector<int> arr = { 1, 2, 3, 4, 5 };
	for (auto val : arr)
	{
		std::cout << val << std::endl;
		arr.push_back(0);扩大容器
	}
	return 0;
}

执行结果(32位 mingw4.8 ):
1
5189584
-17891602
-17891602
-17891602
若把上面的vector换成list,结果又将发生变化。
这是因为基于范围的for循环其实是普通for循环的语法糖,因此,同普通的for循环-样,在迭代时修改容器很可能会引起迭代器失效,导致一些意料之外的结果。由于在这里我们是看不到迭代器的,因此,直接分析对基于范围的for循环中的容器修改会造成什么样的影响是比较困难的。

5 std::function和 bind绑定器

5.1 std::function可调用对象包装器

std:function是可调用对象的包装器。它是一个类模板,可以容纳除了类成员(函数)指针之外的所有可调用对象。通过指定它的模板参数,它可以用统一的方式处理函数、函数对象、函数指针,并允许保存和延迟执行它们。

#include <iostream>
#include <functional>

void func(void)
{
	std::cout << __FUNCTION__ << std::endl;
}

class Foo
{
public:
	static int foo_func(int a)
	{
		std::cout << __FUNCTION__ << "(" << a << ") ->: ";
		return a;
	}
};

class Bar
{
public:
	int operator()(int a)
	{
		std::cout << __FUNCTION__ << "(" << a << ") ->: ";
		return a;
	}
};

int main(void)
{
	//绑定普通函数
	std::function<void(void)> fr1 = func;
	fr1();

	//绑定一个类的静态成员函数
	std::function<int(int)> fr2 = Foo::foo_func;
	std::cout << fr2(123) << std::endl;

	//绑定一个仿函数
	Bar bar;
	fr2 = bar;
	std::cout << fr2(123) << std::endl;

	system("pause");
	return 0;
}

运行结果如下:
func
foo_func (123)->: 123
operator () (123)->: 123
从上面我们可以看到std::function 的使用方法,当我们给std:function填入合适的函数签名(即一个函数类型,只需要包括返回值和参数表)之后,它就变成了一个可以容纳所有这一类调用方式的“函数包装器”。
std::function还可以作为函数入参:

#include <iostream>
#include <functional>

void call_when_even(int x, const std::function<void(int)>& f)
{
	if (!(x & 1))  //x % 2 == 0
	{
		f(x);
	}
}

void output(int x)
{
	std::cout << x << " ";
}

int main(void)
{
	for (int i = 0; i < 10; ++i)
	{
		call_when_even(i, output);
	}

	std::cout << std::endl;

	system("pause");
	return 0;
}

输出结果如下:
0 2 4 6 8

5.2 std::bind绑定器

std::bind用来将可调用对象与其参数一起进行绑定。绑定后的结果可以使用std::function进行保存,并延迟调用到任何我们需要的时候。
通俗来讲,它主要有两大作用:
1)将可调用对象与其参数一起绑定成一个仿函数。
2)将多元(参数个数为n,n>1)可调用对象转成一元或者(n-1)元可调用对象,即只绑定部分参数。

#include <iostream>
#include <functional>

void call_when_even(int x, const std::function<void(int)>& f)
{
	if (!(x & 1))  //x % 2 == 0
	{
		f(x);
	}
}

void output(int x)
{
	std::cout << x << " ";
}

void output_add_2(int x)
{
	std::cout << x + 2 << " ";
}

int main(void)
{
	{
		auto fr = std::bind(output, std::placeholders::_1);
		for (int i = 0; i < 10; ++i)
		{
			call_when_even(i, fr);
		}

		std::cout << std::endl;
	}
	{
	auto fr = std::bind(output_add_2, std::placeholders::_1);

	for (int i = 0; i < 10; ++i)
	{
		call_when_even(i, fr);
	}

	std::cout << std::endl;
}

	system("pause");
	return 0;
}

输出结果如下:
0 2 4 6 8
2 4 6 8 10
同样还是上面std::function中最后的一个例子,只是在这里,我们使用了std::bind,在函数外部通过绑定不同的函数,控制了最后的执行结果。
我们使用auto fr保存std::bind的返回结果,是因为我们并不关心std::bind真正的返回类型(实际上 std::bind的返回类型是一个stl内部定义的仿函数类型),只需要知道它是一个仿函数,可以直接赋值给一个std::function。当然,这里直接使用std::function类型来保存std::bind的返回值也是可以的。
std::placeholders::_1是一个占位符,代表这个位置将在函数调用时,被传人的第一个参数所替代。
因为有了占位符的概念,std:bind的使用非常灵活:

#include <iostream>
#include <functional>

void output(int x, int y)
{
	std::cout << x << " " << y << std::endl;
}

int main(void)
{
	std::bind(output, 1, 2)();
	std::bind(output, std::placeholders::_1, 2)(1);
	std::bind(output, 2, std::placeholders::_1)(1);
	std::bind(output, 2, std::placeholders::_2)(1);  //error:调用时没有第二个参数
	std::bind(output, 2, std::placeholders::_2)(1, 2);  //输出 2 2   调用时第一个参数被吞掉了
	std::bind(output, std::placeholders::_1, std::placeholders::_2)(1, 2);  //输出 1 2
	std::bind(output, std::placeholders::_2, std::placeholders::_1)(1, 2);  //输出 2 1

	system("pause");
	return 0;
}

上面对std::bind的返回结果直接施以调用。可以看到,std::bind可以直接绑定函数的所有参数,也可以仅绑定部分参数。
在绑定部分参数的时候,通过使用std::placeholders,来决定空位参数将会属于调用发生时的第几个参数。
std::bind和std::function配合使用

#include <iostream>
#include <functional>

class A
{
public:
	int i_ = 0;
	void output(int x, int y)
	{
		std::cout << x << " " << y << std::endl;
	}
};

int main(void)
{
	A a;
	std::function<void(int, int)> fr = std::bind(&A::output, &a, std::placeholders::_1, std::placeholders::_2);
	fr(1, 2);  //输出 1 2

	std::function<int&(void)> fr_i = std::bind(&A::i_, &a);  //vs13的bug,绑定成员变量要报错
	fr_i() = 123;
	std::cout << a.i_ << std::endl;  //输出 123

	system("pause");
	return 0;
}

fr的类型是std::function<void(int,int)>。我们通过使用std::bind,将A的成员函数output的指针和a绑定,并转换为一个仿函数放入fr中存储。之后, std::bind将A的成员i_的指针和a绑定,返回的结果被放入 std::function<int&(void)>中存储,并可以在需要时修改访问这个成员。
其实bind简化和增强了之前标准库中bind1st和 bind2nd,它完全可以替代bind1s和bind2st,并且能组合函数。我们知道,bind1st和 bind2nd 的作用是将一个二元算子转换成一个一元算子,代码如下:

//查找元素值大于10的元素个数
int count = std::count_if(coll.begin(), coll.end(), std::bindlst(less<int>(), 10));
//查找元素之小于10的元素个数
int count = std::count_if(coll.begin(), coll.end(), std::bind2nd(less<int>(), 10)) ;

本质上是对一个二元函数less的调用,但是它却要分别用bind1st和bind2nd,并且还要想想到底是用bind1st 还是bind2nd,用起来十分不便。
现在我们有了bind,就可以以统一的方式去实现了,代码如下:

using std::placeholders::_1;
//查找元素值大于10的元素个数
int count = std::count_if(col1.begin(), coll.end() ,std::bind(less<int>(), 10, _1));
//查找元素之小于10的元素个数
int count = std::count_if(coll.begin(), coll.end(), std::bind(less<int>(), _1, 10)) ;

这样就不用关心到底是用bind1st还是bind2nd,只需要使用bind 即可。
bind还有一个强大之处就是可以组合多个函数:

using std::placeholders::_1;
//查找集合中大于5小于10的元素个数
auto f = std::bind(std::logical_and<bool>(), std::bind(std::greater<int>(), _1, 5), std::bind(std::less_equal<int>(), _1, 10));
int count = std::count_if(coll.begin(), coll.end(), f);

6 lambda表达式

lambda表达式定义了一个匿名函数,并且可以捕获一定范围内的变量。lambda表达式的语法形式可简单归纳如下:
[ capture ] ( params ) opt -> ret { body; };
其中:
capture是捕获列表;params是参数表;opt是函数选项;ret是返回值类型;body是函数体。
因此,一个完整的lambda表达式看起来像这样:

auto f = [ ](int a) -> int { return a + l; };
std::cout << f(1) << std::endl;	//输出:2

可以看到,上面通过一行代码定义了一个小小的功能闭包,用来将输入加1并返回。
在C++11中,lambda表达式的返回值是通过前面介绍的返回值后置语法来定义的。其实很多时候,lambda表达式的返回值是非常明显的,比如上例。因此,C++11中允许省略lambda表达式的返回值定义:

auto f = [ ] (int a){ return a + l;};

这样编译器就会根据return语句自动推导出返回值类型。
需要注意的是,初始化列表不能用于返回值的自动推导:

	auto xl = [ ](int i){ return i; };	//OK: return type is int
	auto x2 = [](){ return { 1, 2 }; };	//error:无法推导出返回值类型

这时我们需要显式给出具体的返回值类型。
另外,lambda表达式在没有参数列表时,参数列表是可以省略的。因此像下面的写法都是正确的:

auto f1 = [](){ return 1; };
auto f2 = []{ return 1; };	//省略空参数表

lambda表达式可以通过捕获列表捕获一定范围内的变量:

  1. []不捕获任何变量。
  2. [&]捕获外部作用域中所有变量,并作为引用在函数体中使用(按引用捕获)。
  3. [=]捕获外部作用域中所有变量,并作为副本在函数体中使用(按值捕获)。
  4. [=, &foo]按值捕获外部作用域中所有变量,并按引用捕获 foo变量。
  5. [bar]按值捕获bar变量,同时不捕获其他变量。
  6. [this]捕获当前类中的this指针,让 lambda表达式拥有和当前类成员函数同样的访问权限。如果已经使用了&或者=,就默认添加此选项。捕获this的目的是可以在lamda中使用当前类的成员函数和成员变量。
#include <iostream>

class A
{
	int i_ = 0;
	void func(int x, int y)
	{
		auto x1 = []{return i_; };  //error,没有捕获外部变量
		auto x2 = [=]{return i_ + x + y; };
		auto x3 = [&]{return i_ + x + y; };
		auto x4 = [this]{return i_; };
		auto x5 = [this]{return i_ + x + y; };  //error,没有捕获x和y
		auto x6 = [this, x, y]{return i_ + x + y; };
		auto x7 = [this]{return i_++; };
	}
};

int main(void)
{
	int a = 0;
	int b = 1;
	auto f1 = []{return a; };  //error,没有捕获外部变量 
	auto f2 = [&]{return a++; };
	auto f3 = [=]{return a; };
	auto f4 = [=]{return a++; };  //error,a是以复制方式捕获的,无法修改
	auto f5 = [a]{return a + b; };  //error,没有捕获变量b
	auto f6 = [a, &b]{return a + (b++); };
	auto f7 = [=, &b]{return a + (b++); };

	system("pause");
	return 0;
}

从上例中可以看到,lambda表达式的捕获列表精细地控制了lambda表达式能够访问的外部变量,以及如何访问这些变量。需要注意的是,默认状态下lambda表达式无法修改通过复制方式捕获的外部变量。如果希望修改这些变量的话,我们需要使用引用方式进行捕获。
一个容易出错的细节是关于lambda表达式的延迟调用的:

int a = 0;
auto f = [=]{ return a; };
//按值捕获外部变量
a += 1;
//a被修改了
std: :cout<< f()<< std: :endl;

在这个例子中,lambda表达式按值捕获了所有外部变量。在捕获的一瞬间,a的值就已经被复制到f中了。之后a被修改,但此时f中存储的a仍然还是捕获时的值,因此,最终输出结果是0。如果希望lambda表达式在调用时能够即时访问外部变量,我们应当使用引用方式捕获。从上面的例子中我们知道,按值捕获得到的外部变量值是在 lambda表达式定义时的值。此时所有外部变量均被复制了一份存储在lambda表达式变量中。此时虽然修改lambda表达式中的这些外部变量并不会真正影响到外部,我们却仍然无法修改它们。
那么如果希望去修改按值捕获的外部变量应当怎么办呢?这时,需要显式指明lambda表达式为mutable:

int a = 0 ;
auto f1 = [=]{return a++; }; 				//error,修改按值捕获的外部变量
auto f2 = [=] () mutable { return a++; }; 	//OK,mutable

下面的代码

#include <iostream>
#include <vector>
#include <algorithm>

class CountEven
{
	int& count_;
public:
	CountEven(int& count) : count_(count){}
	void operator()(int val)
	{
		if (!(val & 1))  //val % 2 == 0
			++count_;
	}
};

int main(void)
{
	std::vector<int> v = { 1, 2, 3, 4, 5, 6 };
	int even_count = 0;
	std::for_each(v.begin(), v.end(), CountEven(even_count));
	std::cout << "The number of even is " << even_count << std::endl;

	system("pause");
	return 0;
}

这样写既烦琐又容易出错。有了lambda表达式以后,我们可以使用真正的闭包概念来替换掉这里的仿函数,代码如下:

std::vector<int> v= {1,23,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;

lambda表达式比 std::bind的灵活性更好,也更为简洁。当然,这都得益于lambda表达式的特征,它可以任意封装出功能闭包,使得短小的逻辑可以以最简洁清晰的方式表达出来。

//查找大于5小于10的元素的个数
int count = std::count_if(coll.begin(), coll.end(), [](intx){ return x > 5 && x < 10;});

7 tupe元组

tuple元组是一个固定大小的不同类型值的集合,是泛化的std:pair。
先构造一个tuple:

tuple<constchar*, int>tp = make_tuple(sendPack, nSendSize);	//构造一个tuple

这个tuple等价于一个结构体:

struct A
{
	char* p;
	int len;
};

用tuple<const char*, int>tp就可以不用创建这个结构体了,而作用是一样的。还有一种方法也可以创建元组,用std:tie,它会创建一个元组的左值引用。

auto tp = return std::tie(1, "aa", 2);	//tp的类型实际是std::tuple<int& , string&, int&>

获取元组的值:

constchar* data = tp.get<0>();	//获取第一个值
int len = tp.get<1>();			//获取第二个值

还有一种方法也可以获取元组的值,通过std::tie解包tuple:

int x, y;
string a;
std::tie(x, a, y) = tp;

通过tie解包后,tp中3个值会自动赋值给3个变量。解包时,如果只想解某个位置的值时,可以用std::ignore占位符来表示不解某个位置的值。比如我们只想解第3个值:

std::tie(std::ignore, std::ignore, y) = tp;

还有一个创建右值的引用元组方法: forward_as_tuple。

std::map<int, std::string> m;
m.emplace(std::forward_as_tuple(10, std::string(20, 'a')));

它实际上创建了一个类似于std::tuple<int&&, std::string&&>类型的 tuple。我们还可以通过tuple_cat连接多个tupe:

std::tuple<int, std::string, float> t1(10, "Test", 3.14);
int n = 7;
auto t2 = std::tuple_cat(t1, std::make_pair("Foo", "bar"), t1, std::tie(n)) ;
n = 10;
print(t2)	//(10, Test, 3.14, Foo, bar, 10, Test, 3.14, 10)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

给算法爸爸上香

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值