目录
简介
c++11是c++标准十年磨一剑产生的一个重要的标准,增加了很多新的特性,可以提高程序员的开发效率,使使用更加的简单,稳定,安全,功能更加强大,更好的运用于系统开发和库的开发,也同时使语法变得更像一门全新的语言。
之前的篇幅已经谈过范围for、智能指针等,这些都是C++11新增的内容,在STL中,C++11新增array,forward_list,unordered_map,unordered_set容器和对应方法,array使用较少,forward_list是用单链表实现的。unordered_map和unordered_set的底层是用哈希表实现的,前面已经谈过,本篇不再赘述。
统一列表初始化
C++98标准允许用花括号对数组还有结构体的元素进行列表初始值的设定,C++11扩大了大括号的使用范围,使其可以用于所有的内置类型,还有用户自定义的类型,初始化的时候还可以省略等号。
创建对象可以使用列表初始化方式调用构造函数进行初始化对象
其实这一种列表初始化的方式,C++11新增的一个模板类型叫做initializer_list,花括号中用逗号分隔的元素的类型是相同的:
C++11对容器增加了initializer list作为参数的构造函数,这样子方便于初始化容器,比如:
使用示例:
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<utility>
#include<vector>
using std::vector;
using std::pair;
using std::cout;
using std::endl;
struct point
{
int _x;
int _y;
point(int x, int y) :_x(x), _y(y) {}
};
int main()
{
int a1 = 2;
int a2{ 2 };
int arr1[3] = { 1,2,3 };
pair<int, int>p1(1, 2);
vector<int>va(3, 0);
point po1(1, 2);
// C++11
int arr2[3]{ 1,2,3 };
int arr3[]{ 1,2,3 };//动态数组,C++98不支持
int* arr4 = new int[4] { 1, 2, 3, 4 };
pair<int, int>p2{ 1,3 };
vector<int>vb{ 1,2,3 };
point po2{ 1,2 };
return 0;
}
简化声明方式
auto
C++98在中auto是表明一个存储局部自动存储类型的,局部变量默认具有,所以没有什么价值
C++11将其用于自动推断类型,常用在范围for中,还常用于自动推导较长的类型
使用示例:
decltype
将变量的类型声明为表达式指定的类型,或者用于将变量转化为其类型,比如:
double a = 1.0;
int b = 2;
decltype(a * b) c = 2.0;
推导出的c的类型也是double
nullptr
由于历史原因C++中NULL被定义为0,为了安全新增了nullptr表示空指针((void*)0):
右值引用
概念
左值:
数据表达式(变量或解引用的指针),可以取地址,可以赋值
const修饰的左值不可赋值,但可取地址
右值:
数据表达式(字面常量,表达式返回值,函数返回值)
不能赋值,不能取地址
左值引用:
左值的别名
只能引用左值,不能引用右值,const左值引用对于左值右值都可以引用
右值引用:
右值的别名
被存储到特定位置,可以取地址
只能引用右值,不能引用左值,可以引用move后的左值
应用场景
左值引用:
左值引用做参数和返回值都可以提高效率
缺点:函数返回局部对象时不能左值引用返回,只能传值返回,导致至少一次拷贝构造
右值引用:
解决左值引用短板
string类,STL容器等新增移动构造函数和移动赋值函数
比如:
string类中新增移动构造,构造对象的时候将参数的资源窃取,不用做深拷贝,提高了构造的效率,底层实现是完成了底层指针的交换;string类新增移动赋值,将右值对象赋值给已存在的对象,将二者资源交换完成赋值,底层也是进行了指针的交换,效率很高。
移动构造函数:
移动赋值函数:
完美转发
在模板中的&&不代表右值引用,而是万能引用,既能接收左值,也能接收右值
模板后续使用参数时,都会退化成左值:
使用forward函数模板进行参数的完美转发,保留参数的原生类型属性:
可变参数模板
C++11之前模板的参数个数是有限的,C++11提供了可变个数的模板参数,但是语法较为特殊,使
用示例:
template<class...Args>
void ShowList(Args... args)//传递任意个数的参数
{
//求参数个数
cout << sizeof...(args) << endl;
}
int main()
{
int a = 0;
double b = 1.1;
vector<int>c;
ShowList();
ShowList(a);
ShowList(a, b);
ShowList(a, b, c);
return 0;
}
输出:
Args是一个模板参数包,args是一个函数形参参数包,含0到任意个模板参数,使用sizeof...(args)求模板参数的个数。
倘若要取参数包中的参数,可以递归取参数包,记得要有递归终止函数,因为函数模板在编译期的时候生成函数,如果无限递归,就会发生编译错误。
template<class T>
void fun(T t)
{
cout << t << endl;
}
template<class T,class ...Args>
void fun(T a,Args... args)
{
cout << a << endl;
fun(args...);
}
int main()
{
fun(1, 1.1, 'a');
return 0;
}
输出:
还可以使用逗号表达式展开参数包:
template <class T>
void PrintArg(T t)
{
cout << t << " ";
}
//展开函数
template <class ...Args>
void ShowList(Args... args)
{
int arr[] = { (PrintArg(args), 0)... };
cout << endl;
}
int main()
{
ShowList(1);
ShowList(1, 'A');
ShowList(1, 'A', "hello");
ShowList(1, 'A', "hello",string("world"));
return 0;
}
输出:
![](https://img-blog.csdnimg.cn/8b1d1a14e1254ebd9182091da1a638b7.png)
C++11新增了一些模板函数,函数原型采用了可变参数模板,比如:
部分容器新增了emplace_back函数模板,函数模板支持可变参数,所以可以这么使用:
list<pair<int,string>>mylist;
mylist.emplace_back(1,“sort”);
新增类功能
新增移动构造和移动赋值函数:前面提到了右值引用的应用场景,C++11的类中新增的两个成员函数,是C++11新增的,在原来C++类中有6个成员函数。
对于移动构造和移动赋值函数编译器会自动生成,但自动生成的条件较为苛刻:
总结:
1.未实现且未实现析构函数,拷贝构造,赋值重载时才会自动生成
2.默认生成的,对内置类型逐字节拷贝,对自定义类型若实现移动..函数就调用,没有实现就调用拷贝构造
3.若实现其中一个,编译器不自动提供拷贝构造和拷贝赋值
还有包含之前篇提到过的:
成员变量类内给初始缺省值
default:强制编译器生成默认函数关键字
delete:禁止编译器生成默认函数关键字
final:实现最终类的关键字,当修饰虚函数时则虚函数不能被重写
override:用在派生类虚函数中,用于虚函数是否重写了基类某个虚函数
lambda表达式
在C++11之前,要进行自定义类型的排序,需要自己实现一个类及其仿函数,需要为类命名,较为麻烦。C++11提供了一个简便的方法,就是使用lambda表达式:
C++11之前:
struct Goods
{
string name_; // 名称
double price_; // 价格
int evaluate_; // 评价
Goods(const char* str, double price, int evaluate)
:name_(str)
, price_(price)
, evaluate_(evaluate)
{}
};
struct ComparePriceLess
{
bool operator()(const Goods& gl, const Goods& gr)
{
return gl.price_ < gr.price_;
}
};
struct ComparePriceGreater
{
bool operator()(const Goods& gl, const Goods& gr)
{
return gl.price_ > gr.price_;
}
};
int main()
{
vector<Goods> v = { { "苹果", 2.1, 5 }, { "香蕉", 3, 4 }, { "橙子", 2.2,
3 }, { "菠萝", 1.5, 4 } };
sort(v.begin(), v.end(), ComparePriceLess());
sort(v.begin(), v.end(), ComparePriceGreater());
}
C++11之后:
int main()
{
vector<Goods> v = { { "苹果", 2.1, 5 }, { "香蕉", 3, 4 }, { "橙子", 2.2,
3 }, { "菠萝", 1.5, 4 } };
sort(v.begin(), v.end(), [](const Goods& g1,const Goods& g2)
{return g1.price_ < g2.price_; });
sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2)
{return g1.price_ > g2.price_; });
}
原理
在sort函数中传入的是匿名对象或lambda表达式,其实lambda表达式就是匿名函数或函数对象。
底层编译器通过函数对象方式处理,自动生成一个类,类名为lambda_uuid(生成的全局唯一标识符),重载了operator(),从汇编代码可以看到(不同编译器实现不尽相同):
语法格式
[捕捉列表](参数列表)mutable->返回值类型{语句}
捕捉列表:捕捉上下文变量供lambda表达式使用
[var]值传递捕捉变量var
[=]值传递捕获所有父作用域的变量,包括this(父作用域指包含lambda函数的语句块)
[&var]引用传递捕获变量var
[&]引用传递捕获所有父作用域的变量,包括this
[this]值传递捕获当前this
捕捉列表可有多项,以逗号隔开,比如:
[=, &a, &b]:以引用传递的方式捕捉变量a和b,值传递方式捕捉其他所有变量[&,a, this]:值传递方式捕捉变量a和this,引用方式捕捉其他变量捕捉列表不允许变量重复传递
块作用域外的lambda函数捕捉列表必须为空
只能捕获父作用域中的局部变量,否则报错
参数列表:如果不需要参数传递,则可以连同()一起省略
mutable:lambda表达式参数默认带有const属性,使用mutable可以取消lambda函数默认的const属性,使用时参数列表不能省
->返回值类型,返回值类型明确时可省略,由编译器自动推导
{语句}:语句中可以使用参数和所以捕获到的变量
表达式之间不能相互赋值,即使类型看起来相同
(原因是底层生成的类都不同,每个lambda都是独一的):
auto f1 = []{cout << "hello world" << endl; };
auto f2 = []{cout << "hello world" << endl; };
//f1 = f2;error
函数包装器
function包装器又叫适配器,function是一个类模板,可以用来解决函数模板实例化多份的问题
看下面代码:
template<class F, class T>
T useF(F f, T x)
{
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表达式,虽然本质类似,但是类型是不同的,它们都是可调用类型对象,要是useF函数模板能将它们当做一个类型看待,就只会生成一份函数实例了。
统一可调用类型对象:
函数指针,函数名
仿函数对象
lambda表达式
使用包装器解决上面的问题(引入头文件<functional>):
#include <functional>
template<class F, class T>
T useF(F f, T x)
{
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函数模板看到的func1和func2、fun3的类型都是相同的,这样一来就只会产生一份函数实例了。
函数原型:
class function<Ret(Args...)>中Ret是被调用函数返回值类型,Args是被调用函数形参列表
可以使用bind函数模板搭配包装器使用,主要是为了调整函数参数个数和参数的顺序:
bind是一个函数模板,就像函数适配器,接收一个可调用对象,生成新的适配参数列表的可调用对象。
注意绑定类内成员函数时,要先传成员函数的默认隐含的参数this指针。
使用示例:
#include <functional>
int Plus(int a, int b)
{
return a + b;
}
class Sub
{
public:
int sub(int a, int b)
{
return a - b;
}
};
int main()
{
//表示绑定函数plus,其参数分别由调用 func1 的第一,二个参数指定
std::function<int(int, int)> func1 = std::bind(Plus, std::placeholders::_1,
std::placeholders::_2);
//auto func1 = std::bind(Plus, placeholders::_1, placeholders::_2);
//func2的类型为 function<void(int, int, int)> 与func1类型一样
//表示绑定函数 plus 的第一,二为指定的数值: 1, 2
auto func2 = std::bind(Plus, 1, 2);
cout << func1(1, 2) << endl;
cout << func2() << endl;//参数数值被bind了,调用func2就一直是1+2了
Sub s;
// 绑定成员函数,传入this指针的类型
std::function<int(int, int)> func3 = std::bind(&Sub::sub, &s,
std::placeholders::_1, std::placeholders::_2);
// 参数调换顺序
std::function<int(int, int)> func4 = std::bind(&Sub::sub, &s,
std::placeholders::_2, std::placeholders::_1);
cout << func3(1, 2) << endl;//相当于1-2
cout << func4(1, 2) << endl;//参数顺序调整了,相当于2-1
return 0;
}
总结:
可以用实际值绑定所传递参数
可以用placeholders::调换形参顺序,也可以调整形参个数
适配成员函数时,调整形参顺序前要先传一个this指针
C++11还封装了线程库和其他内容,后续介绍......