C++ 11 新增了类型 long long 和 unsigned long long ,以支持 64 位(或更宽)的整型;新增了类型 char16_t 和 char32_t ,以支持 16 位和 32 位的字符表示;还新增了“原始字符串”。
C++ 11 扩大了用大括号括起的列表(初始化列表)的适用范围,使其可用于所有内置类型和用户定义的类型(即类对象)。使用初始化列表时,可添加等号,也可不添加:
// 常规类型
int x = {5};
double y {2.75};
short quar[5] {4, 5, 2, 76, 1};
int * ar = new int [4] {2, 4, 6, 7}; // new表达式// 类
class Stump {
private:int roots;double weight;
public:Stump(int r, double w) : roots(r), weight(w) {}
};
Stump s1(3, 15.6); // old style
Stump s2{5, 43.4}; // c++11
Stump s3 = {4, 32.1}; //c++11// 缩窄转换
char c1 = 1.57e27; // double to char, allow but undefined behavior
char c2 = 459585821 // int to char, allow but undefined behavior
// 防止缩窄转换
char c1 {1.57e27}; // double to char, not allow
char c2 = {459568434}; // int to char, out of range, not allow
// 允许转换为更宽的类型
char c1 {66}; // int to char, in range, allow
double c2 = {66}; // int to double, allow// 模板
vector a1(10); // 未初始化的,大小为10的向量
vector a2{10}; // 初始化列表,大小为1,值为10;
vector a3{4, 6, 1}; // 初始化列表,大小为3,值为4 6 1
C++ 11 提供了多种简化声明的功能:
auto maton = 112; // 自动推断 int
auto pt = &maton; // int *
double fm(double, int);
auto pf = fm; // double (*)(double, int)
decltype(x) y; // 将y类型转换为x的类型
double x; int n;
decltype(x*n) q; // double
decltype(&x) pd; // double *// 模板常用方法
template
void ef(T t, U u) {decltype(T*U) tu; // 模板实例化时确定类型...
}// 返回类型后置
double f1(double, int); // 传统语法
auto f2(double, int) -> double; // 新语法,返回double
auto eff(T t, U u) -> decltype(T*U); // 常规用法// 模板别名
typedef std::vector::iterator itType; // 传统别名,不能用于模板部分具体化
using itType = std::vector::iterator; // C++ 11新方法,可以用于模板部分具体化
template
using arr12 = std::array; // 模板部分具体化// 空指针
nullptr <=> 0
如果在程序中使用 new 从堆分配内存,等到不再需要时,应使用 delete 释放,新增智能指针帮助自动化完成该过程。C++ 11 摒弃了 auto_ptr ,新增了 unique_ptr 、shared_ptr 、weak_ptr 。
用于指出函数可能引发哪些异常:
// 传统方法,因不好用被C++11摒弃
void f501(int) throw(bad_dog); // 抛出bad_dog异常
void f733(long long) throw(); // 不会引发异常
// 第二种情况可能有意义,因此新增关键字noexcept
void f875(short, short) noexcept; // 不会引发异常
传统枚举:
为解决这些问题,新增枚举:
// 旧枚举
enum old {yes, no, matbe};
// 新枚举,需要显示指定枚举成员作用域
enum class new1 {never, sometimes, often, always};
enum struct new2 {never, lever, sever};
禁止隐式转换运算符:
class Plebe {operator int() const;explicit operator double() const;
};
Plebe a, b;
int n = a; // 隐式转换为int
double x = b; // 隐式转换为double,not allow
x = double(b); // 显示转换为double,allow
类内成员初始化:
class Session {int mem1 = 10; // 类内初始化double mem2 {1966,.54}; // 类内初始化short mem3;
public:Session(){}Session(short s) : mem3(s) {}Session(int n, double d, short s) : mem1(n), mem2(d), mem3(s) {}
};
可以使用等号和大括号初始化,但不能使用圆括号。通过类内初始化可以避免在构造函数内编写重复代码。
基于范围的 for 循环:
vector v(10);
for(int i : v)cout << i << endl;
新增 STL 容器:
容器 | 作用 |
---|---|
forward_list | 单向链表 |
unordered_map | 无序 map |
unordered_multimap | 无序、可重复 map |
unordered_set | 无序 set |
unordered_multiset | 无需、可重复 set |
array | 数组 |
新增 STL 方法:
方法 | 说明 |
---|---|
cbegin() | 指向容器第一个元素,并将元素视为 const |
cend() | 指向容器最后一个元素的后面,并将元素视为 const |
对 valarray 进行升级,添加了 begin() 和 end() ,使之可以被迭代器访问,从而能够将基于范围的 STL 算法用于该容器。
摒弃 export ,原本作用为让程序员能够将模板定义放在接口文件和实现文件中,其中前者包含原型和模板声明,后者包含模板函数和方法的定义。
新增尖括号的识别,避免了与 >> 运算符混淆。
左值是一个表示数据的表达式(变量名、解除引用的指针),最初只能出现在赋值语句左边,但是随着 const 引入,也可以出现在赋值语句右边:
int n;
int * pt = new int;
const int b = 101; // 不能修改b
int & rn = n; // 引用n,即取到了n的地址
int & rt = *pt; // 取到了指针的地址
const int & rb = b; // 取到了b的地址,但不能修改rb
右值则是一个表示运算的表达式(不能对其赋值),不能应用取地址符,这种方式更像是创建并保存了一个表达式结果的副本,并引用了这个副本:
int x = 10;
int y = 23;
int && r1 = 13;
int && r2 = x + y; // r2关联运算的结果,因此改变x或y不影响r2
double && r3 = sqrt(2.0);
double *p = &r3; // 可以取到右值的地址
vector vstr(20000, string(1000)); // 2w个string,每个string有1k个char
vector vstr_copy1(vstr);
vector
和 string
类都使用动态内存分配,因此它们必须定义使用某种 new
版本的拷贝构造函数。为初始化对象 vstr_copy1
,拷贝构造函数 vector
将使用 new
给 2 万个 string
对象分配内存,而每个 string
对象又将调用 string
的拷贝构造函数,该构造函数使用 new
为 1 千个字符分配内存。接下来全部的字符都将从 vstr
控制的内存种复制到 vstr_copy1
控制的内存中。这样的工作量巨大,并且存在问题:
vector allcaps(const vector & vs) {vector tmp;// todo: tmp存储了vs全大写版本的数据return tmp;
}
vector vstr(20000, string(1000));
vector vstr_copy1(vstr); // 拷贝vstr
vector vstr_copy2(allcaps(vstr)); // 创建tmp变量,函数运行结束时删除tmp变量,然后返回其它的副本,非常浪费资源
解决这个问题的方法为不删除 tmp 变量,而是转移所有权,该操作称为移动语义(move semantics),这样避免了删除和拷贝原数据。要实现它,需要让编译器直到何时需要复制,何时不需要,这就是右值发挥作用的地方。可以定义两个构造函数,一个是常规拷贝构造函数,使用 const 左值引用作为参数,这个引用关联到左值实参,如语句 1 的 vstr ;另一个是移动构造函数,使用右值引用作为参数,该引用关联到右值实参,如语句 2 种 allcaps 函数的返回值 tmp 。拷贝构造函数可执行深复制,而移动构造函数只负责调整记录(转换所有权)。由于移动构造函数可能修改其实参,因此右值引用不应是 const 。
Useless::Useless(Useless && f) : n(f.n) { // 此处的f可以是运算表达式++ct;pc = f.pc; // pc是一个指针(私有成员),指向现有数据f.pc = nullptr; // 转换了所有权f.n = 0; // 转换了所有权后原来的对象的长度变为0
}
Useless four(one + three); // 传递一个对象运算表达式,这将生成临时对象,然后调用有移动构造函数
虽然右值引用可支持移动语义,但是需要执行两个步骤来执行。
Useless two = one; // 拷贝构造函数
Useless our(one + three); // 移动构造函数
对象 one 是左值,因此与左值引用匹配,调用拷贝构造函数;one + three 是右值,与右值引用匹配,调用移动构造函数。