c++11

1. C++11简介

2. 列表初始化

3. 变量类型推导

4. 范围for循环

5. finaloverride

6. 智能指针

7. 新增加容器---静态数组arrayforward_list以及unordered系列

8. 默认成员函数控制

9. 右值引用

10. lambda表达式

11. 线程库

1. C++11简介

2003 C++ 标准委员会曾经提交了一份技术勘误表 ( 简称 TC1) ,使得 C++03 这个名字已经取代了 C++98 称为 C++11之前的最新 C++ 标准名称。不过由于 TC1 主要是对 C++98 标准中的漏洞进行修复,语言的核心部分则没 有改动,因此人们习惯性的把两个标准合并称为C++98/03 标准。从 C++0x C++11 C++ 标准 10 年磨一剑, 第二个真正意义上的标准珊珊来迟。相比于 C++98/03 C++11 则带来了数量可观的变化,其中包含了约 140 个新特性,以及对 C++03 标准中约 600 个缺陷的修正,这使得 C++11 更像是从 C++98/03 中孕育出的一种新语 。相比较而言, C++11 能更好地用于系统开发和库开发、语法更加泛华和简单化、更加稳定和安全,不仅 功能更强大,而且能提升程序员的开发效率

2. 列表初始化

2.1 C++98{}的初始化问题

C++98 中,标准允许使用花括号 {} 对数组元素进行统一的列表初始值设定。比如:
int array[] = { 1,2,3,4,5,6,7,8,9 };
int array2[5] = { 0 };
对于一些自定义的类型,却无法使用这样的初始化。比如:
std::vector<int> v  { 1,2,3,4,5,6 };
就无法通过编译,导致每次定义 vector 时,都需要先把 vector 定义出来,然后使用循环对其赋初始值,非常 不方便。C++11 扩大了用大括号括起的列表 ( 初始化列表 ) 的使用范围,使其可用于所有的内置类型和用户自定 义的类型,使用初始化列表时,可添加等号 (=) ,也可不添加

2.2 内置类型的列表初始化

int main()
 { 
 // 内置类型变量
 int x1 = {10};
 int x2{10};
 int x3 = 1+2;
 int x4 = {1+2};
 int x5{1+2};
 // 数组
 int arr1[5] {1,2,3,4,5};
 int arr2[]{1,2,3,4,5};
 
 // 动态数组,在C++98中不支持
 int* arr3 = new int[5]{1,2,3,4,5};
 
 // 标准容器
 vector<int> v{1,2,3,4,5};
 map<int, int> m{{1,1}, {2,2,},{3,3},{4,4}};
 return 0;
 }
注意:列表初始化可以在 {} 之前使用等号,其效果与不使用 = 没有什么区别。

2.3 自定义类型的列表初始化

1. 标准库支持单个对象的列表初始化
class Point
{
public:
 Point(int x = 0, int y = 0): _x(x), _y(y)
 {}
private:
 int _x;
 int _y;
};
int main()
{
 Pointer p{ 1, 2 };
 return 0; }
2. 多个对象的列表初始化
多个对象想要支持列表初始化, 需给该类 ( 模板类 ) 添加一个带有 initializer_list 类型参数的构造函数即 。注意: initializer_list 是系统自定义的类模板,该类模板中主要有三个方法: begin() end() 迭代器 以及获取区间中元素个数的方法 size()
#include <initializer_list>
template<class T>
class Vector {
public:
 // ... 
 Vector(initializer_list<T> l): _capacity(l.size()), _size(0)
 {
 _array = new T[_capacity];
 for(auto e : l)
 _array[_size++] = e;
 }
 
 Vector<T>& operator=(initializer_list<T> l) {
 delete[] _array;
 size_t i = 0;
 for (auto e : l)
 _array[i++] = e;
 return *this;
 } 
 // ...
private:
 T* _array;
 size_t _capacity;
 size_t _size;
};

3. 变量类型推导

3.1 为什么需要类型推导
在定义变量时,必须先给出变量的实际类型,编译器才允许定义,但有些情况下可能不知道需要实际类型怎 么给,或者类型写起来特别复杂,比如:
#include <map>
#include <string>
int main()
{
 short a = 32670;
 short b = 32670;
 
 // c如果给成short,会造成数据丢失,如果能够让编译器根据a+b的结果推导c的实际类型,就不会存
在问题
 short c = a + b;
 
 std::map<std::string, std::string> m{{"apple", "苹果"}, {"banana","香蕉"}}
// 使用迭代器遍历容器, 迭代器类型太繁琐
 std::map<std::string, std::string>::iterator it = m.begin();
//auto it = m.begin();
 while(it != m.end())
 {
 cout<<it->first<<" "<<it->second<<endl;
 ++it;
 }
 
 return 0; }
