《C++初阶之STL》【vector容器:详解 + 实现】

发布于:2025-07-26 ⋅ 阅读:(16) ⋅ 点赞:(0)

【vector容器:详解 + 实现】目录

在这里插入图片描述

往期《C++初阶》回顾:

/------------ 入门基础 ------------/
【C++的前世今生】
【命名空间 + 输入&输出 + 缺省参数 + 函数重载】
【普通引用 + 常量引用 + 内联函数 + nullptr】
/------------ 类和对象 ------------/
【类 + 类域 + 访问限定符 + 对象的大小 + this指针】
【类的六大默认成员函数】
【初始化列表 + 自定义类型转换 + static成员】
【友元 + 内部类 + 匿名对象】
【经典案例:日期类】
/------------ 内存管理 ------------/
【内存分布 + operator new/delete + 定位new】
/------------ STL ------------/
【泛型编程 + STL简介】
【auto关键字 + 范围for循环 + 迭代器】
【string类:详解 + 实现】

前言:

hi~ 小伙伴们大家好呀 (◍・ᴗ・◍) ゝ !偷偷告诉你们一个小秘密✨,今天可是超特别的 “闰六月” 哦~ 先给大家简单科普一下什么是 “闰六月” 吧~​


“闰六月”:是农历(阴历)中的一种置闰现象🌙,专门用来协调农历与回归年(地球绕太阳公转一周的时间)的差异而设置的呢~​
简单一点说: “闰六月” 就是指在农历六月之后再增加一个六月哦~(≧▽≦) 这个增加的月份就称为 “闰六月”,这一年的农历就会有 13 个月啦,其中会有两个六月(前一个是正六月,后一个就是闰六月啦~)
这种情况可不常见哦~ 间隔时间也不固定,可能几年到几十年才会遇到一次呢~⌛​

好了好了 (≧∇≦)ノ 我们开始今天的学习吧~ 【vector 容器:详解 + 实现】,这篇博客将近 2w 字哦~ 内容分为两部分:vector 容器的 详解实现
温馨提示✨:实现才是这篇博客的精髓哟~,所以希望小伙伴们一定要坚持看到最后呀~ 千万不要错过精彩内容啦 (≧▽≦)~


本来博主是想分成两篇来写的,但为了让大家能一口气学完,不影响阅读体验,就选择写成一篇啦~​
咳咳………… 如果小伙伴们一口气学不完的话,点个收藏慢慢学也可以呀~(◍・ᴗ・◍) ゝ
嗯… 其实关注博主也非常推荐的呦~ 哈哈哈 (≧▽≦)

------------标准接口介绍------------

标准模板库中的vector容器是什么样的呢?

cplusplus网站上关于C++的vector容器的介绍vector - C++ Reference

C++ 标准模板库(STL)中的vector容器相关知识,主要可以分为以下三个部分:

  1. 成员函数提供了vector容器的各类操作接口,涵盖元素访问、添加、删除、修改、容量管理等功能
  2. 非成员函数重载:通过重载与vector相关的运算符和函数,实现vector与其他数据类型或容器之间的交互,以及对vector进行便捷的通用操作
  3. 模板特化:针对特定类型对vector模板进行定制化处理,优化性能或满足特殊需求

在这里插入图片描述

1. 常见构造

在这里插入图片描述

构造函数声明 接口说明
vector(size_type n, const value_type& val = value_type())
填充构造函数
std::vector<int> v2(5, 10);
std::vector<std::string> v3(3);
构造并初始化 n 个值为 val 的元素,创建一个包含 n 个指定值元素的 vector 对象。如果未指定 val,元素将使用默认构造函数初始化
vector(const vector& x)
拷贝构造函数
std::vector<int> v4(v2);
std::vector<int> v5 = v2;
通过复制另一个 vector 对象 x 来创建新的 vector 对象,新对象与原对象内容完全相同
vector(InputIterator first, InputIterator last)
迭代器范围构造函数
int arr[] = {1,2,3};
std::vector<int> v6(arr, arr+3);
使用迭代器进行初始化构造,利用给定迭代器范围 [first, last) 内的元素来初始化 vector 对象
#include <iostream>
#include <vector>
using namespace std;

int main()
{
    // 1. 默认构造函数
    vector<int> first; 
    /* 说明:
    *    1. 创建一个空的int类型vector容器
    *    2. 此时容器中没有任何元素,size为0,capacity可能为0
    */

    // 2. 填充构造函数
    vector<int> second(4, 100);
    /* 说明:
    *    1. 创建一个包含4个元素的vector,每个元素初始值为100
    *    2. 构造后容器size为4,所有元素都是100
    */

    // 3. 迭代器范围构造函数 
    vector<int> third(second.begin(), second.end());
    /* 说明:
    *    1. 通过另一个容器的迭代器范围来构造
    *    2. 这里使用second容器的begin()和end()迭代器,构造一个与second元素相同的vector
    *    3. 本质是将second中的所有元素逐个拷贝到third中
    */

    // 4. 拷贝构造函数
    vector<int> fourth(third);
    /* 说明:
    *    1. 通过拷贝另一个vector来构造
    *    2. 这里直接拷贝third容器,构造出一个完全相同的新容器fourth
    */

    // 5. 数组构造:   
    int myints[] = { 16, 2, 77, 29 };   
    vector<int> fifth(myints, myints + sizeof(myints) / sizeof(int));
    /* 说明:通过数组的指针范围来构造vector
    *    1. 先定义一个int数组
    *    2. 计算数组元素个数:总字节数 / 单个元素字节数
    *    3. 然后使用数组的起始地址和结束地址(myints + 4)来构造fifth
    */

    // 使用迭代器遍历fifth容器并输出fifth容器中的内容
    cout << "fifth容器中的内容:";
   
    //1)vector<int>::iterator是vector<int>的迭代器类型,用于访问容器元素
    //2)begin()返回指向第一个元素的迭代器,end()返回指向最后一个元素后一位的迭代器
    for (vector<int>::iterator it = fifth.begin(); it != fifth.end(); ++it) 
    {
        //3)*it获取迭代器指向的元素值
        cout << ' ' << *it;
    }

    cout << '\n';
    return 0;
}

在这里插入图片描述

2. 容量操作

在这里插入图片描述

容量空间相关接口 接口说明
size 用于获取 vector 中实际存储的数据个数
capacity 用于获取 vector 当前已分配的容量大小
即:能够容纳元素的最大数量
empty 用于判断 vector 是否为空
若其中没有存储任何元素则返回 true,否则返回 false
resize 用于改变 vector 的大小(即:有效元素个数),可以增加或减少元素数量
增加元素时,可指定新元素的默认值
减少元素时,多余元素会被截断
reserve 用于改变 vector 的容量
即:提前分配一定数量的存储空间,以避免在后续插入元素时频繁进行内存重新分配和数据拷贝操作

std::vector::size

在这里插入图片描述

// vector::size
#include <iostream>
#include <vector>
using namespace std; 

int main()
{
    //1. 创建一个空的 vector<int> 容器,用于存储 int 类型的数据
    vector<int> myints; 
    cout << "0. size: " << myints.size() << '\n';

    //2. 循环 10 次,每次向 myints 的末尾添加一个元素
    for (int i = 0; i < 10; i++)
    {
        myints.push_back(i);
    }
    cout << "1. size: " << myints.size() << '\n';

    //3. insert 成员函数用于在指定位置插入元素
    myints.insert(myints.end(), 10, 100);  //注意:这里是在 myints 的末尾(myints.end() 指向的位置)插入 10 个值为 100 的元素
    cout << "2. size: " << myints.size() << '\n';

    //4. pop_back 成员函数用于移除 vector 末尾的一个元素
    myints.pop_back();
    cout << "3. size: " << myints.size() << '\n';

    return 0;
}

在这里插入图片描述

std::vector::capacity

在这里插入图片描述

#include <iostream>
#include <vector>
using namespace std;

int main()
{
    //1.创建一个空的int类型vector容器
    vector<int> myvector;

    //2.向vector中添加100个元素(值从0到99)
    for (int i = 0; i < 100; i++)
    {
        myvector.push_back(i); // push_back()会在容器末尾插入元素,当容量不足时会自动扩容
    }
        

    //3.1:size():返回当前容器中实际存储的元素个数
    cout << "size: " << (int)myvector.size() << '\n'; //注:这里添加了100个元素,所以size为100

    //3.2:capacity():返回当前容器在不重新分配内存的情况下,最多能容纳的元素个数
    cout << "capacity: " << (int)myvector.capacity() << '\n';
    /* 说明:
    *    1. 由于vector的扩容策略(通常是翻倍增长),capacity会大于或等于size
    *    2. 此处添加100个元素后,capacity可能是128(取决于具体实现)
    */

    return 0;
}

在这里插入图片描述

std::vector::empty

在这里插入图片描述

#include <iostream>
#include <vector>
using namespace std;

int main()
{
    //1.创建一个空的int类型vector容器
    vector<int> myvector;
    //2.用于存储容器元素的总和
    int sum(0);

    //3.向vector中添加10个元素,值从1到10
    for (int i = 1; i <= 10; i++)
    {
        myvector.push_back(i);
    }

    //4.empty()方法用于判断容器是否为空,返回true表示空,false表示非空
    while (!myvector.empty()) //循环条件:当容器不为空时继续执行
    {
        //4.1:back()方法返回容器中最后一个元素的引用
        sum += myvector.back();

        //4.2:pop_back()方法移除容器中最后一个元素(不会返回元素值)
        myvector.pop_back();
    }

    //5.输出所有元素的总和
    cout << "total: " << sum << '\n';

    return 0;
}

在这里插入图片描述

std::vector::resize

在这里插入图片描述

#include <iostream>   
#include <vector>     
using namespace std;

int main()
{
    //1.创建一个存储int类型的vector容器
    vector<int> myvector;

    //2.向vector中添加初始元素:1到9
    for (int i = 1; i < 10; i++) 
    {
        myvector.push_back(i); 
    }

    //3.调整vector大小为5,超出部分的元素会被删除
    myvector.resize(5);

    //4.调整vector大小为8,新增的3个元素使用100填充
    myvector.resize(8, 100);

    //5.调整vector大小为12,新增的4个元素使用默认值0填充(int类型的默认值)
    myvector.resize(12);

    //6.输出vector中的所有元素
    cout << "myvector容器中内容为:";
    for (int i = 0; i < myvector.size(); i++) 
    {
        cout << ' ' << myvector[i];  
    }
    cout << '\n';  
    return 0;
}

在这里插入图片描述

std::vector::reserve

在这里插入图片描述

#include <iostream>   
#include <vector>    
using namespace std;

