C++——vector模拟实现(下)

简介: 笔记

构造


1.png2.png3.png

这里默认的拷贝构造是浅拷贝,程序结束会析构俩次,所以崩溃

我们这需要自己设置一个深拷贝

传统写法:

  vector(const vector<T>& v)
    {
      _start = new T[v.size()];
      memcpy(_start, v._start, sizeof(T) * v.size());
      _finish = _start + v.size();
      _end_of_storage = _start + v.size();
    }

4.png

也可这样写

vector(const vector<T>& v)
      :_start(nullptr),
      _finish(nullptr),
      _end_of_storage(nullptr)
    {
      reserve(v.size());
      for (const auto& e : v)
      {
        push_back(e);
      }
    }

现代写法:

vector里面有一个构造是支持迭代器区间构造5.png

之前我们已经有了类模板

image.pngimage.png

现在我们在类模板,成员函数里面再加一个模板


也就是说现在的这个拷贝构造函数里面可以用双重模板,在定义模板是因为我们需要一个迭代区间,而之前我们已经有了iterator,现在是InputIterator,这样写代表我们不仅可以用T类型模板,iterator迭代器,也可以用其他模板和其他迭代器,这样比较灵活


便于我们实现这种情况


image.pngimage.png

当前函数写这样运行的时候会直接报错,因为如果编译器没有对s进行初始化,push_back要调用reverse,删除旧数组空间的时候会报错

 因此我们要对s进行初始化

image.png

template <class InputIterator>
    vector(InputIterator first, InputIterator last)
      : _start(nullptr)
      , _finish(nullptr)
      , _end_of_storage(nullptr)
    {
      while (first != last)
      {
        push_back(*first);
        ++first;
      }
    }

11.png

现代写法:

  template <class InputIterator>
    vector(InputIterator first, InputIterator last)
      : _start(nullptr)
      , _finish(nullptr)
      , _end_of_storage(nullptr)
    {
      while (first != last)
      {
        push_back(*first);
        ++first;
      }
    }
    void swap(vector<T>& v)
    {
      std::swap(_start, v._start);
      std::swap(_finish, v._finish);
      std::swap(_end_of_storage, v._end_of_storage);
    }
    vector(const vector<T>& v)
      :_start(nullptr)
      , _finish(nullptr)
      , _end_of_storage(nullptr)
    {
      vector<T> tmp(v.begin(), v.end());
      swap(tmp);
    }
  vector<T>& operator=(vector<T> v)
    {
      swap(v);
      return *this;
    }

这里传参的时候不进行引用传参,他会调用拷贝构造,根据我们写的拷贝可知,实参会被删除,保留形参,v就是保留下来的形参,然后跟一交换即可,这样比较方便

思考题


12.png

 vector(size_t n, const T& val = T())  插入n个val,val是T类型


使用N个vakue去构造,T()是T类型的匿名对象,匿名对象就要调用默认构造,如果是Date这些自定义类型,就需要我们人为的把构造函数写好,如果T是int,int也有自己的默认构造,内置类型也有自己的默认构造

vector(size_t n, const T& val = T())
      :_start(nullptr)
      ,_finish(nullptr)
      ,_end_of_storage(nullptr)
    {
      reserve(n);
      for (int i = 0; i < n; i++)
      {
        push_back(val);
      }
    }


13.png14.png

当这样初始化会报错

15.png

报错信息在这一行:非法的间接寻址

image.png

当编译器进行类型匹配的时候,如果只传了一个参数,编译器会把那个参数传给最左边的形参(前面博客有说过)

而传俩个参数都是int类型,编译器在进行类型匹配的时候会找最匹配的类型来匹配

一个参数是unsigned int,一个是int


image.png

对于这里传参传的是int,int,这个对拷贝构造来说特别匹配,first 和last都是int,然后对int解引用就报错

image.png

而下面这个不报错因为,对于拷贝构造来说不匹配,所以它进不去拷贝构造

image.png

resize


void resize(size_t n, const T& val = T())
    {
      if (n > capacity())
      {
        reserve(n);
      }
      if (n > _finish)
      {
        while (_finish < _start + n)
        {
          *finish = val;
          ++_finish;
        }
      }
      else
      {
        _finish = _start + n;
      }
    }

reserve对_finish不是很敏感,resize可以改变_finish


vector<vevtor<T>>深浅拷贝问题 
  vector(const vector<T>& v) //拷贝构造函数
  {
    _start = new T[v.size()];
    memcpy(_start, v._start, sizeof(T) * v.size());
    _finish = _start + v.size();
    _end_of_storage = _start + v.size();
  }
class Solution {
  public:
  vector<vector<int>> generate(int numRows) {
    vector<vector<int>> vv;
    vv.resize(numRows);
    for (int i = 0; i < vv.size(); ++i)
    {
    vv[i].resize(i + 1, 0);
    vv[i].front() = vv[i].back() = 1;
    }
    for (size_t i = 0; i < vv.size(); ++i)
    {
    for (size_t j = 0; j < vv[i].size(); ++j)
    {
      if (vv[i][j] == 0)
      {
      vv[i][j] = vv[i - 1][j] + vv[i - 1][j - 1];
      }
    }
    }
    return vv;
  }
  };
  void test_vector1()
  {
  Solution().generate(5);
  }

上面代码是拷贝构造函数以及杨辉三角的调用


程序直接崩溃image.png

这是因为在函数返回的时候要调用一次拷贝构造,而该拷贝构造对外面是深拷贝,对里面是浅拷贝

21.pngimage.png