3.2 decltype 类型推导
3.2.1 为什么需要 decltype
auto 使用的前提是:必须要对 auto 声明的类型进行初始化,否则编译器无法推导出 auto 的实际类型 。但有 时候可能需要根据表达式运行完成之后结果的类型进行推导,因为编译期间,代码不会运行,此时auto 也就无能为力。
template<class T1, class T2>
T1 Add(const T1& left, const T2& right) 
{
	return left + right;
}
int main()
{
	auto a = Add<int, int>(1, 2);
	cout << a << endl;
	vector<decltype(a)> v;
	v.push_back(Add<int, int>(1, 2));
	v.push_back(Add<int, int>(1, 2));
	v.push_back(Add<int, int>(1, 2));
	v.push_back(Add<int, int>(1, 2));
	for (const auto& e : v)
	{
		cout << e << endl;
	}
}
如果能用 加完之后结果的实际类型作为函数的返回值类型就不会出错 ,但这需要程序运行完才能知道结果的 实际类型,即RTTI(Run-Time Type Identifification 运行时类型识别 )
C++98 中确实已经支持 RTTI
typeid 只能查看类型不能用其结果类定义类型
dynamic_cast 只能应用于含有虚函数的继承体系中
运行时类型识别的缺陷是降低程序运行的效率。
3.2.2 decltype
decltype 是根据表达式的实际类型推演出定义变量时所用的类型 ,比如:
1. 推演表达式类型作为变量的定义类型
int main()
{
 int a = 10;
 int b = 20;
 
 // 用decltype推演a+b的实际类型,作为定义c的类型
 decltype(a+b) c;
 cout<<typeid(c).name()<<endl;
 return 0; }

 2. 推演函数返回值的类型

void* GetMemory(size_t size) {
 return malloc(size);
}
int main()
{
 // 如果没有带参数,推导函数的类型
 cout << typeid(decltype(GetMemory)).name() << endl;
 
 // 如果带参数列表,推导的是函数返回值的类型,注意:此处只是推演,不会执行函数
 cout << typeid(decltype(GetMemory(0))).name() <<endl;
 
 return 0; }

 4 范围for循环

此处不作讲解,以前的文章有。

5 finaloverride

1. final:修饰虚函数,表示该虚函数不能再被重写,修饰类则不能被继承

2. override: 检查派生类虚函数是否重写了基类某个虚函数,如果没有重写编译报错。

6 智能指针

后面有专门做讲解

7. 新增加容器---静态数组arrayforward_list以及unordered系列

静态数组array比较鸡肋,但是检查错误跟严格,forward_list单链表也比较鸡肋,unordered系列以前讲过。

8. 默认成员函数控制

C++ 中对于 空类编译器会生成一些默认的成员函数 ,比如: 构造函数、拷贝构造函数、运算符重载、析构 函数和 & const& 的重载、移动构造、移动拷贝构造等函数。如果在类中显式定义了,编译器将不会重新生 成默认版本 。有时候这样的规则可能被忘记,最常见的是声明了带参数的构造函数,必要时则需要定义不带 参数的版本以实例化无参的对象。而且有时编译器会生成,有时又不生成,容易造成混乱,于是 C++11 让程 序员可以控制是否需要编译器生成
8.1 显式缺省函数
C++11 中,可以在默认函数定义或者声明时加上 =default ,从而显式的指示编译器生成该函数的默认版 本,用 =default 修饰的函数称为显式缺省函数
class A {
public:
 A(int a): _a(a)
 {}
 // 显式缺省构造函数,由编译器生成
 A() = default;
 
 // 在类中声明,在类外定义时让编译器生成默认赋值运算符重载
 A& operator=(const A& a);
private:
 int _a;
};
A& A::operator=(const A& a) = default;
int main()
{
 A a1(10);
 A a2;
 a2 = a1;
 return 0; }

 8.2 删除默认函数

