文章目录
C++11新特性
今天让我们来看看C++的新特性,右值引用 和 智能指针 我会放在后面单独写一篇博客来介绍。
在C++11之前普遍使用的是C++98,C++11于2011年发布的(到现在已经有点年头了👀),在C++98的基础上增加了一些功能。
初始化列表
在以前我们可以使用花括号来初始化数组元素:
int a[] = { 1,2,3 };
int b[5] = { 1 };
但是对于自定义类型,却无法使用这种方法初始化。但是C++11扩大了花括号(初始化列表)的使用范围,现在的花括号能初始化几乎所有类型(内置类型和自定义类型),使用花括号时前面的=
也可不添加
内置类型的初始化
注意其中的动态数组开辟,这是C++11新添加的
//内置类型的变量
int a1 = 1;
int a1{ 1 }; //两种写法是等价的
int b1 = 1 + 2;
int b1{ 1 + 2 };
//数组
int arr1[]{ 1,2,3,4,5 };
int arr2[5]{ 1,2,3,4,5 };
//动态数组
int* arr3 = new int[5]{ 1,2,3,4,5 };
//标准容器
vector<int> v{ 1,2,3,4,5 };
map<int, int> m{ {1,2},{2,2},{3,3},{4,4} };
自定义类型的初始化
struct Person
{
int _age;
string _name;
Person(int age=10,string name="tony")
:_age(age)
,_name(name)
{
}
};
vector<Person> s1 = { {10,"jack"},{20,"peter"} };
vector<Person> s2 { {10,"jack"},{20,"peter"} }; //两种定义方式都是一样的,都是调用构造函数
初始化列表的原理
初始化列表底层实际上是一个initializer_list
,要想对象支持列表初始化,只需要给该类的构造函数添加一个函数参数为initializer_list
类型的参数即可。你的花括号里面的内容会存到initializer_list
中去,该模板向外面提供:begin()
,end()
、size()
三个接口。
template<class T>
class sht::vector
{
public:
vector(std::initializer_list<T> x) //添加初始化列表初始化
{
for (auto& e : x)
push_back(e);
}
}
int main()
{
sht::vector<string> s{ "hello","bye" };
for (auto e : s)
{
cout << e << endl;
}
}
变量类型推导
auto关键字
auo是一个类型的推导关键字,有以下几点需要注意:
- auto仅仅只是占位符,编译阶段编译器根据初始化表达式推演出实际类型之后会替换auto
- auto最大的作用是使代码变得更加简洁(迭代器的定义)
- auto不能用来修饰函数参数
- auto必须初始化,因为他要根据初始化的值来推断类型
typeid关键字
该关键字可以显示的打印出类型:
int a = 1;
double b = 1.0;
cout << typeid(a*b).name() << endl;
输出结果为:
decltype关键字
该关键字将变量的类型声明为表达式的指定类型:
decltype (表达式) 对象;
这样对象就自动被推导成和表达式一样的类型。
该关键字与auto功能上一样,但是用法上decltype
可以不用初始化
int a = 1;
double b = 1.0;
auto c; //错误的定义
decltype(a * b) c; //正确的定义
Lambda表达式
在C++98中我们对于一个函数的标识方式有:函数指针、仿函数
在C++11中我们新添加了另一种函数的表示方式——lamda表达式
举两个个例子:
- 我们想让一个数组按从大到小的顺序重新排列👇
struct comp //由于sort默认是从小到大的排序,所以我们必须实现自己的仿函数
{
bool operator()(int a, int b)
{
return a > b;
}
};
int main()
{
vector<int> s{ 1,2,4,3,13,51,6,5 };
sort(s.begin(), s.end(),comp());
for (auto e : s)
cout << e << " ";
}
- 自定义类型按照某种属性排序:
struct fruit
{
string name;
double price;
};
struct comp
{
bool operator()(fruit a, fruit b) //我们想让数组按照水果价格倒叙排序
{
return a.price > b.price;
}
};
int main()
{
vector<fruit> s{ {"苹果",10.00},{"香蕉",15.00},{"橘子",20.00} };
sort(s.begin(), s.end(), comp());
}
这样写仿函数有点麻烦所以C++11设计了Lambda表达式
书写格式
[捕捉列表] ( 参数列表) mutable ->returnvalue {函数体}
- lambda表达式各部分说明
-
[capture-list] : 捕捉列表,该列表总是出现在lambda函数的开始位置,编译器根据[]来
判断接下来的代码是否为lambda函数,捕捉列表能够捕捉上下文中的变量供lambda
函数使用。 -
(parameters):参数列表。与普通函数的参数列表一致,如果不需要参数传递,则可以
连同()一起省略 -
mutable:默认情况下,上面捕获列表的所有捕获内容均是const拷贝,mutable可以取消其常量的性质,但是他依然是捕获参数的一份拷贝
性。使用该修饰符时,参数列表不可省略(即使参数为空)。 -
->returntype:返回值类型。用追踪返回类型形式声明函数的返回值类型,没有返回
值时此部分可省略。返回值类型明确情况下,也可省略,由编译器对返回类型进行推
导。 -
{statement}:函数体。在该函数体内,除了可以使用其参数外,还可以使用所有捕获
到的变量。
捕获列表的传参方式
- [var]:表示值传递方式捕捉变量var
- [=]:表示值传递方式捕获所有父作用域中的变量(包括this)
- [&var]:表示引用传递捕捉变量var
- [&]:表示引用传递捕捉所有父作用域中的变量(包括this)
- [this]:表示值传递方式捕捉当前的this指针
注意
- 在块作用域中的lambda函数仅能捕捉父作用域中局部变量,捕捉任何非此作用域或者
非局部变量(全局变量、静态变量)都会导致编译报错 - 语法上捕捉列表可由多个捕捉项组成,并以逗号分割。
- [ =, &a, &b]:以引用传递的方式捕捉变量a和b,值传递方式捕捉其他所有变量
- 捕捉列表不允许变量重复传递,否则就会导致编译错误。
- [=, a]:=已经以值传递方式捕捉了所有变量,捕捉a重复
Lambda表达式是什么
其实Lambda实际上就是一个匿名对象,该对象用你给的函数体重载了operator()
,所以实际上和仿函数没有什么区别😅
包装器
包装器是一个函数模板,上面我们对于函数的范围扩张为:普通函数、仿函数、Lambda表达式。包装器实际上是对函数类型的封装,类似于C语言函数指针,但是我们在C语言中函数指针的类型是一个非常复杂的东西,包装器相对简单。
template<class F, class T>
T useF(F f, T x)
{
static int count = 0;
cout << "count:" << ++count << endl;
cout << "count:" << &count << endl;
return f(x);
}
double f(double i)
{
return i / 2;
}
struct Functor
{
double operator()(double d)
{
return d / 3;
}
};
int main()
{
// 函数名
cout << useF(f, 11.11) << endl;
// 函数对象
cout << useF(Functor(), 11.11) << endl;
// lamber表达式
cout << useF([](double d)->double { return d / 4; }, 11.11) << endl;
return 0;
}
如上的代码,我们向函数模板useF
传入函数、仿函数、Lambda表达式,模板中的类型F分别被识别为了:函数指针、仿函数类对象、Lambda表达式对象。所以实例化出了三份useF
函数,导致了count的地址各不相同(因为静态变量属于整个类,一个类只有一个)
但是包装器在初始化类的成员函数时有些区别:
struct operate
{
public:
double add(double &x, double& y) //类的普通成员函数
{
return x + y;
}
static double add_static(double& x, double& y) //类的静态成员函数
{
return x + y;
}
};
int main()
{
double a = 1;
double b = 2;
//类的非静态成员函数 ——在圆括号中除了参数列表还需传入类名 同时还需注意赋值的函数指针要带上类名和&
function<double(operate,double&, double&)> func4 = &operate::add;
//注意调用该包装器的时候需要把类对象传进去,目的是this指针不能显示传递,只能传入对象来调用函数
func4(operate(), a, b);
//类的静态成员
function<double(double&, double&)> func5 = operate::add_static;
return 0;
}
而我们使用包装器,可以将上述类型包装成同一个类型,从而达到useF
只实例化出一个的结果!
下面来介绍一下包装器吧
-
std::function在头文件
<functional>
// 类模板原型如下 -
template <class T> function; // undefined template <class Ret, class... Args> class function<Ret(Args...)>;
-
模板参数说明:
Ret: 被调用函数的返回类型
Args…: 被调用函数的形参
#include <functional>
template<class F, class T>
T useF(F f, T x)
{
static int count = 0;
cout << "count:" << ++count << endl;
cout << "count:" << &count << endl;
return f(x);
}
double f(double i)
{
return i / 2;
}
struct Functor
{
double operator()(double d)
{
return d / 3;
}
};
int main()
{
// 函数名
std::function<double(double)> func1 = f;
cout << useF(func1, 11.11) << endl;
// 函数对象
std::function<double(double)> func2 = Functor();
cout << useF(func2, 11.11) << endl;
// lamber表达式
std::function<double(double)> func3 = [](double d)->double { return d / 4; };
cout << useF(func3, 11.11) << endl;
return 0;
}
这样useF
中的模板类型F
全部都是类型function<double(double)>
了,所以只会实例化出一个useF
函数!
谈谈我的个人理解
C++自从引入了仿函数,函数调用可以不仅通过函数指针还可以通过对象调用,但是相同功能(相同参数列表、相同返回值类型)的函数却不能用同一个函数指针表示(因为有仿函数的存在),所以包装器在C++中充当是一个大号的"函数指针"。
绑定
关于绑定有三个作用:
#include <functional>
int SubFunc(int a, int b)
{
return a - b;
}
int Plus(int a, int b)
{
return a + b;
}
class Sub
{
public:
int sub(int a, int b)
{
return a - b;
}
};
- 绑定别的函数,相当于给函数起了一个“别名”
这里的placeholders::_1
和placeholder::_2
是函数第一个参数和第二个参数的占位符
std::function<int(int, int)> func1 = std::bind(Plus, placeholders::_1,placeholders::_2);
func1(1,2); //和 Plus(1,2);等价
- 还可以调换函数参数列表的顺序
如下:这样传入参数的顺序就被交换了func2(1,2)
,就变成了2-1
而不是1-2
std::function<int(int, int)> func2 = std::bind(SubFunc, placeholders::_2,placeholders::_1);
func2(1,2);
- 还可以固定参数
如上在使用包装器包装类的成员函数的时候,每次调用包装器的时候都要传入类得对象,使用绑定可以很好解决这个问题。我们在绑定的时候就将类对象传入,这样后面调用的时候就不需要传入对象了
//未使用绑定
std::function<int(Sub,int, int)> func3 = &Sub::sub;
cout<<func3(Sub(), 1, 2);
//使用绑定
std::function<int(int, int)> func4 = std::bind(&Sub::sub, Sub(), placeholders::_1, placeholders::_2);
cout<<func4(2,1);