C++11 新特性整理(2)

基于范围的or循环:

C++中对于for循环的写法:
1. 借助容器的迭代器完成

2. <algorithm>中的for_each算法:
3. 基于范围的for循环

// ConsoleApplication1.cpp : 定义控制台应用程序的入口点。
#include "stdafx.h"
#include <iostream>
#include <memory>
#include <algorithm>
#include <vector>

void func(int n)
{
	std::cout << n << " ";
}

int main()
{
	std::vector<int> v {1,2,3,4,5,6};

	// 基于迭代器
	for (auto iter = v.begin(); iter != v.end(); iter++)
	{
		std::cout << *iter << " ";
	}
	std::cout << std::endl;
	
	// 基于algorithm中的for_each
	std::for_each(v.begin(), v.end(), func);
	std::cout << std::endl;

	// 基于范围的for循环
	for (auto elem : v)
	{
		std::cout << elem << " ";
	}
	std::cout << std::endl;

	return 0;
}

基于范围的for循环实际上是普通for循环的语法糖,它会在循环开始前就确定迭代的范围,而不是在每次迭代之后调用end()

如何让自定义的类型也能够支持基于范围的for循环?

基于范围的for循环实际上是普通for循环的语法糖,它需要查找到容器的begin()迭代器和end()迭代器,所以对于自定义类型来说,分别实现begin()和end()即可。

可调用对象:

C++中可调用对象定义:
1. 函数指针

2. 具有operator()成员函数的类对象

3.可以转换为函数指针的类对象

4. 类成员指针

具有operator()成员函数的类对象称之为仿函数,因此,需要明确两点: 
  1 仿函数不是函数,它是个类; 
  2 仿函数重载了()运算符,使得可以像函数那样子调用(代码的形式好像是在调用函数)。 

上面对可调用类型的定义并没有包括函数类型或者函数引用(只有函数指针),因为函数类型不能直接用来定义对象,而函数引用,可以看作一个const类型的函数指针。

C++中可调用对象虽然有比较统一的操作形式,但是定义的方法却很多,这样会使得使用一个统一的方法保存或者传递可调用对象时,就会十分繁琐。C++11提供了std::function和std::bind统一了可调用对象的各种操作。
 

可调用对象的包装器

std::function是可调用对象的包装器,它是一个类模板。通过指定它的模板参数,可以用同一的方式处理函数,函数对象,函数指针,并允许保存和延迟它们的执行。

例如:

// ConsoleApplication1.cpp : 定义控制台应用程序的入口点。
#include "stdafx.h"
#include <iostream>
#include <functional>

void func()     // 普通函数
{
	std::cout << __FUNCDNAME__ << std::endl;
}

class myex     // 类 静态成员
{
public:
	static int mymethod(int x)
	{
		std::cout << __FUNCDNAME__ << "(" << x << ")->" << std::endl;
		return x;
	}
};

class Bar    // 具有operator()成员函数
{
public:
	int operator()(int x)
	{
		std::cout << __FUNCDNAME__ << "(" << x << ")->" << std::endl;
		return x;
	}
};

int main()
{
	// std::function
	std::function<void(void)> fr1 = func;  // 包装普通函数
	fr1();    // 调用

	// 包装类的静态成员函数
	std::function<int(int)> fr2 = myex::mymethod;
	std::cout << fr2(12) << std::endl;

	// 包装仿函数
	Bar bar;
	fr2 = bar;   // 赋值操作
	std::cout << fr2(22) << std::endl;

	return 0;
}


运行输出:

std::function填入适合的函数签名(即一个函数类型,只需包含返回值类型和参数列表)之后,他就变成一个可以容纳所有这一类调用方式的函数包装器。

std::function作为回调函数

回调函数就是一个通过函数指针调用的函数。如果把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。

回调函数机制: 
1、定义一个函数(普通函数即可); 
2、将此函数的地址注册给调用者; 
3、特定的事件或条件发生时,调用者使用函数指针调用回调函数。 

