C++11语言新特性
微小但重要的语法提升
vector<list<int> >; //表达式闭符之间放一个空格
vector<list<int>>;
nullptr
C++11允许使用nullptr取代0或NULL,用来表示一个指针指向所谓的no value。这个新特性能够帮助你在null point被解释为一个数值时避免误解。
void f(int);
void f(void*);
f(0); //calls f(int)
f(NULL) //calls f(int) if NULL is 0,ambiguous otherwise
f(nullptr); //calls f(void*)
nullptr是个新关键字。它被自动转换为各种point类型,但不会被转换为任何整数类型,它拥有类型std::nullptr_t.
以auto完成类型自动推导
C++11允许声明一个变量或对象而不需要指明其类型,只需说它时auto
auto i = 42; //i has type int
double f();
auto d = f(); //d has type double
以auto声明的变量,其类型会根据初始值被 自动推导出来,因此一定需要一个初始化操作:
auto i; //ERROR:can't deduce the type of i
//可以为它加上额外的限定符,
static auto vat = 0.19;
如果类型很长或表达式很复杂,auto特别有用,
void print(std::initializer_list<int> vals)
{
for(auto p = vals.begin();p!=vals.end();++p){
std::cout << *p << std::endl;
}
}
Range-Based for循环
C++引入了一种崭新的for循环形式,可以逐一迭代某个给定的区间、数组、集合内的每一个元素,
for(decl:coll){
statement
}
其中,decl是给定之coll集合中的每个元素的声明;针对这些元素,给定的statement会被执行。
for(int i :{2,3,4,5,6,7,9,13,16,19}){
std::cout << i << std::endl;
如果要将vector vec的每个元素elem乘以3,可以这么做:
std::vector<double>vec;
...
for(auto& elem:vec){
elem*=3;
}
这里“声明elem为一个reference"很重要,若不这么做,for循环中的语句会作用到元素的一份local copy身上,这就意味着为了避免调用每个元素的copy构造函数和析构函数,你通常应该声明当前元素为一个const reference。于是一个用来”打印某集合内所有元素“的泛型编程应该写成这样:
template<typename T>
void printElements(const T&coll){
for(const auto&elem:coll){
std::cout << elem << std::endl;
}
}
等价于
{
for(auto _pos = coll.begin();_pos !=coll.end();++_pos){
const auto& elem = *_pos;
std::cout << elem << std::endl;
}
}
Move语义和Rvalue Reference
C++11的一个最重要的特性就是,支持move semantic(搬迁语义)。这项特性更进一步进入了C++主要设计目标内,用以避免非必要拷贝(copy)和临时对象(tempprary)。
考虑以下代码:
void createAndInsert(std::set<X>&coll)
{
X x; //create an object of type X
...
coll.insert(x); //insert it into the passed collection
}
在这里我们将新对象插入集合(collection)中,后者提供一个成员函数可为传入的元素建立一份内拷贝(internal copy)
namespace std{
template<typename T,...> class set{
public:
...
insert(const T& v); // copy value of v
...
}
}
这样的行为是有用的,因为集合(collection)提供value semantic 及安插“临时对象”(temporary object)或 “安插后会被使用或改动的对象”的能力:
X x;
coll.insert(x); //inserts copy of x
...
coll.insert(x + x);//inserts copy of temporary rvalue
...
coll.insert(x); //inserts copy of x (although x is not used any longer)
然而,对于后两次x安插动作,更好的是指出“被传入值(也就是x + x的和以及x)不在被调用者使用”,如此一来coll内部就无须为它建立一份copy且“以某种方式move其内容进入新建元素中”。当x的复制成本高昂的时候——例如它是个巨大的string集合——这会带来巨大的效能提高。
自C++11起,上述行为成为可能,然而程序员必须自行指明“move是可行的,除非被安插的 那个临时对象还会被使用”。虽然编译器自身也有可能找出这个情况(在某些浅薄无奇的情况下),允许程序员执行这项工作毕竟可使这个特性被用于逻辑上任何适当之处。先前的代码只需要简单改成这样:
X x;
coll.insert(x); //inserts copy of x(OK ,is still used)
...
coll.insert(x + x);//moves(or copies)contents of temporary rvalue
...
coll.insert(std::move(x)); //moves(or copies) contents of x into coll
有了声明于< utility >的std::move(),x可被move而不被copied。然而std::move()自身并不做任何moving工作,它只是将其实参转成一个所谓的rvalue reference,那是一种被声明为X&&的类型。这种新类型主张rvalue(不具名的临时对象只能出现于赋值操作的右侧)可被改动内容。这份契约的含义是,这是个不在被需要的(临时)对象,所以你可也“偷”其内容和/或其资源。
现在,我们让集合(collection)提供一个insert()重载版本,用以处理这些rvalue reference:
namespace std{
template<class T,...> class set{
public:
...
insert(const T& x); //for lvalues:copies the value
...
insert(T&& x); //for rvalues:moves the value
...
};
}
Lambda
C++11 引入了lambda,允许inline函数的定义式被用作一个参数,或是一个local对象。Lambda改变了C++标准库的用法。
Lambda的语法
所谓lambda是一份功能定义式,可被定义于语句(statement)或表达式(expression)内部。因此你可以拿lambda当作inline函数使用。
最小型的lambda函数没有参数,什么也不做,如下:
[]{
std::cout <<"hello lambda" << std::endl;
}
//可以直接调用它:
[]{
std::cout << "hello lambda" << std::endl;
}(); //prints "hello lambda"
//或是把它传递给对象,使之被调用:
auto l = []{
std:: cout << "hello lambda" <<std::endl;
};
...
l(); //prints "hello lambda"
如你所见,lambda总是一个由所谓的lambda introducer引入:那是一组方括号,你可以在其内指明一个所谓的capture,用来在lambda内部访问“nonstatic外部对象”。如果无须访问外部数据,这组方括号可以为空,就像本例所示。Static对象,诸如std::cout,是可被使用的。
在lambda introducer 和lambdabody之间,你可以指明参数或mutable,或一份异常明细(exception specification)或attribute specifier以及返回类型。
#include<functional>
#include<iostream>
std::function<int(int,int)> returnLambda()
{
return [](int x,int y){
return x*y;
};
}
int main()
{
auto lf = returnLambda();
std::cout << lf(6,7) << std::endl;
}
然而请注意,lambda不可以是template。你始终必须指明所有类型。