【C++】vector的模拟实现

📝前言:

上篇文章我们讲解了Vector的常见用法,这篇文章我们来模拟实现一下vector。由于之前我们已经模拟实现过了string,所以这篇文章在相似的实现上不过多赘述,主要讲点不同于string的,需要注意的地方。

🎬个人简介:努力学习ing

📋个人专栏:C++学习笔记

🎀CSDN主页 愚润求学

🌄其他专栏:C语言入门基础python入门基础python刷题专栏


文章目录


一,实现的功能汇总

具体接口和方法主要包括:

  1. 构造和析构
  2. 迭代器
  3. 容量操作
  4. 运算符重载
  5. 修改操作
cpp 复制代码
namespace tr

{
    template<class T>
    class vector
    {
    public:
        typedef T* iterator;

        typedef const T* const_iterator;

        // 迭代器
        iterator begin(); // end()

        const_iterator begin() const; // end()

        // 构造和析构

        vector();

        vector(int n, const T& value = T());

        template<class InputIterator> 
        vector(InputIterator first, InputIterator last);

        vector(const vector<T>& v);

        vector<T>& operator= (vector<T> v);

        ~vector();

        // 容量操作

        size_t size() const;

        size_t capacity() const;

        void reserve(size_t n);

        void resize(size_t n, const T& value = T());

        // 运算符重载

        T& operator[](size_t pos);

        const T& operator[](size_t pos)const;

        // 修改操作

        void push_back(const T& x);

        void pop_back();

        void swap(vector<T>& v);

        iterator insert(iterator pos, const T& x);

        // 返回删除的位置的下一个元素的迭代器
        iterator erase(iterator pos);

    private:

        iterator _start = nullptr; // 指向数据块的开始
        iterator _finish = nullptr; // 指向有效数据的尾
        iterator _end_of_storage = nullptr; // 指向存储容量的尾
    };
}

汇总如下:


二,具体操作实现

构造,析构

构造函数我们主要实现4中不同的,对应Vector的常见用法这篇文章中提到的四个构造。

对于析构函数,无论有没有写初始化列表,变量都会通过走初始化列表来定义。

cpp 复制代码
vector() // 什么都不用写,因为声明给了缺省值,直接走初始化列表定义了
{}

vector(int n, const T& value = T())
{
    reserve(n);
    while (n--)
    {
        push_back(value);
    }
}

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

// 拷贝构造,注意浅拷贝和深拷贝的问题
vector(const vector<T>& v)
{
    reserve(v.capacity()); // reserve会开空间且会修改end_of_storage
    for (auto x : v)
    {
        push_back(x);
    }
}

vector<T>& operator= (vector<T> v)
{
    // 因为这里的v只是外部的一个拷贝,交换以后,v在函数结束时也会自动销毁,但是v的内容以及所指向的空间却给了*this
    swap(v);
    return *this;
}

~vector()
{
    delete[] _start; // vector 是一个数组,开辟的是连续的存储空间(非单个),需要用delete[]
    _start = _finish = _end_of_storage = nullptr; 
}

需要注意的是:因为vector可能会存储自定义类型等需要开辟空间的类型,所以在拷贝的时候要注意实现深拷贝,而不是简单的浅拷贝。

vector(InputIterator first, InputIterator last)用函数模版实现。(在类模版里面可以再定义函数模板)用函数模板是为了:可以接受vector以外的迭代器,比如用string的区间迭代器来初始化vector


迭代器

模拟实现vector的迭代器是一个原生指针。

迭代器的位置可以通过起始迭代器位置加上偏移量来表示,呈现出一种相对位置的概念。

cpp 复制代码
typedef T* iterator;

typedef const T* const_iterator;

iterator begin() // end()
{
    return _start;
}

const_iterator begin() const // end() 
{
    return _start;
}

注意:const_iterator begin() constconst类型的迭代器,为了提供const版本的begin()const成员使用。


容量操作

Vector的常见用法中提到了迭代器失效问题,我们在模拟实现时,遇到会造成底层容器变化的操作也需要额外注意这个问题。
memcpy只是浅拷贝,因为memcpy是字节复制,对于内置类型有效。但是对于需要开辟空间的自定义类型,字节复制只是简单的把原来的指针的值复制给了新的指针变量,导致两个指针指向同一片空间,无法达到深拷贝的效果。

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

