【C++】认识vector(概念+题目OJ)

祝大家五一假期快乐,今天我们在认识完string后,开始步入vector的章节学习。

废话少说,发车!!!


C++ vector 容器深入解析

1. vector 基本概念

什么是 vector?

std::vector是 C++ 标准模板库(STL)中最常用的动态数组容器。它可以动态调整大小,在运行时自动管理内存,提供了类似数组的随机访问功能,同时具有灵活的动态扩容能力。

核心特性

  • 动态数组:底层基于数组实现,支持随机访问

  • 自动内存管理:自动分配和释放内存

  • 连续内存存储:元素在内存中连续存储,提供良好的缓存局部性

  • 模板类:可存储任意类型的元素

  • RAII 原则:自动管理资源


2.底层原理:三指针与内存布局

复制代码
template<typename T>
class vector {
private:
    T* _start;          // 起始指针:指向首元素
    T* _finish;         // 结束指针:指向最后一个元素的下一位
    T* _end_of_storage; // 容量指针:指向已分配内存末尾
};


3. vector 的基本使用

复制代码
// 创建空 vector
vector<int> vec1;

// 创建指定大小的 vector
vector<int> vec2(10);         // 10个元素,初始值为0
vector<int> vec3(10, 5);      // 10个元素,每个都是5

// 从初始化列表创建
vector<int> vec4({1,2,3,4,5});//传参构造
vector<int> vec5 = {1, 2, 3, 4, 5};//构造+拷贝构造被优化为直接构造
vector<int> vec6{1, 2, 3, 4, 5};

// 拷贝构造
vector<int> vec7(vec4);

// 从迭代器范围创建
int arr[] = {1, 2, 3, 4, 5};
vector<int> vec8(arr, arr + 5);

4.扩容机制

  • 计算新容量:
    • GCC(SGI STL):2 倍扩容(如 4→8→16)
    • MSVC(VS):1.5 倍扩容(如 4→6→9)
  • 申请新内存:分配新容量的连续内存块
  • 拷贝旧元素:旧内存元素拷贝到新内存
  • 释放旧内存:销毁旧内存块,更新三指针
  • 迭代器失效:扩容后原迭代器 / 指针 / 引用全部失效(内存地址改变)

扩容实例


5. 常用操作(代码示例 + 解析)

1.构造与初始化

复制代码
// 1. 默认构造:空vector
vector<int> vec1;

// 2. 指定大小:5个元素,默认初始化为0
vector<int> vec2(5);

// 3. 指定大小+初始值:5个元素,值为10
vector<int> vec3(5, 10);

// 4. 拷贝构造
vector<int> vec4(vec3);

// 5. 初始化列表
vector<int> vec5 = {1,2,3,4,5};

// 6. 迭代器范围构造
vector<int> vec6(vec5.begin(), vec5.end());

2. emplace

与string不同,具有emplace接口。

方法 作用
emplace_back() 尾部原地构造
emplace() 指定位置原地构造
emplace_front() 头部原地构造(list、deque 支持)
try_emplace() map 安全插入(C++17)
复制代码
vector<pair<int, string>> vec;

// 传统方式(创建临时对象 + 拷贝)
vec.push_back(make_pair(1, "hello"));

// emplace 方式(原地构造,无拷贝)
vec.emplace_back(2, "world");

// 在迭代器位置原地构造元素
vec.emplace(vec.begin() + 2, 10, "test");

3.遍历方式(3 种常用)

复制代码
vector<int> vec = {1,2,3,4,5};

// 1. 下标访问(O(1),最常用)
for(int i=0; i<vec.size(); i++) cout << vec[i] << " ";

// 2. 迭代器访问(STL通用)
for(vector<int>::iterator it=vec.begin(); it!=vec.end(); it++)
    cout << *it << " ";

// 3. 范围for(C++11+,简洁)
for(auto x : vec) cout << x << " ";

4. 增删改查

1. 尾部操作(高效)
复制代码
vector<int> vec;
vec.push_back(1); // 尾部插入1
vec.push_back(2); 
vec.pop_back(); // 尾部删除(无返回值)
cout << vec.back(); // 取尾部元素
2. 中间插入 / 删除(低效)
复制代码
vector<int> vec = {1,2,3,4,5};
vec.insert(vec.begin()+2, 10); // 下标2插入10 → {1,2,10,3,4,5}
vec.erase(vec.begin()+3); // 删除下标3 → {1,2,10,4,5}
vec.erase(vec.begin(), vec.begin()+2); // 批量删除 → {10,4,5}
3. 容量管理(优化性能关键)
复制代码
vector<int> vec;
vec.reserve(100); // 预分配100空间,避免频繁扩容
vec.resize(50); // 改变size为50,多余元素删除,不足补0
vec.resize(60, 10); // size=60,新增元素初始化为10
vec.shrink_to_fit(); // 释放capacity>size的多余空间
vec.clear(); // 清空元素,size=0,capacity不变
4. 元素访问
复制代码
vector<int> vec = {1,2,3};
cout << vec[0]; // 下标访问,越界崩溃
cout << vec.at(1); // 安全访问,越界抛out_of_range异常
cout << vec.front(); // 首元素
cout << vec.back(); // 尾元素

6. 迭代器失效问题(避坑重点)

1. 失效场景

1.扩容时:push_back/emplace_back 导致扩容

所有迭代器、指针、引用失效(内存地址改变)

