程序设计实习(9) —— C++ 特性

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):

  1. 如果 e 是一个没有带括号的标记符表达式或者类成员访问表达式, 那么的 decltype(e) 就是 e 所代表的实体的类型.如果没有这种类型或者 e 是一个被重载的函数, 则会导致编译错误.
  2. 如果 e 是一个函数调用或者一个重载操作符调用, 那么 decltype(e) 就是该函数的返回类型.
  3. 如果 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
}

移动规则:

  1. 只写复制构造函数

    • return 局部对象 -> 复制
    • return 全局对象 -> 复制
  2. 只写移动构造函数

    • return 局部对象 -> 移动
    • return 全局对象 -> 默认复制
    • return move(全局对象) -> 移动
  3. 同时写复制构造函数和移动构造函数:

    • 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 起

overridefinal 关键字

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;
}
本文遵循 CC BY-NC-SA 4.0 协议
使用 Hugo 构建