深入解析vector:一个完整的C++动态数组实现

概述

这是一个完整的模板类 yyq::vector 的实现,模仿 C++ 标准库中的 std::vector。作为STL最重要的容器之一,vector的动态数组实现展示了C++模板编程、内存管理和迭代器设计的核心技术。本文将全面分析该实现,不遗漏任何细节。

目录

概述

一、整体架构与设计

[1.1 命名空间与头文件保护](#1.1 命名空间与头文件保护)

[1.2 类成员变量设计](#1.2 类成员变量设计)

二、迭代器与类型定义

[2.1 迭代器类型定义](#2.1 迭代器类型定义)

[2.2 迭代器访问函数](#2.2 迭代器访问函数)

三、构造函数与析构函数

[3.1 默认构造函数](#3.1 默认构造函数)

[3.2 拷贝构造函数](#3.2 拷贝构造函数)

[3.3 指定大小和初始值的构造函数](#3.3 指定大小和初始值的构造函数)

[3.4 析构函数](#3.4 析构函数)

四、赋值操作符与交换函数

[4.1 交换函数](#4.1 交换函数)

[4.2 赋值操作符(现代实现)](#4.2 赋值操作符(现代实现))

[4.3 被注释的传统实现](#4.3 被注释的传统实现)

五、容量管理

[5.1 size和capacity](#5.1 size和capacity)

[5.2 empty函数](#5.2 empty函数)

[5.3 reserve函数(重要)](#5.3 reserve函数(重要))

[5.4 resize函数](#5.4 resize函数)

六、元素访问

[6.1 operator[]重载](#6.1 operator[]重载)

七、修改操作

[7.1 push_back函数](#7.1 push_back函数)

[7.2 pop_back函数](#7.2 pop_back函数)

[7.3 insert函数](#7.3 insert函数)

[7.4 erase函数](#7.4 erase函数)

[7.5 被注释的clear函数](#7.5 被注释的clear函数)

八、辅助函数

[8.1 print_vector函数](#8.1 print_vector函数)

[8.2 print_container函数(通用版本)](#8.2 print_container函数(通用版本))

九、代码中的特殊字符问题

十、设计亮点与改进建议

[10.1 设计亮点](#10.1 设计亮点)

[10.2 存在的问题与改进建议](#10.2 存在的问题与改进建议)

[10.3 改进的reserve实现示例](#10.3 改进的reserve实现示例)

十一、完整性与学习价值

十二、总结


一、整体架构与设计

1.1 命名空间与头文件保护

cpp

复制代码
namespace yyq
{
    template<class T>
    class vector { ... };
}
  • 使用自定义命名空间 yyq 避免与标准库冲突

  • 模板类设计,支持任意类型的元素

  • #pragma once 防止头文件重复包含

1.2 类成员变量设计

cpp

复制代码
private:
    iterator _start = nullptr;          // 指向数组起始位置
    iterator _finish = nullptr;         // 指向最后一个元素的下一个位置
    iterator _end_of_storage = nullptr; // 指向分配内存的末尾

采用三指针设计,这是vector的经典实现方式:

  • _start:数组起始位置

  • _finish:有效元素末尾的下一个位置(size = _finish - _start

  • _end_of_storage:分配内存的末尾(capacity = _end_of_storage - _start

二、迭代器与类型定义

2.1 迭代器类型定义

cpp

复制代码
typedef T* iterator;
typedef const T* const_iterator;
  • 简单地将指针作为迭代器,因为vector在内存中是连续的

  • 提供const和非const版本,支持常对象

2.2 迭代器访问函数

cpp

复制代码
iterator begin() { return _start; }
iterator end() { return _finish; }
const_iterator begin()const { return _start; }
const_iterator end()const { return _finish; }
  • 提供完整的迭代器支持

  • 使vector可以与STL算法配合使用

  • 支持基于范围的for循环

三、构造函数与析构函数

3.1 默认构造函数

cpp

复制代码
// C++11 默认生成默认构造函数
vector() = default;
  • 使用C++11的= default语法

  • 成员变量已初始化为nullptr,确保安全

3.2 拷贝构造函数

cpp

复制代码
vector(const vector<T>& v)
{
    reserve(v.size());
    for (auto e : v)
    {
        push_back(e);
    }
}
  • 先分配与源vector相同大小的内存

  • 使用范围for循环遍历并复制每个元素

  • 注意:这里使用了push_back,会调用元素的赋值操作

3.3 指定大小和初始值的构造函数

cpp

复制代码
vector(size_t n, const T& val = T())
{
    reserve(n);
    for (size_t i = 0; i < n; i++)
    {
        push_back(val);
    }
}

vector(int n, const T& val = T())
{
    reserve(n);
    for (int i = 0; i < n; i++)
    {
        push_back(val);
    }
}

重要细节:

  1. 两个重载版本:size_tint参数

  2. 这是为了解决模板推导时的歧义问题

  3. 使用T()作为默认初始值(调用类型的默认构造函数)

  4. 例如:vector<int> v(10)创建包含10个0的vector

  5. 例如:vector<int> v(10, 5)创建包含10个5的vector

3.4 析构函数

cpp

复制代码
~vector()
{
    if (_start)
    {
        delete[] _start;
        _start = _finish = _end_of_storage;
    }
}
  • 检查_start是否为nullptr再删除

  • 删除后将所有指针置为nullptr,避免悬空指针

  • 注意:对于自定义类型,会调用每个元素的析构函数

四、赋值操作符与交换函数

4.1 交换函数

cpp

复制代码
void swap(vector<T>& v)
{
    std::swap(_start, v._start);
    std::swap(_finish, v._finish);
    std::swap(_end_of_storage, v._end_of_storage);
}
  • 使用std::swap交换三个指针

  • 效率高,仅交换指针,不复制元素

  • 是拷贝交换技法的核心

4.2 赋值操作符(现代实现)

cpp

复制代码
vector<T>& operator=(vector<T> temp)
{
    swap(temp);
    return *this;
}

这是经典的"拷贝并交换"技法:

  1. 参数temp是值传递,会自动调用拷贝构造函数

  2. 交换当前对象和temp的资源

  3. temp离开作用域时自动析构,释放原资源

  4. 异常安全且自动处理自赋值

4.3 被注释的传统实现

cpp

复制代码
// 传统赋值操作符实现
//vector<T>& operator=(const vector<T>& v)
//{
//    if (this != &v)  // 检查自赋值
//    {
//        clear();     // 清空当前内容
//        reserve(v.size());  // 分配足够空间
//        for (auto e : v)    // 复制元素
//        {
//            push_back(e);
//        }
//    }
//    return *this; 
//}
  • 显示检查自赋值

  • 先清空再重新分配和复制

  • 现代实现更简洁优雅

五、容量管理

5.1 size和capacity

cpp

复制代码
size_t size()const
{
    return _finish - _start;  // 有效元素个数
}

size_t capacity()const
{
    return _end_of_storage - _start;  // 分配的内存容量
}
  • 使用指针算术计算大小和容量

  • 都是const成员函数

5.2 empty函数

cpp

复制代码
bool empty()
{
    return _start == _finish;  // 起始等于结束表示空
}
  • 检查vector是否为空

  • 注意:这里应该是const成员函数

5.3 reserve函数(重要)

cpp

复制代码
void reserve(size_t n)
{
    if (n > capacity())
    {
        size_t old_size = size();
        T* temp = new T[n];
        
        // 重要:不能使用memcpy,因为T可能是自定义类型
        for (size_t i = 0; i < size(); i++)
        {
            temp[i] = _start[i];  // 使用赋值操作符
        }
        
        delete[] _start;
        _start = temp;
        _finish = _start + old_size;
        _end_of_storage = _start + n;
    }
}

关键细节:

  1. 为什么不用memcpy?

    cpp

    复制代码
    // 被注释的错误实现:
    // memcpy(temp, _start, size() * sizeof(T));
    • memcpy是浅拷贝,对于包含指针或资源的自定义类型会出问题

    • 使用循环赋值可以调用元素的赋值操作符,正确处理深拷贝

  2. 扩容策略

    • 只扩容不缩容

    • 先分配新内存,再复制元素,最后释放旧内存

    • 保存旧的大小,因为size()在释放后可能失效

5.4 resize函数

cpp

复制代码
void resize(size_t n, T val = T())
{
    if (n < size())  // 缩小
    {
        _finish = _start + n;  // 简单截断
    }
    else  // 扩大
    {
        reserve(n);  // 确保有足够容量
        while (_finish < _start + n)
        {
            *_finish = val;  // 用val填充新元素
            ++_finish;
        }
    }
}
  • 如果新大小小于当前大小,简单截断

  • 如果新大小大于当前大小,用指定值填充新位置

  • 使用T()作为默认填充值

六、元素访问

6.1 operator[]重载

cpp

复制代码
T& operator[](size_t i)
{
    assert(i < size());
    return _start[i];  // 返回引用,可修改
}

const T& operator[](size_t i)const
{
    assert(i < size());
    return _start[i];  // 返回const引用,只读
}
  • 提供非常量和常量两个版本

  • 使用assert进行边界检查

  • 支持随机访问,时间复杂度O(1)

七、修改操作

7.1 push_back函数

cpp

复制代码
void push_back(const T& x)
{
    if (_finish == _end_of_storage)  // 需要扩容
    {
        reserve(capacity() == 0 ? 4 : 2 * capacity());  // 2倍扩容
    }
    *_finish = x;  // 在末尾添加元素
    ++_finish;     // 更新_finish指针
}

扩容策略:

  • 初始容量为0时,扩容到4

  • 后续按2倍扩容(vector的经典策略)

  • 使用引用参数避免不必要的复制

7.2 pop_back函数

cpp

复制代码
void pop_back()
{
    assert(!empty());  // 确保不为空
    --_finish;         // 简单递减_finish
}
  • 仅减少大小,不释放内存

  • 元素会被保留在内存中但不再可访问

  • 对于自定义类型,需要显式调用析构函数(当前实现有缺陷)

7.3 insert函数

cpp

复制代码
iterator insert(iterator pos, const T& x)
{
    // 检查是否需要扩容
    if (_finish == _end_of_storage)
    {
        size_t len = pos - _start;  // 保存偏移量
        reserve(capacity() == 0 ? 4 : 2 * capacity());
        pos = _start + len;  // 重新计算pos(因为_start可能改变)
    }
    
    // 向后移动元素
    iterator end = _finish - 1;
    while (end >= pos)
    {
        *(end + 1) = *end;
        --end;
    }
    
    *pos = x;      // 插入新元素
    ++_finish;     // 更新大小
    return pos;    // 返回插入位置的迭代器
}

重要细节:

  1. 迭代器失效问题 :扩容后,原来的pos会失效,需要重新计算

  2. 保存偏移量len = pos - _start,扩容后pos = _start + len

  3. 从后向前移动:避免覆盖未处理的元素

  4. 返回值:返回指向新插入元素的迭代器(符合STL规范)

7.4 erase函数

cpp

复制代码
iterator erase(iterator pos)
{
    assert(pos >= _start);   // 确保pos有效
    assert(pos < _finish);
    
    iterator it = pos + 1;
    while (it != end())  // 向前移动元素
    {
        *(it - 1) = *it;
        ++it;
    }
    --_finish;          // 更新大小
    return pos;         // 返回被删除元素的下一个位置
}
  • 检查pos的合法性

  • 从前向后移动元素覆盖要删除的位置

  • 返回被删除元素的下一个位置(符合STL规范)

  • 注意:对于自定义类型,应该显式调用析构函数

7.5 被注释的clear函数

cpp

复制代码
//void clear()
//{
//    _finish = _start;  // 简单重置_finish
//}
  • 简单实现:仅重置_finish指针

  • 问题:对于自定义类型,没有调用析构函数

  • 更好的实现应该调用每个元素的析构函数

八、辅助函数

cpp

复制代码
template<class T>
void print_vector(const vector<T>& v)
{
    // 方法1:使用迭代器
    typename vector<T>::const_iterator it = v.begin();
    while (it != v.end())
    {
        cout << *it << " ";
        ++it;
    }
    cout << endl;
    
    // 方法2:使用范围for循环
    for (auto e : v)
    {
        cout << e << " ";
    }
    cout << endl;
}

关键细节:

  1. typename关键字:告诉编译器vector<T>::const_iterator是一个类型

  2. 两种遍历方式:传统迭代器和范围for循环

  3. 模板函数,可以打印任意类型的vector

cpp

复制代码
template<class Container>
void print_container(const Container& v)
{
    // 与方法1相同,但更通用
    typename Container::const_iterator it = v.begin();
    while (it != v.end())
    {
        cout << *it << " ";
        ++it;
    }
    cout << endl;
    
    for (auto e : v)
    {
        cout << e << " ";
    }
    cout << endl;
}
  • 更通用的版本,可以打印任何支持迭代器的容器

  • 使用了模板模板参数

  • 展示了泛型编程的思想

十、设计亮点与改进建议

10.1 设计亮点

  1. 现代C++风格 :使用= default、拷贝交换技法

  2. 完整的迭代器支持:与STL完全兼容

  3. 异常安全:赋值操作符的现代实现是异常安全的

  4. 模板设计:支持任意类型

  5. 合理的扩容策略:2倍扩容,平衡性能和内存使用

10.2 存在的问题与改进建议

  1. 析构问题

    • pop_backeraseclear没有调用元素的析构函数

    • 对于自定义类型可能造成资源泄漏

  2. 异常安全

    • reserve中如果元素赋值抛出异常,会内存泄漏

    • 应该先复制到临时内存,成功后再交换

  3. 缺少的功能

    cpp

    复制代码
    // 应该添加以下功能:
    T& front();  // 访问第一个元素
    T& back();   // 访问最后一个元素
    void shrink_to_fit();  // 缩减内存
  4. 性能优化

    • 可以使用移动语义(C++11)

    • push_back可以添加右值引用版本

    • 可以添加emplace_back直接构造

  5. const正确性

    cpp

    复制代码
    bool empty() const  // 应该是const成员函数
  6. 迭代器失效

    • 需要更完善的迭代器失效处理

    • 文档说明哪些操作会使迭代器失效

10.3 改进的reserve实现示例

cpp

复制代码
void reserve(size_t n)
{
    if (n > capacity())
    {
        T* temp = new T[n];
        size_t i = 0;
        try
        {
            for (; i < size(); i++)
            {
                temp[i] = _start[i];  // 可能抛出异常
            }
        }
        catch (...)
        {
            // 异常时清理已构造的元素
            for (size_t j = 0; j < i; j++)
            {
                temp[j].~T();  // 显式调用析构
            }
            delete[] temp;
            throw;  // 重新抛出异常
        }
        
        // 成功则替换
        for (size_t j = 0; j < size(); j++)
        {
            _start[j].~T();  // 析构旧元素
        }
        delete[] _start;
        _finish = temp + i;
        _start = temp;
        _end_of_storage = _start + n;
    }
}

十一、完整性与学习价值

这个yyq::vector实现涵盖了动态数组的核心功能:

  • ✅ 模板类设计

  • ✅ 动态内存管理

  • ✅ 迭代器支持

  • ✅ 基本操作(增删改查)

  • ✅ 容量管理

  • ✅ 拷贝控制

  • ✅ 运算符重载

十二、总结

这个vector实现展示了C++模板编程和容器设计的多个重要方面:

  1. 模板元编程:通用的容器设计

  2. 内存管理:动态数组的分配与释放

  3. 迭代器设计:连续容器的迭代器实现

  4. 异常安全:拷贝交换技法的应用

  5. 性能考虑:2倍扩容策略

虽然与标准库的std::vector相比还有差距(如缺少移动语义、异常安全不够完善),但它作为一个教学实现,完美展示了vector的核心原理,是学习C++容器设计和模板编程的优秀范例。

通过分析这个实现,我们可以深入理解:

  • vector的内部存储机制

  • 动态扩容的原理和代价

  • 迭代器失效的原因和应对

  • 模板类设计的基本模式

  • 现代C++编程的最佳实践

相关推荐
Codeking__2 小时前
git常用命令小总结
git
tankeven2 小时前
HJ129 小红的双生数
c++·算法
万能的小裴同学2 小时前
C++ 简易的FBX查看工具
开发语言·c++·算法
菜菜小狗的学习笔记2 小时前
剑指Offer算法题(二)栈、队列、堆
java·开发语言
破无差2 小时前
Gitee导入的Github仓库同步更新
gitee·github
研究点啥好呢2 小时前
3月15日GitHub热门项目推荐 | 从本地部署到生产实践
人工智能·python·github·cursor·vibe coding
山川行2 小时前
Git学习笔记:Git进阶操作
笔记·git·vscode·学习·编辑器·visual studio code
李宥小哥2 小时前
SQLite05-常用函数
java·开发语言·jvm
皮卡狮2 小时前
C++面向对象编程的三大核心特性之一:多态
开发语言·c++