size_t capacity() const
{
    return _end_of_storage - _start;
    }

// 注意,在这里我们要先记录old_size,
// 因为当_start改变了 后续计算_finish时使用size()时,是使用新的_start和旧_finish,无法反映原来的size
void reserve(size_t n)
{
    if (n > capacity())
    {
        size_t old_size = size();
        iterator _tmp = new T[n];

        // 拷贝元素
        for (size_t i = 0; i < old_size; i++)
        {
            _tmp[i] = _start[i];  // 比如这里是元素是string,实际上这里调用的是operator=,所以能达到深拷贝的效果
        }
        delete[] _start;
        _start = _tmp;
        _finish = _start + old_size;
        _end_of_storage = _start + n;
    }
}

void resize(size_t n, const T& value = T())
{
    if (n > capacity())
    {
        reserve(n);
    }
    if (n > size())
    {
        for (iterator end = _finish; end != (_start + n); end++) // 最后end指向的是有效元素的下一个位置
        {
            *end = value;
        }
    }
    _finish = _start + n;
}

运算符重载

这里主要重载[]运算符,也需要提供const版本给const成员函数使用。

cpp 复制代码
T& operator[](size_t pos)
{
    return _start[pos];
}

const T& operator[](size_t pos)const
{
    return _start[pos];
}

修改操作

传引用减少拷贝。

cpp 复制代码
void push_back(const T& x)
{
    if (_finish == _end_of_storage)
    {
        size_t n = capacity() == 0 ? 4 : capacity() * 2;
        reserve(n);
    }
    *_finish = x;
    _finish++;
}

void pop_back()
{
    erase(end() - 1);
}

void swap(vector<T>& v)
{
    std::swap(_start, v._start); // 记得带std::
    std::swap(_finish, v._finish);
    std::swap(_end_of_storage, v._end_of_storage);
}

iterator insert(iterator pos, const T& x)
{
    assert(pos < _finish);
    if (_finish == _end_of_storage)
    {
        size_t old_size = size();
        size_t n = capacity() == 0 ? 4 : capacity() * 2;
        reserve(n); // 扩容会导致迭代器失效
        pos = _start + old_size;
    }
    iterator end = _finish - 1;
    while (end >= pos)
    {
        *(end + 1) = *end;
        end--;
    }
    *pos = x;
    _finish++;
    return pos;
}

// 返回删除的位置的下一个元素的迭代器
iterator erase(iterator pos)
{
    assert(pos < _finish);
    for (iterator end = pos; end < _finish - 1; end++)
    {
        *(end) = *(end + 1);
    }
    _finish--;
    return pos;
}

注意:在erase*(end) = *(end + 1),后面元素覆盖前面的,这不会造成迭代器失效,因为没有改变容器的内存布局。(迭代器失效指的是迭代器所指向的元素不再有效,或者迭代器的行为变得不可预)

在这里我们任然可以使用这个迭代器来遍历这块连续的空间,行为可预测。


三,代码汇总及测试代码

my_vector.h文件:

因为vector是一个类模板,不建议声明和定义分离,会导致链接错误。

cpp 复制代码
// vector 模拟实现
#include<iostream>
#include<assert.h>
#pragma once
namespace tr
{
    template<class T>
    class vector
    {

    public:

        // Vector的迭代器是一个原生指针
        // 迭代器的位置的确可以通过起始迭代器位置加上偏移量来表示,呈现出一种相对位置的概念
        typedef T* iterator;

        typedef const T* const_iterator;

        iterator begin()
        {
            return _start;
        }

        iterator end()
        {
            return _finish;
        }

        const_iterator begin() const // 提供const版本的begin()给const成员使用
        {
            return _start;
        }

        const_iterator end() const
        {
            return _finish;
        }

            // construct and destroy

        vector() // 什么都不用写,因为声明给了缺省值,直接走初始化列表定义了
        {}

        vector(int n, const T& value = T())
        {
            reserve(n);
            while (n--)
            {
                push_back(value);
            }
        }

