大家好,这里是彩妙呀~

    在C++的标准模板库(STL)中,vector 绝对是最常用、也最值得初学者优先掌握的容器之一。你可以把它想象成一个“会自己变长的数组”。普通的数组一旦创建,大小就固定了,想添加新元素很麻烦。vector 不同,它能在运行时根据需要自动调整大小帮你管理内存,省去了很多手动操作的麻烦。

    作为序列式容器的代表,vector 是学习其他STL容器(如 list, deque)的基础。掌握好 vector 的常用操作(如初始化、遍历、增删改查)和特性(如容量 capacity 与大小 size 的区别),将为你的C++编程打下坚实的基石。接下来,彩妙将带着大家深入了解并掌握这STL中的利器吧~

这里直达详解-手搓vector-,感兴趣的小伙伴可以点击查看呀~

目录

vector的介绍-参考文档

vector的构建与析构

vector中的构建函数

默认构造函数(empty container constructor)

填充构造函数(fill constructor)

范围构造函数(range constructor)

拷贝构造函数(copy constructor)

析构函数参考文档

运算符重载:vector::operator=

vector的遍历

使用 operator [ ] 运算符重载来使得vector如同int*一样访问元素

使用STL经典遍历方式:迭代器(iterator)遍历

范围for循环(C++11及以上,最推荐的方式)

vector中容器的接口

vector::size 以及 vector::max_size

查看与改变容量的接口:vector::resize ,vector::reserve与 vector::capacity

查“空”与瘦身:vector::empty与vector::shrink_to_fit

vector中修改类型(modifiers)的接口

清空,再重新装填:vector::assign

尾插与尾删元素:vector::push_back与vector::pop_back

随意的插入与删除:vector::insert与vector::erase

交换两个vector:vector::swap

清空vector:vector::clear

vector中的运算符重载:

零星知识点



vector的介绍-参考文档

C++的vector是标准模板库(STL)中的动态数组容器,位于<vector>头文件中。它能够自动管理内存,支持动态扩容,无需手动处理数组大小。vector存储连续的元素,允许通过下标随机访问,效率与普通数组相近。也可以通过百度百科来进行一个了解

在底层逻辑上,vector是一个顺序表。相比string类来说,少了许多的冗余接口,相对精简一些。

本篇博客会挑一些常用的接口来讲解,如果有需求或者要扩展阅读,可以自己查阅一下参考文档。

由于STL容器中,有多重相似性(如迭代器,相似的遍历之类的),所以我在将第一个string类的链接时,着重吧这些点详细讲了一遍,这里我就不多赘述了。

vector的构建与析构

vector中的构建函数

我们发现,一共有四个接口。但是我们可以阅读文档,发现:C++11中优化了这4个接口,新增了两个接口;C++14中没有增加额外接口,而是对已有接口进行改良。

本篇博客将带着大家学习C++98中的接口,C++11中的优化后续博客会集中讲解。

默认构造函数(empty container constructor)

#include <vector>
#include <iostream>

int main() {
    std::vector<int> vec;  // 默认构造一个空的 vector

    std::cout << "Size: " << vec.size() << std::endl;  // 输出 Size: 0
    std::cout << "Capacity: " << vec.capacity() << std::endl;  // 输出 Capacity: 0

    return 0;
}
  • 这个构造函数创建一个空的 vector,初始时没有任何元素。

  • size() 返回 0,表示当前 vector 中没有元素。

  • capacity() 也可能返回 0,表示当前 vector 的容量为 0(即没有预分配内存)。

填充构造函数(fill constructor)

#include <vector>
#include <iostream>

int main() {
    std::vector<int> vec(5, 10);  // 构造一个包含 5 个元素的 vector,每个元素的值为 10
    std::vector<int> v2(vec.begin(),vec.end()); //通过迭代器来初始化vector,但迭代器可以指定范围来存数据

    std::cout << "Size: " << vec.size() << std::endl;  // 输出 Size: 5
    for (int i : vec) {
        std::cout << i << " ";  // 输出 10 10 10 10 10
    }
    std::cout << std::endl;

    return 0;
}

范围构造函数(range constructor)

#include <vector>
#include <iostream>

int main() {
    int arr[] = {1, 2, 3, 4, 5};
    std::vector<int> vec(arr, arr + 5);  // 从数组 arr 的第一个元素到第五个元素构造 vector

    std::cout << "Size: " << vec.size() << std::endl;  // 输出 Size: 5
    for (int i : vec) {
        std::cout << i << " ";  // 输出 1 2 3 4 5
    }
    std::cout << std::endl;

    return 0;
}

