• C++ 学习系列 -- std::vector (未完待续)


    一  std::vector 是什么?

       vector 是c++ 中一种序列式容器,与前面说的 array 类似,其内存分配是连续的,但是与 array 不同的地方在于,vector 在运行时是可以动态扩容的,此外 vector 提供了许多方便的操作,比如:插入、删除、查找、排序等。

    std::vector - cppreference.com

    二  std::vector 的特性与常见面试问题有哪些?

    1  特性

    1.1  vector 底层是基于分配连续空间的数组,因此 vector的访问既可以通过 容器的迭代器的方式,也可以通过数组的下标方式来访问元素

    1.2 vector 可以在运行期间根据需要,进行动态的扩容,当已分配的空间被用满时,会再重新分配一块通常是原来空间两倍的内存,接下来依次将原来内存中的元素依次拷贝过来

    1.3 基于底层的原理 ,其相关操作的时间复杂度如下:

           1)访问元素的时间复杂度 与数组相同,是 O(1) ;

           2) 在 vector末尾插入元素或者删除元素 O(1)

           3)   在 vector 中间某个位置插入或者删除一个元素,O(n)

    2 常见面试问题

    1. vector 的扩容机制 ?

      1.1  扩容倍数一般为 2 倍或者 1.5 倍
    1. // main.cpp
    2. #include
    3. #include
    4. int main()
    5. {
    6. std::vector<int> vec2;
    7. for(int i=0; i<20; i++)
    8. {
    9. std::cout << "size: " << vec2.size() << ", capaticy: " << vec2.capacity() << std::endl;
    10. vec2.push_back(i);
    11. }
    12. return 0;
    13. }

    其实际存储的元素个数,与开辟空间可以存放的元素个数如下:

    从输出结果可以看出,每当存放的元素将分配的空间耗尽以后,vector 就会再次申请两倍于当前内存大小的空间来使用

       1.2  扩容的具体过程

            当存放元素个数将 vector 分配的内存空间耗尽时,当再放入新元素时,会触发如下过程

    •  重新分配新的连续内存空间,大小一般是原来的 2 倍
    •  将原来内存空间的元素依次拷贝到新空间中, 此过程会触发拷贝构造函数
    •  调用存放在原内存空间中各元素的析构函数
    •  释放掉原空间的内存 

      我们可以通过如下代码的结果看到这个过程

    1. #include
    2. #include
    3. class AAA
    4. {
    5. public:
    6. AAA(int i):index(i){
    7. std::cout << "constructor AAA index: " << index << std::endl;
    8. }
    9. ~AAA(){
    10. std::cout << "destructor AAA index: " << index << std::endl;
    11. }
    12. AAA(AAA& a):index(a.index){
    13. std::cout << "copy constructor AAA index: " << index << std::endl;
    14. }
    15. AAA(const AAA& a):index(a.index){
    16. std::cout << "copy constructor const AAA index: " << index << std::endl;
    17. }
    18. AAA(AAA&& a):index(a.index){
    19. std::cout << "move copy constructor AAA index: " << index << std::endl;
    20. a.index = 0;
    21. }
    22. AAA& operator=(AAA& a){
    23. this->index = a.index;
    24. std::cout << "equals AAA index: " << index << std::endl;
    25. return *this;
    26. }
    27. public:
    28. int index;
    29. };
    30. int main()
    31. {
    32. std::vector vec2;
    33. for(int i=0; i<7; i++)
    34. {
    35. std::cout << "size: " << vec2.size() << ", capaticy: " << vec2.capacity() << std::endl;
    36. vec2.emplace_back(i);
    37. }
    38. return 0;
    39. }

    输出:

    2.如何避免扩容导致的效率低下?

        2.1  可以在使用 vector 之前,预估好元素的个数,在使用 vector 之前,将内存空间分配好,而不是在一边插入一边分配新空间与拷贝旧空间的元素

       2.2  若是无法预估,可以在自定义类中实现高效的移动构造函数,然后禁用拷贝构造函数,这样vector 在扩容完毕后的拷贝元素阶段,会自动调用移动构造函数,具体如下:

    (c++ 中声明移动构造函数后,会自动禁用拷贝构造函数)

    1. #include
    2. #include
    3. class AAA
    4. {
    5. public:
    6. AAA(int i):index(i){
    7. std::cout << "constructor AAA index: " << index << std::endl;
    8. }
    9. ~AAA(){
    10. std::cout << "destructor AAA index: " << index << std::endl;
    11. }
    12. AAA(AAA&& a):index(a.index){
    13. std::cout << "move copy constructor AAA index: " << index << std::endl;
    14. a.index = 0;
    15. }
    16. AAA& operator=(AAA& a){
    17. this->index = a.index;
    18. std::cout << "equals AAA index: " << index << std::endl;
    19. return *this;
    20. }
    21. public:
    22. int index;
    23. };
    24. int main()
    25. {
    26. std::vector vec2;
    27. for(int i=0; i<7; i++)
    28. {
    29. std::cout << "size: " << vec2.size() << ", capaticy: " << vec2.capacity() << std::endl;
    30. vec2.emplace_back(i);
    31. }
    32. return 0;
    33. }

    输出: 

    3.为什么选择以1.5倍或者2倍方式进行扩容?而不是3倍4倍扩容?

        面试题:C++vector的动态扩容,为何是1.5倍或者是2倍_vector扩容_森明帮大于黑虎帮的博客-CSDN博客

    4.vs为什么选择1.5倍,linux为什么选择2倍?

    面试题:C++vector的动态扩容,为何是1.5倍或者是2倍_vector扩容_森明帮大于黑虎帮的博客-CSDN博客

    三 std::vector 的使用

      1. vector 常见接口与操作

           std::vector - cppreference.com

         1.1  容器构造

    构造函数说明
    vector();空构造函数

    template

    vector( size_type count,

                     const T& value);

    构造 count 各 value 到vector 中
    template< class InputIt >

    vector( InputIt first, InputIt last,

            const Allocator& alloc = Allocator() );
    利用迭代器构造 vector 
    vector( const vector& other );拷贝构造函数
    vector( vector&& other );移动构造函数

           使用例子:

              

    1. #include
    2. #include
    3. void printVector(std::vector& vec)
    4. {
    5. for(T& v:vec)
    6. {
    7. std::cout << v << " ";
    8. }
    9. std::cout << "" << std::endl;
    10. }
    11. void testVector()
    12. {
    13. std::cout << "testVector --" << std::endl;
    14. // 1. 构造空 vector
    15. std::vector<int> vec1;
    16. for(int i=1;i<7;i++)
    17. {
    18. vec1.push_back(i);
    19. }
    20. std::cout << "------------ 1" << std::endl;
    21. printVector(vec1);
    22. // 2. 构造 count 个 value 到 vector 中
    23. std::vector<int> vec2(6, 88);
    24. std::cout << "------------ 2" << std::endl;
    25. printVector(vec2);
    26. // 3. 利用迭代器构造 vector
    27. std::vector<int> vec3(vec2.begin(), vec2.end());
    28. std::cout << "------------ 3" << std::endl;
    29. printVector(vec3);
    30. // 4. 拷贝构造函数
    31. std::vector<int> vec4(vec1);
    32. std::cout << "------------ 4" << std::endl;
    33. printVector(vec4);
    34. // 5. 移动构造函数
    35. std::vector<int> vec5(std::move(vec1));
    36. std::cout << "------------ 5" << std::endl;
    37. printVector(vec5);
    38. }
    39. int main(int argc, char *argv[])
    40. {
    41. testVector();
    42. return 0;
    43. }

    输出:

      

         1.2  容器访问

    函数说明
    at(size_type pos)返回位置 pos 上的元素引用
    operator[size_type pos]同上
    front返回 vector 上第一个元素的引用
    back返回 vector 上最后一个元素的引用
    data返回底层存储数组的指针

          示例代码:

    1. #include
    2. #include
    3. void testVector2()
    4. {
    5. std::cout << "testVector --" << std::endl;
    6. // 构造 vector
    7. std::vector<int> vec1;
    8. for(int i=1;i<7;i++)
    9. {
    10. vec1.push_back(i);
    11. }
    12. std::cout << "------------ 1" << std::endl;
    13. // 1. at
    14. int pos = 3;
    15. std::cout << " pos = 3, val = " << vec1.at(pos)<< std::endl;
    16. // 可以修改数据
    17. int& tmp1 = vec1.at(pos);
    18. tmp1 = 88;
    19. std::cout << " pos = 3, val = " << vec1.at(pos)<< std::endl;
    20. std::cout << "------------ 2" << std::endl;
    21. // 2. operator[]
    22. std::cout << " pos = 3, val = " << vec1[pos]<< std::endl;
    23. std::cout << "------------ 3" << std::endl;
    24. // 3. front
    25. std::cout << " front, val = " << vec1.front() << std::endl;
    26. std::cout << "------------ 4" << std::endl;
    27. // 4. back
    28. std::cout << " back, val = " << vec1.back()<< std::endl;
    29. std::cout << "------------ 5" << std::endl;
    30. // 5. data
    31. int* tmp5 = vec1.data();
    32. for(int i=0; i<6;i++)
    33. {
    34. std::cout << "i = " << i <<", val = " <<*(tmp5 + i) << std::endl;
    35. }
    36. }
    37. int main()
    38. {
    39. testVector2();
    40. return 0;
    41. }

    输出:

      

        1.3  容器空间

    函数说明
    empty()vector 是否为空
    size()返回存储的元素个数
    reserve(size_type new_cap)提升vector 容量
    capacity()返回vector分配的空间可以存放的元素个数

      示例如下

    1. #include
    2. #include
    3. void testVector3()
    4. {
    5. std::cout << "testVector --" << std::endl;
    6. std::vector<int> vec1;
    7. std::cout << " empty: " << vec1.empty() << ", size: " << vec1.size() << std::endl;
    8. for(int i=0; i<5; i++)
    9. {
    10. std::cout << "size: " << vec1.size() << ", capaticy: " << vec1.capacity() << std::endl;
    11. vec1.emplace_back(i+1);
    12. }
    13. std::cout << " empty: " << vec1.empty() << ", size: " << vec1.size() << std::endl;
    14. vec1.reserve(8);
    15. std::cout << "size: " << vec1.size() << ", capaticy: " << vec1.capacity() << std::endl;
    16. }
    17. int main()
    18. {
    19. testVector3();
    20. return 0;
    21. }

    输出:

    1.4  容器修改

          

    函数说明
    clear()清空 vector 中的所有元素
    insert在位置 pos 前插入元素 value
    emplace与insert类似,不过该函数可以只传元素类的构造参数,实现原地构造,效率上比 insert 高一些,因为缺少了拷贝函数的调用
    push_back在 vector 的最后append 新的元素,若是append前,vector 的size与capacity相等,那么就会重新分配内存
    emplace_back与 push_back 类似,区别在于该函数可以只传元素类的构造参数,实现原地构造,效率上比 push_back 高一些,因为缺少了拷贝函数的调用
    pop_back将 vector 的最后一个元素移除

          示例如下:

             

    1. #include
    2. #include
    3. void testVector4()
    4. {
    5. std::cout << "testVector --" << std::endl;
    6. std::vector<int> vec1;
    7. for(int i=0; i<5; i++)
    8. {
    9. vec1.emplace_back(i+1);
    10. }
    11. printVector(vec1);
    12. // 1. insert
    13. std::cout << "------------ 1" << std::endl;
    14. auto iter = std::find(vec1.begin(), vec1.end(), 3);
    15. vec1.insert(iter,666);
    16. printVector(vec1);
    17. // 2. emplace
    18. std::cout << "------------ 2" << std::endl;
    19. vec1.emplace(iter,888);
    20. printVector(vec1);
    21. // 3. push_back
    22. std::cout << "------------ 3" << std::endl;
    23. vec1.push_back(77);
    24. printVector(vec1);
    25. // 4. emplace_back
    26. std::cout << "------------ 4" << std::endl;
    27. vec1.emplace_back(778);
    28. printVector(vec1);
    29. // 5. pop_back
    30. std::cout << "------------ 5" << std::endl;
    31. vec1.pop_back();
    32. printVector(vec1);
    33. // 6. clear
    34. std::cout << "------------ 6" << std::endl;
    35. vec1.clear();
    36. std::cout << "empyt: " << vec1.empty() << std::endl;
    37. }
    38. int main()
    39. {
    40. testVector4();
    41. return 0;
    42. }

     输出:

     

    四  std::vector 的简单实现

      接下来我们实现自己简单的  vector 

    1. // my_iterator.h
    2. struct input_iterator_tag {};
    3. struct output_iterator_tag {};
    4. struct forward_iterator_tag:public input_iterator_tag {};
    5. struct bidirectional_iterator_tag: public forward_iterator_tag {};
    6. struct random_access_iterator_tag: public bidirectional_iterator_tag {};
    7. template<typename _Category, typename _Tp, typename Distance = long long,
    8. typename Pointer = _Tp*, typename _Refrence = _Tp&>
    9. struct _IterorBase
    10. {
    11. typedef _Category iterator_category;
    12. typedef _Tp value_type;
    13. typedef Distance difference_type;
    14. typedef Pointer poiner;
    15. typedef _Refrence refrence;
    16. };
    17. template <typename T, typename Container>
    18. struct my_iterator:public _IterorBase
    19. {
    20. my_iterator():current(nullptr)
    21. {
    22. }
    23. my_iterator(T* t):current(t)
    24. {
    25. }
    26. my_iterator(my_iterator& other):current(other.current)
    27. {
    28. }
    29. my_iterator(const my_iterator& other):current(other.current)
    30. {
    31. }
    32. my_iterator(my_iterator&& other):current(other.current)
    33. {
    34. other.current = nullptr;
    35. }
    36. ~my_iterator()
    37. {
    38. }
    39. my_iterator& operator++()
    40. {
    41. current++;
    42. return *this;
    43. }
    44. my_iterator& operator++(int)
    45. {
    46. current++;
    47. return *this;
    48. }
    49. my_iterator& operator--()
    50. {
    51. current--;
    52. return *this;
    53. }
    54. T& operator*()
    55. {
    56. return *(this->current);
    57. }
    58. int operator-(my_iterator& other)
    59. {
    60. return current - other.current;
    61. }
    62. my_iterator operator-(int other)
    63. {
    64. return my_iterator(this->current - other);
    65. }
    66. my_iterator operator+(int other)
    67. {
    68. return my_iterator(this->current + other);
    69. }
    70. bool operator<(my_iterator& other)
    71. {
    72. return (this - other) < 0 ? true:false;
    73. }
    74. bool operator>(my_iterator& other)
    75. {
    76. return (this - other) > 0 ? true:false;
    77. }
    78. bool operator!=(my_iterator& other)
    79. {
    80. return this->current != other.current;
    81. }
    82. bool operator!=(const my_iterator& other)
    83. {
    84. return this->current != other.current;
    85. }
    86. bool operator==(my_iterator& other)
    87. {
    88. return this->current == other.current;
    89. }
    90. bool operator==(const my_iterator& other)
    91. {
    92. return this->current == other.current;
    93. }
    94. my_iterator& operator=(my_iterator& other)
    95. {
    96. this->current = other.current;
    97. return *this;
    98. }
    99. my_iterator& operator=(my_iterator&& other)
    100. {
    101. this->current = other.current;
    102. other.current = nullptr;
    103. return *this;
    104. }
    105. T* current;
    106. };
    107. // my_vector.h
    108. #include
    109. #include
    110. #include
    111. template<typename T>
    112. class my_vector
    113. {
    114. public:
    115. typedef my_iterator iterator;
    116. typedef const iterator cons_iterator;
    117. public:
    118. my_vector();
    119. my_vector(int count, const T& value);
    120. my_vector(const my_vector& other);
    121. my_vector(my_vector&& other);
    122. ~my_vector();
    123. T& at(int pos);
    124. T& operator[](int pos);
    125. T& front();
    126. T& back();
    127. T* data();
    128. bool empty();
    129. int size();
    130. void reserve(int new_cap);
    131. int capacity();
    132. void clear();
    133. void insert(cons_iterator iter,T& value);
    134. template<typename ...Args>
    135. void emplace(cons_iterator iter, Args... args)
    136. {
    137. if(iter.current == nullptr)
    138. {
    139. return;
    140. }
    141. if(this->finish == this->end_of_stora)
    142. {
    143. reAlloc();
    144. }
    145. iterator tmp_iter=this->finish + 1;
    146. for(; tmp_iter == iter; tmp_iter++)
    147. {
    148. *tmp_iter = *(tmp_iter - 1);
    149. }
    150. *tmp_iter = T(std::forward(args)...);
    151. this->finish++;
    152. }
    153. void push_back(T& value)
    154. {
    155. std::cout << "push back: &value: "<< value << " " << this->finish.current << " == " << this->end_of_stora.current << std::endl;
    156. if(this->finish == this->end_of_stora)
    157. {
    158. reAlloc();
    159. }
    160. *this->finish = value;
    161. this->finish++;
    162. }
    163. void push_back(T&& value)
    164. {
    165. std::cout << "push back: &&value: " << value << " "<< this->finish.current << " == " << this->end_of_stora.current << std::endl;
    166. if(this->finish == this->end_of_stora)
    167. {
    168. reAlloc();
    169. }
    170. *this->finish = value;
    171. this->finish++;
    172. }
    173. template<typename ...Args>
    174. void emplace_back(Args... args)
    175. {
    176. if(this->finish == this->end_of_stora)
    177. {
    178. reAlloc();
    179. }
    180. this->finish = this->finish + 1;
    181. *this->finish = T(std::forward(args)...);
    182. }
    183. void pop_back()
    184. {
    185. delete *this->finish;
    186. this->finish--;
    187. }
    188. iterator begin()
    189. {
    190. return iterator(dataPtr);
    191. }
    192. iterator end()
    193. {
    194. return iterator(dataPtr + size());
    195. }
    196. private:
    197. void reAlloc()
    198. {
    199. int reLen = (this->end_of_stora - this->start)==0 ? 1 : 2*(this->end_of_stora - this->start);
    200. reAlloc(reLen);
    201. }
    202. void reAlloc(int new_cap)
    203. {
    204. int reLen = (this->end_of_stora - this->start)==0 ? 1 : (this->end_of_stora - this->start);
    205. while (reLen < new_cap) {
    206. reLen *= 2;
    207. }
    208. if(dataPtr == nullptr)
    209. {
    210. dataPtr = new T[reLen];
    211. this->start = iterator(dataPtr);
    212. this->finish = iterator(dataPtr);
    213. this->end_of_stora = iterator((dataPtr+reLen));
    214. }
    215. else
    216. {
    217. T* new_data_ptr = new T[reLen];
    218. int numOfElements = this->finish - this->start;
    219. for(int i = 0; i < numOfElements; i++)
    220. {
    221. new_data_ptr[i] = this->at(i);
    222. }
    223. T* tmpPtr = dataPtr;
    224. dataPtr = new_data_ptr;
    225. delete [] tmpPtr;
    226. this->start = iterator(dataPtr);
    227. this->finish = iterator(dataPtr + numOfElements);
    228. this->end_of_stora = iterator((dataPtr+reLen));
    229. }
    230. }
    231. T* dataPtr = nullptr;
    232. iterator start;
    233. iterator finish;
    234. iterator end_of_stora;
    235. };
    236. template<typename T>
    237. my_vector::my_vector()
    238. {
    239. }
    240. template<typename T>
    241. my_vector::my_vector(int count, const T& value)
    242. {
    243. for(int i=0; i
    244. {
    245. finish++;
    246. }
    247. if(finish < end_of_stora)
    248. {
    249. reAlloc();
    250. }
    251. for(iterator iter=start; iter != finish; iter++)
    252. {
    253. *iter = value;
    254. }
    255. }
    256. template<typename T>
    257. my_vector::my_vector(const my_vector& other)
    258. {
    259. this->start = other.start;
    260. this->finish = other.finish;
    261. this->end_of_stora = other.end_of_stora;
    262. this->dataPtr = other.dataPtr;
    263. }
    264. template<typename T>
    265. my_vector::my_vector(my_vector&& other)
    266. {
    267. this->start = other.start;
    268. this->finish = other.finish;
    269. this->end_of_stora = other.end_of_stora;
    270. this->dataPtr = other.dataPtr;
    271. other.start = iterator();
    272. other.finish = iterator();
    273. other.end_of_stora = iterator();
    274. other.dataPtr = nullptr;
    275. }
    276. template<typename T>
    277. my_vector::~my_vector()
    278. {
    279. if(dataPtr)
    280. delete []dataPtr;
    281. }
    282. template<typename T>
    283. T& my_vector::at(int pos)
    284. {
    285. return *(this->dataPtr + pos);
    286. }
    287. template<typename T>
    288. T& my_vector::operator[](int pos)
    289. {
    290. return *(this->dataPtr + pos);
    291. }
    292. template<typename T>
    293. T& my_vector::front()
    294. {
    295. return *start;
    296. }
    297. template<typename T>
    298. T& my_vector::back()
    299. {
    300. return *finish;
    301. }
    302. template<typename T>
    303. T* my_vector::data()
    304. {
    305. return this->dataPtr;
    306. }
    307. template<typename T>
    308. bool my_vector::empty()
    309. {
    310. return this->begin() == this->end();
    311. }
    312. template<typename T>
    313. int my_vector::size()
    314. {
    315. return this->finish - this->start;
    316. }
    317. template<typename T>
    318. void my_vector::reserve(int new_cap)
    319. {
    320. if(end_of_stora - start < new_cap)
    321. {
    322. reAlloc(new_cap);
    323. }
    324. }
    325. template<typename T>
    326. int my_vector::capacity()
    327. {
    328. return this->end_of_stora - this->begin();
    329. }
    330. template<typename T>
    331. void my_vector::clear()
    332. {
    333. this->start = iterator();
    334. this->finish = iterator();
    335. this->end_of_stora = iterator();
    336. delete dataPtr;
    337. dataPtr = nullptr;
    338. }
    339. template<typename T>
    340. void my_vector::insert(cons_iterator iter,T& value)
    341. {
    342. if(iter.current == nullptr)
    343. {
    344. return;
    345. }
    346. if(this->finish == this->end_of_stora)
    347. {
    348. reAlloc();
    349. }
    350. iterator tmp_iter=this->finish + 1;
    351. for(; tmp_iter == iter; tmp_iter++)
    352. {
    353. *tmp_iter = *(tmp_iter - 1);
    354. }
    355. *tmp_iter = T(value);
    356. this->finish++;
    357. }
    358. // main.cpp
    359. void testMyVector()
    360. {
    361. std::cout << "--- testMyVector ---" << std::endl;
    362. my_vector<int> vec1;
    363. vec1.push_back(666);
    364. for(int i = 0; i < 20; i++)
    365. {
    366. vec1.push_back(i);
    367. }
    368. vec1.push_back(666);
    369. auto iter = vec1.begin();
    370. for(int i = 0; i < 22; i++)
    371. {
    372. std::cout << *(iter + i) << " --- " << vec1[i] << std::endl;
    373. }
    374. }
    375. int main()
    376. {
    377. testMyVector();
    378. return 0;
    379. }

  • 相关阅读:
    Kubernetes控制平面组件:Controller-Manager控制器管理
    无刷直流电机(BLDC)
    Fast-DDS 服务发现简要概述
    SkeyeVSS矿山采盗监控系统智能化管控非法采矿解决方案
    基于QT Creator 5.14的仿QQ聊天系统【UDP通讯】
    巧用clang 的sanitize解决realloc,malloc,calloc失败
    java实现自己的trim效果---去掉首尾指定字符
    唐人街徒步:在异国情调的纽约感受浓厚的中式气息
    hook函数——react——同步获取useState的最新状态值
    公众号如何让更多人看到?这三种方法超有效!
  • 原文地址:https://blog.csdn.net/qq_33775774/article/details/133150742