int main()
{
    //1.定义一个size_type类型的变量,用于存储容器的容量
    vector<int>::size_type sz;

    /*-------------------------情况一:未进行reserve()-------------------------*/
    cout << "-------未进行reserve()-------" << endl;

    //2.创建第一个vector容器foo
    vector<int> foo;
    //3.获取foo的初始容量(此时为空,容量通常为0)
    sz = foo.capacity();
    cout << "making foo grow:\n";

    //4.向foo中添加100个元素(0到99)
    for (int i = 0; i < 100; ++i) 
    {
        //4.1:向vector容器尾插入元素
        foo.push_back(i);

        //4.1:检容量是否发生变化 ---> 如果发生了变化
        if (sz != foo.capacity()) 
        {
            //4.2.1:更新容量值
            sz = foo.capacity();

            //4.2.2:输出新的容量
            cout << "capacity changed: " << sz << '\n';  
        }
    }
    /* 说明:
    *    1. foo没有预先分配空间,每次容量不足时会自动扩容
    *    2. 通常vector的扩容策略是翻倍增长(不同编译器可能有差异)
    */

    /*-------------------------情况二:进行reserve()-------------------------*/
    cout << "\n-------进行reserve()-------" << endl;

    //5.创建第二个vector容器bar
    vector<int> bar;
    //6.获取bar的初始容量
    sz = bar.capacity();
    cout << "making bar grow:\n";

    //7.预先为bar分配至少能容纳100个元素的空间
    bar.reserve(100);

    //8.向bar中添加100个元素(0到99)
    for (int i = 0; i < 100; ++i) 
    {
        //8.1:向vector容器尾插入元素
        bar.push_back(i);

        //8.1:检容量是否发生变化 ---> 如果发生了变化
        if (sz != bar.capacity()) 
        {
            //8.2.1:更新容量值
            sz = bar.capacity();  

            //8.2.2:输出新的容量
            cout << "capacity changed: " << sz << '\n';  
        }
    }
    /* 说明:
    *    1. bar预先分配了足够的空间,添加100个元素过程中不会发生容量变化
    */

    return 0;
}

在这里插入图片描述

3. 访问操作

在这里插入图片描述

访问相关接口 接口说明
operator [] 重载的下标运算符,可像访问数组元素一样直接访问 vector 中的元素
提供高效的随机访问方式

std::vector::operator[]

在这里插入图片描述

#include <iostream>   
#include <vector>     
using namespace std;

int main()
{
    //1.创建一个包含10个int元素的vector,所有元素初始化为0
    vector<int> myvector(10);   

    //2.获取vector的大小(元素数量)
    vector<int>::size_type sz = myvector.size();

    //3.使用operator[]访问并修改vector中的元素
    for (unsigned i = 0; i < sz; i++) 
    {
        myvector[i] = i;  
    }

    //4.使用operator[]反转vector中的元素
    for (unsigned i = 0; i < sz / 2; i++)  //只需要循环到vector的中间位置即可完成反转
    {
        //4.1:定义临时变量,用于交换元素
        int temp;  

        //4.2:通过索引访问要交换的两个元素(对称位置)
        temp = myvector[sz - 1 - i];                // 获取右侧元素
        myvector[sz - 1 - i] = myvector[i];         // 将左侧元素放到右侧位置
        myvector[i] = temp;                         // 将右侧元素放到左侧位置
    }


    //5.使用operator[]访问元素并输出vector中的元素
    cout << "myvector容器中的内容为:";
    for (unsigned i = 0; i < sz; i++) 
    {
        cout << ' ' << myvector[i];  
    }
    cout << '\n';  
    return 0;
}

在这里插入图片描述

4. 修改操作

在这里插入图片描述

增删查改相关接口 接口说明
push_back 在 vector 尾部插入一个元素,当空间不足时会触发扩容操作
pop_back 删除 vector 尾部的一个元素,前提是 vector 不为空
insert 在 vector 指定位置(position)之前插入一个值为 val 的元素,插入后原位置及之后元素后移,可能触发扩容
erase 删除 vector 中指定位置(position)的元素,删除后其后元素依次前移
swap 交换两个 vector 的内部数据空间,通常用于高效地交换两个 vector 的内容

std::vector::push_back

在这里插入图片描述

#include <iostream>   
#include <vector>    
using namespace std;

int main()
{
    //1.创建一个存储int类型的空vector容器
    vector<int> myvector;
    //2.定义一个用于临时存储用户输入的整数
    int myint;
    cout << "请输入一些整数(输入 0 结束):\n";

    //3.循环读取用户输入并添加到vector中
    do 
    {
        cin >> myint;                // 从标准输入读取一个整数
        myvector.push_back(myint);   // 将读取的整数添加到vector的末尾
    } while (myint);                 // 当输入的整数为0时,结束循环

    //4.输出vector中存储的整数数量
    cout << "myvector容器中存储了 " << int(myvector.size()) << " 个数字.\n"; //注:size()返回元素个数,这里转换为int类型以便输出

    return 0;
}

在这里插入图片描述

std::vector::pop_back

在这里插入图片描述

#include <iostream> 
#include <vector>    
using namespace std;

int main()
{
    //1.创建一个存储int类型的vector容器
    vector<int> myvector;
    //2.定义用于存储vector中所有元素总和的变量
    int sum(0);

    //3.向vector中添加三个元素
    myvector.push_back(100);  
    myvector.push_back(200);  
    myvector.push_back(300);  

    //4.循环:当vector不为空时继续执行
    while (!myvector.empty())
    {
        //4.1:取出vector的最后一个元素并加到sum中
        sum += myvector.back();

        //4.2:移除vector的最后一个元素
        myvector.pop_back();
    }

    //5.输出所有元素的总和
    cout << "myvector 中所有元素的和为" << sum << '\n';

    return 0;
}

在这里插入图片描述

std::vector::insert

在这里插入图片描述

#include <iostream>   
#include <vector>     
using namespace std;

int main()
{
    //1.创建一个包含3个元素的vector,每个元素初始值为100
    vector<int> myvector(3, 100);
    //2.定义一个迭代器
    vector<int>::iterator it;

    //3.1:将迭代器指向vector的起始位置(第一个元素)
    it = myvector.begin();
    //3.2:在迭代器it指向的位置插入元素200
    it = myvector.insert(it, 200); //注:插入后迭代器自动指向新插入的元素

    //4.在当前it指向的位置(即第一个元素位置)插入2个300
    myvector.insert(it, 2, 300);

    it = myvector.begin();
    /* 注意:
    *     1. 经过上面的插入操作,之前的it可能已经失效
    *     2. 重新获取迭代器,指向vector的起始位置
    */

    //5.1:创建另一个vector,包含2个元素,每个元素为400
    vector<int> anothervector(2, 400);
    //5.2:在it+2的位置(第三个元素位置)插入anothervector的所有元素
    myvector.insert(it + 2, anothervector.begin(), anothervector.end());

    //6.1:定义一个数组,包含3个元素
    int myarray[] = { 501, 502, 503 };
    //6.2:在vector的起始位置插入数组中的所有元素
    myvector.insert(myvector.begin(), myarray, myarray + 3);

    //7.输出vector中的所有元素
    cout << "myvector 包含:";
    for (it = myvector.begin(); it < myvector.end(); it++) 
    {
        cout << ' ' << *it; 
    }
    cout << '\n';
    return 0;
}

在这里插入图片描述

std::vector::erase

在这里插入图片描述

#include <iostream>   
#include <vector>     
using namespace std;

int main()
{
    //1.创建一个存储int类型的vector容器
    vector<int> myvector;
    //2.向vector中添加元素(1到10)
    for (int i = 1; i <= 10; i++) 
    {
        myvector.push_back(i);
    }

    //3.删除第6个元素
    myvector.erase(myvector.begin() + 5); 
    /* 说明:
    *    1. begin()返回指向第一个元素的迭代器,+5表示向后移动5个位置
    *    2. 删除后myvector包含:1, 2, 3, 4, 5, 7, 8, 9, 10
    */

    //4.删除从第一个元素开始的前3个元素
    myvector.erase(myvector.begin(), myvector.begin() + 3); 
    /* 说明:
    *    1. 范围是[begin(), begin()+3),即包含begin()但不包含begin()+3
    *    2. 删除后myvector包含:4, 5, 7, 8, 9, 10
    */

    //5.输出vector中的所有元素
    cout << "myvector中包含:";
    for (unsigned i = 0; i < myvector.size(); ++i) 
    {
        cout << ' ' << myvector[i];
    }
    cout << '\n';
    return 0;
}

在这里插入图片描述

std::vector::swap

在这里插入图片描述

#include <iostream>  
#include <vector>     
using namespace std;

int main()
{
    //1.创建第一个vector:包含3个元素,每个元素的值都是100
    vector<int> foo(3, 100);  
    //2.创建第二个vector:包含5个元素,每个元素的值都是200
    vector<int> bar(5, 200);   

    //3.交换foo和bar两个vector的内容
    foo.swap(bar);

    //4.输出交换后的foo
    cout << "foo 包含:";
    for (unsigned i = 0; i < foo.size(); i++) 
    {
        cout << ' ' << foo[i];
    }
    cout << '\n';

    //5.输出交换后的bar
    cout << "bar 包含:";
    for (unsigned i = 0; i < bar.size(); i++)
    {
        cout << ' ' << bar[i];
    }
    cout << '\n';

    return 0;
}

在这里插入图片描述

std::vector::clear

在这里插入图片描述

#include <iostream>   
#include <vector>     
using namespace std;


int main()
{
    //1.创建一个存储int类型的vector容器
    vector<int> myvector;

    //2.向vector中添加三个元素
    myvector.push_back(100);  
    myvector.push_back(200);  
    myvector.push_back(300);  

    //3.输出clear前vector中的元素
    cout << "myvector 包含:";
    for (unsigned i = 0; i < myvector.size(); i++) 
    {
        cout << ' ' << myvector[i];
    }
    cout << '\n';

    //4.清空vector中的所有元素
    myvector.clear();  // 注意:clear()会删除所有元素,使size()变为0,但不会改变capacity()
    
    //5.清空后添加新的元素
    myvector.push_back(1101); 
    myvector.push_back(2202);  

    //6.输出clear后vector中的元素
    cout << "myvector 包含:";
    for (unsigned i = 0; i < myvector.size(); i++) 
    {
        cout << ' ' << myvector[i];
    }
    cout << '\n';

    return 0;
}

在这里插入图片描述

------------模拟实现展示------------

vector的存储结构的设计

在这里插入图片描述

C++ 里 vector 容器的存储结构设计,围绕动态数组核心,通过巧妙的指针(迭代器)管理、扩容策略,在性能与灵活性间找平衡。


一、核心结构:三指针(迭代器)管理

vector 内部靠 startfinishend_of_storage 三个关键标记,划分内存区域:

  • start:指向数据区起始位置,是首个元素的 “入口”,类似数组名(但更灵活,支持动态调整)

  • finish:指向当前最后一个元素的下一个位置finish - start 就是 size()(实际存储元素数量)

  • end_of_storage:指向已分配内存的末尾位置end_of_storage - start 对应 capacity()(总可用容量,含未用 “备用空间”)

    总结:这三指针,让 vector 清晰区分 “已存数据”“备用空间”,为动态扩容、高效访问铺路。


