Skip to content

Commit 8ad6089

Browse files
committed
update
1 parent f404e27 commit 8ad6089

File tree

1 file changed

+83
-0
lines changed

1 file changed

+83
-0
lines changed

Qt/C++.md

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,3 +61,86 @@ Object obj(); // 错误,初始化了一个函数
6161
## 类的静态成员变量
6262
当一个变量只存在这个类内,但该类又会创建出多份,且都会使用同一个该变量,这种情况下可以使用静态成员变量。
6363

64+
## 迭代器的删除
65+
对于关联容器,删除当前的 iterator,仅仅会使当前的 iterator 失效,只要在调用 erase 时,递增当前的 iterator 即可。这是因为 map 之类的容器,使用了红黑树来实现,插入、删除一个结点不会对其他结点造成影响。
66+
67+
对于顺序容器,删除当前的 iterator 会使后面所有元素的 iterator 都失效。这是因为 vector 、deque 使用了连续分配的内存,删除一个元素导致后面所有的元素会向前移动一个位置。不过 erase 方法可以返回下一个有效的 iterator 。
68+
69+
## std::string_view
70+
C++17 引入`std::string_view` 来优化string的性能。`string_view` 本身不own内存,它只维护了一个指针和长度。而 `string_view` 仅存储了对原始数据的引用,该过程不涉及任何复制操作,所以它在处理大字符串时,性能上要比 string 更优。
71+
72+
```c++
73+
#include <string_view>
74+
static const string_view str = "HELLO ByteDance!"; // 没有任何开销
75+
char foo() {
76+
return str[2];
77+
}
78+
等价
79+
#include <string_view>
80+
char foo() {
81+
return 'L';
82+
}
83+
```
84+
85+
使用`string_view`的注意事项:
86+
- `std::string_view` 可以与`std::string`互相转换,但要注意`string_view`的生命周期问题。由于`std::string_view`并不持有字符串的内存,所以它的生命周期一定要比源字符串的生命周期长,源字符串被消毁,行为未定义。
87+
```C++
88+
std::string_view PrintStringView() {
89+
std::string s = "How are you..";
90+
std::string_view str_view = s;
91+
return str_view;
92+
}
93+
// 行为未定义,悬垂指针
94+
std::cout << "PrintLocalStringView: " << PrintStringView() << std::endl;
95+
```
96+
97+
- `std::string_view`并不提供修改其引用的字符串的方法。任何尝试修改string_view引用的字符串的操作都可能导致未定义的行为。
98+
99+
- 与其他字符串类型不同,应该按值传递`string_view`,就像传递int或double一样,因为`string_view`是一个小值。
100+
101+
- `string_view`不一定以null结尾。因此,使用printf函数输出`string_view`是不安全的:
102+
```c++
103+
printf("%s\n", sv.data()); // DON’T DO THIS
104+
// 可以像string或const char*使用<<输出string_view:
105+
std::cout << "Took '" << s << "'";
106+
```
107+
108+
109+
## 尽量提前使用reserve/resize来避免不必要的内存分配
110+
对于vector和string,增长过程是这样来实现的:每当需要更多空间时,就调用与realloc类似的操作。这一类似于realloc的操作分为四个步聚:
111+
1. 分配一块大小为当前容量的某个倍数的新内存。在大多数实现中,vector和string的容量每次以2的倍数增长,即,每当容器需要扩张时,它们的容量即加倍。
112+
2. 把容器的所有元素从旧的内存拷贝到新的内存中。
113+
3. 析构掉旧内存中的对象。
114+
4. 释放旧内存。
115+
116+
```C++
117+
// Bad Code !!!
118+
std::vector<int> container;
119+
for (int i = 0; i < 1000; ++i)
120+
{
121+
container.push_back(i);
122+
}
123+
124+
改为:
125+
126+
std::vector<int> container;
127+
container.reserve(1000);
128+
for (int i = 0; i < 1000; ++i)
129+
{
130+
container.push_back(i);
131+
}
132+
```
133+
134+
## 容器元素
135+
元素拷贝:当向容器中加入对象时,存入容器的是你所指定的对象的拷贝。当从容器中取出一个对象时,你所得到的是容器中所保存的对象的拷贝。进去的是拷贝,出来的也是拷贝(copy in, copy out)。
136+
所以如果容器存储大对象,一般都会存指针以提升性能(指针的拷贝比对象拷贝代价少)
137+
138+
元素析构:STL的容器自身被析构时,它们会自动析构容器内所包含的每个对象。如果容器内的元素是通过new创建的,那么在释放容器的时候,指针的“析构函数”不会做任何事情,因此new所创建的内存就不会释放掉。如果不手动delete掉,那么就会造成内存泄漏。
139+
140+
为了防止内存泄漏,最简单的方法就是通过delete释放防止内存泄漏
141+
但是这种方式仍存在一个问题:如果在new操作和delete操作之间程序抛出了异常导致程序终止,那么delete语句将永远不会执行,同样也会产生内存泄漏。
142+
143+
使用智能指针来保证内存不会泄露,特别是map的value如果存储大对象,使用智能指针可提升性能,指针的拷贝比对象拷贝代价少,因此建议存储为map<key, std::shard_ptr<value>>,这样既能保证指针高性能,又不需要担心erase的析构的问题。
144+
145+
与析构类似,remove操作也需要注意:当容器中存放的是new分配对象的指针时,应该避免使用remove和remove_if。如果容器中存放的不是普通指针,而是具有引用计数功能的智能指针,那么就可以直接使用erase-remove的习惯用法。
146+

0 commit comments

Comments
 (0)