C++ vector容器的解析和使用

C++的大部分接口是互通的 详解已经在string类中写过了 这里重复的接口属性就不详细写了

文章目录

1.Member functions

C++提供的顺序表容器 在使用前建议先系统的学习过数据结构中的顺序表 详情请点

constructor


explicit 说明:防止隐式转换(vector v = 5 这是错误的 )

  1. 默认构造函数(无参构造)
    功能:创建一个空的 vector,不包含任何元素。
cpp 复制代码
std::vector<int> v;  // 创建空的 int 类型 vector
std::cout << v.size();  // 输出 0(无元素)
  1. 填充构造函数(指定大小和初始值)
    功能:创建一个包含 n 个元素的 vector,每个元素的初始值为 val。
cpp 复制代码
std::vector<int> v(3, 10);  // 创建包含3个元素的vector,每个元素为10
// 结果:v = [10, 10, 10]
  1. 范围构造函数(用迭代器区间初始化)
    功能:通过迭代器区间 [first, last) 中的元素初始化 vector,即复制该区间内的所有元素(包含 first 指向的元素,不包含 last 指向的元素)。
cpp 复制代码
int arr[] = {1, 2, 3, 4};
std::vector<int> v(arr, arr + 3);  // 复制arr[0]、arr[1]、arr[2]
// 结果:v = [1, 2, 3]

std::vector<int> v2(vec.begin(), vec.end());  // 复制另一个vector的所有元素
// 结果:v2 = [1, 2, 3]
  1. 拷贝构造函数(复制另一个 vector)
    功能:创建一个新的 vector,并复制另一个 vector x 中的所有元素(深拷贝,两个容器后续修改互不影响)。
cpp 复制代码
std::vector<int> v1 = {1, 2, 3};
std::vector<int> v2(v1);  // 拷贝v1的元素
// 结果:v2=[1, 2, 3]

然后C++11中还引入了initializer_list(使用需要包含initializer_list的头文件)

cpp 复制代码
#include<iostream>
#include<vector>
#include<initializer_list> 
int main() {
    // 假设 il 是一个整数类型的初始化列表
    auto il = {1, 2, 3, 4, 5};  // 推断为 initializer_list<int>
    // 输出 il 的类型名(不同编译器可能显示不同,通常为 "initializer_list<int>" 相关形式)
    cout << typeid(il).name()<<endl;
    // 遍历初始化列表中的元素并输出
    for (auto e : il) 
    {
        cout << e << " ";
    }
    cout<<endl;  // 输出:1 2 3 4 5
    // 用初始化列表直接初始化 vector(C++11 及以上支持)
    vector<int> v1({10, 20, 30});  // v1 包含元素 10, 20, 30
    return 0;
}
cpp 复制代码
vector (initializer_list<value_type> il,
const allocator_type& alloc = allocator_type());

vector 的初始化:
vector<int> v1({10,20,30})或者vector<int> v1=({10,20,30}) (构造传参)是 C++11 引入的列表初始化方式,等价于更简洁的 vector<int> v1{10,20,30} 或者vector<int> v1={10,20,30}(走隐式类型转换 构造+拷贝构造 直接优化为构造),直接构造包含指定元素的向量。

注意:initializer_list 中的元素是常量,无法修改(如 vector[0] = 5 错误)。

而且 他的遍历是要通过迭代器的方式 但是放在别的容器中还可以让他像数组一样!

initializer_list初始化的本质相当于 在范围for中 一直用push_back()插入数据。

std::vector::operator=

cpp 复制代码
copy (1) vector& operator= (const vector& x);

赋值重载 将新内容分配给容器,替换其当前内容,并相应地修改其大小。

功能说明

  • 作用:把 vector x 中的所有元素复制到当前 vector 中。
  • 特性:这是一个深拷贝操作,赋值后两个 vector 各自拥有独立的元素,修改其中一个不会影响另一个。
  • 行为:赋值前会先清空当前 vector 中的原有元素,再复制 x 的元素。
cpp 复制代码
// vector assignment
#include <iostream>
#include <vector>

int main ()
{
  vector<int> a (3,0);
  vector<int> b (5,0);

  b = a;
  a = vector<int>();

  cout << "Size of a: " << int(a.size()) << '\n';
  cout << "Size of b: " << int(b.size()) << '\n';
  return 0;
}

结果:Size of a: 0 ;Size of b: 3