二、动态扩容:空间预分配与 “倍增” 策略

“新增元素超容量时,容量扩充至两倍(或足够大)”,这是 vector 性能优化的关键:

  • 预分配备用空间:创建 vector 或扩容时,不只为当前元素分配内存,会多留 “备用空间”。

    • 比如:初始 vector<int> iv(2,9),后续 push_back 没立刻扩容,因为预分配了空间
  • “倍增” 扩容:默认扩容策略是容量翻倍(若翻倍不够,直接扩到所需大小)。

    • 这么做是为了减少扩容次数—— 每次扩容要 “重新分配内存 → 搬移元素 → 释放原内存”,成本极高,翻倍扩容能降低 “搬移频率”,平衡时间开销。

注意:扩容不是简单 “在原内存后追加”,而是要:

  1. 新分配更大内存(通常是原容量 2 倍)

  2. 把原数据拷贝/搬移到新内存

  3. 释放原内存

这过程涉及大量元素复制,所以频繁扩容会拖慢程序,需结合业务场景合理用 reserve 预分配空间。


三、内存连续性:模拟数组的高效访问

vector 数据在内存里连续存储,和数组一样:

  • 随机访问 O ( 1 ) O(1) O(1):通过 [](或 at())访问元素,直接用 start + 索引 计算地址,效率和数组一致。
  • 缓存友好:连续内存让 CPU 缓存更 “友好”—— 预读机制能批量加载相邻元素,减少内存访问次数,遍历、运算时更快。

这种设计,让 vector 既有数组的高效,又摆脱了 “固定大小” 的束缚。


四、设计权衡:灵活与成本的平衡

vector 的结构设计,本质是 “空间换时间” 的权衡:

  • 优势:动态扩容 + 连续存储,让它兼具 “动态数组” 的灵活(随时增删元素)和 “数组” 的高效(随机访问、缓存友好)
  • 劣势:扩容时的 “搬移操作” 有性能损耗,且若 vector 存大对象(或大量元素),频繁扩容可能导致内存碎片、拖慢程序

所以实际用 vector,常结合这些技巧:

  • reserve(n) 提前分配足够空间,避免多次扩容
  • 若元素是复杂对象,优先用移动语义(C++11+)减少搬移成本

总结来说vector 的存储结构设计,围绕 动态数组的 “灵活扩容” 与 “高效访问” 展开:

用三指针管理内存区域,靠 “预分配 + 倍增扩容” 减少性能损耗,借内存连续性实现数组级访问效率。

理解这些,才能在写代码时避开 “频繁扩容坑”,让 vector 发挥最大价值~

/*==========================================任务2.1:实现类模板vector==========================================*/
template <class T>
class vector
{
public:
	/*-------------------------------第一部分:类型定义-------------------------------*/
	//1.定义“正向迭代器”(原始指针,支持随机访问)
	typedef T* iterator;
	//2.定义“常量的正向迭代器”(指向常量的指针,禁止通过迭代器修改元素)
	typedef const T* const_iterator;

	/*-------------------------------第二部分:迭代器接口-------------------------------*/


	/*-------------------------------第三部分:构造and析构默认函数-------------------------------*/


	/*-------------------------------第四部分:容量管理操作-------------------------------*/


	/*-------------------------------第五部分:元素访问操作-------------------------------*/



	/*-------------------------------第六部分:元素修改操作-------------------------------*/

private:
	/*-----------------------私有成员:三指针结构-----------------------*/
	iterator _start;			//指向数据起始位置
	iterator _finish;			//指向有效数据的下一个位置
	iterator _end_of_storage;	//指向容量末尾的下一个位置
};

头文件:vector.h

#pragma once

/*==========================================任务1:包含需要使用的头文件==========================================*/
#include <iostream>
#include <assert.h>
#include <vector>
#include <list>
using namespace std;

/*==========================================任务2:定义mySpace自定义命名空间==========================================*/

//任务2:定义mySpace自定义命名空间
//		 任务2.1:实现类模板:vector
//	     任务2.2:实现函数模板:print_vector
//		 任务2.3:实现函数模板:print_container
namespace mySpace
{
	/*==========================================任务2.1:实现类模板vector==========================================*/
	template <class T>
	class vector
	{
	public:
		/*-------------------------------第一部分:类型定义-------------------------------*/
		//1.定义“正向迭代器”(原始指针,支持随机访问)
		typedef T* iterator;
		//2.定义“常量的正向迭代器”(指向常量的指针,禁止通过迭代器修改元素)
		typedef const T* const_iterator;

		/*-------------------------------第二部分:迭代器接口-------------------------------*/
		//1.实现:获取正向迭代器的首部
		iterator begin()
		{
			return _start;
		}

		//2.实现:获取正向迭代器的尾部
		iterator end()
		{
			return _finish;
		}

		//3.实现:获取常量正向迭代器的首部
		const_iterator begin()const       //注意:在C++11中使用的是cbegin()进行返回:常量正向迭代器,我们这里还是直接使用begin来返回常量正向迭代器
		{
			return _start;
		}

		//4.实现:获取常量迭代器的尾部
		const_iterator end()const
		{
			return _finish;
		}


		/*-------------------------------第三部分:构造and析构默认函数-------------------------------*/
		//1.实现:C++11默认构造函数
		vector() = default;  //自动生成无参构造,初始化指针为nullptr


		//2.实现:拷贝构造函数
		vector(const vector<T>& v)  //深拷贝另一个vector的内容
		{			//注意:vector<T>是模板实例化后的具体“类类型”
			//1.先预留目标容量,避免多次扩容
			reserve(v.size());

			//2.再遍历原vector容器,逐个将容器中的元素插入
			for (auto& it : v)
			{
				push_back(it);  //调用push_back实现元素拷贝(支持自定义类型拷贝)
			}
		}

		
		//3.实现:迭代器范围构造函数
		//template <class InputIterator>   //通过任意输入迭代器范围初始化vector
		//vector(InputIterator first, InputIterator last)  //注意:类模板的成员函数,还可以继续是函数模版
		//{
		//	//遍历迭代器的范围逐个插入元素
		//	while (first != last)
		//	{
		//		//1.插入
		//		push_back(*first);


		//		//2.移动
		//		++first;
		//	}
		//}
		

		//4.实现:填充构造函数(size_t版本)
		vector(size_t n, const T& val = T())  //创建n个值为val的元素
		{
			//1.先预留目标容量,避免多次扩容
			reserve(n);

			//2.再使用for循环,连续插入n个val
			for (size_t i = 0; i < n; i++)
			{
				push_back(val);
			}
		}

		//5.实现:填充构造函数(int版本)
		//vector(int n, const T& val = T())  //创建n个值为val的元素
		//{
		//	//1.先预留目标容量,避免多次扩容
		//	reserve(n);

		//	//2.再使用for循环,连续插入n个val
		//	for (int i = 0; i < n; i++)
		//	{
		//		push_back(val);
		//	}
		//}

		/*
		//6.1.实现:“拷贝赋值运算符重载函数”(传统写法:引用传递+赋值)
		vector<T>& operator=(const vector<T>& v)  //注意:这里的vector的类类型是vecotor<T>,而并不是vector
		{									//这是因为:vector不是普通的类,而是类模板
			//1.先判断是否对自身进行赋值操作
			if (this != &v)
			{
				//情况1:传入的容器的容量比较“小”
				//2.将当前的容器的中的内容清空 ---> 应对“小”容量的情况
				clear();

				//情况2:传入的容器的容量比较“大”
				//3.使用reserve开辟指定容量的空间 --->  应对“大”容量的情况
				reserve(v.size());

				//4.处理了上面的情况之后我们才可以进行赋值
				for (auto& it : v)
				{
					push_back(it);
				}
			}
			return *this;
		}
		*/

		//6.2.实现:拷贝赋值运算符重载函数(现代写法:值传递+交换)
		//vector<T>& operator=(vector<T> v) //注意:参数为值传递,自动触发拷贝构造
		//{
		//	//1.交换当前对象与临时对象v的数据
		//	swap(v);

		//	//2.返回当前对象(临时对象v离开作用域后自动析构)
		//	return *this;
		//}

		vector<T>& operator=(const vector<T>& v) 
		{
			//1.先判断是否是对自身进行的赋值操作
			if (this != &v) 
			{
				//2.再使用拷贝构造出临时对象
				vector<T> temp(v);  

				//3.然后进行交换
				swap(temp);        
			}
			//4.最后返回自身对象即可
			return *this;
		}

		//7.实现:析构函数
		~vector()
		{
			//1.确保指针非空
			if (_start)
			{
				//2.释放_start指针指向的资源
				delete[] _start;

				//2.将三指针置为空指针
				_start = _finish = _end_of_storage = nullptr;
			}
		}


		/*-------------------------------第四部分:容量管理操作-------------------------------*/
		//1.实现:“获取有效元素的个数的函数”
		size_t size() const
		{
			return _finish - _start;
		}

		//2.实现:“获取容器的总的容量的函数”
		size_t capacity()const
		{
			return _end_of_storage - _start;
		}

		//3.实现:“判断容器是否为空的函数”
		bool empty() const
		{
			return _start == _finish; //起始指针与结束指针重合
		}


		//4.实现:“预留容量的函数”
		void reserve(size_t n)  //预留至少n个元素的容量(不改变有效元素个数)
		{
			//注意:预留容量的操作只有在预留的容量n 大于 原vector容器的容量时候才会进行操作
			if (n > capacity())
			{
				/*---------第一步:保存原有元素的个数---------*/
				size_t old_size = size();


				/*---------第二步:分配新内存---------*/
				T* tmp = new T[n];


				/*---------第三步:拷贝旧数据---------*/
				//注意:使用for循环支持自定义类型的拷贝构造
				for (size_t i = 0; i < size(); i++)
				{
					tmp[i] = _start[i];  //调用T的拷贝赋值运算符
				}

				/*---------第四步:释放旧内存---------*/
				delete[] _start;


				/*---------第五步:更新指针成员---------*/

				//方法一:使用一个变量来记录之前vector中的元素的数量(三个迭代器的更新顺序没有要求)
				_start = tmp;

				//_finish = _start + size();    //失败
				//_finish = _start + old_size;  //成功

				//_finish = tmp + size();	    //失败
				_finish = tmp + old_size;       //成功

				_end_of_storage = tmp + n;

				/* 方法二:也可以不记录原vector中元素的数量来避免迭代器失效的问题,但是必须确保:_finish 的更新一定要在 _start 之前
				_finish = tmp + size();

				_start = tmp;

				_end_of_storage = tmp + n; 
				*/
				

			}
		}


