@@ -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