C++11 特性
统一的初始化方法
1
2
3
4
|
int x{5};
int y{};// 默认初始化为 0(不同于 int y; 未初始化)
int arr[3]{1, 2, 3};
vector<int> iv{1, 2, 3};
|
这种方法可以防止缩窄类型转换.
1
2
|
char c1 = 3.14e10 // ok
char c2{3.14e10} // 编译错误
|
C++11 提供了模板类 initializer_list
, 可将其用作构造函数的参数
1
2
3
4
5
6
7
8
|
double SumByIntialList(std::initializer_list<double> il) {
double sum = 0.0;
for (auto p = il.begin(); p != il.end(); p++) {
sum += *p;
}
return sum;
}
double total = SumByIntialList({ 2.5, 3.1, 4 });
|
MVP 问题
Most Vexing Parse(MVP) 是 C++ 中一个经典的语法歧义问题, 源于编译器将对象初始化语句错误解析为函数声明, 导致代码行为与预期不符. C++ 优先认为是函数声明而不是对象定义.
1
2
|
class Timer { /* ... */ };
Timer t(); // 函数声明
|
auto
关键字
auto
关键字用于自动推导变量类型, 编译器根据初始化表达式的类型来确定变量的类型.
1
2
3
4
5
6
|
auto x = 5; // x 的类型为 int
auto p = new A() // p 的类型为 A*
vector<int> v;
for (auto it = v.begin(); it != v.end(); ++it) {
cout << *it << " ";
} // it 的类型为 vector<int>::iterator
|
函数的返回值类型也可以使用 auto
关键字来推导.
1
2
3
4
5
6
7
8
9
|
A operator+(int n, const A &a) {
return a;
}
template <class T1, class T2>
auto add(T1 x, T2 y) -> decltype(x + y) {
return x + y;
}
auto d = add(100, 1.5); // d 是 double d=101.5
auto k = add(100, A()); // d 是 A 类型
|
decltype
关键字
对于 decltype(e)
:
- 如果
e
是一个没有带括号的标记符表达式或者类成员访问表达式, 那么的 decltype(e)
就是 e
所代表的实体的类型.如果没有这种类型或者 e
是一个被重载的函数, 则会导致编译错误.
- 如果
e
是一个函数调用或者一个重载操作符调用, 那么 decltype(e)
就是该函数的返回类型.
- 如果
e
不属于以上所述的情况, 则假设 e
的类型是 T
: 当 e
是一个左值时, decltype(e)
就是 T&
; 否则 (e
是一个右值), decltype(e)
是 T.
1
2
3
4
5
6
7
8
|
int i;
double t;
struct A { double x; };
const A* a = new A();
decltype(a) x1; // A* (规则 2)
decltype(i) x2; // int (规则 1)
decltype(a->x) x3; // double (规则 1)
decltype((a->x)) x4 = t; // double& (规则 3)
|
基于范围的 for 循环
1
2
3
4
|
int ary[] = {1,2,3,4,5};
for (int &e: ary) {}
vector<int> v = {1,2,3,4,5};
for (auto &it: v) {}
|
lambda 表达式
格式:
1
|
[capture] (parameters) -> return_type { body }
|
-> return_type
也可以没有, 没有则编译器自动判断返回值类型.
关于外部变量访问方式说明符:
[]
不使用任何外部变量
[=]
以传值的形式使用所有外部变量
[&]
以引用形式使用所有外部变量
[x, &y]
x
以传值形式使用, y
以引用形式使用
[=, &x, &y]
x, y
以引用形式使用, 其余变量以传值形式使用
[&, x, y]
x, y
以传值的形式使用, 其余变量以引用形式使用
1
2
|
int a[4];
sort(a, a + 4, [](int x, int y) -> bool { return x % 10 < y % 10; });
|
1
2
3
|
function<int(int)> fib = [](int n) {
return n <= 2 ? 1 : fib(n-1) + fib(n-2);
}; // function<int(int)> 表示参数为一个 int, 返回值为 int 的函数
|
右值引用和 move 语义
一般来说, 不能取地址的表达式, 就是右值, 能取地址的 (代表一个在内存中占有确定位置的对象), 就是左值
1
2
3
|
class A;
A &r = A(); // error , A() 是无名变量, 是右值
A &&r = A(); //ok, r 是右值引用
|
主要目的是提高程序运行的效率, 减少需要进行深拷贝的对象进行深拷贝的次数.
1
2
3
4
5
6
7
8
9
10
11
12
13
|
String(String &&s): str(s.str) {
// 将 s.str 的所有权转移到当前对象
cout << "move constructor called" << endl;
s.str = new char[1];
s.str[0] = 0;
}
template<class T>
void MoveSwap(T& a, T& b) {
T tmp(move(a)); // std::move(a) 为右值, 这里会调用 move constructor
a = move(b); // move(b) 为右值, 因此这里会调用 move assigment
b = move(tmp); // move(tmp) 为右值, 因此这里会调用 move assigment
}
|
移动规则:
-
只写复制构造函数
- return 局部对象 -> 复制
- return 全局对象 -> 复制
-
只写移动构造函数
- return 局部对象 -> 移动
- return 全局对象 -> 默认复制
- return move(全局对象) -> 移动
-
同时写复制构造函数和移动构造函数:
- return 局部对象 -> 移动
- return 全局对象 -> 复制
- return move(全局对象) -> 移动
可移动但不可复制:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
struct A{
A(const A &a) = delete;
A(const A && a) { cout << "move" << endl; }
A() { };
};
A b;
A func() {
A a;
return a;
}
void func2(A a) { }
int main() {
A a1;
A a2(a1); // compile error
func2(a1); // compile error
func();
return 0;
}
|
智能指针 shared_ptr
让 shared_ptr 对象托管一个 new 运算符返回的指针.
1
2
|
#include <memory> // 头文件
std::shared_ptr<T> ptr(new T);
|
多个 shared_ptr
对象可以同时托管一个指针, 系统会维护一个托管计数. 当无 shared_ptr
托管该指针时, delete
该指针.
1
2
3
4
5
|
shared_ptr<A> sp1(new A(2)); // sp1 托管 A(2)
A* p = sp1.get(); // p 指向 A(2)
shared_ptr<A> sp2(sp1); // sp2 也托管 A(2)
shared_ptr<A> sp3 = sp1; //sp3 也托管 A(2)
sp2.reset() // sp2 放弃对 A(2) 的托管, 变为 nullptr
|
创建 shared_ptr
对象时, 可以使用 make_shared
函数创建空对象. 添加托管时, 一定要用另一个已存在的 shared_ptr
对象来添加托管, 不能用原生指针.
1
2
3
4
5
6
7
8
9
|
int main() {
A* p = new A();
shared_ptr<A> ptr(p);
shared_ptr<A> ptr2;
ptr2.reset(p); // 并不增加 ptr 中对 p 的托管计数
cout << "end" << endl;
return 0;
// 程序会崩溃, A 对象会被 delete 两次
}
|
空指针 nullptr
1
2
3
4
5
6
7
8
9
10
11
12
|
int main() {
int* p1 = NULL;
int* p2 = nullptr;
shared_ptr<double> p3 = nullptr;
if (p1 == p2); // yes
if (p3 == nullptr); // yes
if (p3 == p2); // error
if (p3 == NULL); // yes
bool b = nullptr; // error, bool b(nullptr); ok
int i = nullptr; // error, nullptr 不能自动转换成整型
return 0;
}
|
关于 nullptr
的定义:
1
2
3
|
#define NULL ((void *)0) // C 语言
#define NULL 0
#define NULL nullptr // C++11 起
|
override
和 final
关键字
1
2
3
4
5
6
|
class Base {
virtual void foo() final {} // 禁止子类重写
};
class Derived : public Base {
void foo() override {} // 显式标记重写(编译时报错,因为基类已 final)
};
|
无序容器 (哈希表)
哈希表插入和查询的时间复杂度几乎是常数, 不过内存占用较高.
1
2
3
4
5
|
#include <unordered_map> // 头文件
int main() {
unordered_map<string, int> map;
auto p = map.find(name); // p 是一个迭代器
}
|
正则表达式
1
2
3
4
5
6
|
#include <regex> // 头文件
int main() {
regex reg("b.?p.*k");
cout << regex_match("bopggk",reg) << endl; // 输出 1, 表示匹配成功
cout << regex_match("boopgggk",reg) << endl; // 输出 0, 匹配失败
}
|
多线程
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
#include <thread> // 头文件
struct MyThread {
void operator()() {
while(true)
cout << "IN MYTHREAD\n";
}
};
void my_thread(int x) {
while(x)
cout << "in my_thread\n";
}
int main() {
MyThread x; // 对 x 的要求:可复制
thread th(x); // 创建线程并执行
thread th1(my_thread, 100);
while(true)
cout << "in main\n";
return 0;
}
|
C++14 特性
泛型 lambda
lambda 参数支持 auto,实现泛型:
1
2
3
|
auto print = [](const auto& x){ std::cout << x; };
print(42); // int
print("hello"); // const char
|
二进制字面量和数字分隔符
提高可读性:
1
2
|
int bin = 0b1100'1010; // 二进制表示
double pi = 3.1415'9265;
|
智能指针 unique_ptr
1
2
|
auto ptr = std::make_unique<MyClass>(42, "example");
auto arr = std::make_unique<int[]>(10); // 管理 10 个 int 的数组
|
C++17 特性
结构化绑定
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
struct Point { int x; int y; };
int main() {
// 结构体绑定
Point p{3, 4};
auto [a, b] = p;
// 数组绑定
int arr[] = {5, 6};
auto& [c, d] = arr;
c = 7; // arr[0] = 7
// 元组绑定
auto t = std::make_tuple(8, 9.5, 'A');
auto [e, f, g] = t;
// 引用语义
auto& [x, y] = p;
x = 10; // p.x = 10
}
|
if/switch 初始化语句
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
if (auto it = m.find(key); it != m.end()) {
// it 仅在此作用域有效
}
switch (auto code = fetch_status(); code) {
// code 仅在此作用域有效
case 200:
std::cout << "OK" << endl;
break;
case 404:
std::cout << "Not Found" << endl;
break;
default:
std::cout << "Unknown code: " << code << endl;
}
|