        template<class InputIterator> // 在类模版里面可以再定义函数模板
        // 这里用函数模板是因为:可以接受除了vector以外的迭代器,比如用string的区间迭代器来初始化vector
        vector(InputIterator first, InputIterator last)
        {
            while (first != last)
            {
                push_back(*first);
                ++first;
            }
        }

        // 拷贝构造,注意浅拷贝和深拷贝的问题
        vector(const vector<T>& v)
        {
            reserve(v.capacity()); // reserve会开空间且会修改end_of_storage
            for (auto x : v)
            {
                push_back(x);
            }
        }

        vector<T>& operator= (vector<T> v)
        {
            // 因为这里的v只是外部的一个拷贝,交换以后,v在函数结束时也会自动销毁,但是v的内容以及所指向的空间却给了*this
            swap(v);
            return *this;
        }

        ~vector()
        {
            delete[] _start;
            _start = _finish = _end_of_storage = nullptr;
        }

            // capacity

        size_t size() const
        {
            return _finish - _start;
        }

        size_t capacity() const
        {
            return _end_of_storage - _start;
            }

        // 注意,在这里我们要先记录old_size,
        // 因为当_start改变了 后续计算_finish时使用size()时,是使用新的_start和旧_finish,无法反映原来的size
        void reserve(size_t n)
        {
            if (n > capacity())
            {
                size_t old_size = size();
                iterator _tmp = new T[n];

                // 拷贝元素
                // memcpy只是浅拷贝,因为memcpy是字节复制,对于内置类型有效,但是对于需要开辟空间的就无法达到深拷贝的效果
                for (size_t i = 0; i < old_size; i++)
                {
                    _tmp[i] = _start[i];  // 比如这里是元素是string,实际上这里调用的是operator=,所以能达到深拷贝的效果
                }
                delete[] _start;
                _start = _tmp;
                _finish = _start + old_size;
                _end_of_storage = _start + n;
            }
        }

        void resize(size_t n, const T& value = T())
        {
            if (n > capacity())
            {
                reserve(n);
            }
            if (n > size())
            {
                for (iterator end = _finish; end != (_start + n); end++) // 最后end指向的是有效元素的下一个位置
                {
                    *end = value;
                }
            }
            _finish = _start + n;
        }


            ///access///

        T& operator[](size_t pos)
        {
            return _start[pos];
        }

        const T& operator[](size_t pos)const
        {
            return _start[pos];
        }



            ///modify/

        void push_back(const T& x)
        {
            if (_finish == _end_of_storage)
            {
                size_t n = capacity() == 0 ? 4 : capacity() * 2;
                reserve(n);
            }
            *_finish = x;
            _finish++;
        }

        void pop_back()
        {
            erase(end() - 1);
        }

        void swap(vector<T>& v)
        {
            std::swap(_start, v._start);
            std::swap(_finish, v._finish);
            std::swap(_end_of_storage, v._end_of_storage);
        }

        iterator insert(iterator pos, const T& x)
        {
            assert(pos < _finish);
            if (_finish == _end_of_storage)
            {
                size_t old_size = size();
                size_t n = capacity() == 0 ? 4 : capacity() * 2;
                reserve(n); // 扩容会导致迭代器失效
                pos = _start + old_size;
            }
            iterator end = _finish - 1;
            while (end >= pos)
            {
                *(end + 1) = *end;
                end--;
            }
            *pos = x;
            _finish++;
            return pos;
        }

        // 返回删除的位置的下一个元素的迭代器
        iterator erase(iterator pos)
        {
            assert(pos < _finish);
            for (iterator end = pos; end < _finish - 1; end++)
            {
                *(end) = *(end + 1);
            }
            _finish--;
            return pos;
        }

    private:
        iterator _start = nullptr; // 指向数据块的开始
        iterator _finish = nullptr; // 指向有效数据的尾
        iterator _end_of_storage = nullptr; // 指向存储容量的尾
    };
}

test_my_vector文件:

cpp 复制代码
#include <iostream>
#include "my_vector.h"

using namespace tr;

