C++ STL 容器 -- 道与术 篇(一) vector

目录

概述       

vector 道法篇:

vector  两个重点:

系列特性说明

(1) capacity(容量)

(2)size(大小)

(3) 在尾部插入数据很(最快O(1)1,最慢O(n)。

(4) 内存布局:

vector 术法篇:

📋 目录

🏗️ 成员类型

🏗️ 构造函数

🗑️ 析构函数

🔄 赋值操作

🔍 元素访问

🔄 迭代器

📦 容量操作

🛠️ 修改器

🔗 非成员函数

代码示例


概述       

        在道家哲学中,“道”代表宇宙的根本规律和本质原理,无形而永恒;“术”代表具体的方法和技巧,有形而实践。

        简而言之:学会“术”,就学会了怎么使用具体的容器,知道怎么去定义一个具体的容器以及对应的基本操作;学会“道”,你就知道具体容器在内存上具体长什么样,每一个操作的具体原理,以及存在的性能瓶颈。

        在我后续对容器教程的系列的文章中,我都将从“道”与“术”两个角度加以说明。如今AI已经非常强大,任意一个AI都可以让一个小白,可以“零”成本、快速地运用“术法”内容。

        如果你迫切想要知道怎么使用vector,可以直接跳过道法篇,进入术法篇,里面记录着vector 每一招每一式的用法,不懂道法,亦可成功。

        如果你希望提高的水平,建议你先从道法篇看起,一旦你正在理解了其核心原理,术法篇你看起来将无招胜有招,成为真正的高手,就像武侠小说中内力深厚的高手,看一遍招式就能学会。

vector 道法篇:

        vector  核心原理基于动态数组的设计,它通过连续内存块存储元素,支持快速随机访问和动态调整大小。

随机访问:随机访问指的是可以直接通过索引位置立即访问容器中的任意元素,而不需要从头开始遍历。

        相信很多初学者对“随机访问”这个词不是很理解,根据道长的经验,即使是很多摸爬滚打很多年的程序员,尤其是常在应用层活动的API工程师,常有困惑。

举个例子:

(1)如果5个人并排站在一起(内存上连续的),你就可以很容易地找到第五个人(随机访问)。(2)假如这五个人战的位置并不是并排,而是藏起来的,第一个人知道第二个人在哪,第二个人又知道第三个人在哪,以此类推,第五个人的位置只有第四个人知道,那么你要找到第5个人,你就只能从第一个人逐个找(非随机访问)。

vector  两个重点:

(1)在内存布局上是连续的--> 支持随机访问。

(2)可以动态调整大小。

       

系列特性说明

基于这两个“道法”原理,就决定了他的一系列特性。

(1) capacity(容量)

        由于在内存布局上是联系的,它需要预先分配出一块空间,这块空间可以存储多少个数据,就是容量。

(2)size(大小)

        当前已经存储的数据个数。

(3) 在尾部插入数据很(最快O(1)1,最慢O(n)。

        如果当前数据还没占满容量,就能很快在结尾处插入一个数据(O(1)),如果刚好容量已经满了,那么基于“动态大小”的特性,vector可以进行扩容(不是简单地增加内存大小,做不到),重新申请一块更大的连续的内存,在把原来的数据,全部搬运到新的内存,在在新内存最后插入数据,这种情况就是O(n)。

(4) 内存布局:

Vector 对象内部包含三个指针:

  • start:指向内存块的起始地址(第一个元素)。
  • finish:指向最后一个元素的下一个位置(即有效元素结束点)。
  • end_of_storage:指向内存块的结束地址(容量边界)。 这些指针定义了一个连续的内存区间:[start, finish) 存储有效元素,[finish, end_of_storage) 是空闲空间。

也就是说,定义一个vector,大小 = 三个指针大小+capacity*每个元素的大小。

我们通过重定义new 操作符,来捕获内存分配情况:
 

#include <new>
#include <iostream>
#include <vector>
// 重载全局new操作符
void* operator new(size_t size) {
    void* ptr = std::malloc(size);
    std::cout <<"-->new size: "<<size  <<" byte"<<" src: "<<ptr<<std::endl;
    return ptr;
}
// 重载全局delete操作符
void operator delete(void* ptr) noexcept {
    if (ptr) {
        std::cout <<"-->free src: "<<ptr<<std::endl;
        std::free(ptr);
    }
}
int main(){
     std::cout<<"test t0"<<std::endl;
    std::vector<int>* vec1 = new std::vector<int>();
    std::cout<<"capacity t1: "<<vec1->capacity()<<std::endl;
    vec1->push_back(1);
    std::cout<<"capacity t2: "<<vec1->capacity()<<std::endl;
    vec1->push_back(2);
    std::cout<<"capacity t3: "<<vec1->capacity()<<std::endl;
    vec1->push_back(3);
    std::cout<<"capacity t4: "<<vec1->capacity()<<std::endl;
    vec1->push_back(4);
    std::cout<<"capacity t5: "<<vec1->capacity()<<std::endl;
}

可以直接使用在线编译工具运行https://www.onlinegdb.com/,输出如下:

test t0
-->new size: 24 byte src: 0x58220ca11d30
capacity t1: 0
-->new size: 4 byte src: 0x58220ca11d50
capacity t2: 1
-->new size: 8 byte src: 0x58220ca11d70
-->free src: 0x58220ca11d50
capacity t3: 2
-->new size: 16 byte src: 0x58220ca11d50
-->free src: 0x58220ca11d70
capacity t4: 4
capacity t5: 4

          我的编译平台64位,即每个指针占8个字节,可见初始new 出一个Vector,申请的内存为24字节(3个指针),t1 和 t2 之间加入一个数据,容量变为1,申请4字节;t2和t3之间容量变为2,重新申请8字节,释放之前申请的4字节内存,t3-t4 容量变为4,重新申请16字节,是否之前的内存。可见符合们之前所描述的内存布局。

        既然会自动扩容,那么就存在一个扩容因子,即容量不够时候,扩充的容量,这个不同编译器实现不一样,gcc的扩容因子为2,从上面的结果也可以看到,每次扩容时候,容量翻倍。 

vector 术法篇:

        前面我通过“道法”篇,详细介绍了vector的实现原理,以及在内存上的布局,至此,对于vector 你应该知道其真正长什么样了,不再被其表像所迷惑,也应该知道其性能瓶颈了。

📋 目录

  1. 成员类型

  2. 构造函数

  3. 析构函数

  4. 赋值操作

  5. 元素访问

  6. 迭代器

  7. 容量操作

  8. 修改器

  9. 非成员函数

  10. 完整示例

🏗️ 成员类型

类型定义说明
value_typeT容器中存储的元素类型
allocator_typeAllocator用于管理内存的分配器类型
size_typestd::size_t无符号整数类型,用于表示大小
difference_typestd::ptrdiff_t有符号整数类型,用于表示距离
referencevalue_type&元素的引用类型
const_referenceconst value_type&元素的常量引用类型
pointerAllocator::pointer指向元素的指针类型
const_pointerAllocator::const_pointer指向常量元素的指针类型
iterator随机访问迭代器指向元素的迭代器
const_iterator常量随机访问迭代器指向常量元素的迭代器
reverse_iteratorstd::reverse_iterator<iterator>反向迭代器
const_reverse_iteratorstd::reverse_iterator<const_iterator>常量反向迭代器

🏗️ 构造函数

构造函数说明示例
vector() noexcept默认构造函数std::vector<int> v1;
explicit vector(const Allocator& alloc) noexcept带分配器的默认构造std::vector<int> v2(alloc);
vector(size_type count, const T& value, const Allocator& alloc = Allocator())构造count个valuestd::vector<int> v3(5, 10);
explicit vector(size_type count, const Allocator& alloc = Allocator())构造count个默认值std::vector<int> v4(5);
template<class InputIt> vector(InputIt first, InputIt last, const Allocator& alloc = Allocator())范围构造std::vector<int> v5(arr, arr+3);
vector(const vector& other)拷贝构造std::vector<int> v6(v5);
vector(const vector& other, const Allocator& alloc)带分配器的拷贝构造std::vector<int> v7(v5, alloc);
vector(vector&& other) noexcept移动构造std::vector<int> v8(std::move(v6));
vector(vector&& other, const Allocator& alloc)带分配器的移动构造std::vector<int> v9(std::move(v7), alloc);
vector(std::initializer_list<T> init, const Allocator& alloc = Allocator())初始化列表构造std::vector<int> v10{1,2,3};

🗑️ 析构函数

函数说明示例
~vector()销毁所有元素并释放内存自动调用

🔄 赋值操作

函数说明示例
vector& operator=(const vector& other)拷贝赋值v1 = v2;
vector& operator=(vector&& other) noexcept移动赋值v1 = std::move(v2);
vector& operator=(std::initializer_list<T> ilist)初始化列表赋值v1 = {4,5,6};
void assign(size_type count, const T& value)赋值count个valuev1.assign(3, 100);
template<class InputIt> void assign(InputIt first, InputIt last)赋值范围元素v1.assign(arr, arr+3);
void assign(std::initializer_list<T> ilist)赋值初始化列表v1.assign({7,8,9});

🔍 元素访问

函数说明示例异常安全
reference at(size_type pos)带边界检查访问int x = v1.at(0);抛出std::out_of_range
const_reference at(size_type pos) constconst版本atconst int x = v1.at(0);同上
reference operator[](size_type pos)无边界检查访问int x = v1[0];未定义行为(越界时)
const_reference operator[](size_type pos) constconst版本operator[]const int x = v1[0];同上
reference front()访问首元素int x = v1.front();空vector未定义行为
const_reference front() constconst版本frontconst int x = v1.front();同上
reference back()访问尾元素int x = v1.back();空vector未定义行为
const_reference back() constconst版本backconst int x = v1.back();同上
T* data() noexcept访问底层数组int* p = v1.data();
const T* data() const noexceptconst版本dataconst int* p = v1.data();

🔄 迭代器

函数说明示例
iterator begin() noexcept指向首元素的迭代器auto it = v1.begin();
const_iterator begin() const noexceptconst版本beginauto it = v1.begin();
const_iterator cbegin() const noexcept指向首元素的const迭代器auto it = v1.cbegin();
iterator end() noexcept指向尾后位置的迭代器auto it = v1.end();
const_iterator end() const noexceptconst版本endauto it = v1.end();
const_iterator cend() const noexcept指向尾后位置的const迭代器auto it = v1.cend();
reverse_iterator rbegin() noexcept指向反向首元素的迭代器auto it = v1.rbegin();
const_reverse_iterator rbegin() const noexceptconst版本rbeginauto it = v1.rbegin();
const_reverse_iterator crbegin() const noexcept指向反向首元素的const迭代器auto it = v1.crbegin();
reverse_iterator rend() noexcept指向反向尾后位置的迭代器auto it = v1.rend();
const_reverse_iterator rend() const noexceptconst版本rendauto it = v1.rend();
const_reverse_iterator crend() const noexcept指向反向尾后位置的const迭代器auto it = v1.crend();

📦 容量操作

函数说明示例时间复杂度
bool empty() const noexcept检查是否为空if(v1.empty())O(1)
size_type size() const noexcept返回元素数量size_t s = v1.size();O(1)
size_type max_size() const noexcept返回最大可能大小size_t m = v1.max_size();O(1)
void reserve(size_type new_cap)增加容量v1.reserve(100);最多O(n)
size_type capacity() const noexcept返回当前容量size_t c = v1.capacity();O(1)
void shrink_to_fit()减少容量适应大小v1.shrink_to_fit();最多O(n)

🛠️ 修改器

函数说明示例时间复杂度
void clear() noexcept清空所有元素v1.clear();O(n)
iterator insert(const_iterator pos, const T& value)插入元素v1.insert(v1.begin(),10);最多O(n)
iterator insert(const_iterator pos, T&& value)移动插入元素v1.insert(v1.begin(),std::move(x));最多O(n)
iterator insert(const_iterator pos, size_type count, const T& value)插入count个元素v1.insert(v1.begin(),3,10);最多O(n)
template<class InputIt> iterator insert(const_iterator pos, InputIt first, InputIt last)插入范围v1.insert(v1.begin(),arr,arr+3);最多O(n)
iterator insert(const_iterator pos, std::initializer_list<T> ilist)插入初始化列表v1.insert(v1.begin(),{1,2,3});最多O(n)
template<class... Args> iterator emplace(const_iterator pos, Args&&... args)原位构造元素v1.emplace(v1.begin(),10);最多O(n)
template<class... Args> void emplace_back(Args&&... args)在末尾原位构造v1.emplace_back(10);平摊O(1)
void push_back(const T& value)在末尾添加元素v1.push_back(10);平摊O(1)
void push_back(T&& value)在末尾移动添加v1.push_back(std::move(x));平摊O(1)
void pop_back()移除末尾元素v1.pop_back();O(1)
iterator erase(const_iterator pos)擦除单个元素v1.erase(v1.begin());最多O(n)
iterator erase(const_iterator first, const_iterator last)擦除范围v1.erase(v1.begin(),v1.begin()+2);最多O(n)
void resize(size_type count)改变大小(默认构造)v1.resize(10);最多O(n)
void resize(size_type count, const T& value)改变大小(指定值)v1.resize(10,5);最多O(n)
void swap(vector& other) noexcept交换内容v1.swap(v2);O(1)

🔗 非成员函数

函数说明示例
bool operator==(const vector& lhs, const vector& rhs)相等比较if(v1 == v2)
bool operator!=(const vector& lhs, const vector& rhs)不等比较if(v1 != v2)
bool operator<(const vector& lhs, const vector& rhs)小于比较if(v1 < v2)
bool operator<=(const vector& lhs, const vector& rhs)小于等于比较if(v1 <= v2)
bool operator>(const vector& lhs, const vector& rhs)大于比较if(v1 > v2)
bool operator>=(const vector& lhs, const vector& rhs)大于等于比较if(v1 >= v2)
void swap(vector& lhs, vector& rhs) noexcept特化swap算法std::swap(v1, v2);

代码示例

#include <iostream>
#include <vector>
#include <algorithm>

int main() {
    // 1. 创建和初始化
    std::vector<int> vec = {1, 2, 3, 4, 5};
    
    // 2. 元素访问
    std::cout << "第一个元素: " << vec.front() << std::endl;
    std::cout << "最后一个元素: " << vec.back() << std::endl;
    std::cout << "第三个元素: " << vec[2] << std::endl;
    std::cout << "带边界检查访问: " << vec.at(1) << std::endl;
    
    // 3. 容量操作
    std::cout << "大小: " << vec.size() << std::endl;
    std::cout << "容量: " << vec.capacity() << std::endl;
    std::cout << "是否为空: " << (vec.empty() ? "是" : "否") << std::endl;
    
    // 4. 修改操作
    vec.push_back(6);                    // 添加元素
    vec.pop_back();                      // 移除最后一个元素
    vec.insert(vec.begin() + 2, 99);     // 在位置2插入
    vec.erase(vec.begin() + 1);          // 移除位置1的元素
    
    // 5. C++11 新特性
    vec.emplace_back(100);               // 原位构造
    vec.shrink_to_fit();                 // 缩减容量
    
    // 6. 迭代器使用
    std::cout << "正向遍历: ";
    for (auto it = vec.begin(); it != vec.end(); ++it) {
        std::cout << *it << " ";
    }
    std::cout << std::endl;
    
    std::cout << "反向遍历: ";
    for (auto it = vec.rbegin(); it != vec.rend(); ++it) {
        std::cout << *it << " ";
    }
    std::cout << std::endl;
    
    // 7. 范围for循环 (C++11)
    std::cout << "范围for循环: ";
    for (const auto& element : vec) {
        std::cout << element << " ";
    }
    std::cout << std::endl;
    
    // 8. 算法使用
    auto found = std::find(vec.begin(), vec.end(), 99);
    if (found != vec.end()) {
        std::cout << "找到元素99在位置: " << std::distance(vec.begin(), found) << std::endl;
    }
    
    return 0;
}
第一个元素: 1
最后一个元素: 5
第三个元素: 3
带边界检查访问: 2
大小: 5
容量: 5
是否为空: 否
正向遍历: 1 99 3 4 5 100 
反向遍历: 100 5 4 3 99 1 
范围for循环: 1 99 3 4 5 100 
找到元素99在位置: 1

结语,关于vector 的封装,我在OmniCpp 项目中有封装,感兴趣的可以关注下。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值