2.Iterators

迭代器 没什么好说的 但是有一说一 vevtor容器是不支持直接像数组一样遍历 必须使用迭代器

其接口特性和string 基本一致 详情请转跳

3.Capacity


vector的容器部分 其效果基本也与string 相似 本质上是并没有太大的区别
详情请转跳

4.Element access


vector的容器部分 其效果基本也与string 相似 本质上是并没有太大的区别
详情请转跳

5. vector 增删查改

这里面会产生一些不同于string容器的变化 我这里会挑一些重点讲

push_back&pop_back

cpp 复制代码
void push_back (const value_type& val);
void push_back (value_type&& val);
cpp 复制代码
vector<int> v1;
    v1.reserve(100);//扩容到100 减少多次扩容
    size_t old=v1.capacity();
    cout<<old<<endl;
    for (size_t i=0; i<100; i++)//尾插
    {
        v1.push_back(i);
        if (v1.capacity()!=old)
        {
            cout<<v1.capacity()<<endl;
            old=v1.capacity();
        }
    }
cpp 复制代码
void pop_back();
Delete last element

vector::pop_back() 只会减少 vector 的元素数量(size),不会改变其容量(capacity)。

移除向量中的最后一个元素,实际上会使容器大小减少一个。

这会销毁被移除的元素。 这里就不演示了

insert&&find

  1. 插入单个元素
cpp 复制代码
iterator insert (iterator position, const value_type& val);

string不同 vector没有办法使用pos 而是要使用迭代器

返回值:指向新插入元素的迭代器(方便后续操作)

cpp 复制代码
vector<int> v = {1, 3, 4};
// 在索引1位置插入元素2(通过迭代器 v.begin() + 1 指定位置)
v.insert(v.begin() + 1, 2);
// 结果:v = [1, 2, 3, 4],it 指向新插入的 2
  1. 插入 n 个重复元素
cpp 复制代码
void insert (iterator position, size_type n, const value_type& val);

返回值:void

cpp 复制代码
std::vector<int> v = {1, 4};
// 在开头插入 2 个元素 0
v.insert(v.begin(), 2, 0);
// 结果:v = [0, 0, 1, 4]
  1. 插入迭代器范围元素
cpp 复制代码
template <class InputIterator>
void insert (iterator position, InputIterator first, InputIterator last);

返回值:void

cpp 复制代码
std::vector<int> v1 = {1, 5};
std::vector<int> v2 = {2, 3, 4};
// 在 v1 的索引1位置插入 v2 的所有元素
v1.insert(v1.begin() + 1, v2.begin(), v2.end());
// 结果:v1 = [1, 2, 3, 4, 5]

虽然vector容器里面并没有现成的find 但是在std中是有的

cpp 复制代码
template <class InputIterator, class T>
   InputIterator find (InputIterator first, InputIterator last, const T& val);

返回一个迭代器,指向范围 [first, last) 中第一个与 val 比较相等的元素。如果未找到这样的元素,则函数返回 last。

cpp 复制代码
auto it=find(v1.begin(), v1.end(), 5);//返回的也是迭代器
    if (it!=v1.end())
    {
        v1.insert(it, 50);//在数字5的位置插入50
    }

erase

cpp 复制代码
iterator erase (iterator position);
iterator erase (iterator first, iterator last);
  1. 删除单个元素
cpp 复制代码
vector<int> v = {1, 2, 3, 4};
// 删除索引1处的元素(值为2)
auto it = v.erase(v.begin() + 1);
// 结果:v = [1, 3, 4],it 指向元素3(被删除的下一个元素)
  1. 删除范围内的元素
cpp 复制代码
vector<int> v = {1, 2, 3, 4, 5};
// 删除索引1到3(不包含3)的元素(即2和3)
auto it = v.erase(v.begin() + 1, v.begin() + 3);
// 结果:v = [1, 4, 5],it 指向元素4

emplace_back

这个接口因为目前知识深度的原因 没发讲全 但是也是可以讲一部分

cpp 复制代码
vector<int> v1{10,20,30};//让其像数组一样
    cout<<v1[1]<<endl;//输出 20
    v1.push_back(1);
    v1.emplace_back(1);//这么用可以理解成两者没有区别

push_back和emplace_back的区别

这个是我们自定义的结构体

