Huawei: 现代C++与STL

Franky lol

现代C++与STL

vector

  • 建在堆上,可伸缩,可能有预留空间的动态数组

  • end()只能用于表示位置,不能用于访问元素

  • vector构造/析构/拷贝/移动时,其中所有元素自动构造/析构/拷贝/移动

  • 增大capacity时,其实是把原vector复制一遍,再把新加入的元素拷贝到新的地址,然后释放原来的vector的内存。因此当capacity扩大后,原迭代器会失效。

  • 如果元素类型是指针,vector不管指针所指向的对象的生命周期

  • ```cpp
    vector v(3);
    v.push_back(1);
    v.push_back(1);
    //v.capacity() == 6
    vector v2 = v;
    //v2.capacity() == 5

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23

    ##### vector的内存管理

    - 插入元素时,会分配新的内存空间,把pos之前的元素拷贝到新空间,插入新元素,再将pos及之后的元素拷贝到新空间,最后销毁原空间。**开销很大**

    ## 迭代器

    - 迭代器支持原生指针的多数操作,vector的迭代器支持所有操作

    - **迭代器的有效性**

    有些容器的某些操作会使已创建的迭代器失效

    尽量避免在遍历的同时增删元素

    ## 泛型算法

    ```cpp
    sort(vec.begin(), vec.end());
    sort(arr, arr + 3);

    //原生数组和其他容器都支持
    std::sort(std::begin(x), std::end(x));

string

1
2
3
4
5
6
7
8
9
10
std::string s1 = "abc";
std::string s2 = "abc";

//字符串常量是左值,所有内容相同的字符串常量都共用一个地址
cout<<&"abc"<<endl;
cout<<&"abc"<<endl;
/**输出
0x100a1bf94
0x100a1bf94
*/
  • capacity和size永远相等,按需扩展,和vector的capacity成倍增长不同

  • 尽可能使用std::string,不用c风格字符串

固定宽度类型

  • C++内置类型的short、int、long等类型的字节宽度在不同平台是不一样的,应当优先使用C++11标准库定义的跨平台的固定宽度类型:int8_t int16_t int32_t int64_t uint8_t uint16_t uint32_t uint64_t

  • printf等格式化函数中,格式化符是与内置类型绑定的,如%d对应int。使用固定宽度类型后,不应该再使用这类函数。

byte

  • 将对象编码为字节信息(跨系统传递对象时)。以前经常使用char或unsigned char来表示字节

  • 为避免歧义,C++17引入了std::byte类型,代表二进制bit内容,只能做位运算,不能直接赋值。

  • 可用static_cast将其他类型转换成byte

  • 将对象编码为字节信息时,应当保证编码方式在任何平台上都是固定的,而不依赖于当前平台的内存字节布局。主流网络协议都是大端序