拷贝构造函数(copy constructor)

#include <vector>
#include <iostream>

int main() {
    std::vector<int> vec1 = {1, 2, 3, 4, 5};  // 初始化 vec1
    std::vector<int> vec2(vec1);  // 用 vec1 拷贝构造 vec2

    std::cout << "Size of vec1: " << vec1.size() << std::endl;  // 输出 Size of vec1: 5
    std::cout << "Size of vec2: " << vec2.size() << std::endl;  // 输出 Size of vec2: 5

    for (int i : vec2) {
        std::cout << i << " ";  // 输出 1 2 3 4 5
    }
    std::cout << std::endl;

    return 0;
}

析构函数参考文档

一般来说,我们只使用而不去探究其内部工作原理,不怎么要深究这部分知识。

运算符重载:vector::operator=

这个接口底层是套用了默认构造函数与拷贝构造函数,属于一种简便方式:

#include <vector>
#include <iostream>

int main() {
    std::vector<int> vec1 = {1, 2, 3, 4, 5};  // 初始化 vec1
    std::vector<int> vec2 = vec1;  // 用 vec1 拷贝构造 vec2 ,等同于 vec2(vec1)

    std::cout << "Size of vec1: " << vec1.size() << std::endl;  // 输出 Size of vec1: 5
    std::cout << "Size of vec2: " << vec2.size() << std::endl;  // 输出 Size of vec2: 5

    for (int i : vec2) {
        std::cout << i << " ";  // 输出 1 2 3 4 5
    }
    std::cout << std::endl;

    return 0;
}

vector的遍历

使用 operator [ ] 运算符重载来使得vector如同int*一样访问元素

#include <vector>
#include <iostream>

int main() {
    std::vector<int> vec = {1, 2, 3, 4, 5};
    
    for (size_t i = 0; i < vec.size(); ++i) {
        std::cout << vec[i] << " "; // 输出: 1 2 3 4 5
    }
    
    return 0;
}

这中遍历方式是最基础的一种方式,使用数组下标方式访问元素,类似于C风格数组。

  • 优点:简单直观,适合需要知道元素索引的场景
  • 缺点:需要手动管理索引,容易越界(虽然vector的[]操作不检查边界,但at()会检查)

使用STL经典遍历方式:迭代器(iterator)遍历

#include <vector>
#include <iostream>

int main() {
    std::vector<int> vec = {1, 2, 3, 4, 5};
    
    // 使用普通迭代器
    for (std::vector<int>::iterator it = vec.begin(); it != vec.end(); ++it) {
        std::cout << *it << " "; // 输出: 1 2 3 4 5
    }
    
    // 使用auto简化(推荐)
    for (auto it = vec.begin(); it != vec.end(); ++it) {
        std::cout << *it << " ";
    }
    
    return 0;
}
  • 迭代器是STL的核心概念,提供了统一的容器访问接口

  • begin()返回指向第一个元素的迭代器,end()返回指向最后一个元素之后的迭代器

  • *it解引用获取元素值

  • 优点:适用于所有STL容器,可以配合算法使用

  • 重要提示:使用++it(前置递增)而不是it++(后置递增),避免不必要的临时对象

范围for循环(C++11及以上,最推荐的方式)

#include <vector>
#include <iostream>

int main() {
    std::vector<int> vec = {1, 2, 3, 4, 5};
    
    // 只读遍历
    for (int num : vec) {
        std::cout << num << " "; // 输出: 1 2 3 4 5
    }
    
    // 修改元素(使用引用)
    for (auto& num : vec) {
        num *= 2; // 修改元素值
    }
    
    // 读取修改后的值
    for (int num : vec) {
        std::cout << num << " "; // 输出: 2 4 6 8 10
    }
    
    return 0;
}
  • C++11引入的特性,语法最简洁,但注意的是:范围for底层也是运用迭代器来进行遍历的,相较于性能效率而言,两者并无区别。这样写只是提高了代码的可读性。

  • 读取元素时使用const auto&避免拷贝(推荐)

  • 修改元素时使用auto&引用)

  • 最推荐的遍历方式,现代C++编程的首选

vector中容器的接口

vector::size 以及 vector::max_size