(https://blog.csdn.net/yidu_fanchen/article/details/80513359)

现在,std::function可以替代函数指针,引用在回调函数:

// ConsoleApplication1.cpp : 定义控制台应用程序的入口点。
#include "stdafx.h"
#include <iostream>
#include <functional>

class A
{
	std::function<void(void)> callback_;   //回调函数
public:
	A(const std::function<void(void)>& f) : callback_(f)   // 初始化callback_
	{

	}

	void use_f()
	{
		callback_();     // 调用回调函数
	}
};

// 定义回调函数  仿函数
class foo
{
public:
	void operator()()
	{
		std::cout << __FUNCDNAME__ << std::endl;
	}
};


int main()
{
	foo foo_;
	A a(foo_);
	a.use_f();   // 调用回调函数

	return 0;
}


所以,可以用std::function替代函数指针,作为参数传递到其他的函数中:

// ConsoleApplication1.cpp : 定义控制台应用程序的入口点。
#include "stdafx.h"
#include <iostream>
#include <functional>

void call_even(int x, const std::function<void(int)>& f)    // 作为参数
{
	if (x % 2 == 0)
	{
		f(x);
	}
}

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


int main()
{
	for (int i = 0; i < 10; i++)
	{
		call_even(i, print_f);
	}
	std::cout << std::endl;
	return 0;
}


std::bind绑定器:

std::bind用来将可调用对象和参其数一起进行绑定,绑定后的结果可以使用std::function进行保存,它的作用可以概括为:

1.将可调用对象与其参数绑定到一起形成一个新的可调用对象

2.将多元的可调用对象进行转换(绑定某个或者某一些参数)

例如:

// ConsoleApplication1.cpp : 定义控制台应用程序的入口点。
#include "stdafx.h"
#include <iostream>
#include <functional>

void call_even(int x, const std::function<void(int)>& f)    // 作为参数
{
	if (x % 2 == 0)
	{
		f(x);   // 第一个参数x
	}
}

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

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


int main()
{
	{
		std::function<void(int)> fr = std::bind(print_f, std::placeholders::_1);   // 占位符,这个位置将在函数调用时被第一个传入的参数取代
		for (int i = 0; i < 10; i++)
		{
			call_even(i, fr);
		}
		std::cout << std::endl;
	}

	// 通过绑定不同的函数呈现出不同的行为
	{
		std::function<void(int)> fr = std::bind(printf_f2, std::placeholders::_1);   // 占位符,这个位置将在函数调用时被第一个传入的参数取代
		for (int i = 0; i < 10; i++)
		{
			call_even(i, fr);
		}
		std::cout << std::endl;
	}
	return 0;
}

占位符使得std::bind大的应用非常灵活:占位符表示这个位置将在函数调用时被第几个传入的参数取代:
可以举一个例子:

// ConsoleApplication1.cpp : 定义控制台应用程序的入口点。
#include "stdafx.h"
#include <iostream>
#include <functional>

void func(int x, int y)
{
	std::cout << x << " " << y << std::endl;
}
int main()
{
	std::bind(func, std::placeholders::_1, 3)(2, 4);  // 输出2 3
	std::bind(func, std::placeholders::_2, std::placeholders::_1)(1, 2, 3);  // 输出2 1
	std::bind(func, std::placeholders::_2, std::placeholders::_3)(1, 2, 3);  // 输出2 3
	return 0;
}

将类成员函数的指针和对象进行绑定,构造成一个仿函数(可调用对象),也可以将类中的数据成员和对象进行绑定,然后包装成一个可调用类型:

// ConsoleApplication1.cpp : 定义控制台应用程序的入口点。
#include "stdafx.h"
#include <iostream>
#include <functional>

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

};
int main()
{
	A a;
	std::function<void(int, int)> fr = std::bind(&A::printf, &a, std::placeholders::_1, std::placeholders::_2);  // 将类方法和对象绑定
	fr(1, 2);

	// 还可以绑定类的数据成员
	std::function<int&(void)> fi = std::bind(&A::i_, &a);
	fi() = 23;    // 直接改变对象的数据成员的值
	return 0;
}

C++11中的lambda表达式:

lambda表达式定义了一个你们匿名函数、,并且可以捕获一定范围内的变量。lambda表达式的语法如下:

[capture]  (paras)  opt->ret { body; }

capture: 捕获列表

paras: 参数

opt: 函数选项

ret: 返回值类型

body: 函数体

例如:

int main()
{
	auto f = [](int a)->int {return 2 * a; };
	std::cout << f(1) << std::endl;
	return 0;
}

C++允许省略lambda返回值的返回值定义:

int main()
{
	auto f = [](int a){return 2 * a; };
	std::cout << f(1) << std::endl;
	return 0;
}

同时,再在没有参数的情况下,lamda表达式的参数列表也是可以省略的:

int main()
{
	auto f = []{return 2; };
	std::cout << f() << std::endl;
	return 0;
}

lambda的捕获列表可以捕获一定范围内的变量:
1. 【】表示不捕获任何变量

2. 【&】捕获外部作用域中的所有变量,并作为引用在函数体中使用(按引用捕获)

3. 【=】捕获外部作用域中的所有变量,并作为副本在函数体中使用(按值捕获)

4. 【=,&foo】按值捕获外部作用域中的所有变量,并且按引用捕获foo变量

5. 【bar】按值只捕获bar变量

6.【this】捕获当前类中的this指针,让lambda表达式拥有和当前类成员函数同样的访问权限

例如:

class A
{
public:
	int i_ = 0;
	void func(int x, int y)
	{
		auto x1 = []() {return i_; };   // error 没有捕获任何外部的变量
		
		auto x2 = [this]() {return i_; };  // right
		auto x3 = [this]() {return i_ + x + y; };  // error,没有捕获外部变量x,y
		auto x5 = [this, x, y]() {return i_ + x + y; };  // right

		auto x6 = [=]() {return i_ + x + y; };  //捕获所有外部变量 
		auto x7 = [&]() {return i_ + x + y; };  //捕获所有外部变量

	}

};

lambda表达式可以非常方便的实现闭包,使得程序更加灵活:

int main()
{
	std::vector<int> v;
	int cnt = std::count_if(v.begin(), v.end(), [](int x) {return x > 10; });
	int cnt = std::count_if(v.begin(), v.end(), [](int x) {return x < 10; });
	int cnt = std::count_if(v.begin(), v.end(), [](int x) {return x > 10 && x<20; });
	return 0;
}

---------------------------------------------------分割线---------------------------------------------------------

posted @ 2019-08-16 14:28  Alpha205  阅读(80)  评论(0编辑  收藏  举报