auto

  • 防止未初始化变量、类型名太长

  • auto x = -1;
    cout << boolalpha << (x < sizeof(x)) << endl;
    //x被转换为unsigned,输出false
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20

    - 当想要的类型不是*int*时,不要使用*auto+整数数字*初始化

    - 不要在表达式中混用有符号和无符号类型

    - auto不会自动添加const、引用等类型(使用模板推导类型)

    ```cpp
    int &getRef();
    int main()
    {
    //这里x是int,而不是int&
    auto x = getRef();

    //int &
    auto &y = getRef();

        //const int
    const auto z = x + 1;
    }
  • 使用auto &时,总是得到引用,且const属性会被保留

  • auto不可能推导出原生数组,原因是不能用一个数组直接初始化另一个数组

  • auto可以用于推导函数返回值,前提是函数定义可见、每个return表达式类型一致。如果函数定义在当前文件不可见,则编译器无法推导返回值类型,报编译错误。

统一{}初始化

1
2
3
4
//多种初始化方式
int x(0);
int y = 0;
int z = {0};
  • 在C++11中,*{}初始化方式*适用于所有初始化场景

  • {}初始化不允许implicit缩窄转换

  • 大括号列表在某些时候会被自动转换为std::initializer_list 类型,此时每个元素的类型必须一样

  • std::initializer_list 中的元素不能修改。他只是对原始大括号列表的数据引用,其本身的复制开销很小,没有必要传递它的引用。

  • 不要直接保存std::initializer_list ,以免发生悬空引用

  • 可以通过添加std::initializer_list 参数的构造函数,让自定义的类支持统一初始化

  • 大括号列表表达式没有类型,不能用于模板参数类型推导。但如果确定类型为initializer_list ,则大括号列表可以自动转换为initializer_list

constexpr

  • const修饰的变量,其值不能修改,但其值不一定能在编译器确定

  • constexpr确保在编译期实现求值,可以用于数组长度和模板参数。

  • 使用constexpr有利于编译器做更好的优化

  • constexpr常量初始化时,其值必须在编译时就能算出来,因此不能使用普通变量和普通函数,但可以使用constexpr的变量和函数

    1
    2
    3
    4
    5
    constexpr int x = 5;    //👌
    constexpr int y = x + 1;//👌
    int m = x; //👌
    constexpr int z = m; //だめ
    constexpr double w = std::pow(1, 2.0);//だめ
  • constexpr函数可以在运行时调用

  • constexpr函数隐含inline属性,因此对外提供的接口一般直接定义在头文件中

  • 尽可能把函数定义成constexpr,但是一个函数要能参与编译期计算,必须符合一定条件

    • constexpr函数内只能调用constexpr函数(C库、C++17库的函数都不是constexpr

    • 禁止分配堆内存(如很多容器)(从C++20开始,大部分容器可以在constexpr中使用)

    • 函数内的局部变量必须在声明时初始化

    • 不会抛出异常、不能带虚函数、没有指针类型的转换

  • 构造函数也可以是constexpr

static_assert编译期校验

std::array

  • 类似传统数组,内部元素必须是同一类型,大小必须在编译时已知

  • size() 直接获取元素个数

  • 传参时不会退化为指针

  • 使用at() 方法,越界时会抛出异常

  • 没有构造/析构函数

  • 初始化行为和普通数组一样

  • C++17可以同时推导类型和长度

    1
    2
    std::array arr = {1, 2, 3};
    //指定类型后不能推导长度

右值引用

  • C++11新增右值引用,可以将对象内容移动,避免拷贝

  • 对const T &&进行move,不会move,而是拷贝

  • 所有参数都是左值

=delete

1
2
3
4
5
6
class MyClass
{
public:
MyClass(const MyClass &) = delete;
MyClass &operator = (const MyClass &) = delete;
};

=default

  • 构造函数:对于有默认构造函数的成员,调用其默认构造函数,否则不赋予初始值

  • 析构函数:队友有析构函数的成员,调用其析构函数

  • 拷贝构造和拷贝赋值:依次调用每个成员的拷贝构造/赋值

  • 移动构造和移动赋值:依次调用每个成员的移动构造/赋值

  • 小心浅拷贝

unique_ptr

  • 不允许拷贝,只允许移动

  • 实现了解引用(*),成员访问(->),和其他指针比较(==),在使用时和普通指针一样

  • 使用std::make_unique

shared_ptr

  • 一个对象多处使用,不确定最后由谁释放

  • std::make_shared

  • 当对象之间出现循环引用时,会导致引用计数永不减为0,导致内存泄漏

  • 当能确定对象的所有权时,使用unique_ptr

函数对象 & lambda函数

  • lambda函数:在需要使用函数对象的地方,让编译器自动生成一个

  • 编译器自动生成的lambda函数对象没有类名,只能用decltype 获取类型。任意两个lambda的类型都不同

    1
    2
    int thres = 10;
    auto lambdaA = [thres](int x) {return x < thres;};
    • 开销很大的对象应该按引用捕获
  • 保存的引用不能超过引用对象的生命周期

  • 默认不允许修改,相当于operator() 函数加上了const 。如果需要修改,需要explicitly添加mutable

    1
    2
    int thres = 10;
    auto lambdaB = [thres](int x) mutable {return ++x < ++thres;};

Range-Based Loop

  • 可给自定义的类实现begin()end() 从而实现range-based loop

带有作用域的enum

  • Post title:Huawei: 现代C++与STL
  • Post author:Franky
  • Create time:2023-03-09 21:35:59
  • Post link:https://franky.pro/2023/03/09/Huawei-现代C-与STL/
  • Copyright Notice:All articles in this blog are licensed under BY-NC-SA unless stating additionally.