这个接口可以查看我们vector实际存放的元素数量,来看代码:

#include <vector>
#include <iostream>
using namespace std;

int main() {
    vector<int> box{1, 2, 3}; // 装了3个整数
    cout << box.size(); // 输出:3
    return 0;
}

这个接口建议查看 vector理论上能装的最大元素数量(受系统 / 编译器限制的上限,比如内存、数据类型大小)。但这个接口几乎用不上,所以不在展示。

查看与改变容量的接口:vector::resize vector::reserve与 vector::capacity

这个接口可以主动修改vector实际存放的元素数量(可以 “加位置” 也可以 “减位置”)。

使用方法:

  • resize(n):把元素数量改成 n。

    • 若 n > 原 size:新增位置填充默认值(int 填 0、string 填空串);

    • 若 n < 原 size:删掉后面的元素。

  • resize(n, val):新增位置填充指定值 val(更灵活)。

vector<int> box{1, 2}; // 初始size=2
box.resize(4);         // size变4,新增位置填0 → {1,2,0,0}
box.resize(2);         // size变2,删掉后面元素 → {1,2}
box.resize(5, 9);      // size变5,新增位置填9 → {1,2,9,9,9}

这个接口可以提前申请指定大小的存储空间(只改 capacity,不改 size),避免频繁扩容导致性能损耗。相当于扩容(在vs编译器下,扩容倍率是按照1.5倍来进行扩容(向上取整)g++中采用的是2倍扩容)

注意:reserve(n),仅当 n > 当前 capacity 时生效;若 n < 当前 capacity,无任何变化。

vector<int> box;
box.reserve(100); // 提前申请能装100个元素的空间
cout << box.size();     // 输出:0(没装任何东西)
cout << box.capacity(); // 输出:100(能装100个)

这个接口可以查看 vector实际分配的存储空间能装多少元素(盒子本身的容量,不是实际装的数量)

size是 “装了多少”,capacity是 “能装多少”。vector 为了效率,会提前多分配空间,避免每次加元素都重新分配内存。

vector<int> box;
box.push_back(1); // 装1个元素:size=1,capacity=1(不同编译器可能不同)
box.push_back(2); // 装2个元素:size=2,capacity=2
box.push_back(3); // 装3个元素:size=3,capacity=4(自动扩容,比如成倍扩)
cout << box.capacity(); // 输出:4

查“空”与瘦身:vector::emptyvector::shrink_to_fit

这个接口可以检查 vector 里是否为空(有没有装任何元素),返回值为bool值。

vector<int> box;
cout << boolalpha; // 让cout输出true/false而不是1/0
cout << box.empty(); // 输出:true
box.push_back(1);
cout << box.empty(); // 输出:false

这个接口可以把 vector 的存储空间 瘦身 到和实际元素数量一致(本质上是让 capacity = size,从而释放空间),释放多余的内存。

vector<int> box;
box.reserve(100);    // capacity=100,size=0
box.push_back(5);    // capacity=100,size=1
box.shrink_to_fit(); // 瘦身:capacity=1,size=1
cout << box.capacity(); // 输出:1

小总结:

  • size/empty:关注 “实际装了多少”——size 查数量,empty 判空(优先用 empty);
  • capacity/reserve/shrink_to_fit:关注 “分配了多少空间”——capacity 查容量,reserve 提前扩容,shrink_to_fit 释放多余空间;
  • resize/max_size:resize 改实际元素数,max_size 是理论上限(几乎不用)。

vector中修改类型(modifiers)的接口

清空,再重新装填:vector::assign

把 vector 里现有的所有东西全清掉,重新装一批新元素(新东西)(相当于 “重置” 整个盒子的内容)。

这个接口有两个函数重载:

  • assign(n, val):装 n 个值为 val 的元素;
  • assign(起始迭代器, 结束迭代器):从其他容器 / 数组里复制一段元素过来;
#include <vector>
#include <iostream>
using namespace std;

int main() {
    vector<int> box{1,2,3}; // 初始:{1,2,3}
    box.assign(2, 9);       // 清空后装2个9 → {9,9}
    
    vector<int> other{4,5,6};
    box.assign(other.begin(), other.end()); // 复制other的所有元素 → {4,5,6}
    return 0;
}

尾插与尾删元素:vector::push_backvector::pop_back

这个接口可以往 vector 的最后面塞一个元素。而且会自动扩容(相当于调用reserve()接口):

