C++容器:线性容器[vector - array]

文章详细介绍了C++中的两种线性容器——vector和array,分析了它们的特点、内存管理和操作方法。vector作为动态数组,具有自动扩容功能,而array在编译时固定大小,更适合替代C风格数组。文章还通过示例展示了vector的扩容行为、常用操作,以及与C风格数组的操作对比,强调了迭代器在容器操作中的重要性。
摘要由CSDN通过智能技术生成
//!
//! C++容器: 线性容器[vector - array]
//!
//! == 容器简介 ==
//! 本章讲解的是C++ STL中的线性容器,所谓线性容器就容器底层只会分配一块连续内存,
//!     在C++11 STL标准容器中只有array与vector容器算的上是线性容器,
//!     它们都是只分配一块连续内存作为数据的存储。
//!
//! array容器特点:与C语言数组几乎一致,但被封装成C++容器接口,是类对象而不是指针
//! vector容器特点:动态数组,会动态的规划内存,使用者无需关心内存分配问题
//! == 容器简介 ==
//!
//!
//! == array ==
//! 模板声明:
//!     template<class T,std::size_t N> struct array;
//!
//! array容器与其他的STL标准容器不同,不存在分配器,
//!     使用std::size_t N参数在编译期指定分配内存的数量,
//!     不能在运行时重新规划内存大小,是类对象而不是指针,
//!     所以不用担心作为返回值时内存释放问题
//! == array ==
//!
//!
//! == vector ==
//! 模板声明:
//!     template<class T,class Allocator = std::allocator<T>> class vector;
//!
//! vector容器其他STL标准容器一样,带有std::allocator<T>分配器,
//!     可自动管理内存的分配与释放
//! vector容器存在总容量与已使用容量的概念,当已使用容量超出总容量,
//!     会开辟新控件进行隐式扩容,通常新的总容量是旧的2倍
//! vector容器扩容的时间成本很高,扩容问题是所有线性容器的通病
//! == vector ==
//!
//!
//! == 对比数组 ==
//! array容器:可以完全平替C风格数组,无副作用,也可以与C风格数组相互转换,
//!                 array容器内置STL容器的常用操作接口,
//!                 支持迭代器,可以与STL算法结合使用,
//!                 所以array容器更佳方便操作,作为接口传参更友好
//!
//! vector容器:C++风格代码在连续空间的内存选择上,
//!                 通常vector容器比C风格数组与array数组的出场率都要高的多,
//!                 所以vector容器是线程数组的常用选择。
//!             优点:可以自动扩容,对于小量且多次的内存分配更快,无需考虑数组大小,更方便
//!             缺点:存在总空间与使用空间的概念,会消耗额外的空间
//! == 对比数组 ==
//!
//!
//! == 容器操作 ==
//!     1.vector容器扩容测试
//!     2.vector容器常用操作
//!     3.C风格与array容器数组操作对比
//!     4.vector应用场景
//! == 容器操作 ==
//!
//!
//! 结束语:
//!     因为STL算法的加持,C++风格代码更推荐使用array代替C风格数组,
//!         如果非对内存管理不严格,建议使用vector代替array以得到更多灵活性,
//!         所以C++风格代码对数组的优先选择应该是vector容器
//!
#include <iostream>
#include <algorithm>
#include <array>
#include <vector>
#include <list>
#include <ctime>

using namespace std;

//===== vector容器扩容测试  =====
void test_1()
{
    cout<<endl<<endl<<"== 默认总空间下追加 =="<<endl;
    vector<int> vec;

    cout<<"总空间容量: "<<vec.capacity()<<endl;
    for(size_t i=0;i<10000;i++)
    {
        vec.push_back(i);
        if(vec.size() == vec.capacity())
            cout<<"size = "<<i<<"  |"<<vec.size()<<"|"<<vec.capacity()<<endl;
    }
    cout<<"退出时总空间容量: "<<vec.capacity()<<" | 已使用:"<<vec.size()<<endl;
}

void test_2()
{
    cout<<endl<<endl<<"== 预分配下追加: resize[扩容使用空间] =="<<endl;
    vector<int> vec;
    vec.resize(50);

    cout<<"预分配 50,总空间容量: "<<vec.capacity()<<endl;
    for(size_t i=0;i<10000;i++)
    {
        vec.push_back(i);
        if(vec.size() == vec.capacity())
            cout<<"size = "<<i<<"  |"<<vec.size()<<"|"<<vec.capacity()<<endl;
    }
    cout<<"退出时总空间容量: "<<vec.capacity()<<" | 已使用:"<<vec.size()<<endl;
}