这种拷贝方式只是对最外层完成了慎深拷贝,而对于里层确实浅拷贝

image.png

问题出在了memcpy上,memcpy是一个字节一个字节往过拷贝,导致这里里层的_start指向同一块空间,析构的时候会析构俩次,因此这里不能用memcpy


这样修改拷贝构造,程序即可正常运行


vector(const vector<T>& v)
  {
    _start = new T[v.size()];
  //  memcpy(_start, v._start, sizeof(T) * v.size());
    for (int i = 0; i < v.size(); ++i)
    {
    _start[i] = v._start[i];
    }
    _finish = _start + v.size();
    _end_of_storage = _start + v.size();
  }

防止reserve出现这种问题,对reserve也可进行修改


void reserve(size_t n)
  {
    if (n > capacity())
    {
    size_t sz = size();
    T* tmp = new T[n];
    if (_start)
    {
      //memcpy(tmp, _start, sizeof(T) * sz);
      for (size_t i = 0; i < sz; ++i)
      {
      tmp[i] = _start[i];
      }
      delete[] _start;
    }
    _start = tmp;
    _finish = _start + sz;
    _end_of_storage = _start + n;
    }
  }

习题 电话号码的字母组合


17. 电话号码的字母组合 - 力扣(LeetCode)

image.png

class Solution {
    char *numToStr[10]={"","","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"};
public:
void Combine(string digits,int di,vector<string>& retv,string CombinStr)
{
    if(di==digits.size())
    {
        retv.push_back(CombinStr);
        return ;
    }
    int num=digits[di]-'0';
    string str=numToStr[num];
    for(auto ch:str)
    {
        Combine(digits,di+1,retv,CombinStr+ch);
    }
}
    vector<string> letterCombinations(string digits) {
 vector<string> v;
  if(digits.empty())
 return v;
 string str;
 Combine(digits,0,v,str);
 return v;
    }
};

str是一个用来临时存储组合的结果,digits是用户输入的数字,di是下标,如digits是“238”,di=0,就代表表示字符2,所以要给-'0',把每个按键对应的字母存到一个数组中,通过下标来访问


习题 删除有序数组中的重复项


26. 删除有序数组中的重复项 - 力扣(LeetCode)

class Solution {
public:
    int removeDuplicates(vector<int>& nums) {
int src=0;
int dst=0;
while(src<nums.size())
{
    if(nums[src]==nums[dst])
    {
        ++src;
    }
    else
    {
   ++dst;
        nums[dst]=nums[src];
        src++;
    }
}
return dst+1;
    }
};
相关文章
|
4月前
|
存储 编译器 C++
【C++】vector介绍+模拟实现
【C++】vector介绍+模拟实现
|
24天前
|
存储 编译器 C语言
【c++丨STL】vector的使用
本文介绍了C++ STL中的`vector`容器,包括其基本概念、主要接口及其使用方法。`vector`是一种动态数组,能够根据需要自动调整大小,提供了丰富的操作接口,如增删查改等。文章详细解释了`vector`的构造函数、赋值运算符、容量接口、迭代器接口、元素访问接口以及一些常用的增删操作函数。最后,还展示了如何使用`vector`创建字符串数组,体现了`vector`在实际编程中的灵活性和实用性。
49 4
|
6天前
|
存储 对象存储 C++
C++ 中 std::array<int, array_size> 与 std::vector<int> 的深入对比
本文深入对比了 C++ 标准库中的 `std::array` 和 `std::vector`,从内存管理、性能、功能特性、使用场景等方面详细分析了两者的差异。`std::array` 适合固定大小的数据和高性能需求,而 `std::vector` 则提供了动态调整大小的灵活性,适用于数据量不确定或需要频繁操作的场景。选择合适的容器可以提高代码的效率和可靠性。
25 0
|
10天前
|
存储 编译器 C语言
【c++丨STL】vector模拟实现
本文深入探讨了 `vector` 的底层实现原理,并尝试模拟实现其结构及常用接口。首先介绍了 `vector` 的底层是动态顺序表,使用三个迭代器(指针)来维护数组,分别为 `start`、`finish` 和 `end_of_storage`。接着详细讲解了如何实现 `vector` 的各种构造函数、析构函数、容量接口、迭代器接口、插入和删除操作等。最后提供了完整的模拟实现代码,帮助读者更好地理解和掌握 `vector` 的实现细节。
21 0
|
2月前
|
存储 C++ 索引
【C++打怪之路Lv9】-- vector
【C++打怪之路Lv9】-- vector
26 1
|
2月前
|
安全 测试技术 C++
【C++篇】从零实现 C++ Vector:深度剖析 STL 的核心机制与优化2
【C++篇】从零实现 C++ Vector:深度剖析 STL 的核心机制与优化
76 6
|
2月前
|
安全 测试技术 C++
【C++篇】从零实现 C++ Vector:深度剖析 STL 的核心机制与优化1
【C++篇】从零实现 C++ Vector:深度剖析 STL 的核心机制与优化
89 7
|
2月前
|
编译器 C++
【C++】—— vector模拟实现
【C++】—— vector模拟实现
|
2月前
|
编译器 C语言 C++
【C++篇】解密 STL 动态之魂:全面掌握 C++ vector 的高效与优雅
【C++篇】解密 STL 动态之魂:全面掌握 C++ vector 的高效与优雅
58 3
|
2月前
|
C++
【C++】C++ STL探索:Vector使用与背后底层逻辑(三)
【C++】C++ STL探索:Vector使用与背后底层逻辑