如果能想要限制某些默认函数的生成, C++98 中,是该函数设置成 private ,并且不给定义 ,这样只要其他人想要调用就会报错。 C++11 中更简单,只需在该函数声明加上 =delete 即可,该语法指示编译器不生成对 应函数的默认版本,称 =delete 修饰的函数为删除函数
class A {
public:
 A(int a): _a(a)
 {}
 
 // 禁止编译器生成默认的拷贝构造函数以及赋值运算符重载
 A(const A&) = delete;
 A& operator(const A&) = delete;
private:
 int _a;
};
int main()
{
 A a1(10);
 // 编译失败,因为该类没有拷贝构造函数
 //A a2(a1);
// 编译失败,因为该类没有赋值运算符重载
 A a3(20);
 a3 = a2;
 return 0; }

注意:避免删除函数和explicit一起使

9 右值引用

9.1 右值引用概念
C++98 中提出了引用的概念,引用即别名,引用变量与其引用实体公共同一块内存空间,而引用的底层是通过指针来实现的,因此使用引用,可以提高程序的可读性。
void Swap(int& left, int& right) {
 int temp = left;
 left = right;
 right = temp; }
int main()
{
 int a = 10;
 int b = 20;
 Swap(a, b);
}
为了提高程序运行效率 C++11 中引入了右值引用 ,右值引用也是别名,但其只能对右值引用。
int Add(int a, int b) {
 return a + b; }
int main()
{
 const int&& ra = 10;
 
 // 引用函数返回值,返回值是一个临时变量,为右值
 int&& rRet = Add(10, 20);
 return 0; 
}
为了与 C++98 中的引用进行区分, C++11 将该种方式称之为右值引用。
9.2 左值与右值
左值与右值是 C 语言中的概念,但 C 标准并没有给出严格的区分方式,一般认为: 可以放在 = 左边的,或者能 够取地址的称为左值,只能放在 = 右边的,或者不能取地址的称为右值 ,但是也不一定完全正确。
int g_a = 10;
// 函数的返回值结果为引用
int& GetG_A()
{
 return g_a; }
int main()
{
 int a = 10;
 int b = 20;
 
 // a和b都是左值,b既可以在=的左侧,也可在右侧,
 // 说明:左值既可放在=的左侧,也可放在=的右侧
 a = b;
 b = a;
 const int c = 30;
 // 编译失败,c为const常量,只读不允许被修改
 //c = a;
 // 因为可以对c取地址,因此c严格来说不算是左值
 cout << &c << endl;
 
 // 编译失败:因为b+1的结果是一个临时变量,没有具体名称,也不能取地址,因此为右值
 //b + 1 = 20;
 
 GetG_A() = 100;
 return 0; }
因此关于左值与右值的区分不是很好区分,一般认为:
1. 普通类型的变量,因为有名字,可以取地址,都认为是左值。
2. const 修饰的常量,不可修改,只读类型的,理论应该按照右值对待,但因为其可以取地址 ( 如果只是 const类型常量的定义,编译器不给其开辟空间,如果对该常量取地址时,编译器才为其开辟空间 ) , C++11认为其是左值。
3. 如果表达式的运行结果是一个临时变量或者对象,认为是右值。
4. 如果表达式运行结果或单个变量是一个引用则认为是左值。
总结:
1. 不能简单地通过能否放在 = 左侧右侧或者取地址来判断左值或者右值,要根据表达式结果或变量的性质 判断,比如上述:c 常量
2. 能得到引用的表达式一定能够作为引用,否则就用常引用。
C++11 对右值进行了严格的区分:
C 语言中的纯右值,比如: a+b, 100
将亡值。比如:表达式的中间结果、函数按照值的方式进行返回。

9.3 引用与右值引用比较

C++98 中的普通引用与 const 引用在引用实体上的区别:
int main()
{
 // 普通类型引用只能引用左值,不能引用右值
 int a = 10;
 int& ra1 = a; // ra为a的别名
 //int& ra2 = 10; // 编译失败,因为10是右值
 
 const int& ra3 = 10;
 const int& ra4 = a;
 return 0; }
注意: 普通引用只能引用左值,不能引用右值, const 引用既可引 用左值,也可引用右值
C++11 中右值引用: 只能引用右值,一般情况不能直接引用左值
int main()
{
 // 10纯右值,本来只是一个符号,没有具体的空间,
 // 右值引用变量r1在定义过程中,编译器产生了一个临时变量,r1实际引用的是临时变量
 int&& r1 = 10;
 r1 = 100;
 int a = 10;
 int&& r2 = a; // 编译失败:右值引用不能引用左值
 return 0; }