void test_3()
{
    cout<<endl<<endl<<"== 预分配下追加: reserve[扩容总空间] =="<<endl;
    vector<int> vec;
    vec.reserve(50);

    cout<<"预分配 50,总空间容量: "<<vec.capacity()<<endl;
    for(size_t i=0;i<10000;i++)
    {
        vec.push_back(i);
        if(vec.size() == vec.capacity())
            cout<<"size = "<<i<<"  |"<<vec.size()<<"|"<<vec.capacity()<<endl;
    }
    cout<<"退出时总空间容量: "<<vec.capacity()<<" | 已使用:"<<vec.size()<<endl;
}
//===== vector容器扩容测试  =====



//===== vector容器常用操作  =====
void test_4()
{
    cout<<endl<<endl<<"===== vector容器常用操作  ====="<<endl;

    //初始化
    size_t size = 10;
    vector<int> vec;

    //添加元素
    for(size_t i=0;i<size;i++) { vec.push_back(i); }

    //切片
    vector<int> vec2(vec.begin(),vec.begin()+size/2);
    cout<<endl<<"迭代器切片大小: "<<vec2.size()<<endl;


    //== 遍历 ==
    //遍历:at()函数
    cout<<endl<<"== 正常遍历 =="<<endl;
    cout<<"at()函数: "<<endl;
    try {
        for(size_t i=0;i<size;i++) { cout<<vec.at(i)<<" "; }
        cout<<endl;
    } catch (...) { cout<<"越界"<<endl; }

    //遍历:下标
    cout<<"下标: "<<endl;
    for(size_t i=0;i<size;i++) { cout<<vec[i]<<" "; }
    cout<<endl;

    //遍历:迭代器
    cout<<"迭代器: "<<endl;
    for(auto it = vec.begin();it!=vec.end();it++){ cout<<*it<<" "; }
    cout<<endl;

    //遍历:快速for
    cout<<"快速for: "<<endl;
    for(const auto &a :vec) { cout<<a<<" "; }
    cout<<endl;

    //越界遍历:
    //  使用下标遍历可能会出现越界现象,
    //      但是一个合格的程序员几乎不可能出现数组越界的情况,
    //      使用哪一种遍历方式完全由个人风格决定,
    //      如果是接口函数,优先选择迭代器可以适配不同容器
    cout<<endl<<"== 越界遍历 =="<<endl;
    cout<<"at()函数: "<<endl;
    try {
        for(size_t i=0;i<size +10;i++) { cout<<vec.at(i)<<" "; }
        cout<<endl;
    } catch (...) { cout<<"越界"<<endl; }

    //遍历:下标
    cout<<"下标: "<<endl;
    for(size_t i=0;i<size +10;i++) { cout<<vec[i]<<" "; }
    cout<<endl;

    vec.clear();
    cout<<"清空后大小: "<<vec.size()<<" | 总容量: "<<vec.capacity()<<endl;
    //== 遍历 ==


    //== 插入方式 ==
    //  push_back插入是vector最常用的方式之一,
    //      emplace_back与push_back的插入方式的却别在于,
    //      如果vector内存储的是类,emplace_back无需提前声明类,
    //      传入的参数就可以让容器原地构造对象并插入到容器内,
    //      push_back则需要提前声明对象,再插入到容器
    //  insert插入可以指定元素插入位置,并将位置之后的元素后推,非常消耗时间
    //
    //尾插,不会重排
    cout<<endl<<"== 插入与移除 =="<<endl;
    for(size_t i=0;i<size;i++) { vec.push_back(i); }
    cout<<"插入方式:push_back[尾插值] | "<<vec.size()<<endl;

    //尾插,不会重排,类似push_back
    for(size_t i=0;i<size;i++) { vec.emplace_back(i); }
    cout<<"插入方式:emplace_back[尾插值] | "<<vec.size()<<endl;

    //迭代器位置插入元素,会重排,重排时将迭代器之后的元素整体向后推
    for(size_t i=0;i<size;i++) { vec.insert(vec.begin(),i); }
    cout<<"插入方式:insert[位置迭代器 + 值] | "<<vec.size()<<endl;

    //从迭代器位置插入其他迭代器范围内的元素,不限容器类型,只需要支持迭代器
    vec.insert(vec.end(),vec2.begin(),vec2.end());
    list<int> ls{1,2,3,4,5};
    vec.insert(vec.end(),ls.begin(),ls.end());
    cout<<"插入方式:insert[位置迭代器 + 开始迭代器 + 结束迭代器] | "<<vec.size()<<endl;
    //== 插入方式 ==


    //== 移除方式 ==
    //移除最后一个元素,不会重排,时间最快
    for(size_t i=0;i<size/2;i++) { vec.pop_back(); }
    cout<<endl<<"移除方式:pop_back | "<<vec.size()<<endl;

    //移除迭代器指定元素的下标,会重排,数组的通病
    for(size_t i=0;i<size/2;i++) { vec.erase(vec.begin()); }
    cout<<"移除方式:erase [位置迭代器]| "<<vec.size()<<endl;

    //移除迭代器指定范围的元素,会重排
    vec.erase(vec.begin(),vec.begin() + vec.size()/2);
    cout<<"移除方式:erase [开始迭代器 + 结束迭代器]| "<<vec.size()<<endl;
    //== 移除方式 ==


    //== 迭代器失效 ==
    //  迭代器失效往往会造成结果的错误,或者程序崩溃
    //      所以在操作STL容器时应该要时刻注意迭代器的位置,
    //      因为STL不提供任何范围矫正
    //  通常来说只要对容器的值做了任何的修改、删除、添加都要及时矫正迭代器,
    //      尽管不是所有的操作都会影响到迭代器的移动或者失效,
    //      但随时保持矫正迭代器的习惯总是能避免出错
    cout<<endl<<"== 迭代器失效 =="<<endl;
    cout<<"容器使用空间: "<<vec.size()<<"|总容量: "<<vec.capacity()<<endl;
    vector<int>::iterator it = vec.begin() + vec.size()/2;
    cout<<"迭代器位置: "<<it - vec.begin() <<"|值: "<<*it<<endl;

    vec.insert(vec.begin(),0);
    cout<<"迭代器位置前方插入数值,迭代器位置[已移动]: "<<it - vec.begin() <<"|值: "<<*it<<endl;

    vector<int> vec3;
    vec3.resize(1000);
    vec.insert(vec.begin(),vec3.begin(),vec3.end());
    cout<<"插入大量数据: "<<vec3.size()<<endl;
    cout<<"容器开辟新空间,新空间容器,使用空间: "<<vec.size()<<"|总容量: "<<vec.capacity()<<endl;
    cout<<"迭代器位置[已失效]: "<<it - vec.begin()<<"|"<<*it<<endl;

    cout<<endl<<"== 迭代器失效 : 矫正 =="<<endl;
    cout<<"容器使用空间: "<<vec.size()<<"|总容量: "<<vec.capacity()<<endl;
    vector<int>::iterator it2 = vec.begin() + vec.size()/2;
    cout<<"迭代器位置: "<<it2 - vec.begin() <<"|值: "<<*it2<<endl;

    vec.insert(vec.begin(),0);
    it2 = vec.begin() + vec.size()/2;
    cout<<"迭代器位置[修正移动]: "<<it2 - vec.begin() <<"|值: "<<*it2<<endl;

    vector<int> vec4;
    vec4.resize(3000);
    vec.insert(vec.begin(),vec4.begin(),vec4.end());
    cout<<"插入大量数据: "<<vec4.size()<<endl;
    cout<<"容器开辟新空间,新空间容器,使用空间: "<<vec.size()<<"|总容量: "<<vec.capacity()<<endl;

    it2 = vec.begin() + vec.size()/2;
    cout<<"迭代器位置[修正失效]: "<<it2 - vec.begin()<<"|"<<*it<<endl;
    //== 迭代器失效 ==


    //== 空间回收 ==
    cout<<endl<<"== 空间回收 =="<<endl;
    vec.erase(vec.begin(),vec.begin() + vec.size()/2);
    cout<<"[回收前] 使用空间: "<<vec.size()<<"|总容量: "<<vec.capacity()<<endl;

    //vector容器在内存分配上只增不减,总容量永远保存峰值状态,即时不使用
    //      当想回收内存时,可以利用栈空间自动释放的原理,释放多余空间
    {
        auto tm = vec;
        vec.swap(tm);
    }
    cout<<"[回收后] 使用空间: "<<vec.size()<<"|总容量: "<<vec.capacity()<<endl;
    //== 空间回收 ==
}
//===== vector容器常用操作  =====