// 简单的测试函数
void testVector() {
    // 测试默认构造函数
    vector<int> v1;
    std::cout << "v1 size: " << v1.size() << ", capacity: " << v1.capacity() << std::endl;

    // 测试带参数的构造函数
    vector<int> v2(5, 10);
    std::cout << "v2 size: " << v2.size() << ", capacity: " << v2.capacity() << std::endl;
    for (size_t i = 0; i < v2.size(); ++i) {
        std::cout << v2[i] << " ";
    }
    std::cout << std::endl;

    // 测试区间构造函数
    int arr[] = { 1, 2, 3, 4, 5 };
    vector<int> v3(arr, arr + 5);
    std::cout << "v3 size: " << v3.size() << ", capacity: " << v3.capacity() << std::endl;
    for (size_t i = 0; i < v3.size(); ++i) {
        std::cout << v3[i] << " ";
    }
    std::cout << std::endl;

    // 测试拷贝构造函数
    vector<int> v4(v3);
    std::cout << "v4 size: " << v4.size() << ", capacity: " << v4.capacity() << std::endl;
    for (size_t i = 0; i < v4.size(); ++i) {
        std::cout << v4[i] << " ";
    }
    std::cout << std::endl;

    // 测试赋值运算符
    vector<int> v5;
    v5 = v2;
    std::cout << "v5 size: " << v5.size() << ", capacity: " << v5.capacity() << std::endl;
    for (size_t i = 0; i < v5.size(); ++i) {
        std::cout << v5[i] << " ";
    }
    std::cout << std::endl;

    // 测试push_back和pop_back
    v1.push_back(1);
    v1.push_back(2);
    v1.push_back(3);
    std::cout << "v1 size after push_back: " << v1.size() << ", capacity: " << v1.capacity() << std::endl;
    v1.pop_back();
    std::cout << "v1 size after pop_back: " << v1.size() << ", capacity: " << v1.capacity() << std::endl;

    // 测试insert
    v1.insert(v1.begin(), 0);
    std::cout << "v1 size after insert: " << v1.size() << ", capacity: " << v1.capacity() << std::endl;
    for (size_t i = 0; i < v1.size(); ++i) {
        std::cout << v1[i] << " ";
    }
    std::cout << std::endl;

    // 测试erase
    v1.erase(v1.begin());
    std::cout << "v1 size after erase: " << v1.size() << ", capacity: " << v1.capacity() << std::endl;
    for (size_t i = 0; i < v1.size(); ++i) {
        std::cout << v1[i] << " ";
    }
    std::cout << std::endl;

    // 测试resize
    v1.resize(5, 5);
    std::cout << "v1 size after resize: " << v1.size() << ", capacity: " << v1.capacity() << std::endl;
    for (size_t i = 0; i < v1.size(); ++i) {
        std::cout << v1[i] << " ";
    }
    std::cout << std::endl;
}

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

运行结果:


🌈我的分享也就到此结束啦🌈

要是我的分享也能对你的学习起到帮助,那简直是太酷啦!

若有不足,还请大家多多指正,我们一起学习交流!

📢公主,王子:点赞👍→收藏⭐→关注🔍

感谢大家的观看和支持!祝大家都能得偿所愿,天天开心!!!

相关推荐
我命由我123452 小时前
Spring Boot 自定义日志打印(日志级别、logback-spring.xml 文件、自定义日志打印解读)
java·开发语言·jvm·spring boot·spring·java-ee·logback
徐小黑ACG3 小时前
GO语言 使用protobuf
开发语言·后端·golang·protobuf
0白露4 小时前
Apifox Helper 与 Swagger3 区别
开发语言
Tanecious.5 小时前
机器视觉--python基础语法
开发语言·python
叠叠乐5 小时前
rust Send Sync 以及对象安全和对象不安全
开发语言·安全·rust
Tttian6226 小时前
Python办公自动化(3)对Excel的操作
开发语言·python·excel
Merokes7 小时前
关于Gstreamer+MPP硬件加速推流问题:视频输入video0被占用
c++·音视频·rk3588
独好紫罗兰7 小时前
洛谷题单2-P5713 【深基3.例5】洛谷团队系统-python-流程图重构
开发语言·python·算法
闪电麦坤958 小时前
C#:base 关键字
开发语言·c#
Mason Lin8 小时前
2025年3月29日(matlab -ss -lti)
开发语言·matlab