C++11新特性
C++11是曾经被叫做C++0x,是对目前C++语言的扩展和修正,C++11不仅包含核心语言的新机能,而且扩 展了C++的标准程序库(STL),并入了大部分的C++ Technical Report 1(TR1)程序库(数学的特殊 函数除外)。
C++11包括大量的新特性:包括lambda表达式,类型推导关键字auto、decltype,和模板的大量改进。 编译器通过添加
-std=c++11
或者-std=c++0x
参数来支持C++11。
核心语言运行时性能强化
以下特性基本上用于提升程序运行时的性能。
右值引用和move语义
右值引用(R-values)是C++11标准中一个令人难以捉摸的特性。在C语言时代,左值和右值的定义如下: 左值是一个可以出现在赋值运算符左边或者右边的表达式exp,而右值是只能出现在右边的表达式。 例如:
int a = 1;
int b = 2;
// a和b是左值
a = b; // ok
b = a; // ok
// a * b是右值
a * b = 11; // error
在C++中由于自定义类型的引入使得上述的定义变得不那么准确,另一个较好的定义左值是一个指向某内存 空间的表达式,并且我们可以用&操作符获得该内存空间的地址,右值就是非左值的表达式。
int a = 1; // ok
int *ptr = &a; // ok
int& func();
func() = 42; // ok
// ptr, a和func()都是左值
int rfunc();
int *ptr1 = &rfunc(); // error rfunc()是右值
int *ptr = &42; // error 42是右值
右值引用的加入主要是为了解决move语义和完美转发。
(未完待续)
常量表达式constexpr
C++11中引入了新的关键字constexpr,其语义是“常量表达式”,即在编译期间可以求值的表达式。如:
constexpr int func(int t) {
return t + 1;
}
constexpr int a = func(1); // ok
constexpr int b = func(cin.get()); // error
constexpr int c = a * 2 + 3; // ok
constexpr还可以用于修饰类的构造函数,在保证如果提供给构造函数的参数都是constexpr的前提下,那 么产生的对象中的所有成员都会是constexpr,可用于各种只能使用constexpr对象的场合。
核心语言编译期间的加强
外部模板
在标准C++中,在编译过程中需要对出现的每一处模板进行实例化;链接时,链接器还要移除重复的实例化代 码,有时候这会大大增加编译和链接的时间。而在C++中已经有强制编译器在特定位置进行实例化的语法了:
template class std::vector<MyClass>;
在C++11中简单地加入了外部模板的语法:
extern template class std::vector<MyClass>;
例如:
// func.h
template <typename T>
void func(T t) { }
// src1.cpp
void test0() {
func<int>(1);
}
// src2.cpp
extern template void func<int>(int);
void test1() {
func<int>(1);
}
核心语言使用性的加强
统一初始化语法
C++11用大括号统一了初始化的方法。
对于POD类型(Plain Old Data):
int arr[3] = {1, 2, 3};
struct tm today = {0};
在类(class/struct)是极简的、属于标准布局,以及它的所有非静态变量成员都是POD时,会被识别为POD。
struct A { int m; }; // POD
struct B { ~B(); int m; }; // non-POD
struct C { C() : m() {}; ~(); int m; }; // non-POD
对于POD类型如上述中类A, new A
和new A()
是不一样的,前者m没有被初始化话,后者被初始化了。
而对于non-POD类型,两种方式m都会被初始化。因此,在C++11中对初始化进行了统一。
int * nums = new int[3]{1, 2, 3}; // C++11 only
class X {
private:
int nums_[4];
public:
X() : nums_{1, 2, 3, 4} {} // C++11 only
};
vector<string> vs = {"first", "second", "third"};
map<string, string> user = {
{"Lady Gaga", "123456789"},
{"Michael Joseph", "987654321"}};
类型推导
在标准C++和C中,使用变量定义时必须指明其类型。但是在模板编程中有时候函数的返回值难以确定,为了 解决上述的问题,C++11引入了两个新的关键字: auto和decltype。 auto的示例如下:
auto a = 0; // int
auto b = 'b'; // char
vecotr<string> vs;
auto it = vs.begin(); // vector<string>::const_iterator
decltype用于评估括号内的类型,对于decltype(expr)其规则如下:
- 如果表达式expr是一个变量,那么代表该变量的类型。
- 如果表达式expr是一个函数,那么代表函数返回值的类型。
- 如果不符合1和2,如果expr是左值,类型为T,那么decltype(e)是T&;如果是右值,则是T。
示例如下:
template <typename X, typename Y>
auto z = [](X x, Y y)->decltype(x+y) { return x + y;}
基于范围的for循环
直接上示例:
int arr[3] = {1, 2, 3};
for(const int& e : arr) {
std::cout << e << std::endl;
}
for(auto& e : arr) { // or auto& e => int& e
e &= 0xFF;
}
Lambda 表达式
C++11中引入了Lambda表达式,既匿名函数。其语法如下
[capture](parameters) -> return_type { body }
[] // body中不能引用外部变量
[=] // body中外部变量以值传递的形式传入
[&] // body中外部变量以引用的形式传入
[x, &y] // body中x值传递的形式传入,y为引用传入
[&, x] // body中除了x为值传递外其他都是引用传递
[=, &x] // body除x为引用传递外,其他外部变量都是值传递
->return_type // 为可选项
示例:
int sum = 0;
vector<int> v{1, 2, 3};
for_each(v.begin(), v.end(), [&sum](int x) {
sum += x;
});
返回值后置的函数定义
在C++03中一下代码是不合法的,因为ReturnType的类型必须在x+y的结果出来后才能确定。
template <typename X, typename Y>
ReturnType Func(const X& x, const Y& y) {
return x + y; // ReturnType 必须是x + y类型
}
template <typename X, typename Y>
decltype(x+y) Func(const X& x, const Y& y) {
return x + y;
} // error: x和y的定义编译器在分析到函数原型的后半部分时才出现
C++11引入了一种新的函数定义声明语法:
template <typename X, typename Y>
auto Func(const X& x, const Y& y) -> decltype(x+y) {
return x + y; // ok
}
委托构造
在以前版本的C++中,构造函数之间不能相互调用。如果要写内容相似的构造函数就必须把相同的内容放到私 有的成员函数中,并且基类的构造函数不能直接成为派生类的构造函数,即使它已经够用了。C++11中允许构 函数调用其他构造函数,称为委托构造。
class User {
public:
User() : User("None", 0){}
User(string name) : User(name, 0) {}
User(string name, ulong id) : name_(name), id_(id) {}
private:
string name_;
ulong id_;
};
class Student : public User{
public:
using User::User;
private:
string school = "xmu"; // c++11 only
};
虚函数重载
在C++里面,派生类容易意外地重载虚函数,比如说
class Base {
public:
virtual void Func();
};
class Derived: Base {
public:
void Func();
};
此时如果基类Base中的虚函数签名被改变,那么派生类将不再重载虚函数,程序员很可能不会注意到这个问 题,从而导致错误的调用。
C++11中加入override来防止上述情况的发生,并在编译期间捕获此类错误,该功能是向下兼容的。
class Base {
public:
virtual void Func();
};
class Derived: Base {
public:
virtual void Func() override; //ok
virtual void Func(int) override; // error
};
C++11中还提供了关键字final来防止类被继承或是函数被重载。
class Base final {};
class Derived : public Base {}; // error: Base is marked as final
class User {
public:
virtual void Func() final;
};
class Student {
public:
virtual void Func(); // error
};
空指针nullptr
在C语言中空指针NULL被定义为((void*)0)
或是0。在C++中不允许void*
隐式转换为别的类型的指针,
为了使代码char *ptr = NULL
通过编译,NULL只能被设为0。这就出现了一些无法区分代码语义的情况。
void Func(char *);
void Func(int);
当你调用Func(NULL)时,想调用上者,实际上将调用的却是Func(int)函数。C++11中引入的空指针nullptr, nullptr的类型为nullptr_t,可以隐式地转换成任何指针类型,也可以进行相等或不等的比较,但不能隐 式转换成整数,也不能与整数进行比较。
强枚举类型
在标准C++中,枚举类型不是类型安全的。枚举类型被视为整型,这使得不同类型之间可以比较。此外枚举类型 所使用的整型的类型无法确定。最重要的是枚举类型名暴露在一般范围中,两个不同的枚举类型不能拥有相同的 枚举名。
enum Side{ LEFT, RIGHT};
enum Dir { UP, DOWN, LEFT, RIGHT}; // error
C++11中需要为枚举类型提供类型,默认为int类型。
enum Enum1; // error
enum Enum2 : unsigned int; // c++11 only
enum class Enum3; // c++11 only, default(int)
enum class Enum4 : unsigned int; // c++11 only
角括号修正
C++11的编译器可以更好地解析右角括号的语法
std::vector<vector<int>> v; // error with C++03 but ok with C++11