//===== C风格与array容器数组操作对比 =====
void test_5()
{
    cout<<endl<<endl<<"===== C风格与array容器数组操作对比 ====="<<endl;

    //初始化
    const size_t size = 10;
    int arr_c[size];
    array<int,size> arr_cpp;

    //添加元素
    for(size_t i=0;i<size;i++) { arr_c[i] = i; }
    for(size_t i=0;i<size;i++) { arr_cpp[i] = i; }

    //遍历容器元素
    cout<<endl<<"== 遍历元素 =="<<endl;
    auto show = [](int *c,array<int,size>cpp){
        for(size_t i=0;i<size;i++)
        { cout<<c[i]<<"|"<<cpp[i]<<"  "; }
        cout<<endl;
    };
    show(arr_c,arr_cpp);

    //复制到新数组
    cout<<endl<<"== 复制到新数组 =="<<endl;
    array<int,size> arr_cpp2 = arr_cpp;

    int arr_c2[size];
    for(size_t i=0;i<size;i++) { arr_c2[i] = arr_c[i]; }
    show(arr_c2,arr_cpp2);

    //新容器内容反转
    cout<<endl<<"== 新容器内容反转 =="<<endl;
    std::reverse(arr_cpp2.begin(),arr_cpp2.end());

    for(size_t i=0;i<size/2;i++)
    { int tm = arr_c2[i]; arr_c2[i] = arr_c2[size -i -1]; arr_c2[size -i -1] = tm;}
    show(arr_c2,arr_cpp2);

    //与新容器交换内容
    cout<<endl<<"== 新容器内容反转 =="<<endl;
    arr_cpp.swap(arr_cpp2);

    for(size_t i=0;i<size;i++)
    { int tm = arr_c[i]; arr_c[i] = arr_c2[i]; arr_c2[i] = tm; }

    cout<<"旧容器: ";
    show(arr_c,arr_cpp);

    cout<<"新容器: ";
    show(arr_c2,arr_cpp2);

    //array容器转C风格数组
    cout<<"array容器转C风格数组: ";
    int *arr_tm = arr_cpp.data();
    show(arr_tm,arr_cpp);

}
//===== C风格与array容器数组操作对比 =====



