STL容器vector应用注意事项
【1】提前分配足够空间以免不必要的重新分配和复制代价
关于vector容器重新分配和复制及析构释放的代价,请参见随笔《STL容器之vector》。
应用示例对比代码如下:
1 #include <vector> 2 #include <ctime> 3 #include <iostream> 4 using namespace std; 5 6 // 计时器 7 // 调用clock()函数实现,返回毫秒(ms)数 8 class TestProgramRunTimer 9 { 10 enum { kClockPerSecond = CLOCKS_PER_SEC }; // 每秒时钟的跳数 11 12 public: 13 TestProgramRunTimer() 14 : cost_time(0) 15 , start_time(0) 16 , end_time(0) 17 { 18 start(); 19 } 20 21 ~TestProgramRunTimer() 22 {} 23 24 void reset() 25 { 26 cost_time = 0; 27 start_time = 0; 28 end_time = 0; 29 } 30 31 void start() 32 { 33 start_time = clock(); 34 } 35 36 void stop() 37 { 38 end_time = clock(); 39 cost_time = (end_time - start_time) / (kClockPerSecond / 1000.0); 40 return; 41 } 42 43 double cost() 44 { 45 return cost_time; 46 } 47 48 protected: 49 double cost_time; 50 clock_t start_time; 51 clock_t end_time; 52 }; 53 54 TestProgramRunTimer tt; 55 56 struct BigTestStruct 57 { 58 int iValue; 59 float fValue; 60 long lValue; 61 double dValue; 62 char cNameArr[10]; 63 int iValArr[100]; 64 }; 65 66 void FillVector(vector<BigTestStruct>& testVector) 67 { 68 for (int i = 0; i < 10000; ++i) 69 { 70 BigTestStruct bt; 71 testVector.push_back(bt); 72 } 73 } 74 75 void main() 76 { 77 cout << sizeof(BigTestStruct) << endl; 78 79 vector<BigTestStruct> myVec1, myVec2, myVec3; 80 tt.start(); 81 FillVector(myVec1); 82 tt.stop(); 83 cout << "cost time to Fill vector without reservation: " << tt.cost() << endl; 84 85 myVec2.reserve(10); 86 tt.reset(); 87 tt.start(); 88 FillVector(myVec2); 89 tt.stop(); 90 cout << "cost time to Fill vector with reservation(100): " << tt.cost() << endl; 91 92 myVec3.reserve(10000); 93 tt.reset(); 94 tt.start(); 95 FillVector(myVec3); 96 tt.stop(); 97 cout << "cost time to Fill vector with reservation(10000): " << tt.cost() << endl; 98 99 system("pause"); 100 } 101 102 // run out: 103 /* 104 440 105 cost time to Fill vector without reservation: 31 106 cost time to Fill vector with reservation(100): 16 107 cost time to Fill vector with reservation(10000): 0 108 请按任意键继续. . . 109 */
同样是push_back操作,预分配足够空间和不分配空间的时间代价显而易见。
【2】使用shrink_to_fit()释放vector占用的内存。(备注:clear() 和 erase()不会释放内存)
shrink to fit 压缩到合适的大小空间,即把多余的内存空间释放掉。
示例代码如下:
1 #include <vector> 2 #include <iostream> 3 using namespace std; 4 5 struct BigTestStruct 6 { 7 int iValue; 8 float fValue; 9 long lValue; 10 double dValue; 11 char cNameArr[10]; 12 int iValArr[100]; 13 }; 14 15 void FillVector(vector<BigTestStruct>& testVector, int nNum = 0) 16 { 17 nNum = (0 == nNum) ? 10000 : nNum; 18 for (int i = 0; i < nNum; ++i) 19 { 20 BigTestStruct bt; 21 testVector.push_back(bt); 22 } 23 } 24 25 // shrink_to_fit函数原形 26 /* 27 void shrink_to_fit() 28 { // reduce capacity 29 if (size() < capacity()) 30 { // worth shrinking, do it 31 _Myt _Tmp(*this); 32 swap(_Tmp); 33 } 34 } 35 */ 36 37 void main() 38 { 39 vector<BigTestStruct> myVec1, myVec2, myVec3; 40 FillVector(myVec1, 100); 41 size_t capacity = myVec1.capacity(); 42 cout << "删除元素前,容器可容纳元素数量:" << capacity << endl; 43 myVec1.erase(myVec1.begin(), myVec1.begin() + 3); 44 capacity = myVec1.capacity(); 45 cout << "删除元素后,容器可容纳元素数量:" << capacity << endl; 46 47 myVec1.clear(); 48 capacity = myVec1.capacity(); 49 cout << "清空元素后,容器可容纳元素数量:" << capacity << endl; 50 51 FillVector(myVec1, 100); 52 cout << "重新添加100个元素后,容器可容纳元素数量:" << capacity << endl; 53 vector<BigTestStruct> tempVec1(myVec1); 54 tempVec1.swap(myVec1); // 压缩到合适大小(因为myVec1只填充了100个元素,所以相当于释放多余内存空间) 55 capacity = myVec1.capacity(); 56 cout << "利用拷贝构造新容器,与之交换后容器可容纳元素数量:" << capacity << endl; 57 58 FillVector(myVec2, 200); 59 cout << endl << "添加200个元素后,容器可容纳元素数量:" << capacity << endl; 60 capacity = myVec2.capacity(); 61 vector<BigTestStruct> tempVec2; 62 tempVec2.swap(myVec2); // 清空容器(因为tempVec2是空容器,所以相当于释放内存空间) 63 capacity = myVec2.capacity(); 64 cout << "利用空容器,与之交换后容器可容纳元素数量:" << capacity << endl; 65 66 FillVector(myVec3, 300); 67 capacity = myVec3.capacity(); 68 cout << endl << "压缩前,容器可容纳元素数量:" << capacity << endl; 69 // 压缩到合适大小(因为myVec3只填充了300个值,所以相当于释放多余内存空间) 70 myVec3.shrink_to_fit(); 71 capacity = myVec3.capacity(); 72 cout << "压缩后,容器可容纳元素数量:" << capacity << endl; 73 74 system("pause"); 75 } 76 77 // run out: 78 /* 79 删除元素前,容器可容纳元素数量:141 80 删除元素后,容器可容纳元素数量:141 81 清空元素后,容器可容纳元素数量:141 82 重新添加100个元素后,容器可容纳元素数量:141 83 利用拷贝构造新容器,与之交换后容器可容纳元素数量:100 84 85 添加200个元素后,容器可容纳元素数量:100 86 利用空容器,与之交换后容器可容纳元素数量:0 87 88 压缩前,容器可容纳元素数量:316 89 压缩后,容器可容纳元素数量:300 90 请按任意键继续. . . 91 */
通过上面的示例代码及运行输出结果分析可知:
1、erase和clear函数并不释放内存空间。执行两者后,容器的容量输出结果不变,说明并不会减少vector占用的内存空间。
2、shrink_to_fit压缩容器内存空间到合适大小(即capacity()容量等于元素个数size() )。
2.1 由于vector容器是动态自动扩容的,但自动扩容的规则不保证每次增加空间后刚好能容纳所有元素而没有一点浪费。
所以,当元素填充完全后,为了释放多余的内存空间,可以调用shrink_to_fit函数达到目的。
2.2 释放容器内存空间。一般人们总以为调用erase或clear后,容器内存空间也释放掉了,如上示例证明根本不是那么回事。
清理掉(erase或clear)容器所有元素之后,相当于容器元素个数为0,但容器容量仍不变(即内存空间仍存在)。
如果想释放掉容器内存空间,可以调用shrink_to_fit函数,使容器的容量值等于元素个数0。
注意:容器的容量值为0,意味着容器没有任何内存空间再可以填充元素,即释放了内存空间。
3、shrink_to_fit函数其本质同swap函数。
从示例代码中注释部分的shrink_to_fit函数原形可以看到,真正实现过程调用swap函数。
【3】填充或拷贝vector时,应该使用赋值而不是拷贝构造函数 或 insert()及 push_back()
从一个旧的vector取出元素填充另一个vector时,常有四种方式:
1、赋值构造函数。
2、拷贝构造函数。
3、基于迭代器的insert函数。
4、基于循环的push_back函数。
示例代码如下:
1 #include <vector> 2 #include <ctime> 3 #include <iostream> 4 using namespace std; 5 6 // 计时器 7 // 调用clock()函数实现,返回毫秒(ms)数 8 class TestProgramRunTimer 9 { 10 enum { kClockPerSecond = CLOCKS_PER_SEC }; // 每秒时钟的跳数 11 12 public: 13 TestProgramRunTimer() 14 : cost_time(0) 15 , start_time(0) 16 , end_time(0) 17 { 18 start(); 19 } 20 21 ~TestProgramRunTimer() 22 {} 23 24 void reset() 25 { 26 cost_time = 0; 27 start_time = 0; 28 end_time = 0; 29 } 30 31 void start() 32 { 33 start_time = clock(); 34 } 35 36 void stop() 37 { 38 end_time = clock(); 39 cost_time = (end_time - start_time) / (kClockPerSecond / 1000.0); 40 return; 41 } 42 43 double cost() 44 { 45 return cost_time; 46 } 47 48 protected: 49 double cost_time; 50 clock_t start_time; 51 clock_t end_time; 52 }; 53 54 TestProgramRunTimer tt; 55 56 struct BigTestStruct 57 { 58 int iValue; 59 float fValue; 60 long lValue; 61 double dValue; 62 char cNameArr[10]; 63 int iValArr[100]; 64 }; 65 66 void FillVector(vector<BigTestStruct>& testVector) 67 { 68 for (int i = 0; i < 100000; ++i) 69 { 70 BigTestStruct bt; 71 testVector.push_back(bt); 72 } 73 } 74 75 void main() 76 { 77 // assign 78 vector<BigTestStruct> sourceVec0, destVec0; 79 FillVector(sourceVec0); 80 tt.start(); 81 destVec0 = sourceVec0; 82 tt.stop(); 83 cout << "assign :: " << tt.cost() << endl; 84 85 // copy 86 vector<BigTestStruct> sourceVec1; 87 FillVector(sourceVec1); 88 tt.reset(); 89 tt.start(); 90 vector<BigTestStruct> destVec1(sourceVec1); 91 tt.stop(); 92 cout << "copy :: " << tt.cost() << endl; 93 94 // insert 95 vector<BigTestStruct> sourceVec2, destVec2; 96 FillVector(sourceVec2); 97 tt.reset(); 98 tt.start(); 99 destVec2.insert(destVec2.begin(), sourceVec2.begin(), sourceVec2.end()); 100 tt.stop(); 101 cout << "insert :: " << tt.cost() << endl; 102 103 // push_back 104 vector<BigTestStruct> sourceVec3, destVec3; 105 FillVector(sourceVec3); 106 tt.reset(); 107 tt.start(); 108 vector<BigTestStruct>::iterator iter = sourceVec3.begin(); 109 for (; iter != sourceVec3.end(); ++iter) 110 { 111 destVec3.push_back(*iter); 112 } 113 tt.stop(); 114 cout << "push_back :: " << tt.cost() << endl; 115 116 system("pause"); 117 } 118 119 // run out: 120 /* 121 assign :: 31 122 copy :: 78 123 insert :: 78 124 push_back :: 281 125 请按任意键继续. . . 126 */
通过输出结果,可以看到vector赋值比insert和copy构造函数快,比push_back()更快。
为什么会这样?
赋值非常有效率,因为它知道要拷贝的vector有多大,然后只需要通过内存管理一次性拷贝vector内部的缓存。
所以,想高效填充vector:
首先应尝试使用assignment,然后再考虑基于迭代器的insert()或拷贝构造,最后考虑push_back。
【4】遍历vector元素时,避免使用迭代器。建议使用下标方式。
下面比较三种遍历方式,示例代码如下:
1 #include <vector> 2 #include <ctime> 3 #include <iostream> 4 using namespace std; 5 6 #define MAXSIZE 100000 7 8 // 计时器 9 // 调用clock()函数实现,返回毫秒(ms)数 10 class TestProgramRunTimer 11 { 12 enum { kClockPerSecond = CLOCKS_PER_SEC }; // 每秒时钟的跳数 13 14 public: 15 TestProgramRunTimer() 16 : cost_time(0) 17 , start_time(0) 18 , end_time(0) 19 { 20 start(); 21 } 22 23 ~TestProgramRunTimer() 24 {} 25 26 void reset() 27 { 28 cost_time = 0; 29 start_time = 0; 30 end_time = 0; 31 } 32 33 void start() 34 { 35 start_time = clock(); 36 } 37 38 void stop() 39 { 40 end_time = clock(); 41 cost_time = (end_time - start_time) / (kClockPerSecond / 1000.0); 42 return; 43 } 44 45 double cost() 46 { 47 return cost_time; 48 } 49 50 protected: 51 double cost_time; 52 clock_t start_time; 53 clock_t end_time; 54 }; 55 56 TestProgramRunTimer tt; 57 58 struct BigTestStruct 59 { 60 int iValue; 61 float fValue; 62 long lValue; 63 double dValue; 64 char cNameArr[10]; 65 int iValArr[100]; 66 }; 67 68 void FillVector(vector<BigTestStruct>& testVector) 69 { 70 for (int i = 0; i < MAXSIZE; ++i) 71 { 72 BigTestStruct bt; 73 testVector.push_back(bt); 74 } 75 } 76 77 // 使用下标[]运算符 78 void funSubscript() 79 { 80 vector<BigTestStruct> myVec; 81 FillVector(myVec); 82 tt.reset(); 83 tt.start(); 84 int sum = 0; 85 for (unsigned i = 0; i < MAXSIZE; ++i) 86 { 87 sum += myVec[i].iValue; 88 } 89 tt.stop(); 90 cout << "funSubscript :: " << tt.cost() << endl; 91 } 92 93 // 使用vector::at()成员函数 94 void funAt() 95 { 96 vector<BigTestStruct> myVec; 97 FillVector(myVec); 98 tt.reset(); 99 tt.start(); 100 int sum = 0; 101 for (unsigned i = 0; i < MAXSIZE; ++i) 102 { 103 sum += myVec.at(i).iValue; 104 } 105 tt.stop(); 106 cout << "funAt :: " << tt.cost() << endl; 107 } 108 109 // 使用迭代器 110 void funIter() 111 { 112 vector<BigTestStruct> myVec; 113 FillVector(myVec); 114 tt.reset(); 115 tt.start(); 116 int sum = 0; 117 for (auto iter = myVec.begin(); iter != myVec.end(); ++iter) 118 { 119 sum += iter->iValue; 120 } 121 tt.stop(); 122 cout << "funIter :: " << tt.cost() << endl; 123 } 124 125 void main() 126 { 127 funAt(); 128 funSubscript(); 129 funIter(); 130 131 system("pause"); 132 } 133 134 // run out: 135 /* 136 funAt :: 16 137 funSubscript :: 3 138 funIter :: 78 139 请按任意键继续. . . 140 */
相比较后,强烈建议使用下标或者at()成员函数,可见迭代器(迭代器的设计主要为了算法通用)的效率最低。
【5】尽量避免在vector前部插入元素。
下面对比一下向list和vector两种容器前插入数据的效率,示例代码如下:
1 #include <list> 2 #include <vector> 3 #include <ctime> 4 #include <iostream> 5 using namespace std; 6 7 #define MAXSIZE 10000 8 9 // 计时器 10 // 调用clock()函数实现,返回毫秒(ms)数 11 class TestProgramRunTimer 12 { 13 enum { kClockPerSecond = CLOCKS_PER_SEC }; // 每秒时钟的跳数 14 15 public: 16 TestProgramRunTimer() 17 : cost_time(0) 18 , start_time(0) 19 , end_time(0) 20 { 21 start(); 22 } 23 24 ~TestProgramRunTimer() 25 {} 26 27 void reset() 28 { 29 cost_time = 0; 30 start_time = 0; 31 end_time = 0; 32 } 33 34 void start() 35 { 36 start_time = clock(); 37 } 38 39 void stop() 40 { 41 end_time = clock(); 42 cost_time = (end_time - start_time) / (kClockPerSecond / 1000.0); 43 return; 44 } 45 46 double cost() 47 { 48 return cost_time; 49 } 50 51 protected: 52 double cost_time; 53 clock_t start_time; 54 clock_t end_time; 55 }; 56 57 TestProgramRunTimer tt; 58 59 struct BigTestStruct 60 { 61 int iValue; 62 float fValue; 63 long lValue; 64 double dValue; 65 char cNameArr[10]; 66 int iValArr[100]; 67 }; 68 69 void FillVector(vector<BigTestStruct>& testVector) 70 { 71 for (int i = 0; i < MAXSIZE; ++i) 72 { 73 BigTestStruct bt; 74 testVector.insert(testVector.begin(), bt); 75 } 76 } 77 78 void FillList(list<BigTestStruct>& testList) 79 { 80 for (int i = 0; i < MAXSIZE; ++i) 81 { 82 BigTestStruct bt; 83 testList.insert(testList.begin(), bt); 84 } 85 } 86 87 void main() 88 { 89 list<BigTestStruct> myList; 90 vector<BigTestStruct> myVec; 91 tt.reset(); 92 tt.start(); 93 FillList(myList); 94 tt.stop(); 95 cout << "FillList :: " << tt.cost() << endl; 96 97 tt.reset(); 98 tt.start(); 99 FillVector(myVec); 100 tt.stop(); 101 cout << "FillVector :: " << tt.cost() << endl; 102 103 system("pause"); 104 } 105 106 // run out: 107 /* 108 FillList :: 47 109 FillVector :: 12658 110 请按任意键继续. . . 111 */
任何在 vetor 前部部做的插入操作其复杂度都是 O(n) 的。
在前部插入数据十分低效,因为 vector 容器中的每个元素项都必须为新插入的元素项腾出空间而被复制移动。
如果在应用 vector 时需要从前部连续插入很多元素,那可能需要重新评估你的总体架构。
【6】emplace_back
关于emplace_back与push_back两者的区别,请参见随笔《emplace_back与push_back的区别》
【7】std::vector 比较两个vector是否相同
关于比较两个vector是否相同以及注意事项,请参见随笔《std::vector 比较两个vector是否相等》
【8】std::vector 判断vector容器中是否存在某元素
关于查找vector容器中是否存在某个元素以及注意事项,请参见随笔《std::vector 判断vector容器中是否存在某元素》
Good Good Study, Day Day Up.
顺序 选择 循环 总结