• 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】向vector容器插入元素时使用emplace_back而不是push_back。

    对比示例代码如下:

      1 #include <vector>
      2 #include <ctime>
      3 #include <iostream>
      4 using namespace std;
      5 
      6 #define MAXSIZE 10000
      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 FV_ByPushBack(vector<BigTestStruct>& testVector)
     69 {
     70     tt.reset();
     71     tt.start();
     72     for (int i = 0; i < MAXSIZE; ++i)
     73     {
     74         BigTestStruct bt;
     75         testVector.push_back(bt);
     76     }
     77     tt.stop();
     78     cout << "FV_ByPushBack :: " << tt.cost() << endl;
     79 }
     80 
     81 void FV_ByEmplaceBack(vector<BigTestStruct>& testVector)
     82 {
     83     tt.reset();
     84     tt.start();
     85     for (int i = 0; i < MAXSIZE; ++i)
     86     {
     87         BigTestStruct bt;
     88         testVector.emplace_back(bt);
     89     }
     90     tt.stop();
     91     cout << "FV_ByEmplaceBack :: " << tt.cost() << endl;
     92 }
     93 
     94 void main()
     95 {
     96     vector<BigTestStruct> myVec1, myVec2;
     97     FV_ByPushBack(myVec1);
     98     FV_ByEmplaceBack(myVec2);
     99 
    100     system("pause");
    101 }
    102 
    103 // run out:
    104 /*
    105 FV_ByPushBack :: 31
    106 FV_ByEmplaceBack :: 16
    107 请按任意键继续. . .
    108 */

     通过结果分析,“安置”函数比插入函数性能更好。

     关于emplace_back与push_back两者的区别,请参见随笔《emplace_back与push_back的区别

    Good Good Study, Day Day Up.

    顺序 选择 循环 总结

  • 相关阅读:
    动态规划(DP计数):HDU 5117 Fluorescent
    动态规划(DP计数):HDU 5116 Everlasting L
    动态规划(区间DP):HDU 5115 Dire Wolf
    数学(扩展欧几里得算法):HDU 5114 Collision
    二叉树面试题
    linux eval命令
    关于Shell中命令替换$(...)与后置引用`...`的不同
    linux set,env和export
    linux crontab 命令
    排序
  • 原文地址:https://www.cnblogs.com/Braveliu/p/6622298.html
Copyright © 2020-2023  润新知