		//5.实现:“调整有效元素的数量的函数”
		void resize(size_t n, T val = T())  //调整有效元素个数为n,不足时用val填充,多余时截断
		{
			//情景1:截断场景
			if (n < size())
			{
				//1.1:直接缩短有效长度为n
				_finish = _start + n;
			}

			//情景2:扩容场景
			else
			{
				//2.1:直接开辟最大容量为n
				reserve(n);

				//2.2:填充新元素
				while (_finish < _start + n)
				{
					//1.赋值
					*_finish = val;  //可能调用拷贝赋值

					//2.移动
					++_finish;
				}
			}

			//注意:这里的resize和reserve的区别:
					//reserve:对于每种调用情况并不是都一定会执行 (只有指定的扩容容量n大于当前vector容量是才会进行扩容)
					//resize:对于每种调用情况都会执行
		}

		/*-------------------------------第五部分:元素访问操作-------------------------------*/
		//1.实现:“下标访问运算符重载函数”(可读可写)
		T& operator[](size_t pos)
		{
			//1.使用断言:检查下标是否越界
			assert(pos < size());

			//2.通过指针偏移访问
			return *(_start + pos);

			//return _start[pos];
		}

		//2.实现:“下标访问运算符重载函数”(只读)
		const T& operator[](size_t pos)const
		{
			//1.使用断言:检查下标是否越界
			assert(pos < size());

			//2.通过指针偏移访问
			return *(_start + pos);

			//return _start[pos];
		}



		/*-------------------------------第六部分:元素修改操作-------------------------------*/
		//1.实现:“清空vector容器的函数”
		void clear()   //逻辑上清空,容量不变
		{
			_start = _finish;
		}

		//2.实现:“交换vector容器元素的函数”
		void swap(vector<T>& v) //对于自定义对象实现高效赋值
		{
			//使用标准库中的swap交换vector的三个私有的指针成员
			std::swap(_start, v._start);
			std::swap(_finish, v._finish);
			std::swap(_end_of_storage, v._end_of_storage);
		}


		//3.实现:“尾插元素到vector容器的函数”
		void push_back(const T& x)
		{
			//1.检查vector是否需要进行扩容
			if (_finish == _end_of_storage)
			{
				//扩容策略:
				//	1.vector容器的容量为0时:为vector容器分配4字节的容量
				//	2.vector容器的容量不为0时:为vector容器分匹配原容量的2倍容量

				//1.1:进行扩容
				reserve(capacity() == 0 ? 4 : 2 * capacity());

				//注意:push_back这里的扩容的大小是使用一个三目运算计算出来的
				//而并不像我们在operator=和resize中的直接使用reserve(v.size())或reserve(n)就可以了
			}

			//2.将新元素直接插入到vector容器的尾部
			*_finish = x;  //直接赋值,要求T支持拷贝赋值

			//3.有效指针后移
			++_finish;
		}


		//4.实现:“尾删vector容器中元素的函数”
		void pop_back()
		{
			//1.使用断言:确保vector容器非空容器
			assert(!empty());

			//2.有效指针前移
			--_finish;
		}

		//5.实现:“在pos位置插入元素的函数”(返回新元素的迭代器)
		iterator insert(iterator pos, const T& x)
		{//注意:这里我们可以对比我们在实现string类中的insert函数时候传入的pos的类型,一个是size_t,一个是iterator
			//1.使用断言:确保迭代器的有效性
			assert(pos >= _start && pos <= _finish);  //注意:取等号的时候类似于“头插”“尾插”

			//2.记录插入位置相对于起始的偏移量
			size_t len = pos - _start;

			//3.检查vector是否需要进行扩容
			if (_finish == _end_of_storage)
			{
				//扩容策略:
					//	1.vector容器的容量为0时:为vector容器分配4字节的容量
					//	2.vector容器的容量不为0时:为vector容器分匹配原容量的2倍容量

				//3.1:进行扩容
				reserve(capacity() == 0 ? 4 : 2 * capacity());

				//3.2:更新pos指针
				pos = _start + len;
			}


			//4.从末尾到pos位置的元素都向后挪动一位

			//4.1:定义一个迭代器指向我们需要挪动的元素中的最后一元素
			iterator end = _finish - 1;  //注意:回想一下我们string中实现的insert函数中的定义的end的类型是size_t,但是这里使用是迭代器
			while (end >= pos)
			{
				//4.2:赋值后移
				//_start[end + 1] = _start[end];  注意:这里不要使用[],因为我们重载[]运算符时候,使用了断言:assert(pos < size());
				*(end+1)=*end;					  //这就意味着我们使用[]时候,必须是[0~size-1]

				//4.3:end迭代器前移
				--end;
			}

			//5.插入元素
			//_start[pos] = x;  //这里也是和上面一样的情况

			*(pos)=x;

			//6.有效元素个数增加
			++_finish;

			//7.返回新元素的迭代器
			return pos;
		}


		//6.实现:“删除pos位置元素的函数”
		void erase(iterator pos)
		{
			//1.使用断言:确保迭代器的有效性
			assert(pos >= _start && pos < _finish);//注意:这里pos迭代器不能等于_finish,
			//这是因为_finish指向的其实是vecotr中最后一元素的下一个位置,所以我们不能删除_finish指向的值

			//2.从pos+1到末尾的元素都向前挪动一位

			//2.1:定义一个迭代器指向我们需要挪动的元素中的第一个元素
			iterator begin = pos + 1;
			while (begin != end())
			{
				//2.2:赋值交换
				_start[begin - 1] = _start[begin];
				//*(begin-1)=*(begin);

				//2.3:begin迭代器后移
				++begin;
			}

			//3.有效元素个数减少
			--_finish;
		}


	private:
		/*-----------------------私有成员:三指针结构-----------------------*/
		iterator _start;			//指向数据起始位置
		iterator _finish;			//指向有效数据的下一个位置
		iterator _end_of_storage;	//指向容量末尾的下一个位置
	};



	/*==========================================任务2.2:实现函数模板:print_vector==========================================*/


	//1.不使用模板实现print_vector函数
	void print_vector(const vector<int>& v)
	{
		//注意:下面我们可以使用两种不同的方式实现vector容器的遍历

		/*----------------方法一:使用迭代器遍历----------------*/
		//1.定义vector容器的迭代器
		//vector<int>::const_iterator it = v.begin();
		auto it = v.begin(); //注意:也可写成这个形式,其实范围for中的auto it本质上就是这行代码(所以:范围for的使用依赖迭代器)

		//2.使用while循环进行迭代遍历vector容器
		while (it != v.end())
		{
			//1.输出遍历到的元素
			std::cout << *it << " ";

			//2.让迭代器向后移动
			++it;
		}
		std::cout << std::endl;


		/*----------------方法二:使用范围for循环遍历----------------*/
		for (auto it : v)
		{
			std::cout << it << " ";
		}
		std::cout << std::endl;
	}




	//2.使用模板实现print_vector函数
	template<class T>
	void print_vector(const vector<T>& v)
	{
		//注意:下面我们可以使用两种不同的方式实现vector容器的遍历

		/*
		//----------------方法一:使用迭代器遍历----------------
		//1.定义vector容器的迭代器
		//vector<T>::const_iterator it = v.begin();
		auto it = v.begin();

		//2.使用while循环进行迭代遍历vector容器
		while (it != v.end())
		{
			//1.输出遍历到的元素
			std::cout << *it << " ";

			//2.让迭代器向后移动
			++it;
		}
		std::cout << std::endl;
		*/

		/*----------------方法二:使用范围for循环遍历----------------*/
		for (auto it : v)
		{
			std::cout << it << " ";
		}
		std::cout << std::endl;
	}



	/*==========================================任务2.3:实现函数模板:print_container==========================================*/

	template<class container>
	void print_container(const container& con)
	{
		/*
		//----------------方法一:使用迭代器遍历----------------
		auto it = con.begin();
		while (it != con.end())
		{
			//1.输出
			cout << *it << " ";

			//2.移动
			++it;
		}
		cout << endl;
		*/

		/*----------------方法二:使用范围for循环遍历----------------*/
		for (auto it : con)
		{
			std::cout << it << " ";
		}

		std::cout << std::endl;
	}
}

测试文件:Test.cpp

#include"vector.h" 

namespace mySpace
{
	void test_vector01()
	{
		//1.
		vector<int> v;

		//2.尾插元素
		v.push_back(1);
		v.push_back(2);
		v.push_back(3);
		v.push_back(4);
		v.push_back(5);


		/*------------------测试:三种遍历方式------------------*/
		cout << "==========测试:vector的遍历方式==========" << endl;
		//3.1:使用“下标”进行访问
		cout << "使用“下标”进行访问" << endl;

		for (size_t i = 0; i < v.size(); i++)
		{
			cout << v[i] << " ";
		}
		cout << endl;


		//3.2:使用“迭代器”进行访问
		cout << "使用“迭代器”进行访问" << endl;
		vector<int>::iterator it = v.begin();
		while (it != v.end())
		{
			cout << *it << " ";
			++it;
		}
		cout << endl;


		//3.3:使用“范围for”进行访问
		cout << "使用“范围for”进行访问" << endl;
		for (auto e : v)
		{
			cout << e << " ";
		}
		cout << endl;


		//4.调用模板函数打印
		cout << "调用模板函数打印" << endl;
		print_vector(v);

		//测试存储double类型
		cout << "==========测试:存储double类型==========" << endl;
		vector<double> vd;

		vd.push_back(1.1);
		vd.push_back(2.1);
		vd.push_back(3.1);
		vd.push_back(4.1);
		vd.push_back(5.1);

		print_vector(vd);
	}



	void test_vector02()
	{
		cout << "==========测试:查找元素的操作==========" << endl;
		std::vector<int> v; // 测试标准库vector与自定义vector的兼容性

		v.push_back(1);
		v.push_back(2);
		v.push_back(3);
		v.push_back(4);
		v.push_back(5);

		cout << "vector中原始的元素为:" << endl;
		//print_vector(v);  错误,无法打印出vector容器v中的元素
		print_container(v);


		/*------------------测试:查找元素的操作------------------*/
		cout << "请输入要查找的元素>:";
		//1.输入要查找的元素
		int x;
		cin >> x;

		//2.使用标准算法find查找元素(依赖迭代器兼容性)
		auto p = find(v.begin(), v.end(), x);
		if (p != v.end())
		{
			//3.在p位置插入40,返回新元素的迭代器
			cout << "在" << x << "元素位置的前面的插入元素40" << endl;
			p = v.insert(p, 40); //注意:插入元素后迭代器p会失效,需用insert返回值更新

			//4.修改插入位置的下一个元素(演示迭代器算术操作)
			cout << "将" << x << "元素+1后再*10" << endl;
			(*(p + 1)) *= 10;
		}

		print_container(v);
	}