问题:既然 C++98 中的 const 类型引用左值和右值都可以引用,那为什么 C++11 还要复杂的提出右值引用呢?
9.4 值的形式返回对象的缺陷
class String
{
public:
	String(const char* str = "")
	{
		if (nullptr == str)
			str = "";
		_str = new char[strlen(str) + 1];
		strcpy(_str, str);
	}

	String(const String& s)
		: _str(new char[strlen(s._str) + 1])
	{
		strcpy(_str, s._str);
		cout << "深拷贝" << endl;
	}
	String& operator=(const String& s)
	{
		if (this != &s)
		{
			char* pTemp = new char[strlen(s._str) + 1];
			strcpy(pTemp, s._str);
			delete[] _str;
			_str = pTemp;
		}
		return *this;
	}

	String operator+(const String& s)
	{
		char* pTemp = new char[strlen(_str) + strlen(s._str) + 1];
		strcpy(pTemp, _str);
		strcpy(pTemp + strlen(_str), s._str);
		String strRet(pTemp);
		return strRet;
	}

	~String()
	{
		if (_str) delete[] _str;
	}
private:
	char* _str;
};
int main()
{
	String s1("hello");
	String s2("world");
	String s3(s1 + s2);
	return 0;
}
上述代码看起来没有什么问题,但是有一个不太尽人意的地方:

operator+ 中: strRet 在按照值返回时,必须创建一个临时对象,临时对象创建好之后, strRet 就被销毁 了,最后使用返回的临时对象构造 s3 s3 构造好之后,临时对象就被销毁了 。仔细观察会发现: strRet 、临 时对象、 s3 每个对象创建后,都有自己独立的空间,而空间中存放内容也都相同,相当于创建了三个内容完 全相同的对象,对于空间是一种浪费,程序的效率也会降低,而且临时对象确实作用不是很大 ,那能否对该种情况进行优化呢?

 9.5 移动语义

C++11 提出了移动语义概念,即:将一个对象中资源移动到另一个对象中的方式,可以有效缓解该问题。

 C++11

String(String&& s)
 : _str(s._str)
{ 
 s._str = nullptr;
}

如果需要实现移动语义,必须使用右值引用。上述String类增加移动构造:

因为 strRet 对象的生命周期在创建好临时对象后就结束了,即将亡值, C++11 认为其为右值,在用 strRet 构造临时对象时,就会采用移动构造,即将strRet 中资源转移到临时对象中。而临时对象也是右值,因此在用临 时对象构造s3 时,也采用移动构造,将临时对象中资源转移到 s3 中,整个过程,只需要创建一块堆内存即可,既省了空间,又大大提高程序运行的效率。
注意:
1. 移动构造函数的参数千万不能设置成 const 类型的右值引用,因为资源无法转移而导致移动语义失效。
2. C++11 中,编译器会为类默认生成一个移动构造,该移动构造为浅拷贝,因此当类中涉及到资源管理时,用户必须显式定义自己的移动构造。

9.6 右值引用引用左值

按照语法,右值引用只能引用右值,但右值引用一定不能引用左值吗?因为:有些场景下,可能真的需要用 右值去引用左值实现移动语义。当需要用右值引用引用一个左值时,可以通过 move 函数将左值转化为右 C++11 中, std::move() 函数 位于 头文件中,该函数名字具有迷惑性,它 并不搬移任何东西,唯一的功 能就是将一个左值强制转化为右值引用,然后实现移动语义
template<class _Ty>
inline typename remove_reference<_Ty>::type&& move(_Ty&& _Arg) _NOEXCEPT
{
 // forward _Arg as movable
 return ((typename remove_reference<_Ty>::type&&)_Arg);
}
注意:
1. 被转化的左值,其生命周期并没有随着左值的转化而改变,即 std::move 转化的左值变量 lvalue 不会被销 毁。
2. STL 中也有另一个 move 函数,就是将一个范围中的元素搬移到另一个位置。

 可以看出当使用move后调用的移动构造。但是此时s2就是无效字符串了。 

9.7 完美转发