vector<int> box{1,2};
box.push_back(3); // 尾部加3 → {1,2,3}
box.push_back(4); // 尾部加4 → {1,2,3,4}

这个接口只做一件事情:把 vector最后一个元素拿走(只删末尾,不删中间),操作只改变vector的大小,而不改变容量。

vector<int> box{1,2,3,4};
box.pop_back(); // 删掉最后一个4 → {1,2,3}
box.pop_back(); // 删掉最后一个3 → {1,2}

随意的插入与删除:vector::insertvector::erase

这个接口可以在 vector任意位置插入元素(比如开头、中间),插入位置后的元素会自动往后挪。但因为要牵扯到元素的移动,使用效率会有损失。

常见的三种使用方式:

  • insert(位置迭代器, val):在指定位置插 1 个val
  • insert(位置迭代器, n, val):在指定位置插nval
  • insert(位置迭代器, 起始迭代器, 结束迭代器):插一段其他元素;
vector<int> box{1,4};
// 在第二个位置(begin()+1)插一个2 → {1,2,4}
box.insert(box.begin()+1, 2);
// 在第三个位置(begin()+2)插2个3 → {1,2,3,3,4}
box.insert(box.begin()+2, 2, 3);

这个接口可以删除 vector指定位置 / 指定范围的元素,删除后后面的元素会自动往前挪(同样有效率损失)。

常见的两种使用方式:

  • erase(位置迭代器):删除单个元素;
  • erase(起始迭代器, 结束迭代器):删除一个范围的元素;
vector<int> box{1,2,3,4,5};
box.erase(box.begin()+2); // 删除第三个元素(3)→ {1,2,4,5}
// 删除第2到第4个元素(2,4)→ {1,5}(注意:结束迭代器是“删到但不包含”)
box.erase(box.begin()+1, box.begin()+3);

交换两个vector:vector::swap

这个接口把两个 vector 的所有内容互换(包括里面的元素、size、capacity),效率极高(只是换个 “盒子标签”(类比指针的交换),不拷贝元素)。

vector<int> box1{1,2}; // size=2, capacity=2
vector<int> box2{3,4,5}; // size=3, capacity=3
box1.swap(box2);
// 互换后:box1={3,4,5},box2={1,2}

清空vector:vector::clear

这个接口把 vector 里的所有元素清空(盒子里空了),但盒子本身的容量(capacity)不变(盒子大小还在)。

vector<int> box{1,2,3};
box.clear(); // 清空后size=0,capacity仍为3(盒子还能装3个)
cout << box.size();     // 输出:0
cout << box.capacity(); // 输出:3

emplace是C++11中提出的新玩意,后续会将。

vector中的运算符重载:

(1)
template <class T, class Alloc>  bool operator== (const vector<T,Alloc>& lhs, const vector<T,Alloc>& rhs);
(2)
template <class T, class Alloc>  bool operator!= (const vector<T,Alloc>& lhs, const vector<T,Alloc>& rhs);
(3)
template <class T, class Alloc>  bool operator<  (const vector<T,Alloc>& lhs, const vector<T,Alloc>& rhs);
(4)
template <class T, class Alloc>  bool operator<= (const vector<T,Alloc>& lhs, const vector<T,Alloc>& rhs);
(5)
template <class T, class Alloc>  bool operator>  (const vector<T,Alloc>& lhs, const vector<T,Alloc>& rhs);
(6)
template <class T, class Alloc>  bool operator>= (const vector<T,Alloc>& lhs, const vector<T,Alloc>& rhs);

这类运算符重载可以实现两个vector的大小比较,但大多数的运算符重载都用了复用:

operationequivalent operation
a!=b!(a==b)
a>bb<a
a<=b!(b<a)
a>=b!(a<b)

零星知识点

  1. vector不支持流插入与流提取,大多数情况下都是使用一个临时变量int x来进行vector的尾插(或者使用C语言中数组输入的风格)。这里不是因为实现麻烦,而是因为输入输出不固定,可以多样化来输入输出。
  2. vector可以使用其他类型初始化:例如vector<char> , vector<double>,甚至可以用类类型初始化,但是不保证其可以满足预期(例如vector<char>不会自动补齐'\0',string类可以自动补齐,且可以实现编码需求)。

本篇到这里就结束了,喜欢文章的小伙伴可以关注一下彩妙,我们下一篇再见~

更多推荐