	void test_vector03()
	{
		cout << "==========测试:删除元素的操作==========" << endl;
		std::vector<int> v;
		v.push_back(1);
		v.push_back(2);
		v.push_back(3);
		v.push_back(4);

		cout << "vector中原始的元素为:" << endl;
		print_container(v);

		/*------------------测试:删除元素的操作------------------*/
		cout << "删除所有偶数:" << endl;
		//删除所有偶数(经典erase-remove惯用法)
		auto it = v.begin();
		while (it != v.end())
		{
			if (*it % 2 == 0)
			{
				it = v.erase(it); //注意:erase返回下一个元素的迭代器
			}
			else
			{
				++it;
			}
		}
		print_container(v);
	}



	void test_vector04()
	{
		cout << "==========测试:resize和reserve的区别==========" << endl;
		/*------------------测试:resize和reserve的区别------------------*/
		vector<int> v;
		v.resize(10, 1); //有效元素10个,值为1,容量至少10
		v.reserve(20);   //容量扩大到20,有效元素仍为10

		cout << "vector中原始容器的:“内容/长度/容量” 为:" << endl;
		print_container(v);
		cout << v.size() << endl;     // 输出10
		cout << v.capacity() << endl; // 输出≥20(具体值取决于扩容策略)


		cout << "调用:resize(15, 2)后:" << endl;
		v.resize(15, 2);   //有效元素15个,新增5个元素值为2
		print_container(v);

		cout << "调用:resize(25, 3)后:" << endl;
		v.resize(25, 3);   //容量不足25,先扩容再填充
		print_container(v);

		cout << "调用:resize(5)后:" << endl;
		v.resize(5);       //截断到5个元素,剩余元素被"销毁"(此处为简单类型,无实际销毁操作)
		print_container(v);
	}



	void test_vector05()
	{
		cout << "==========测试:拷贝构造和赋值运算符==========" << endl;
		cout << "v1中原始的元素为:" << endl;
		vector<int> v1;
		v1.push_back(1);
		v1.push_back(2);
		v1.push_back(3);
		v1.push_back(4);
		print_container(v1);

		cout << "v3中原始的元素为:" << endl;
		vector<int> v3;
		v3.push_back(10);
		v3.push_back(20);
		v3.push_back(30);
		v3.push_back(40);
		v3.push_back(50);
		print_container(v3);
		/*------------------测试:拷贝构造和赋值运算符------------------*/

		cout << "调用拷贝构造函数:vector<int> v2 = v1 后,v2为:" << endl;
		vector<int> v2 = v1; //调用拷贝构造函数
		print_container(v2);

		cout << "调用拷贝赋值运算符:v1 = v3 后,v1为:" << endl;
		v1 = v3;			 //调用拷贝赋值运算符
		print_container(v1);
	}



	void test_vector06()
	{
		cout << "==========测试:各种构造函数==========" << endl;
		cout << "v1中原始的元素为:" << endl;
		vector<int> v1;
		v1.push_back(1);
		v1.push_back(2);
		v1.push_back(3);
		v1.push_back(4);
		v1.push_back(4);
		v1.push_back(4);
		print_container(v1);
		/*------------------测试:各种构造函数------------------*/

		/*--------------迭代器范围构造--------------*/
		//cout << "迭代器范围构造:" << endl;
		////1.截取部分数据
		//vector<int> v2(v1.begin(), v1.end());
		//print_container(v1);
		//print_container(v2);

		////2.从其他容器复制数据
		//list<int> lt;
		//lt.push_back(10);
		//lt.push_back(10);
		//lt.push_back(10);
		//lt.push_back(10);

		//vector<int> v3(lt.begin(), lt.end());  //注意:list迭代器是双向迭代器,支持输入迭代器操作
		//print_container(lt);
		//print_container(v3);


		/*--------------填充构造--------------*/
		cout << "填充构造:" << endl;
		cout << "vector<string> v4(5, \"1111111\")" << endl;
		vector<string> v4(5, "1111111");  //填充构造:5个string类型元素,初始值为"1111111"
		print_container(v4);

		//不同参数类型的填充构造(int和size_t)
		cout << "vector<int> v5(10)" << endl;
		vector<int> v5(10);      //10个默认构造的int(值为0)
		print_container(v5);

		cout << "vector<int> v6(10u, 1)" << endl;
		vector<int> v6(10u, 1);  //10个int,值为1(size_t参数)
		print_container(v6);

		cout << "vector<int> v7(10, 1)" << endl;
		vector<int> v7(10, 1);   //同上(int参数自动转换为size_t)
		print_container(v7);
	}


	void test_vector07()
	{

		/*------------------测试:大容量字符串的存储与扩容------------------*/
		cout << "==========测试:大容量字符串的存储与扩容==========" << endl;
		vector<string> v;
		v.push_back("11111111111111111111"); // 20个字符的字符串
		v.push_back("11111111111111111111");
		v.push_back("11111111111111111111");
		v.push_back("11111111111111111111");
		print_container(v);
		cout << "此时v的长度为:" << v.size() << endl;
		cout << "此时v的容量为:" << v.capacity() << endl;

		cout << "-----------向v中再添加一个字符串-----------" << endl;

		v.push_back("11111111111111111111"); // 触发扩容(假设当前容量为4,扩容后为8)
		print_container(v);
		cout << "此时v的长度为:" << v.size() << endl;
		cout << "此时v的容量为:" << v.capacity() << endl;
	}



	/*-----------------------测试:vector的扩容策略-----------------------*/

	void test_vector08()
	{
		cout << "==========测试:vector的扩容策略==========" << endl;
		/*---------------第一部分:准备阶段---------------*/
		//1.创建一个空vector容器
		vector<int> v;
		//2.定义一个变量的用来记录容量的变化
		size_t sz;
		//3. 预分配100个元素的容量
		v.reserve(100);  //注意:直接设置容量为100,不触发多次扩容

		/*---------------第二部分:初始状态输出---------------*/
		//1.获取当前容器的初始容量
		sz = v.capacity();
		cout << "容器的初始容量为: " << sz << '\n';   //输出:100(一次分配到位)

		/*---------------第三部分:不断的向容器中添加元素同时记录容量的变化---------------*/
		cout << "容器的容量的变化……:\n";
		for (int i = 0; i < 100; ++i)
		{
			//1.插入元素
			v.push_back(i);			//容量足够,不会触发扩容

			//2.监测容器的容量的变化
			if (sz != v.capacity()) //容量变化时打印(此处不会执行)
			{
				sz = v.capacity();
				cout << "容量的变为: " << sz << '\n';
			}
		}
	}


	/*-----------------------测试:reserve函数对容量的影响(不改变有效元素个数)-----------------------*/
	void test_vector09()
	{
		cout << "==========测试:reserve函数对容量的影响==========" << endl;
		//TestVectorExpand(); // 注释:如需测试扩容,取消此行注释

		cout << "v容器的初始状态为:" << endl;
		vector<int> v(10, 1);   //构造包含10个1的vector,初始容量≥10(具体由自定义vector实现决定)
		print_container(v);

		cout << "调用v.reserve(20)之后," << endl;
		v.reserve(20);			//确保容量至少20(若原容量不足则扩容)
		cout << "长度:" << v.size() << endl;     //输出:10  (有效元素个数不变)
		cout << "容量:" << v.capacity() << endl; //输出:≥20(例如:自定义vector可能扩容到20或更大)

		cout << "调用v.reserve(15)之后," << endl;
		v.reserve(15);			  //容量已≥15,无操作
		cout << "长度:" << v.size() << endl; //仍为10
		cout << "容量:" << v.capacity() << endl; //保持不变

		cout << "调用v.reserve(5)之后," << endl;
		v.reserve(5); //容量≥5,无操作(reserve不会减少容量)
		cout << "长度:" << v.size() << endl; //仍为10
		cout << "容量:" << v.capacity() << endl; //保持不变(不会回退到5)
	}



	/*-----------------------测试:resize函数对元素个数和容量的影响-----------------------*/
	void test_vector10()
	{
		cout << "==========测试:resize函数对元素个数和容量的影响==========" << endl;
		//TestVectorExpand(); // 注释:如需测试扩容,取消此行注释

		cout << "v容器的初始状态为:" << endl;
		vector<int> v(10, 1); // 10个1,容量≥10
		print_container(v);
		cout << "调用v.reserve(20)之后," << endl;
		v.reserve(20);
		cout << "长度:" << v.size() << endl;
		cout << "容量:" << v.capacity() << endl;

		cout << "调用v.resize(15, 2)之后," << endl;
		v.resize(15, 2);
		cout << "长度:" << v.size() << endl;
		cout << "容量:" << v.capacity() << endl;

		cout << "调用v.resize(25, 3)之后," << endl;
		v.resize(25, 3);
		cout << "长度:" << v.size() << endl;
		cout << "容量:" << v.capacity() << endl;

		cout << "调用v.resize(5)之后," << endl;
		v.resize(5);
		cout << "长度:" << v.size() << endl;
		cout << "容量:" << v.capacity() << endl;
	}




	/*-----------------------测试:插入、输入输出及不同类型存储-----------------------*/
	void test_vector11()
	{
		cout << "==========测试:插入、输入输出及不同类型存储==========" << endl;
		cout << "v容器的初始状态为:" << endl;

		vector<int> v(10, 1);	//10个1
		v.push_back(2);			//尾插2,变为11个元素
		v.insert(v.begin(), 0); //在头部插入0,变为12个元素(0,1,1,...,1,2)
		print_container(v);

		cout << "在索引为3处插入10之后的vector为:";
		v.insert(v.begin() + 3, 10); //在索引3处插入10,元素后移
		for (auto it : v)
		{
			cout << it << " ";   //输出:0 1 1 10 1 ... 1 2(索引3变为10)
		}
		cout << endl;

		cout << "请输入5个整数>:" << endl;
		vector<int> v1(5, 0);
		for (size_t i = 0; i < 5; i++)
		{
			cin >> v1[i];   //输入5个整数,覆盖默认值0
		}

		cout << "该vector中的内容为:" << endl;
		for (auto e : v1)
		{
			cout << e << ","; // 例如输入1 2 3 4 5,输出1,2,3,4,5,
		}
		cout << endl;
	}




