1. C++11简介
2. 列表初始化
3. 变量类型推导
4. 范围for循环
5. final与override
6. 智能指针
7. 新增加容器---静态数组array、forward_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 final与override
1. final:修饰虚函数,表示该虚函数不能再被重写,修饰类则不能被继承
2. override: 检查派生类虚函数是否重写了基类某个虚函数,如果没有重写编译报错。
6 智能指针
后面有专门做讲解
7. 新增加容器---静态数组array、forward_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;
}
上述代码看起来没有什么问题,但是有一个不太尽人意的地方:
![](https://i-blog.csdnimg.cn/blog_migrate/2c2773c45575b68c1f2d657f6eff9ee1.png)
在
operator+
中:
strRet
在按照值返回时,必须创建一个临时对象,临时对象创建好之后,
strRet
就被销毁
了,最后使用返回的临时对象构造
s3
,
s3
构造好之后,临时对象就被销毁了
。仔细观察会发现:
strRet
、临
时对象、
s3
每个对象创建后,都有自己独立的空间,而空间中存放内容也都相同,相当于创建了三个内容完
全相同的对象,对于空间是一种浪费,程序的效率也会降低,而且临时对象确实作用不是很大
,那能否对该种情况进行优化呢?
9.5 移动语义
C++11
提出了移动语义概念,即:将一个对象中资源移动到另一个对象中的方式,可以有效缓解该问题。
![](https://i-blog.csdnimg.cn/blog_migrate/9bc3f51dfdde98144e0dbf98b0bd8988.png)
在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
函数,就是将一个范围中的元素搬移到另一个位置。
![](https://i-blog.csdnimg.cn/blog_migrate/94a99114ca4e64f4911eb934c7e3f388.png)
可以看出当使用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 << " ";
}
}
![](https://i-blog.csdnimg.cn/blog_migrate/741f27210e7dbbef2058ac45657d710c.png)
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;
}
![](https://i-blog.csdnimg.cn/blog_migrate/458d9798fb0b58dd0e684e63a3c9c55d.png)
通过上述例子可以看出,
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
表达式通过捕获列表可以直接将该变量捕获到。
![](https://i-blog.csdnimg.cn/blog_migrate/6bef368b40d8b222cd170207b041a1b7.png)
实际在底层编译器对于lambda表达式的处理方式,完全就是按照函数对象的方式处理的,即:如果定义了一个lambda表达式,编译器会自动生成一个类,在该类中重载了operator()。