完美转发是指在函数模板中,完全依照模板的参数的类型,将参数传递给函数模板中调用的另外一个函数
void Func(int x) {
 // ......
}
template<typename T>
void PerfectForward(T t) {
 Fun(t);
}
PerfectForward 为转发的模板函数, Func 为实际目标函数 ,但是上述转发还不算完美, 完美转发是目标函 总希望将参数按照传递给转发函数的实际类型转给目标函数,而不产生额外的开销 ,就好像转发者不存在 一样。
所谓完美: 函数模板在向其他函数传递自身形参时,如果相应实参是左值,它就应该被转发为左值;如果相 应实参是右值,它就应该被转发为右值 。这样做是为了保留在其他函数针对转发而来的参数的左右值属性进 行不同处理(比如参数为左值时实施拷贝语义;参数为右值时实施移动语义)。
C++11 通过 forward 函数来实现完美转发 , 比如:
void Fun(int& x) { cout << "左值" << endl; }
void Fun(int&& x) { cout << "右值" << endl; }
void Fun(const int& x) { cout << "const 左值" << endl; }
void Fun(const int&& x) { cout << "const 右值" << endl; }
template<typename T>
void PerfectForward(T&& t) { Fun((t)); }
int main()
{
	PerfectForward(10); // rvalue ref

	int a;
	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;
}

如果&&用于模板那么是万能引用,现在看结果。

但是现在全是左值,将右值传递过去给t,t变成左值,需要保持参数的类型不变,就需要用到完美转发。 

可以看出库里面右值也是用的完美转发。

9.8 右值引用作用
C++98 中引用作用:因为引用是一个别名,需要用指针操作的地方,可以使用指针来代替,可以提高代码的 可读性以及安全性。
C++11 中右值引用主要有以下作用:
1. 实现移动语义 ( 移动构造与移动赋值 )
2. 给中间临时变量取别名
int main()
{
 string s1("hello");
 string s2(" world");
 string s3 = s1 + s2; // s3是用s1和s2拼接完成之后的结果拷贝构造的新对象
 stirng&& s4 = s1 + s2; // s4就是s1和s2拼接完成之后结果的别名
 return 0; }
3. 实现完美转发

10 lambda表达式

10.1 C++98中的一个例子

C++98 中,如果想要对一个数据集合中的元素进行排序,可以使用 std::sort 方法。

#include<algorithm>

int main()
{
	int array[] = { 1,2,7,6,4,3,89,3 };
	sort(array, array + sizeof(array) / 4, greater<int>());
	for (const auto& e : array)
	{
		cout << e << " ";
	}
}

 lamdba的写法

 如果待排序元素为自定义类型,需要用户定义排序时的比较规则:

struct Goods
{
 string _name;
 double _price;
};
struct Compare
{
 bool operator()(const Goods& gl, const Goods& gr)
 {
 return gl._price <= gr._price;
 }
};
int main()
{
 Goods gds[] = { { "苹果", 2.1 }, { "相交", 3 }, { "橙子", 2.2 }, {"菠萝", 1.5} };
 sort(gds, gds+sizeof(gds) / sizeof(gds[0]), Compare());
 return 0; }

 lamdba表达式的方式

随着 C++ 语法的发展, 人们开始觉得上面的写法太复杂了,每次为了实现一个 algorithm 算法, 都要重新去 写一个类,如果每次比较的逻辑不一样,还要去实现多个类,特别是相同类的命名,这些都给编程者带来了 极大的不便 。因此,在 C11 语法中出现了 Lambda表达式。        

10.2 lambda表达式

int main()
{
 Goods gds[] = { { "苹果", 2.1 }, { "相交", 3 }, { "橙子", 2.2 }, {"菠萝", 1.5} };
 sort(gds, gds + sizeof(gds) / sizeof(gds[0]), [](const Goods& l, const Goods& r)
 ->bool
 {
 return l._price < r._price;
 });
 return 0; }
上述代码就是使用 C++11 中的 lambda 表达式来解决,可以看出 lamb 表达式实际是一个匿名函数。

10.3 lambda表达式语法