2.中间插入 / 删除时:insert/erase 使当前位置及之后的迭代器失效(元素后移)

2. 解决方法

1.预分配容量:reserve(n) 提前申请足够空间,避免扩容

2.重新获取迭代器:插入 / 删除后重新调用 begin()/end()

3.尾部操作优先:尽量用 push_back/pop_back,避免中间操作


7.C++ 结构化绑定(Structured Bindings)

结构化绑定(Structured Bindings),C++17 标准引入,允许将聚合类型的成员直接拆解为独立变量。

复制代码
pair<int,string> p{1,"cpp"};
auto [id,str] = p;
id = 666; // 只改副本,不影响原p

auto& [id,str] = p;
id = 999;  // 直接修改 p.first

const auto& [id,str] = p;
// id = 100; 报错,只读


8.题目运用

1. 找单身狗

136. 只出现一次的数字https://leetcode.cn/problems/single-number/

给你一个 非空 整数数组 nums ,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。

你必须设计并实现线性时间复杂度的算法来解决此问题,且该算法只使用常量额外空间。

复制代码
class Solution {
public:
    int singleNumber(vector<int>& nums) {
        int c=0;
        for(auto ch:nums)
        {
            c^=ch;
        }
        return c;
    }
};

2. 杨辉三角形

118. 杨辉三角https://leetcode.cn/problems/pascals-triangle/

**给定一个非负整数 numRows 生成「杨辉三角」的前 numRows行。

在「杨辉三角」中,每个数是它左上方和右上方的数的和

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

3.删除数组中重复的项

26. 删除有序数组中的重复项https://leetcode.cn/problems/remove-duplicates-from-sorted-array/

给你一个 非严格递增排列 的数组 nums ,请你原地 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。然后返回 nums 中唯一元素的个数。

考虑 nums 的唯一元素的数量为 k。去重后,返回唯一元素的数量 k

nums 的前 k 个元素应包含 排序后 的唯一数字。下标 k - 1 之后的剩余元素可以忽略

复制代码
class Solution {
public:
    int removeDuplicates(vector<int>& nums) {
        if(nums.size()==0)
        return 0;
        int left=0,right=0;
        for(right;right<nums.size();right++)
        {
            if(nums[left]!=nums[right])
            {
                nums[++left]=nums[right];
            }
        }
        return left+1;
    }
};

4.数组中出现次数超过一半的数字

https://www.nowcoder.com/share/jump/4804461771777908407850https://www.nowcoder.com/share/jump/4804461771777908407850

**给一个长度为 n 的数组,数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。**例如输入一个长度为9的数组[1,2,3,2,2,2,5,4,2]。由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2。

数据范围:n≤50000n≤50000,数组中元素的值 0≤val≤100000≤val≤10000

要求:空间复杂度:O(1)O(1),时间复杂度 O(n)O(n)

1.排序法

题目要求我们找出现次数大于数组长度一半的数字,想到如果将input数组排序后,会变成怎样?对于示例input数组排序后结果,我们可以观察到,大于数组长度一半的元素一定分布在数组的中间。

复制代码
int MoreThanHalfNum_Solution(vector<int>& numbers) {
        int s=numbers.size();
        int s2=s/2;
        if(s==0)
        return 0;
        sort(numbers.begin(),numbers.end());
        return numbers[s2];

    }

2.Boyer-Moore 投票算法

想象一下,如果把这些数字当做人种,一个数字和另外一个数字打了起来,同归于尽。最后剩下的是不是人数最多的那种人。这里要满足一个条件:某类人的数目一定要大于总人数的一半。

算法步骤:我们选择输入数组中第一个元素作为候选元素candidate,并设置其出现次数为count=1。随后遍历数组。当遇到与candidate相同的元素,count+1;不同的元素,count-1。当count为0的时候,选择下一个元素为候选元素,并且置count=1。遍历到数组的最后,剩下的candidate就是要求的结果

复制代码
int MoreThanHalfNum_Solution(vector<int> &numbers) {
    int candidate = numbers[0];
    int count = 1;
    for (int i = 1; i < numbers.size(); i++) {
        if (numbers[i] == candidate) {
            count++;
        } else {
            count--;
        }
        if (count == 0) {
            candidate = numbers[i + 1];
            count++;
        }
    }
    return candidate;
}

相关推荐
U盘失踪了1 小时前
【笔记】Cookie 请求头的写法
笔记
m0_46644103詹湛1 小时前
(一)FPGA :基础概念详解(Xilinx平台)
笔记·学习·fpga开发·verilog
逻辑驱动的ken1 小时前
Java高频面试考点场景题22
java·开发语言·jvm·面试·职场和发展·求职招聘·春招
zuozewei1 小时前
Agent Teams 实验笔记:让 Claude Code 三个 Agent 跑一遍 Todo Demo
笔记
枫叶丹41 小时前
【HarmonyOS 6.0】Core File Kit:端云文件版本管理能力解析与实践
开发语言·华为·harmonyos
初心未改HD1 小时前
Go 文件与 I/O 操作完全指南
开发语言·golang
上弦月-编程1 小时前
C语言链表详解,新手也能看懂! ——从入门到精通的完整教程
java·c语言·c++
szial2 小时前
uv 实战指南:用一个工具重塑 Python 开发工作流
开发语言·python·uv
生成论实验室2 小时前
《事件关系阴阳博弈动力学:识势应势之道》第十篇:识势应势——从认知到行动的完整闭环
人工智能·算法·架构·创业创新·安全架构