	/*-----------------------测试:vector存储自定义类型(如:string)及二维vector-----------------------*/
	void test_vector12()
	{
		cout << "==========测试:vector存储自定义类型(如:string)及二维vector==========" << endl;
		cout << "vector中存储的自定义类型的string为:" << endl;
		//1.定义一个存储string的vector容器
		vector<string> v1;

		/*------------情况1:插入字符串对象------------*/
		//2.1:构造string对象
		string s1("xxxx");
		//2.2:插入string对象(深拷贝)
		v1.push_back(s1);

		/*------------情况2:插入字符串字面量------------*/
		//2.
		v1.push_back("yyyyy");

		//3.
		for (const auto& it : v1) //常量引用遍历,避免拷贝大字符串
		{
			cout << it << " ";
		}
		cout << endl;


		cout << "修改二维数组的第3行第2列的元素为2" << endl;
		/*-------------------第一部分:创建一个二维数组-------------------*/
		//构造二维vector:7个元素,每个元素是vector<int>(5,1)

		/*-------方法一:填充构造法-------*/
		std::vector<int> v(5, 1);				  //5个1的一维vector
		std::vector<std::vector<int>> vv(7, v); //7个v的拷贝,形成“7行5列”的二维数组

		/*-------方法二:嵌套构造法-------*/
		//std::vector<std::vector<int>> vv(7, std::vector<int>(5, 1)); // 直接指定每行的初始值

		/*-------------------第二部分:修改二维数组-------------------*/
		vv[2][1] = 2; //修改第3行(索引2)第2列(索引1)的元素为2
		//等价于:vv.operator[](2).operator[](1) = 2; 注释:operator[]的嵌套调用


		/*-------------------第三部分:打印二维数组-------------------*/
		//遍历二维vector并输出
		for (size_t i = 0; i < vv.size(); i++)
		{
			for (size_t j = 0; j < vv[i].size(); ++j)
			{
				cout << vv[i][j] << " "; //前两行全为1,第三行第二个元素为2,其余为1
			}
			cout << endl;
		}
		cout << endl;
	}

}


int main()
{
	mySpace::test_vector01(); // 调用命名空间mySpace中的test_vector1函数
	mySpace::test_vector02();
	mySpace::test_vector03();
	mySpace::test_vector04();
	mySpace::test_vector05();
	mySpace::test_vector06();
	mySpace::test_vector07();



	mySpace::test_vector08();
	mySpace::test_vector09();
	mySpace::test_vector10();
	mySpace::test_vector11();
	mySpace::test_vector12();

	return 0;
}

运行结果:

在这里插入图片描述

----------------

1. 为什么vector容器的功能实现都是在头文件中?

在 C++ 中:
类模板的成员函数通常需要在头文件中实现,而不是像普通类那样分离到源文件(.cpp)中。

这是由 C++ 的 模板实例化机制编译模型 决定的。


模板实例化的工作原理:

1. 编译期生成代码

  • 模板(如:vector<T>)本身不是完整的代码,而是一个编译期蓝图

  • 编译器在遇到模板的使用(如:vector<int> v;)时,会根据实参类型(如:int实例化出具体的类和函数。

示例

// 模板定义(头文件中)
template <typename T>
T add(T a, T b)
{
	return a + b;
}

// 使用模板(源文件中)
int sum = add(1, 2);  // 编译器生成 add<int>(int, int)

2. 实例化的必要条件

  • 编译器在实例化模板时,必须同时看到模板的定义使用

  • 如果定义和使用分散在不同文件中,会导致编译错误

2. 为什么普通类可以分离实现,而模板不行?

一、普通类的编译模型:

对于普通类(非模板),编译器分两步处理:

  1. 编译阶段:将每个源文件(.cpp)单独编译为目标文件(.o),此时只需要类的声明(头文件)
  2. 链接阶段:将所有目标文件链接成可执行文件,通过函数符号表解析跨文件的调用

示例

// MyClass.h(声明)
class MyClass
{
public:
    void func();
};

// MyClass.cpp(实现)
#include "MyClass.h"
void MyClass::func() 
{ 
    /* ... */ 
}

// main.cpp(使用)
#include "MyClass.h"
int main()
{
    MyClass obj;
    obj.func();  //编译时只需知道func的声明,链接时解析到MyClass.cpp中的实现
}

二、模板类的编译困境:

模板的实例化发生在编译期,而非链接期,如果模板的实现放在源文件中,编译器在编译主源文件main.cpp时无法看到模板的实现,导致无法实例化。


假如:模板的实现(如:vector<T>::push_back)、容器源文件(如:vector.cpp)、

主源文件(如:main.cpp)、模板的实例化(如:vector<int>::push_back

如果模板实现不在头文件中,编译器在:

  • 编译时:main.cpp时只能看到模板声明(由于先经过了的预处理过程),无法生成vector<int>::push_back的具体代码
  • 链接时:链接器找不到该函数的实现,导致未定义符号错误

错误示例

// vector.h(只包含声明)
template <typename T>
class vector
{
public:
    void push_back(const T& x);
};

// vector.cpp(实现)
template <typename T>
void vector<T>::push_back(const T& x)
{
    /* ... */
}

// main.cpp(使用)
#include "vector.h"
int main()
{
    vector<int> v;
    v.push_back(42);  //编译时无法实例化push_back,因为看不到vector.cpp中的实现
}

解决方案:将模板实现放在头文件中

为了让编译器在实例化模板时能看到完整定义,模板的实现必须与声明放在同一文件中(通常是头文件)

  • 此时,当main.cpp包含vector.h时,经过预处理阶段之后,在编译阶段编译器能同时看到声明实现,成功实例化vector<int>::push_back
  • 这虽然可能导致头文件变大,但能保证模板的通用性和编译正确性。在实际开发中,这是实现模板类的标准做法,也是大多数开源库(:Boost、Eigen)采用的方式

正确示例

// vector.h(声明+实现)
template <typename T>
class vector
{
public:
    void push_back(const T& x);
};

template <typename T>
void vector<T>::push_back(const T& x)
{
    // 实现代码
}

------------核心问题深究------------

一、迭代器失效问题

1. 什么是迭代器失效?

迭代器失效:是指在 C++ 中,当容器的结构发生某些变化时,原本有效的迭代器变得不再有效,无法再正确地指向容器中的元素 。


迭代器失效的原理:

  • 迭代器本质上是一种 抽象的指针————指向容器内部元素的指针或指针封装,用于指向容器中的元素,方便对容器元素进行遍历操作

  • 容器内部通过维护一定的数据结构来存储元素,当容器执行某些操作(:插入、删除元素,改变容量等)时,其内部数据结构可能发生改变

    • 例如:内存重新分配、元素位置调整等。
  • 这就会导致原本迭代器所指向的内存地址、元素顺序等情况发生变化,使得迭代器无法再准确指向预期元素,即:迭代器失效

总结

  1. 内存重新分配:容器扩容时,元素被移动到新内存,旧内存被释放
  2. 元素位置移动:插入/删除操作导致元素位置整体前移或后移

迭代器失效的表现:

  • 访问错误:当使用失效的迭代器去访问容器元素时,程序可能会出现未定义行为。

    • 比如:崩溃、获取到错误的数据值等 。

      #include <vector>
      #include <iostream>
                  
      int main() 
      {
          std::vector<int> v = {1, 2, 3};
          auto it = v.begin();
                      
          v.push_back(4); 			   // 可能导致扩容,it失效
          std::cout << *it << std::endl; // 使用失效迭代器,行为未定义
                      
          return 0;
      }
      
  • 迭代错误:在迭代过程中,如果迭代器失效,会导致迭代逻辑出错。

    • 比如:在循环遍历容器时,迭代器失效后继续进行迭代操作,可能无法正确遍历完容器或者跳过某些元素等 。

2. vector容器中哪些操作会导致迭代器失效?

一、容量改变操作

1. 扩容操作

  • push_back():当size达到capacity时
  • insert():插入元素导致扩容时
  • emplace_back():同push_back
  • resize():当新size大于当前capacity时
  • reserve():当新capacity大于当前capacity时
  • assign():几乎总会重新分配内存

2. 缩容操作

  • shrink_to_fit():可能重新分配更小的内存空间

二、元素修改操作

1. 插入操作

  • insert():在任意位置插入元素
    • 插入点之后的所有迭代器都会失效
    • 如果触发扩容,则所有迭代器失效

2. 删除操作

  • erase():删除任意位置元素
    • 被删除元素之后的所有迭代器都会失效
  • pop_back():删除尾部元素
    • 只使指向最后一个元素的迭代器失效
  • clear():清空所有元素
    • 使所有迭代器失效

3. 案例一:容量改变

#include <iostream>
#include <vector>
using namespace std;

int main()
{
    //1.初始化一个包含6个元素的vector,元素值为1-6
    vector<int> v{ 1,2,3,4,5,6 };

    //2.获取vector的起始迭代器
    auto it = v.begin();

    /*
     * 注意:以下操作都会可能导致vector扩容,使原有迭代器失效:
     */

     /*-----------------resize操作-----------------*/

     // 1. resize操作:将有效元素个数增加到100个,多出的位置用8填充
     // 如果新大小大于当前容量,会触发扩容
     // v.resize(100, 8);


    /*-----------------reserve操作-----------------*/

     // 2. reserve操作:预留100个元素的空间
     // 只改变容量不改变元素数量,如果100大于当前容量会重新分配内存
     // v.reserve(100);


    /*-----------------插入操作-----------------*/

     // 3. 插入操作:
     // 在头部插入0,可能触发扩容
     // v.insert(v.begin(), 0);
     // 在尾部插入8,可能触发扩容
     // v.push_back(8);


    /*-----------------assign操作-----------------*/

     // 4. assign操作:重新赋值100个8
     // 这会完全替换原有内容,几乎肯定会重新分配内存
    v.assign(100, 8);

    /*
     * 问题分析:
     * 以上操作都可能导致vector扩容,导致:
     * 1. 原内存空间被释放
     * 2. it迭代器仍指向已释放的旧内存空间
     * 3. 使用失效的迭代器会导致未定义行为(通常崩溃)
     *
     * 解决方案:
     * 在执行可能导致扩容的操作后,必须重新获取迭代器
     */

     // 正确做法:在操作后重新获取迭代器
    //it = v.begin();

    // 使用重新获取的迭代器遍历vector
    while (it != v.end())
    {
        cout << *it << " ";
        ++it;
    }
    cout << endl;

    return 0;
}

在这里插入图片描述

4. 案例二:删除操作

#include <iostream>
#include <vector>   
#include <algorithm> 
using namespace std;


int main()
{
    /*-----------1.定义一个原始数组-----------*/
    int a[] = { 1, 2, 3, 4 };

    /*-----------2.使用数组初始化vector-----------*/
    // a: 数组首地址
    // a + sizeof(a)/sizeof(int): 数组尾后地址
    // sizeof(a)/sizeof(int)计算数组元素个数
    vector<int> v(a, a + sizeof(a) / sizeof(int));

    /*-----------3.使用STL的find算法查找值为3的元素-----------*/
    // find返回一个迭代器指向找到的元素
    // 如果没找到,返回end()迭代器
    vector<int>::iterator pos = find(v.begin(), v.end(), 3);

    /*-----------4.检查是否找到元素-----------*/
    if (pos != v.end())
    {
        //4.1:删除pos位置的元素
            //erase操作会使pos及其后所有迭代器失效
            //但是erase会返回一个新的有效迭代器,指向被删除元素的下一个位置

        //演示:删除操作erase导致的迭代器失效的情况:
        v.erase(pos);
        // 危险操作:直接使用已失效的迭代器
        cout << *pos << endl; // 会导致未定义行为(通常是崩溃)
         
        //4.2:使用返回值更新迭代器
        pos = v.erase(pos);  //正确做法:使用返回值更新迭代器

        //4.3:使用经过更新的之后的迭代器
        if (pos != v.end()) 
        {
            cout << "删除后下一个元素是: " << *pos << endl;
        }
        else 
        {
            cout << "删除的是最后一个元素" << endl;
        }
    }
    else
    {
        cout << "未找到元素3" << endl;
    }

    return 0;
}

/*
关键点说明:
1. 迭代器失效问题:
   - erase操作会使被删除元素及其后所有迭代器失效
   - 但erase会返回一个新的有效迭代器,指向被删元素的下一个位置

2. 安全实践:
   - 总是使用erase的返回值更新迭代器
   - 不要继续使用失效的迭代器

3. 其他注意事项:
   - 使用范围初始化vector是C++中常见的容器初始化方式
*/

在这里插入图片描述

5. 案例三:小测试

看到这里,相信小伙伴们已经对迭代器失效有了一定了解。

下面有两段代码,它们的功能均为删除vector中所有的偶数 。

那么,哪段代码是正确的呢?原因是什么呢?

#include <iostream>
#include <vector>
using namespace std;


// 第一个main函数 - “错误的删除偶数元素方式”
int main()
{
    vector<int> v{ 1, 2, 3, 4 };  //初始化vector包含1,2,3,4

    auto it = v.begin();  //获取vector的起始迭代器

    while (it != v.end())  //遍历整个vector
    {
        if (*it % 2 == 0)  //检查当前元素是否为偶数
            v.erase(it);   //删除当前元素 - 危险操作!使it迭代器失效

        //问题所在:在it可能已经失效的情况下仍然递增它
        //如果执行了erase,it已经失效,++it是未定义行为
        ++it;
    }
    return 0;
}

// 第二个main函数 - “正确的删除偶数元素方式”
int main()
{
    vector<int> v{ 1, 2, 3, 4 };  //初始化vector包含1,2,3,4

    auto it = v.begin();  //获取vector的起始迭代器

    while (it != v.end())  //遍历整个vector
    {
        if (*it % 2 == 0)  //检查当前元素是否为偶数
            it = v.erase(it);  //正确做法:使用erase返回的新迭代器
        else
            ++it;  //只有不删除时才正常递增迭代器
    }

    // 验证结果
    for (auto num : v) 
    {
        cout << num << " ";  //输出: 1 3
    }
    return 0;
}

/*
关键区别说明:
1. 错误版本的问题:
   - erase会使当前迭代器失效
   - 失效后继续使用++it会导致未定义行为(通常程序崩溃)

2. 正确版本的做法:
   - erase会返回一个指向被删除元素下一个位置的有效迭代器
   - 只有不删除元素时才手动递增迭代器
   - 这样可以安全地遍历并删除元素

3. 迭代器失效规则:
   - 对于vector,erase会使被删除位置及其后的所有迭代器失效
   - 但erase会返回一个新的有效迭代器
   - 其他容器(如:list)的erase可能不会使其他迭代器失效

*/

在这里插入图片描述

二、memcpy浅拷贝问题

在这里插入图片描述

假设:在模拟实现的vector中,reserve接口使用了memcpy进行拷贝。

以那么下面的代码会出现什么问题呢?

int main()
{
	mySpace::vector v;
	v.push_back("1111");
	v.push_back("2222");
	v.push_back("3333");
	return 0;
}

问题分析如下:

memcpy是内存的二进制格式拷贝,它会将一段内存空间中的内容原封不动地拷贝到另一段内存空间中

  1. 当拷贝的是普通自定义类型元素时,memcpy既高效又不会出错。
  2. 但如果自定义类型元素涉及资源管理时,使用memcpy就会出错,因为memcpy本质上是 浅拷贝

在这里插入图片描述

在这里插入图片描述

总结当对象涉及资源管理时,切不可使用memcpy来进行对象间的拷贝。

因为:memcpy执行的是浅拷贝`操作,这可能会引发内存泄漏,甚至导致程序崩溃。

三、二维vector的存储问题

下面的动图展示了经典的杨辉三角,我们可以直观地看到这个三角的规模在不断变化。

因此,如果要存储杨辉三角,我们就要使用二维vector,但是二维vector又是怎么存储的呢?

在这里插入图片描述

mySpace::vector> vv(n);:构造一个vv动态二维数组。

  • vv中总共有n个元素
  • 每个元素都是vector类型

每行没有包含任何元素,如果n为5时如下所示:

在这里插入图片描述

vv中元素填充完成之后,如下图所示:

在这里插入图片描述

------------代码片段剖析------------

片段一:=default默认构造函数

//1.实现:C++11的 =default默认构造函数
vector() = default;  //自动生成无参构造,初始化指针为nullptr

这段代码 vector() = default; 是 C++11 引入的 显式默认构造函数定义,用于自动生成类的默认构造行为。

1. 什么是默认构造函数?

默认构造函数:是 无需参数即可调用的构造函数,用于创建类的对象。

vector<int> v;  // 调用默认构造函数

在 C++ 中,编译器会 自动生成默认构造函数,但仅在以下条件满足时:

  • 类没有显式定义任何构造函数(包括带参数的构造函数)
  • 类的所有非静态成员和基类都可以被默认初始化

2. 为什么需要显式使用 = default?

当类中 显式定义了其他构造函数(如:带参数的构造函数)时,编译器 不再自动生成默认构造函数

此时,若仍需要默认构造函数,需手动定义

class vector 
{
public:
    // 显式定义带参数的构造函数
    vector(size_t n, const T& value);
    
    // 由于上面定义了构造函数,必须显式声明默认构造函数
    vector() = default;  // 显式要求编译器生成默认实现
};

片段二:迭代器范围构造函数

//3.实现:迭代器范围构造函数
template <class InputIterator>   //通过任意输入迭代器范围初始化vector
vector(InputIterator first, InputIterator last)  //注意:类模板的成员函数,还可以继续是函数模版
{
	//遍历迭代器的范围逐个插入元素
	while (first != last)
	{
		//1.插入
		push_back(*first);

		//2.移动
		++first;
	}
}

1. 什么是迭代器范围构造函数?

迭代器范围构造函数(Iterator Range Constructor):是 C++ STL容器(如:vectorlistmap 等)提供的一种特殊构造函数,允许通过 两个迭代器界定的范围 来初始化容器。

  • 迭代器范围构造函数通常接受 输入迭代器(InputIterator) 或更高级别的迭代器。
  • 这种设计是 C++ 泛型编程的核心体现,极大地增强了容器间的数据传递灵活性。

构造函数签名:

template <class InputIterator>
container(InputIterator first, InputIterator last);
  • first:是指向数据源起始位置的迭代器
  • last:是指向数据源结束位置的迭代器
    • 不包含该位置的元素,即:左闭右开区间 [first, last)

2. 迭代器范围构造函数怎么使用?

//方法1:从其他容器复制数据
vector<int> src = {1, 2, 3};
vector<int> dst(src.begin(), src.end());  //用 src 的全部元素初始化 dst


//方法2:截取部分数据
vector<int> sub(src.begin() + 1, src.begin() + 3);  //截取 src 的中间部分


//方法3:从数组或其他数据源初始化
int arr[] = {4, 5, 6};
vector<int> vec(arr, arr + 3);  //用数组的前3个元素初始化 vec

片段三:填充构造函数

//4.实现:填充构造函数(size_t版本)
vector(size_t n, const T& val = T())  //创建n个值为val的元素
{
	//1.先预留目标容量,避免多次扩容
	reserve(n);

	//2.再使用for循环,连续插入n个val
	for (size_t i = 0; i < n; i++)
	{
		push_back(val);
	}
}

1. 什么是填充构造函数?

填充构造函数(Fill Constructor):是 C++ STL 容器(如:vectorlistdeque 等)提供的一种特殊构造函数,用于创建一个包含 指定数量的相同值元素 的容器。

  • 它是初始化容器的高效方式之一,尤其适合批量生成默认数据。

构造函数签名:

container(size_type n, const T& value = T());
  • n:是容器的初始大小(元素数量)
  • value:是每个元素的初始值(默认值为 T(),即:类型 T 的默认构造值)

函数参数解析:

vector(size_t n, const T& val = T())
  • size_t n:指定容器的初始大小
  • const T& val:指定元素的初始值,使用引用避免拷贝
  • = T():默认参数,使用类型 T 的默认构造值(如:int 为 0,string 为空字符串)

2. 内置类型的变量是怎么进行初始化的?

此时我相信有一部分小伙伴对上面的内容会感觉有点疑惑?

啊?= T():默认参数,使用类型 T 的默认构造值。

“那如果模板参数T被编译器在编译期间的类型推导为了内置类型的话,还能调用构造函数吗?”

太棒了,这个问题问的非常的好O(∩_∩)O


在 C++ 中:

  • 内置类型(intdoublechar 等)没有传统意义上的构造函数。

  • 但它们确实支持 值初始化默认初始化,这使得它们在语法上可以表现得类似有构造函数。


内置类型的初始化规则:

1. 默认初始化(Default Initialization)

  • 发生场景当变量被声明但未显式初始化时。

  • 行为

    • 局部变量:值未定义(垃圾值)
    • 静态变量:初始化为 0(或 nullptr 对于指针)
  • 示例

    int x;          // 局部变量,值未定义
    static int y;   // 静态变量,初始化为 0
    

2. 值初始化(Value Initialization)

  • 发生场景使用 空括号花括号 进行初始化(如:T()T{}

  • 行为:初始化为 该类型的零值(如:00.0nullptr

  • 示例

    int x = int();      // 值初始化为 0
    double y = double();// 值初始化为 0.0
    int* ptr = int*();  // 值初始化为 nullptr
          
    int x = 0;       // 直接赋值
    int y = int();   // 值初始化(等效于 0)
    

3. T()的含义是什么?

在模板代码中,T() 表示 类型 T 的值初始化

  • 对于 类类型:调用默认构造函数。
  • 对于 内置类型:初始化为零值。

这是 C++ 的 统一初始化语法 特性

示例

template <typename T>
void func(const T& val = T()) //默认参数为 T 的值初始化
{  
    // ...
}

//1.类类型
func<string>();  // val 默认为 ""(string 的默认构造)


//2.内置类型
func<int>();     // val 默认为 0(int 的零值)

4. 内置类型和类类型使用T()的不同之处是什么?

虽然 T()内置类型类类型的语法相同,但本质不同:

特性 内置类型(如:int 类类型(如:string
初始化语法 int x = int();
int x{};
string s = string();
string s{};
底层机制 零初始化(直接赋值为 0) 调用默认构造函数
是否有构造函数

5. 填充构造函数怎么使用?

//1.创建包含5个100的vector
vector<int> v(5, 100);     //v = {100, 100, 100, 100, 100}

//2.创建包含3个空字符串的vector
vector<string> s(3);       //s = {"", "", ""}

//3.创建包含10个默认构造的对象
vector<MyClass> objs(10);  //每个元素都是 MyClass()

片段四:reserve()的迭代器失效

//4.实现:“预留容量的函数”
void reserve(size_t n)  //预留至少n个元素的容量(不改变有效元素个数)
{
	//注意:预留容量的操作只有在预留的容量n 大于 原vector容器的容量时候才会进行操作
	if (n > capacity())
	{
		/*---------第一步:保存原有元素的个数---------*/
		size_t old_size = size();


		/*---------第二步:分配新内存---------*/
		T* tmp = new T[n];


		/*---------第三步:拷贝旧数据---------*/
		//注意:使用for循环支持自定义类型的拷贝构造
		for (size_t i = 0; i < size(); i++)
		{
			tmp[i] = _start[i];  //调用T的拷贝赋值运算符
		}

		/*---------第四步:释放旧内存---------*/
		delete[] _start;


		/*---------第五步:更新指针成员---------*/

		//方法一:使用一个变量来记录之前vector中的元素的数量(三个迭代器的更新顺序没有要求)
		_start = tmp;

		//_finish = _start + size();    //失败
		//_finish = _start + old_size;  //成功

		//_finish = tmp + size();	    //失败
		_finish = tmp + old_size;       //成功

		_end_of_storage = tmp + n;

		/* 方法二:也可以不记录原vector中元素的数量来避免迭代器失效的问题,但是必须确保:_finish 的更新一定要在 _start 之前
		_finish = tmp + size();

		_start = tmp;

		_end_of_storage = tmp + n;
		*/

	}
}

小朋友你是不是有很多问好?:

1. 为什么在reserve函数中,使用 _finish = tmp + size();不能避免迭代器失效问题,而 _finish = tmp + old_size; 却可以?

2. reserve不是只会改变 vector的容量吗?,所以使用 reserve()扩完容之后 size()不应该还是和之前的值一样吗?难道说不一样了,啊,这是怎么回事?

哈哈,就是你想的那个样子,扩容之后size()接口函数确实无法再正确获取当前vector容器中元素的数量了,下面我们来详细分析一下其中的原因。


size()函数在扩容过程中的语义变化:

  • reserve函数执行过程中,当进入扩容逻辑时,原有的vector底层数组内存已经被释放(delete[] _start; 这一步)

  • 而此时,size()函数获取的是当前对象中_finish_start差值

    • 但由于_start已经指向新分配的内存(没有使用变量old_size保留之前vector中元素的数量,并且先更新了_start = tmp; —> 将会造成_finish迭代器失效)
    • _finish尚未更新,已经更新的_start和未进行更新的_finish相减得到的值,也即是扩容后调用size()返回的值,必然不再是原vector中的元素的数量

所以:扩容后如果使用_finish = tmp + size(); ,因为size()返回值不对,会导致_finish指向错误位置,这样后续对vector的操作(如:插入元素等)就会出现越界等错误,迭代器也会失效。


old_size的正确语义:

  • old_size在函数一开始通过size_t old_size = size();获取,它记录的是扩容前vector中有效元素的个数。

  • 在扩容过程中,虽然底层数组内存改变,但有效元素个数并未改变(只是被拷贝到新内存 )

在扩容后使用_finish = tmp + old_size; ,能够正确地将_finish指向新分配内存中,存放完所有原有效元素后的下一个位置,保证了vector内部结构的正确性,从而避免迭代器失效。


总结

  • 简单来说,size()在扩容过程中因内部状态变化导致迭代器失效,从而不能使用迭代器准确反映有效元素个数。

  • old_size记录的是扩容前的有效元素个数,能正确用于更新_finish的位置,维持vector的正确结构和迭代器有效性。

片段五:print_vector(v);无法打印std::vector的原因

//2.使用模板实现print_vector函数
template<class T>
void print_vector(const vector<T>& v)
{
	//注意:下面我们可以使用两种不同的方式实现vector容器的遍历

	/*
	//----------------方法一:使用迭代器遍历----------------
	//1.定义vector容器的迭代器
	//vector<T>::const_iterator it = v.begin();
	auto it = v.begin();

	//2.使用while循环进行迭代遍历vector容器
	while (it != v.end())
	{
		//1.输出遍历到的元素
		std::cout << *it << " ";

		//2.让迭代器向后移动
		++it;
	}
	std::cout << std::endl;
	*/

	/*----------------方法二:使用范围for循环遍历----------------*/
	for (auto it : v)
	{
		std::cout << it << " ";
	}
	std::cout << std::endl;
}



/*==========================================任务2.3:实现函数模板:print_container==========================================*/

template<class container>
void print_container(const container& con)
{
	/*
	//----------------方法一:使用迭代器遍历----------------
	auto it = con.begin();
	while (it != con.end())
	{
		//1.输出
		cout << *it << " ";

		//2.移动
		++it;
	}
	cout << endl;
	*/

	/*----------------方法二:使用范围for循环遍历----------------*/
	for (auto it : con)
	{
		std::cout << it << " ";
	}

	std::cout << std::endl;
}



cout << "==========测试:查找元素的操作==========" << endl;
std::vector<int> v; // 测试标准库vector与自定义vector的兼容性

v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
v.push_back(5);

cout << "vecto中原始的元素为:" << endl;
//print_vector(v);  错误,无法打印出vector容器v中的元素
print_container(v);

上面的代码中,print_container(v) 能正常工作而 print_vector(v) 报错的根本原因在于 命名空间模板参数匹配规则 的差异。


(1) 模板参数类型严格匹配问题

  • print_vector 函数定义:

    template<class T>
    void print_vector(const vector<T>& v)
    

    这里的 vector非限定名称,编译器会优先在 当前命名空间mySpace)中查找。

  • 当传入 std::vector<int> 时:

    • 编译器期望 mySpace::vector<T>
    • 实际传入 std::vector<int>
    • 类型不匹配导致编译错误

(2) print_container 的通用性设计

  • print_container 函数定义:

    template<class container>
    void print_container(const container& con)
    
  • 这是完全泛型的:

    • 接受 任何具有 begin()/end() 接口的类型

    • 不限制具体容器类型

    • 因此兼容 std::vectormySpace::vector

函数 参数类型 推导机制 兼容性
print_vector const vector<T>& 要求精确匹配 mySpace::vector 仅限自定义容器
print_container const container& 匹配任何具有迭代器的类型 标准库和自定义容器

在这里插入图片描述

片段六:二维数组的实现原理

/*-------------------第一部分:创建一个二维数组-------------------*/
//构造二维vector:7个元素,每个元素是vector<int>(5,1)

/*-------方法一:填充构造法-------*/
std::vector<int> v(5, 1);				  //5个1的一维vector
std::vector<std::vector<int>> vv(7, v); //7个v的拷贝,形成“7行5列”的二维数组

/*-------方法二:嵌套构造法-------*/
//std::vector<std::vector<int>> vv(7, std::vector<int>(5, 1)); // 直接指定每行的初始值

/*-------------------第二部分:修改二维数组-------------------*/
vv[2][1] = 2; //修改第3行(索引2)第2列(索引1)的元素为2
//等价于:vv.operator[](2).operator[](1) = 2; 注释:operator[]的嵌套调用

1. 二维vector的访问机制是什么?

//这是一个简化的vector模板类示意,非实际代码中的实现,只是为了说明二维vector的实现原理
template<class T>
class vector
{
	T& operator[](int pos)   //下标运算符,返回元素引用
	{
		assert(pos < _size); //断言下标有效
		return _a[pos];      //返回数组中对应位置的元素
	}
private:
	T* _a;			  //数据指针
	size_t _size;     //有效元素个数
	size_t _capacity; //总容量
};

/*---------------当T为int时,vector<int>的operator[]返回int&---------------*/
//当T为int时 -----> vector<int>的operator[]返回“int&”
class vector
{
	int& operator[](int i)
	{
		assert(i < _size);
		return _a[i];
	}
private:
	int* _a;
	size_t _size;
	size_t _capacity;
};

/*---------------当T为vector<int>时,vector<vector<int>>的operator[]返回vector<int>&---------------*/
//当T为vector<int>时 -----> vector<vector<int>>的operator[]返回“vector<int>&”
class vector
{
	vector<int>& operator[](int i) // 返回内层vector的引用
	{
		assert(i < _size);
		return _a[i];
	}
private:
	vector<int>* _a; // 指针数组,每个元素是vector<int>
	size_t _size;
	size_t _capacity;
};

通过上面的这段代码,我们可以深入理解 C++ 中 模板类的实例化过程,以及 二维 vector 的内存结构和访问机制


模板类的实例化机制:

模板类 vector<T> 在编译时会根据实际使用的类型参数(如:intvector<int>)生成对应的具体类。

  • vector<int>T 被替换为 int_a 成为 int*operator[] 返回 int&
  • vector<vector<int>>T 被替换为 vector<int>_a 成为 vector<int>*operator[] 返回 vector<int>&

这种实例化过程是 静态的(编译期完成),不同类型参数生成的类是独立的。


二维 vector 的内存结构:

二维 vectorvector<vector<int>> 本质是一个 动态数组的动态数组

  • 外层vector 管理一个 vector<int>* 类型的指针数组 _a,每个元素是一个指向内层 vector<int> 的指针。
  • 内层vector<int> 各自管理一个 int* 类型的指针数组,存储实际数据。

内存示意图:

vector<vector<int>> vv:
  _a ───> [vector<int>] ───> [1, 2, 3]  // 第一行
          [vector<int>] ───> [4, 5, 6]  // 第二行
          [vector<int>] ───> [7, 8, 9]  // 第三行

多级下标访问的原理:

通过 vv[i][j] 访问元素时:

  1. vv[i]:调用外层 vector<vector<int>>operator[],返回第 i 个内层 vector<int> 的引用。
  2. [j]:调用内层 vector<int>operator[],返回第 jint 的引用。

代码示例

vv[1][2] = 10;  

// 等价于:
auto& row = vv.operator[](1);  // 获取第二行(vector<int>&)
row.operator[](2) = 10;        // 修改第二行第三列的元素

2. 二维vector和原生二维数组的区别?

特性 二维vector 原生二维数组
内存结构 动态数组的动态数组 连续的内存块
大小灵活性 支持动态调整行列数 大小必须在编译期确定
内存连续性 内层 vector 可能不连续 完全连续
访问方式 vv[i][j](两次函数调用) arr[i][j](直接偏移)
类型安全性 更高(边界检查、类型推导) 较低(需手动管理内存)

总结:

二维vectorvector<vector<int>> 的本质是模板类的嵌套实例化,通过多级指针和引用实现灵活的多维数据管理。

在这里插入图片描述


网站公告

今日签到

点亮在社区的每一天
去签到