ambda 表达式书写格式: [capture-list] (parameters) mutable -> return-type { statement }
1. lambda 表达式各部分说明 [capture-list] : 捕捉列表 ,该列表总是出现在 lambda 函数的开始位置, 编译器根据 [] 来判断接下来 的代码是否为 lambda 函数 捕捉列表能够捕捉上下文中的变量供 lambda 函数使用 。 (parameters):参数列表。与 普通函数的参数列表一致 ,如果不需要参数传递,则可以连同 () 一起 省略
mutable :默认情况下, lambda 函数总是一个 const 函数, mutable 可以取消其常量性。使用该修
饰符时,参数列表不可省略 ( 即使参数为空 )
->returntype :返回值类型 。用 追踪返回类型形式声明函数的返回值类型 ,没有返回值时此部分
可省略。 返回值类型明确情况下,也可省略,由编译器对返回类型进行推导
{statement} :函数体 。在该函数体内,除了可以使用其参数外,还可以使用所有捕获到的变量。
注意: lambda 函数定义中, 参数列表和返回值类型都是可选部分,而捕捉列表和函数体可以为空
因此 C++11 最简单的 lambda 函数为: []{} ; lambda 函数不能做任何事情。
int main()
{
	// 最简单的lambda表达式, 该lambda表达式没有任何意义
	[] {};

	// 省略参数列表和返回值类型,返回值类型由编译器推导为int
	int a = 3, b = 4;
	[=] {return a + 3; };

	// 省略了返回值类型,无返回值类型
	auto fun1 = [&](int c) {b = a + c; };
	fun1(10);
		cout << a << " " << b << endl;

	// 各部分都很完善的lambda函数
	auto fun2 = [=, &b](int c)->int {return b += a + c; };
	cout << fun2(10) << endl;

	// 复制捕捉x
	int x = 10;

	auto add_x = [x](int a) mutable { x *= 2; return a + x; };
	cout << add_x(10) << endl;
	return 0;
}

通过上述例子可以看出, lambda 表达式实际上可以理解为无名函数,该函数无法直接调用,如果想要 直接调用,可借助auto 将其赋值给一个变量。
2. 捕获列表说明
捕捉列表描述了上下文中那些数据可以被 lambda 使用 ,以及 使用的方式传值还是传引用
[var] :表示值传递方式捕捉变量 var
[=] :表示值传递方式捕获所有父作用域中的变量 ( 包括 this)
[&var] :表示引用传递捕捉变量 var
[&] :表示引用传递捕捉所有父作用域中的变量 ( 包括 this)
[this] :表示值传递方式捕捉当前的 this 指针
注意:
a. 父作用域指包含 lambda 函数的语句块
b. 语法上捕捉列表可由多个捕捉项组成,并以逗号分割
比如: [=, &a, &b] :以引用传递的方式捕捉变量 a b ,值传递方式捕捉其他所有变量 [& a, this] :值 传递方式捕捉变量a this ,引用方式捕捉其他变量 c. 捕捉列表不允许变量重复传递,否则就会导致编 译错误 。 比如: [=, a] = 已经以值传递方式捕捉了所有变量,捕捉 a 重复
d. 在块作用域以外的 lambda 函数捕捉列表必须为空
e. 在块作用域中的 lambda 函数仅能捕捉父作用域中局部变量,捕捉任何非此作用域或者非局部变量都会导致编译报错。
f. lambda 表达式之间不能相互赋值 ,即使看起来类型相同
void (*PF)();
int main()
{
 auto f1 = []{cout << "hello world" << endl; };
 auto f2 = []{cout << "hello world" << endl; };
 // 此处先不解释原因,等lambda表达式底层实现原理看完后,大家就清楚了
 //f1 = f2; // 编译失败--->提示找不到operator=()
 // 允许使用一个lambda表达式拷贝构造一个新的副本
 auto f3(f2);
 f3();
 // 可以将lambda表达式赋值给相同类型的函数指针
 PF = f2;
 PF();
 return 0; }

10.4 函数对象与lambda表达式  

函数对象,又称为仿函数,即可以想函数一样使用的对象,就是在类中重载了 operator() 运算符的类对象
class Rate
{
public:
 Rate(double rate): _rate(rate)
 {}
 double operator()(double money, int year)
 { return money * _rate * year;}
private:
 double _rate;
};
int main()
{
 // 函数对象
 double rate = 0.49;
 Rate r1(rate);
 r1(10000, 2);
 // lamber
 auto r2 = [=](double monty, int year)->double{return monty*rate*year; };
 r2(10000, 2);
 return 0; }
从使用方式上来看,函数对象与 lambda 表达式完全一样。
函数对象将 rate 作为其成员变量,在定义对象时给出初始值即可, lambda 表达式通过捕获列表可以直接将该变量捕获到。

 实际在底层编译器对于lambda表达式的处理方式,完全就是按照函数对象的方式处理的,即:如果定义了一个lambda表达式,编译器会自动生成一个类,在该类中重载了operator()

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值