cpp 复制代码
struct A
{
    A(int a1,int a2)
    :_a1(a1)
    ,_a2(a2)
    {
        cout<<"A(int a1,int a2)"<<endl;
    }
    
    A(const A& aa)
       :_a1(aa._a1)
       ,_a2(aa._a2)
    {
        cout<<"A(const A& aa)"<<endl;
    }
    int _a1;
    int _a2;
};
cpp 复制代码
    vector<A> v2;
    //push_back的三种方法
    //1.有名对象
    A aa1(1,1);
    v2.push_back(aa1);
    //2.匿名对象
    v2.push_back(A(2,2));
    //3.隐式类型转换
    v2.push_back({3,3});
    
    v2.emplace_back(aa1);
    v2.emplace_back(A(2,2));
    //v2.emplace_back({3,3)}; 不能这么用
    //可以这么用
    //传构造A的参数,效率比较高
    v2.emplace_back(3,3);//直接构造

前两种可以理解二者没有区别 最后一种是采用直接的构造效率更高

自定义类的遍历(含结构化绑定知识点)

cpp 复制代码
    //A*
    vector<A>::iterator it2=v2.begin();
    while (it2!=v2.end())
    {
       // cout<<*it2<<" ";这么写是错误的 因为没有重载流插入
        cout<<(*it2)._a1<<":"<<(*it2)._a2<<endl;
        cout<<it2->_a1<<":"<<it2->_a2<<endl;//这么写也可以
        ++it2;
    }
    cout<<endl;
    
    //当然肯定是范围for更香一点 c++11
    for(auto& aa:v2)
    {
        cout<<aa._a1<<" "<<aa._a2<<endl;
    }
    
    //c++17 新出的语法糖 当容器是结构的时候就可以这么用
    auto[x,y]=aa1;//取aa1的成员依次进行赋值
    for(auto[x,y] :v2)//结构化绑定
    {
        cout<<x<<":"<<y<<endl;
    }
    
    for(auto&[x,y] :v2)//不想有拷贝构造函数
    {
        cout<<x<<":"<<y<<endl;
    }

6. 例题板块

例题1.只出现一次的数字(^ 的用法)

cpp 复制代码
class Solution {
public:
    int singleNumber(vector<int>& nums) {
        int x = 0;  // 初始化为0
        for (auto e : nums) {  // 遍历数组中的每个元素
            x ^= e;  // 累积异或运算
        }
        return x;  // 最终结果就是只出现一次的数字
    }
};

核心原理:异或运算的特性

  • 自反性:a ^ a = 0(任何数与自身异或结果为 0)
  • 恒等性:a ^ 0 = a(任何数与 0 异或结果为其本身)
  • 交换律和结合律:a ^ b ^ c = a ^ c ^ b(运算顺序不影响结果)

假设数组为 [2, 3, 2, 4, 4]

x = 0 ^ 2 → 2

x = 2 ^ 3 → 1(二进制 10 ^ 11 = 01)

x = 1 ^ 2 → 3(二进制 01 ^ 10 = 11)

x = 3 ^ 4 → 7(二进制 11 ^ 100 = 111)

x = 7 ^ 4 → 3(二进制 111 ^ 100 = 011)

最后结果就是 3

异或运算的自反性(a ^ a = 0)和恒等性(a ^ 0 = a)在此过程中起关键作用:

数组中出现两次的数字(如 2、4),经过两次异或后会相互抵消(结果为 0)。

最终剩下的数字就是只出现一次的数字(如 3),因为它只被异或了一次。

例题2.杨辉三角(C和C++做法的比较)

如果用C语言来写的话 你需要动态开辟二纬数组 释放的时候需要讲空间进行分别进行释放

如果你想用c写,你需要返回两个你不怎么认识的指针给测试者。

其中 *returnSize是让调用者知道有多少行,然后 *returnColumnSizes是让调用者知道为每一行分配的列数。