//===== vector应用场景 =====
class dog
{
public:
    dog(string name,int age) : _name(name),_age(age){}
    void show() { cout<<"dog: "<<_name<<"|"<<_age<<endl; }
private:
    string _name;
    int _age = 0;
};

void test_6()
{
    cout<<endl<<endl<<"===== vector应用场景 ====="<<endl;

    //== 场景1:容器嵌套 生成矩阵 10X10 ==
    srand(time(NULL));
    const int size = 10;
    vector<vector<int>> vec1(size);
    for(int x=0;x<size;x++)
    {
        vector<int> tm(size);
        for(int y=0;y<size;y++) { tm[y] = rand() % 100; }
        vec1[x] = tm;
    }

    cout<<endl<<"== 场景1:生成矩阵 10X10 =="<<endl;
    for(size_t x1=0;x1<vec1.size();x1++)
    {
        for(size_t y1=0;y1<vec1[x1].size();y1++) { cout<<vec1[x1][y1]<<"\t"; }
        cout<<endl;
    }


    //== 场景2:存储对象 10个 ==
    vector<dog> vec2;
    for(int i=0;i<size;i++)
    {
        string name = "小狗" + std::to_string(rand() % 100);
        vec2.emplace_back(name,rand() % 20); //直接构造对象到容器
    }

    cout<<endl<<"== 场景2:存储对象 =="<<endl;
    for(size_t i=0;i<vec2.size();i++)
    { vec2[i].show(); }


    //== 场景3:存储指针 10个 ==
    vector<dog*> vec3;
    for(int i=0;i<size;i++)
    {
        string name = "小狗" + std::to_string(rand() % 100);
        dog* p = new dog(name,rand() % 20);
        vec3.push_back(p);
    }

    cout<<endl<<"== 场景3:存储指针 =="<<endl;
    for(size_t i=0;i<vec3.size();i++)
    { vec3[i]->show(); }

    //释放
    for(size_t i=0;i<vec3.size();i++)
    { delete vec3[i]; }
}
//===== vector应用场景 =====



//== windows中文显示 ==
#ifdef _WIN32
#include <windows.h>
#endif


int m_main()
{
    //== Windows控制台: UTF-8编码 ==
#ifdef _WIN32
    SetConsoleOutputCP(65001);
#endif

    cout<<"== begin =="<<endl;

    test_1();
    test_2();
    test_3();
    test_4();
    test_5();
    test_6();

    cout<<"== end =="<<endl;
    return 0;
}

int main()
{
    m_main();
    return 0;
}

