Huawei: 现代C++与STL
现代C++与STL
vector
建在堆上,可伸缩,可能有预留空间的动态数组
end()只能用于表示位置,不能用于访问元素
vector构造/析构/拷贝/移动时,其中所有元素自动构造/析构/拷贝/移动
增大capacity时,其实是把原vector复制一遍,再把新加入的元素拷贝到新的地址,然后释放原来的vector的内存。因此当capacity扩大后,原迭代器会失效。
如果元素类型是指针,vector不管指针所指向的对象的生命周期
```cpp
vectorv(3);
v.push_back(1);
v.push_back(1);
//v.capacity() == 6
vectorv2 = v;
//v2.capacity() == 51
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 | std::string s1 = "abc"; |
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 | //多种初始化方式 |
在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
5constexpr 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
2std::array arr = {1, 2, 3};
//指定类型后不能推导长度
右值引用
C++11新增右值引用,可以将对象内容移动,避免拷贝
对const T &&进行move,不会move,而是拷贝
所有参数都是左值
=delete
1 | class MyClass |
=default
构造函数:对于有默认构造函数的成员,调用其默认构造函数,否则不赋予初始值
析构函数:队友有析构函数的成员,调用其析构函数
拷贝构造和拷贝赋值:依次调用每个成员的拷贝构造/赋值
移动构造和移动赋值:依次调用每个成员的移动构造/赋值
小心浅拷贝
unique_ptr
不允许拷贝,只允许移动
实现了解引用(*),成员访问(->),和其他指针比较(==),在使用时和普通指针一样
使用std::make_unique
shared_ptr
一个对象多处使用,不确定最后由谁释放
std::make_shared
当对象之间出现循环引用时,会导致引用计数永不减为0,导致内存泄漏
当能确定对象的所有权时,使用unique_ptr
函数对象 & lambda函数
lambda函数:在需要使用函数对象的地方,让编译器自动生成一个
编译器自动生成的lambda函数对象没有类名,只能用decltype 获取类型。任意两个lambda的类型都不同
1
2int thres = 10;
auto lambdaA = [thres](int x) {return x < thres;};- 开销很大的对象应该按引用捕获
保存的引用不能超过引用对象的生命周期
默认不允许修改,相当于operator() 函数加上了const 。如果需要修改,需要explicitly添加mutable
1
2int 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.