cpp 复制代码
int** generate(int numRows, int* returnSize, int** returnColumnSizes) {

    int** aa =(int*) malloc(numRows * sizeof(int*));//为指针数组分配内存,定义行数
    
    //对程序结构没影响,但如果缺少则无法与出题者交互,进而不能通过测试
    *returnSize = numRows;//让调用者知道有多少行
    *returnColumnSizes = (int*)malloc(numRows * sizeof(int));//为每一行分配列数做准备【x,x,x,x,x...】

    for (int i = 0; i < numRows; i++) {
        triangle[i] =(int*) malloc((i+ 1) * sizeof(int));//为特定的行分配内存,定义了该行的具体元素数量
        (*returnColumnSizes)[i] = i + 1; // 为每一行分配列数【1,2,3,4,5...】,让调用者知道

        // 每行的第一个和最后一个元素为 1
        aa[i][0] = 1;
        aa[i][i] = 1;

        // 计算当前行中间的元素
        for (int j = 1; j < i; j++) {
            aa[i][j] = aa[i - 1][j - 1] + aa[i - 1][j];
        }
    }

    return aa; // 返回生成的杨辉三角

}

让我们看看C++是怎么做的

cpp 复制代码
class Solution {
public:
    vector<vector<int>> generate(int numRows) {
        vector<vector<int>> vv;
        vv.resize(numRows,vector<int>());
        for(size_t i=0;i<numRows;++i)
        {
            vv[i].resize(i+1,1);
        }
        for(size_t i=2;i<vv.size();i++)
        {
            for(size_t j=1;j<vv[i].size()-1;j++)
            {
                vv[i][j];//本质逻辑 
                //vv.operator[](i).operator[](j)
               vv[i][j]=vv[i-1][j]+vv[i-1][j-1];
            }
        }
        return vv;
    }
};
cpp 复制代码
vv.resize(numRows, vector<int>());

这个操作将 vv 调整为有 numRows 行,每行是一个空的 vector。注意,vector() 是一个空的 int 类型的向量,表示每一行的初始状态为空

里面的vector<int>相当于 一个int*的数组 同时伴随容器的sizecapacity,而外层的vector则是一个包含着一个vector<int>*数组的一个类。

  • 外部 vector (vv): 存储的是指向内部 vector 的指针。
  • 内部 vector (vector): 每个内部的 vector 实际上是一个独立的动态数组。

而这么做的目的也就是为开辟一个动态的二维数组。这也就是STL容器所带来的简便性!

7. vector模拟实现和实现过程中的一些重难点

_start:指向内存空间的起始位置,即第一个元素的地址。

_finish:指向当前已存储元素的下一个位置,即末尾元素的地址 + 1。

_endofstorage:指向已开辟内存空间的末尾位置(已开辟内存的最后一个字节的下一个位置)。

7.1模拟实现完整代码

cpp 复制代码
#include<iostream>
#include <cassert>
using namespace std;

//模版 申明和定义不能分离定义到两个文件.h .cpp
namespace fcy
{
template<class T>
class vector
{
public:
    typedef T* iterator;
    typedef const T* const_iterator;
    
    iterator begin()
    {
        return _start;
    }
    
    iterator end()
    {
        return _finish;
    }
    const_iterator begin() const
    {
        return _start;
    }
    
   const_iterator end() const
    {
        return _finish;
    }
//    vector():
//    _start(nullptr)
//    ,_finish(nullptr)
//    ,_endofstorage(nullptr)
//    {}
    //因为有了缺省值所以还可以
//    vector()
//    {}
    
    //c++11强制编译器生成默认构造
    vector()=default;
    
    //多参数拷贝构造
    vector(initializer_list<T> il)
    {
        reserve(il.size());
        for(auto& e:il)
        {
            push_back(e);
        }
    }
  //迭代器区间初始化 但这么写只能传vector 的iterator
//    vector(iterator start,iterator end)
//    {
//        while (start!=end)
//        {
//            push_back(*start);
//            ++start;
//        }
//    }
    template<class it>
    //函数模版
    //只要类型匹配 我可以用任意类型迭代器来进行初始化
    vector(it first,it last)
    {
        while (first!=last)
        {
            push_back(*first);
            first++;
        }
    }
    //非法间接寻址
//    vector(size_t n,T val=T())
//    {
//        resize(n,val);
//    }
    //改成int就没问题吗? 这只能解决一种情况 所以咱们就多搞两个
    vector(int n,T val=T())
    {
        resize(n,val);
    }