/*
== begin ==


== 默认总空间下追加 ==
总空间容量: 0
size = 0  |1|1
size = 1  |2|2
size = 3  |4|4
size = 7  |8|8
size = 15  |16|16
size = 31  |32|32
size = 63  |64|64
size = 127  |128|128
size = 255  |256|256
size = 511  |512|512
size = 1023  |1024|1024
size = 2047  |2048|2048
size = 4095  |4096|4096
size = 8191  |8192|8192
退出时总空间容量: 16384 | 已使用:10000


== 预分配下追加: resize[扩容使用空间] ==
预分配 50,总空间容量: 50
size = 49  |100|100
size = 149  |200|200
size = 349  |400|400
size = 749  |800|800
size = 1549  |1600|1600
size = 3149  |3200|3200
size = 6349  |6400|6400
退出时总空间容量: 12800 | 已使用:10050


== 预分配下追加: reserve[扩容总空间] ==
预分配 50,总空间容量: 50
size = 49  |50|50
size = 99  |100|100
size = 199  |200|200
size = 399  |400|400
size = 799  |800|800
size = 1599  |1600|1600
size = 3199  |3200|3200
size = 6399  |6400|6400
退出时总空间容量: 12800 | 已使用:10000


===== vector容器常用操作  =====

迭代器切片大小: 5

== 正常遍历 ==
at()函数:
0 1 2 3 4 5 6 7 8 9
下标:
0 1 2 3 4 5 6 7 8 9
迭代器:
0 1 2 3 4 5 6 7 8 9
快速for:
0 1 2 3 4 5 6 7 8 9

== 越界遍历 ==
at()函数:
0 1 2 3 4 5 6 7 8 9 越界
下标:
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 -1443481923 14393
清空后大小: 0 | 总容量: 16

== 插入与移除 ==
插入方式:push_back[尾插值] | 10
插入方式:emplace_back[尾插值] | 20
插入方式:insert[位置迭代器 + 值] | 30
插入方式:insert[位置迭代器 + 开始迭代器 + 结束迭代器] | 40

移除方式:pop_back | 35
移除方式:erase [位置迭代器]| 30
移除方式:erase [开始迭代器 + 结束迭代器]| 15

== 迭代器失效 ==
容器使用空间: 15|总容量: 60
迭代器位置: 7|值: 7
迭代器位置前方插入数值,迭代器位置[已移动]: 7|值: 6
插入大量数据: 1000
容器开辟新空间,新空间容器,使用空间: 1016|总容量: 1016
迭代器位置[已失效]: -1061|6

== 迭代器失效 : 矫正 ==
容器使用空间: 1016|总容量: 1016
迭代器位置: 508|值: 0
迭代器位置[修正移动]: 508|值: 0
插入大量数据: 3000
容器开辟新空间,新空间容器,使用空间: 4017|总容量: 4017
迭代器位置[修正失效]: 2008|6

== 空间回收 ==
[回收前] 使用空间: 2009|总容量: 4017
[回收后] 使用空间: 2009|总容量: 2009


===== C风格与array容器数组操作对比 =====

== 遍历元素 ==
0|0  1|1  2|2  3|3  4|4  5|5  6|6  7|7  8|8  9|9

== 复制到新数组 ==
0|0  1|1  2|2  3|3  4|4  5|5  6|6  7|7  8|8  9|9

== 新容器内容反转 ==
9|9  8|8  7|7  6|6  5|5  4|4  3|3  2|2  1|1  0|0

== 新容器内容反转 ==
旧容器: 9|9  8|8  7|7  6|6  5|5  4|4  3|3  2|2  1|1  0|0
新容器: 0|0  1|1  2|2  3|3  4|4  5|5  6|6  7|7  8|8  9|9
array容器转C风格数组: 9|9  8|8  7|7  6|6  5|5  4|4  3|3  2|2  1|1  0|0


===== vector应用场景 =====

== 场景1:生成矩阵 10X10 ==
78      5       96      4       94      54      74      55      94      47
10      6       99      75      62      26      90      78      67      66
62      27      51      39      11      90      50      57      94      3
26      22      66      39      56      42      78      86      64      55
28      62      0       62      86      80      11      33      55      88
79      66      96      38      94      88      51      22      73      28
67      55      74      79      46      71      27      2       24      85
2       70      80      25      86      2       8       61      52      73
4       80      31      36      27      3       1       59      98      37
65      87      73      95      92      93      23      48      89      76

== 场景2:存储对象 ==
dog: 小狗83|19
dog: 小狗8|5
dog: 小狗68|19
dog: 小狗67|10
dog: 小狗26|6
dog: 小狗54|7
dog: 小狗95|1
dog: 小狗29|0
dog: 小狗85|7
dog: 小狗84|6

== 场景3:存储指针 ==
dog: 小狗36|0
dog: 小狗7|18
dog: 小狗62|15
dog: 小狗25|4
dog: 小狗37|11
dog: 小狗78|9
dog: 小狗29|15
dog: 小狗18|2
dog: 小狗46|17
dog: 小狗0|14
== end ==

*/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值