C++ | vector 详解

文章目录

  • [C++中的 std::vector 详解](#C++中的 std::vector 详解)
    • [一、vector 的介绍及使用](#一、vector 的介绍及使用)
      • [1. vector 的介绍](#1. vector 的介绍)
      • [2. vector 的使用](#2. vector 的使用)
        • [2.1 vector 的定义](#2.1 vector 的定义)
        • [2.2 vector iterator 的使用](#2.2 vector iterator 的使用)
        • [2.3 vector 空间增长问题](#2.3 vector 空间增长问题)
        • [2.4 vector 增删查改](#2.4 vector 增删查改)
        • [2.5 vector 迭代器失效问题](#2.5 vector 迭代器失效问题)
        • [2.6 vector 在 OJ 中的使用](#2.6 vector 在 OJ 中的使用)
    • [二、vector 深度剖析及模拟实现](#二、vector 深度剖析及模拟实现)
      • [1. std::vector 的核心框架接口的模拟实现 bit::vector](#1. std::vector 的核心框架接口的模拟实现 bit::vector)
      • [2. 使用 memcpy 拷贝问题](#2. 使用 memcpy 拷贝问题)
      • [3. 动态二维数组理解](#3. 动态二维数组理解)

C++中的 std::vector 详解

在 C++中,std::vector是一个非常强大且常用的容器。它提供了动态数组的功能,让我们在处理数据集合时更加方便高效。

一、vector 的介绍及使用

1. vector 的介绍

std::vector是 C++标准模板库(STL)中的一个序列容器,它实现了动态数组的功能。使用 STL 有三个境界:能用、明理、能扩展。学习vector也可以按照这个方法进行。

2. vector 的使用

2.1 vector 的定义

vector的构造方式有多种:

  • 无参构造:vector()
  • 构造并初始化nvalvector(size_type n, const value_type& val = value_type())
  • 拷贝构造:vector (const vector& x)
  • 使用迭代器进行初始化构造:vector (InputIterator first, InputIterator last)

以下是构造代码演示:

cpp 复制代码
#include <iostream>
#include <vector>

int main() {
    // 无参构造
    std::vector<int> v1;

    // 构造并初始化 n 个 val
    std::vector<int> v2(5, 10); 

    // 拷贝构造
    std://vector<int> v3(v2); 

    // 使用迭代器进行初始化构造
    int arr[] = {1, 2, 3};
    std::vector<int> v4(arr, arr + sizeof(arr) / sizeof(int));

    return 0;
}
2.2 vector iterator 的使用
  • begin()end():分别获取第一个数据位置的iterator/const_iterator和获取最后一个数据的下一个位置的iterator/const_iterator
  • rbegin()rend():分别获取最后一个数据位置的reverse_iterator和获取第一个数据前一个位置的reverse_iterator

同时,vector还提供了一些关于容量和空间的接口:

  • size():获取数据个数。
  • capacity():获取容量大小。
  • empty():判断是否为空。
  • resize():改变vectorsize
  • reserve():改变vectorcapacity

以下是代码演示:

cpp 复制代码
#include <iostream>
#include <vector>

int main() {
    std::vector<int> v;
    std::cout << "Size: " << v.size() << ", Capacity: " << v.capacity() << std::endl;
    v.reserve(10);
    std::cout << "After reserve(10), Size: " << v.size() << ", Capacity: " << v.capacity() << std::endl;
    for (int i = 0; i < 5; ++i) {
        v.push_back(i);
    }
    std::cout << "After push_back 5 elements, Size: " << v.size() << ", Capacity: " << v.capacity() << std::endl;
    v.resize(8);
    std::cout << "After resize(8), Size: " << v.size() << ", Capacity: " << v.capacity() << std::endl;

    return 0;
}
2.3 vector 空间增长问题

在不同的编译器下,vector的扩容机制可能不同。在 VS 下,容量通常是按 1.5 倍增长;在 G++下,通常是按 2 倍增长。但具体的增长倍数可能会根据实际需求定义,不能固化认为都是特定的倍数。

例如:

cpp 复制代码
void TestVectorExpand() {
    size_t sz;
    std::vector<int> v;
    sz = v.capacity();
    std::cout << "making v grow:\n";
    for (int i = 0; i < 100; ++i) {
        v.push_back(i);
        if (sz!= v.capacity()) {
            sz = v.capacity();
            std::cout << "capacity changed: " << sz << '\n';
        }
    }
}

如果确定知道需要用多少空间,可以提前使用reserve函数开辟空间,以缓解vector增容的代价缺陷问题。而resize函数在开空间的同时还会进行初始化,影响size

2.4 vector 增删查改
  • push_back():尾插。
  • pop_back():尾删。
  • find:查找(注意这是算法模块实现,不是vector的成员接口)。
  • insert:在position之前插入val
  • erase:删除position位置的数据。
  • swap:交换两个vector的数据空间。
  • operator[]:像数组一样访问。

以下是代码演示:

cpp 复制代码
#include <iostream>
#include <vector>

int main() {
    std::vector<int> v{1, 2, 3, 4, 5};
    v.push_back(6);
    std::cout << "After push_back: ";
    for (auto e : v) {
        std::cout << e << " ";
    }
    std::cout << std::endl;

    v.pop_back();
    std::cout << "After pop_back: ";
    for (auto e : v) {
        std::cout << e << " ";
    }
    std::cout << std::endl;

    // 使用 insert 在特定位置插入元素
    v.insert(v.begin() + 2, 7);
    std::cout << "After insert: ";
    for (auto e : v) {
        std::cout << e << " ";
    }
    std::cout << std::endl;

    // 使用 erase 删除特定位置元素
    v.erase(v.begin() + 3);
    std::cout << "After erase: ";
    for (auto e : v) {
        std::cout << e << " ";
    }
    std::cout << std::endl;

    // 使用 swap 交换两个 vector 的数据空间
    std::vector<int> v2{8, 9, 10};
    v.swap(v2);
    std::cout << "After swap with v2: ";
    for (auto e : v) {
        std::cout << e << " ";
    }
    std::cout << std::endl;

    // 使用 operator[] 像数组一样访问元素
    std::cout << "Element at index 2: " << v[2] << std::endl;

    return 0;
}
2.5 vector 迭代器失效问题

对于vector,可能会导致其迭代器失效的操作有:

  1. 会引起其底层空间改变的操作,都有可能使迭代器失效,比如:resizereserveinsertassignpush_back等。
  2. 指定位置元素的删除操作erase

例如:

cpp 复制代码
#include <iostream>
#include <vector>

int main() {
    std::vector<int> v{1, 2, 3, 4, 5, 6};
    auto it = v.begin();
    // 将有效元素个数增加到 100 个,多出的位置使用 8 填充,操作期间底层会扩容
    // v.resize(100, 8);
    // reserve 的作用就是改变扩容大小但不改变有效元素个数,操作期间可能会引起底层容量改变
    // v.reserve(100);
    // 插入元素期间,可能会引起扩容,而导致原空间被释放
    // v.insert(v.begin(), 0);
    // v.push_back(8);
    // 给 vector 重新赋值,可能会引起底层容量改变
    v.assign(100, 8);

    // 以上操作可能导致迭代器失效,继续使用可能会使程序崩溃
    // 解决方式:在这些操作完成之后,如果想要继续通过迭代器操作 vector 中的元素,只需给 it 重新赋值即可。

    while (it!= v.end()) {
        std::cout << *it << " ";
        ++it;
    }
    std::cout << std::endl;

    return 0;
}

当使用erase删除vector中元素时,如果删除的是最后一个元素,可能会导致迭代器失效。在 VS 下,认为该位置迭代器失效了。

以下是删除vector中所有偶数的正确代码:

cpp 复制代码
int main() {
    std::vector<int> v{1, 2, 3, 4};
    auto it = v.begin();
    while (it!= v.end()) {
        if (*it % 2 == 0) {
            it = v.erase(it);
        } else {
            ++it;
        }
    }
    return 0;
}

迭代器失效解决办法:在使用前,对迭代器重新赋值即可。

2.6 vector 在 OJ 中的使用

例如:

  • 只出现一次的数字 i。
  • 杨辉三角 OJ。
cpp 复制代码
class Solution {
public:
    int singleNumber(std::vector<int>& nums) {
        int value = 0;
        for (auto e : nums) {
            value ^= e;
        }
        return value;
    }
};

class Solution {
public:
    std::vector<std::vector<int>> generate(int numRows) {
        std::vector<std::vector<int>> vv(numRows);
        for (int i = 0; i < numRows; ++i) {
            vv[i].resize(i + 1, 1);
        }
        for (int i = 2; i < numRows; ++i) {
            for (int j = 1; j < i; ++j) {
                vv[i][j] = vv[i - 1][j] + vv[i - 1][j - 1];
            }
        }
        return vv;
    }
};

二、vector 深度剖析及模拟实现

1. std::vector 的核心框架接口的模拟实现 bit::vector

可以尝试模拟实现vector的一些核心接口,以加深对其理解。

2. 使用 memcpy 拷贝问题

如果在模拟实现的vector中的reserve接口中使用memcpy进行拷贝,当拷贝的对象中涉及到资源管理时,会出现问题,因为memcpy是浅拷贝。

例如:

cpp 复制代码
int main() {
    bite::vector<bite::string> v;
    v.push_back("1111");
    v.push_back("2222");
    v.push_back("3333");
    return 0;
}

结论:如果对象中涉及到资源管理时,千万不能使用memcpy进行对象之间的拷贝,否则可能会引起内存泄漏甚至程序崩溃。

3. 动态二维数组理解

可以使用标准库中的vector构建动态二维数组,例如以杨辉三角的前n行为例:

cpp 复制代码
void test2vector(size_t n) {
    bit::vector<bit::vector<int>> vv(n);
    for (size_t i = 0; i < n; ++i) {
        vv[i].resize(i + 1, 1);
    }
    for (int i = 2; i < n; ++i) {
        for (int j = 1; j < i; ++j) {
            vv[i][j] = vv[i - 1][j] + vv[i - 1][j - 1];
        }
    }
}

总之,std::vector是 C++中非常重要的容器,掌握其各种特性和使用方法对于高效编程至关重要。通过深入理解其原理并进行模拟实现,可以更好地运用它来解决实际问题。

相关推荐
此生只爱蛋4 分钟前
【手撕排序2】快速排序
c语言·c++·算法·排序算法
blammmp12 分钟前
Java:数据结构-枚举
java·开发语言·数据结构
何曾参静谧24 分钟前
「C/C++」C/C++ 指针篇 之 指针运算
c语言·开发语言·c++
暗黑起源喵30 分钟前
设计模式-工厂设计模式
java·开发语言·设计模式
WaaTong35 分钟前
Java反射
java·开发语言·反射
Troc_wangpeng36 分钟前
R language 关于二维平面直角坐标系的制作
开发语言·机器学习
努力的家伙是不讨厌的38 分钟前
解析json导出csv或者直接入库
开发语言·python·json
Envyᥫᩣ1 小时前
C#语言:从入门到精通
开发语言·c#
童先生1 小时前
Go 项目中实现类似 Java Shiro 的权限控制中间件?
开发语言·go
lulu_gh_yu1 小时前
数据结构之排序补充
c语言·开发语言·数据结构·c++·学习·算法·排序算法