    vector(size_t n,T val=T())
    {
        resize(n,val);
    }
    //v2(v1) 拷贝构造 写法1 这种写法必须要初始化 不然reserve鬼知道会发生什么 迭代器都是随机值 当然也可以用缺省值进行初始化
    vector(const vector<T>& v)
   // _start(nullptr)
    //,_finish(nullptr)
    //,_endofstorage(nullptr)
    {
        reserve(v.capacity());
        for(auto& e:v)
        {
          push_back(e);
        }
    }
    void swap(vector<T>& v)
    {
        std::swap(_start, v._start);
        std::swap(_finish, v._finish);
        std::swap(_endofstorage, v._endofstorage);
    }
    //v1=v7
    vector<T>& operator=(vector<T> v)
    {
        swap(v);
        return *this;
    }
    
    ~vector()
    {
        if(_start)
        {
            delete[] _start;
        }
        _start=_finish=_endofstorage=nullptr;
    }
    
    //string 由_str _size _capacity 组成
    
    
    //扩容
    void reserve(size_t n)
    {
        if(n>capacity())
        {
            size_t old_size=size();//修正1
            T* tmp=new T[n];
            //拷贝旧空间数据到新空间
            if (_start)
            {
               // memcpy(tmp,_start,sizeof(T)*size());
                //string中的指针也会原样赋值 会导致str*指向同一空间
                //然后空间释放时候导致tmp所指向的也一样释放了
                //导致了浅拷贝
                //所以建议用赋值来解决
                for(size_t i=0;i<old_size;i++)
                {
                    tmp[i]=_start[i];
                }
                //int这样的类型,赋值可以完成浅拷贝
                //string这样的类型,赋值重载完成深拷贝
                delete[] _start;
            }
            _start=tmp;
            //_finish=_start+size();
            //此时的size()是有问题的 因为此时
            //size=_finish(原来的)-_start=0(因为初始化时候为0)-_start
            //所以要提前更新 参考修正1
            _finish=_start+old_size;
            _endofstorage=_start+n;
        }
    }
    
    void resize(size_t n,T val =T())//本质就是调用默认构造
    {
      if(n>size())
      {
          reserve(n);
          while (_finish!=_start+n)
          {
              *_finish=val;
              ++_finish;
          }
      }
        else
        {
            _finish=_start+n;
        }
    }
    
    
    
    
    T& operator[](size_t i)
    {
        assert(i<size());
        
        return _start[i];
    }
    
    const T& operator[](size_t i)const
    {
        assert(i<size());
        
        return _start[i];
    }
    void clear()
    {
        _finish=_start;
    }
    
    size_t size() const
    {
        return _finish-_start;
    }
    
    size_t capacity() const
    {
        return  _endofstorage-_start;
    }
    
    //尾插
    void push_back(const T& x)
    {
        if (_finish==_endofstorage)
        {
            reserve(capacity()==0?4:capacity()*2);
        }
        *_finish=x;
        _finish++;
    }
    
    //尾删
    void pop_back()
    {
        assert(!empty());
        --_finish;
    }
    
    bool empty() const
    {
        return  _start==_finish;
    }
    
    iterator insert(iterator pos,const T& x)//不能用& 因为假设传的begin()调用函数iterator begin() 或者说 是表达式的形式 返回的是零时对象 具有常性
    {
        assert(pos>=_start);
        assert(pos<=_finish);
        
        if(_finish==_endofstorage)
        {
            size_t len=pos-_start;
            reserve(capacity()==0?4:capacity()*2);//扩容会引起迭代器失效
            //更新pos
            pos=_start+len;
            //指向新内存中插入的数值的位置,是有效的迭代器
       }
        
        iterator end=_finish-1;
        while(end>=pos)
        {
            *(end+1)=*end;
            --end;
        }
        *pos=x;
        _finish++;
        
        return pos;
    }
    
    iterator erase(iterator pos)
    {
        assert(pos>=_start);
        assert(pos<_finish);
        //挪动数据
        iterator it=pos+1;
        while (it!=_finish)
        {
            *(it-1)=*it;
            it++;
        }
        _finish--;
        
        return pos;//返回指向删除数据的下一个位置
                  //比如删除位置为2 结束后指向位置3
    }
    
    
private:
    iterator _start=nullptr;
    iterator _finish=nullptr;//最后一个数据的下一个
    iterator _endofstorage=nullptr;//已开辟内存的最后一个字节的下一个位置
};
}

7.2迭代器失效问题

问题解析

首先我们需要先了解迭代器是什么?

迭代器的本质:指向内存地址的指针

vector的迭代器本质是原生指针(或行为类似指针的对象),直接指向元素在内存中的地址。例如:

若pos迭代器指向内存地址0x1000(对应元素A),那么*pos就是访问0x1000地址上的数据。
为什么迭代器会失效呢?

迭代器的核心作用是 "指向特定元素"。删除后,pos原本指向的元素A已被移除,0x1000地址上的元素变成了B,pos不再指向 "原来的元素",语义上已失效。

cpp 复制代码
//insert 部分
    iterator insert(iterator pos,const T& x)
    {
        if(_finish==_endofstorage)
        {
            size_t len=pos-_start;
            reserve(capacity()==0?4:capacity()*2);//扩容会引起迭代器失效
                   pos=_start+len;
       }
        

这是扩容的部分 代码 引起迭代器失效的罪魁祸首就是扩容操作里的

cpp 复制代码
            T* tmp=new T[n];
            //拷贝旧空间数据到新空间
            if (_start)
            {
                  for(size_t i=0;i<old_size;i++)
                {
                    tmp[i]=_start[i];
                }
                    delete[] _start;
            }
            _start=tmp;

这里delete空间释放是导致其出现迭代器失效的罪魁祸首。

cpp 复制代码
//erase部分
        iterator it=pos+1;
        while (it!=_finish)
        {
            *(it-1)=*it;
            it++;
        }
        _finish--;

而这里导致迭代器失效的主要原因就是因为 删除元素后 替他元素的前移 虽然指向的地址不变 但是指向的内容发生改变 所以迭代器失效哦。

解决方案

首先在std库中我们参考源代码
erase 提供了一个可以返回的 新的有效迭代器,指向 "被删除元素的下一个元素",供用户更新迭代器,避免使用失效的迭代器。

假设我们有一个 vector 存储元素 [10, 20, 30, 40],内存布局如下(地址为示例):

  1. 删除 pos 指向的元素(30)
    执行 erase(pos) 时,vector 会将 pos 之后的元素(40)往前移动,覆盖 pos 位置的元素:
    40 从 0x100C 移到 0x1008(原 30 的位置)。

最终元素变为 [10, 20, 40],_finish 指向 0x100C(尾后位置)。

  1. 原 pos 迭代器指向的内容
    原 pos 迭代器本质是指针,仍指向 0x1008 这个内存地址,但该地址上的元素已从 30 变成了 40。
    此时:若解引用原 pos(*pos),会得到 40(而非被删除的 30)。

原 pos 迭代器在语义上已失效:它本应指向 "被删除的 30",但现在指向的是 "原 30 后面的 40",与预期逻辑不符。

  1. erase 返回值指向的内容
    erase 会返回 被删除元素的下一个元素的迭代器,即原 40 的位置(移动后 40 在 0x1008,下一个元素是尾后位置 0x100C)。

因此返回的迭代器指向 0x100C(新的尾后位置),是有效的。


insert的解决方案 则是如果扩容 通过记录和更新pos的方式来阻止迭代器失效

cpp 复制代码
        if(_finish==_endofstorage)
        {
            size_t len=pos-_start;
            reserve(capacity()==0?4:capacity()*2);//扩容会引起迭代器失效
            //更新pos
            pos=_start+len;
            //指向新内存中插入的数值的位置,是有效的迭代器
       }

这里提一嘴 insert的返回值 始终指向新插入的元素的位置,是唯一有效的迭代器,需用它更新外部迭代器(比如你在 1 3 4 数组的 1后插入个2 那迭代器返回的就是2 的位置和元素)

7.3 作为函数参数时 能否写成&的形式

iterator insert(iterator& pos,const T& x) 能否这么写?

答案是 NO!

这里举个例子

cpp 复制代码
    fcy::vector<int> v;
    v.push_back(1);
    v.push_back(2);
    v.push_back(3);
    v.push_back(4);
    v.insert(v.begin(), 0);

当我们想在开头位置传入0时候 如果以引用的方式写 会发生报错。
为什么?

cpp 复制代码
    iterator begin()
    {
        return _start;
    }

当调用 insert(v.begin(), 0) 时,begin() 返回的是一个临时迭代器对象(右值 具有常性质)。C++ 规定 "非 const 左值引用(iterator&)不能绑定到右值",此时编译会直接报错。

而这也就是我们常说的权限放大问题!

首先临时对象是具有常性的 具体为何这里会是临时对象 可以参考我以前写过的文章

7.4 reserve的两个注意点

大部分人一开始写这个功能都会这么写

cpp 复制代码
   void reserve(size_t n)
    {
       if(n>capacity())
       {
           T* tmp=new T[n];
           //拷贝旧空间数据到新空间
           if (_start)
           {
               memcpy(tmp,_start,sizeof(T)*size());
               delete[] _start;
           }
           _start=tmp;
           _finish=_start+size();
           _endofstorage=_start+n;
       }
    }

乍一看没问题 其实漏洞百出

size()失效问题

cpp 复制代码
    size_t size() const
    {
        return _finish-_start;
    }

问题就出现在了这三行代码中

cpp 复制代码
_start=tmp;
_finish=_start+size();
_endofstorage=_start+n;

_finish = _start + size()中,size()的计算是_finish - _start,但此时_start已被更新为新空间的指针(tmp),旧的_finish指针已经失效,导致size()计算结果错误。

解决方案

记录原来的size()即可

cpp 复制代码
size_t old_size=size();//提前记录原来的size
 _start=tmp;
 _finish=_start+old_size;
 _endofstorage=_start+n;

浅拷贝导致的内存管理问题

问题出现在这里

cpp 复制代码
memcpy(tmp,_start,sizeof(T)*size());
delete[] _start;

memcpy是字节级别的浅拷贝,仅适用于内置类型(如int、double)。对于自定义类型(如std::string、包含指针成员的类),memcpy会直接拷贝指针值,导致新空间和旧空间的元素共用同一块内存,析构时会触发双重释放(delete[] _start释放旧空间后,新空间的元素指针仍指向已释放的内存,后续析构新空间时再次释放)。
解决方案

用循环赋值的方法

复制代码
        if (_start)
        {
           // memcpy(tmp,_start,sizeof(T)*size());
            //string中的指针也会原样赋值 会导致str*指向同一空间
            //然后空间释放时候导致tmp所指向的也一样释放了
            //导致了浅拷贝
            //所以建议用赋值来解决
            for(size_t i=0;i<old_size;i++)
            {
                tmp[i]=_start[i];
            }
            //int这样的类型,赋值可以完成浅拷贝
            //string这样的类型,赋值重载完成深拷贝
            delete[] _start;
        }

7.5 Tips

模版赋值

cpp 复制代码
template<class T>
vector(int n,T val=T())

模版赋值可以采用匿名对象的方式进行赋值

缺省赋值

cpp 复制代码
    iterator _start=nullptr;
    iterator _finish=nullptr;//最后一个数据的下一个
    iterator _endofstorage=nullptr;//已开辟内存的最后一个字节的下一个位置

缺省值是要走初始化列表的 这么写可以让初始化更方便

cpp 复制代码
//    vector():
//    _start(nullptr)
//    ,_finish(nullptr)
//    ,_endofstorage(nullptr)
//    {}
    //因为有了缺省值所以还可以
   vector()
   {}
    
    //c++11强制编译器生成默认构造
    vector()=default;

多参数构造

才用初始化列表的方式

cpp 复制代码
vector(initializer_list<T> il)
    {
        reserve(il.size());
        for(auto& e:il)
        {
            push_back(e);
        }
    }

创作不易 求三连!

相关推荐
无限进步_3 小时前
C语言文件操作全面解析:从基础概念到高级应用
c语言·开发语言·c++·后端·visual studio
_OP_CHEN3 小时前
C++基础:(十五)queue的深度解析和模拟实现
开发语言·c++·stl·bfs·queue·容器适配器·queue模拟实现
sulikey3 小时前
一文彻底理解:如何判断单链表是否成环(含原理推导与环入口推算)
c++·算法·leetcode·链表·floyd·快慢指针·floyd判圈算法
起床气2333 小时前
C++海战棋开发日记(序)
开发语言·c++
APItesterCris3 小时前
TypeScript 与淘宝 API:构建类型安全的商品数据查询前端 / Node.js 服务
开发语言·php
ftpeak3 小时前
《Cargo 参考手册》第二十一章:Cargo 包命令
开发语言·rust
_码力全开_3 小时前
P1005 [NOIP 2007 提高组] 矩阵取数游戏
java·c语言·c++·python·算法·矩阵·go
陈一Tender4 小时前
JavaWeb后端实战(登录认证 & 令牌技术 & 拦截器 & 过滤器)
java·开发语言·spring boot·mysql