C++11 新特性¶
auto 与 decltype¶
auto 可以让编译器在编译的时候就自动推导出右值的类型,然后赋给左值。因此,使用 auto 来定义编译必须附上初始值。
auto i = v.begin();
vector<int>::iterator i = v.begin();
decltype 可以像函数一样传入一个变量作为参数,然后“返回”变量的类型用与使用。这种方法在定义变量的时候与 auto 很像,但是更多地用作定义新的类型使用。
vector<int> a;
decltype(a) b = a;
// 在标准库当中
typedef decltype(nullptr) nullptr_t;
左右值¶
- 左值:有命名、可以取地址的对象;
- 右值:不能取地址、没有名字的对象。
这样子可能不是很理解,可以看一段代码。
int a = 1, b = 2;
int c = a + b;
在这里,\(a\) 和 \(b\) 都是左值,是可以赋值的对象,并且有名字。\(1\) 和 \(2\) 还有 \(a+b\) 都是右值,他们是不能赋值、只能在等号右边的东西。其中,\(a+b\) 是“纯右值”,是在运算过程中产生出的临时变量。
左值与右值的引用也完全不同。左值的引用使用的是 &
,表示引用一个左值。右值的引用是 &&
,表示引用的是一个右值(而不是引用引用)。
int a, &b = a; // 引用左值
b = 4; // a 也变成了 4
const int &c = 10; // 常引用,引用常量
int a = 1, b = 2;
int &&c = a + b; // ok,引用了纯右值
int &&d = std::move(a); // 转化为右值
范围 for 循环¶
对于有迭代器的对象(包括数组),可以使用范围 for 循环简单地获取里面所有的值。有迭代器的 STL:vector, list, deque……注意默认只能正向迭代,反向迭代还是要手写。
// 甚至 C++11 之前连 auto 都不能使
for (auto i = v.begin(); i != v.end(); i++) {
cout << *i << ' ';
}
for (int i : v) {
cout << i << ' ';
}
列表初始化¶
在 C++11 中,可以使用一对花括号来对数组、vector 和对象进行初始化,也可以用于函数的返回值的初始化。
struct A {
int a;
A(const int &a) {
this->a = a;
}
}
A a(114514);
A b = {114514};
A c {114514};
vector<int> v {1, 1, 4, 5, 1, 4};
int a[] {1, 1, 4, 5, 1, 4};
可以使用 std::initializer_list
来接受列表,并且支持任意长度。比如,一下代码就可以在多个值当中求出最大值,但是需要使用花括号括起来。
template <typename Ty>
Ty max(std::initializer_list<Ty> list) {
if (list.empty()) {
return Ty();
}
Ty mx = *list.begin();
for (int i : list) {
mx = max(mx, i);
}
return mx;
}
Lambda 表达式¶
在 C++11 中,可以使用 Lambda 表达式来定义匿名函数,让你的代码变得更加灵活简介。特别是在排序的时候了,这时你就没有必要去思考这个排序函数到底需要使用什么名字,而是直接调用就行了。
Lambda 表达式的定义方式如下:
[调用方式](参数列表) -> 返回值 {
函数体
};
比如,可以使用这样子的方式定义一个匿名的计算两数之和的函数,并赋给 plus
这个对象。
auto plus = [](int a, int b) {
return a + b;
};
你肯定会说,这里的调用方式是空的啊。别急,我来详细讲解:
调用方式 | 最终结果 |
---|---|
[] |
什么也不会捕获,Lambda 表达式无法使用外部变量 |
[=] |
按赋值方式捕获所有的外部变量 |
[&] |
按引用方式捕获所有的外部变量 |
[=,&a] |
引用捕获 \(a\),其他按赋值方式捕获 |
[&,a] |
复制捕获 \(a\),其他按引用方式捕获 |
[a,&b] |
以指定方式捕获指定变量 |
[this] |
捕获 this 指针 |
比如,可以这样获得每一个数排序之前的位置。
for (int i = 1; i <= n; i++) {
id[i] = i;
}
sort(id + 1, id + n + 1, [](int &x, int &y)) {
return a[x] < b[y];
};
constexpr¶
constexpr
是 C++11 的新型关键字,用于修饰编译时的常量。与 const
不同的是,const
修饰的变量只是在运行的时候不会被更改,但是在内存中它仍然是动态的。constexpr
才是真正的常量。
当它修饰一个常量的时候,这个常量在编译的时候就会替换后面的值。当它修饰一个函数的时候,如果这个函数的值可以直接在编译的时候算出来,那么它就会直接算出来,否则就像普通的函数一样处理。
template <typename Ty>
constexpr Ty max(const Ty &a, const Ty &b) {
return a > b ? a : b;
}
int a, b;
int main() {
cin >> a >> b;
cout << max(0, 1) << '\n'; // 直接算出来
cout << max(a, b) << '\n'; // 当做普通的函数
return 0;
}
final 与 override¶
两者都是 C++ 在类与对象当中的补充。final
可以修饰一个类,表示这一个类禁止被继承。override
用来修饰派生类里面重写的基类函数,避免重写错误。
struct A final {
void out() {
cout << "好鱼鱼炸裂!" << endl;
}
};
struct B : public A { // 错误,无法继承
void out() override { // 重写成功
cout << "好渴鹅炸裂!" << endl;
}
void print() override { // 错误,父类没有该函数
cout << "输出!" << endl;
}
}
default 与 delete¶
在 C++11 之前,自己定义的类如果已经自己定义了其他构造函数,那么编译器就不会自动生成默认构造函数,因此你就无法直接初始一个对象。现在,只需要在构造函数后面加上 =default
,那么编译器就会自动生成默认构造函数和拷贝构造函数等。
struct Node {
Node(int a) { }
} a; // 找不到构造函数
struct Node {
Node() = default;
Node(int a) { }
} a; // ok
而如果你的类里面没有定义拷贝的成员函数,那么编译器就会自动生成拷贝的构造函数和 operator=
运算符。但是有时候我们并不想要对象进行拷贝操作,那么我们就可以在后面标上 =delete
的标识,表示让编译器不要自动生成。
struct Node {
Node() = default;
Node(const Node &n) = delete;
Node &operator=(const Node &n) = delete;
};
这样就不能进行拷贝操作了。
explicit¶
explicit
是用来修饰构造函数的,被修饰的构造函数只能被显式地构造,而不能隐式转换,通俗来说就是需要强制类型转换。比如:
struct A {
A(int x) { }
};
struct B {
explicit B(int x) { }
};
A a = 1; // 可以转换
B b = 1; // 不行
b(1); // 可以
模板的改进¶
右尖括号改进¶
在 C++11 以前,我们定义一个小根堆需要写 std::priority_queue<int, std::vector<int>, std::greater<int> >
,很多人搞不懂为什么两个右尖括号之间需要空上一格,其实是因为不加空格的话,编译器会认为这是一个右移运算符而不是模板的参数。而在 C++11 之后,这个空格可以省略了,只需要写 std::priority_queue<int, std::vector<int>, std::greater<int>>
即可。
值得注意的是,greater<int>
当中的 int
也可以省略,以为默认模板参数已经定义成了 int
了。
std::priority_queue<int, std::vector<int>, greater<>> q; // ok
函数模板默认参数¶
而在 C++11 之后,函数模板也支持了默认参数了。(而在以前只有类模板可以使用)
template <class Ty = int, class Tp>
Ty max(Ty a, Tp b) {
return a;
}
甚至可以直接跳过 Ty
,把参数传入 Tp
这种写法也可以。(在函数形参里面只有最后面几个形参支持默认参数)
max<double>(1, 1.0); // ok
加强版 using¶
C++11 还引入了 using
来定义别名。很多人觉得它跟 typedef
没有什么不同,但实际上 using
的功能更加强大,因为它支持使用模板。比如可以以以下的方式来定义一个小根堆:
template <class Ty>
using heap = std::priority_queue<Ty, std::vector<Ty>, std::greater<Ty>>;
heap<int> q; // ok
C++11 还对参数模板进行了优化,衍生出了“变长参数模板”。比如,可以以一下方式来定义一个输出若干个数的函数:
template <class Ty>
void debug(const Ty &x) { // 递归的边界
cout << x << '\n'; // 只输出一个数
}
template <class Ty, class... More>
void debug(const Ty &x, More... more) {
cout << x << ' '; // 输出当前的第一个数
debug(more...); // 输出后面的数
}
线程¶
thread¶
C++11 引入了 std::thread
用来创建线程。话不多说,看代码可能更直观一些:
using namespace std;
auto haokee = [](int x = 0) {
cout << "我是好渴鹅!" << x << endl;
this_thread::sleep_for(1s);
};
thread t1(haokee); // 不添加参数
t1.join(); // 堵住当前线程来执行,程序会堵上一秒
cout << "执行完成" << endl; // 执行完成
thread t2(haokee, 114514); // 加入参数
t2.detach(); // 不等它一起运行,可能没有输出(因为主线程已经结束了)
对于一个线程,我们可以使用 join
来让其堵住一秒的时间;可以使用 detach
来让这个线程与主线程一起执行。需要注意的是,当主线程已经执行完了的话,那么其他还没有执行完的线程就会在后台运行,直到运行结束为止。还有一个函数是 joinable
,使用来判断一个线程能否 join
或 dedtach
的。
注意一个线程必须调用这两个函数当中的一个一次,不然就会发生错误。