首页 > 编程语言 >C++ vector:代码舞台上伸缩自如的数据精灵舞链

C++ vector:代码舞台上伸缩自如的数据精灵舞链

时间:2024-12-22 23:56:01浏览次数:5  
标签:capacity 迭代 舞链 int 元素 C++ vector include

一、C++ vector 容器介绍

1. STL 容器概览

C++ STL 涵盖多种容器,分顺序容器如 (vector)与关联容器(如 mapset)。vector 作为常用顺序容器,是动态数组运行时可自动扩容量且随机访问高效。

2. 选用 vector 缘由

  • 动态伸缩:对比 C 风格数组大小固定,vector 能自动管理内存,按需调整大小。如 vector<int> v = {1, 2, 3, 4, 5}; 自动处理内存。
  • 接口多样:提供插入、删除、查找等丰富接口,像 push_back 添元素,erase 删元素,处理数据灵活。
  • 内存管理佳:C 风格数组易越界,vector 有内置机制防越界,保障程序稳定。

3. vector 与 C语言数组 对比

使用 C 风格数组的代码:

int arr[5] = {1, 2, 3, 4, 5};

与之相比,使用 vector 的方式更加灵活:

#include <vector>
using namespace std;

vector<int> v = {1, 2, 3, 4, 5};  // 自动管理内存和大小

4. vector 利弊分析

  • 优点:动态扩缩容,随机访问便捷,多数操作效率优,尾部操作性能好。
  • 缺点:头部插入与删除时,因需移动大量元素致速度慢。

二、vector 的使用

1. vector的构造方法

C++ vector 提供了多种构造方法,可以创建不同初始状态的 vector 对象。

构造函数声明(constructor)接口说明(功能)
vector()(重点)构造一个空的 vector
vector(size_type n, const T& val)构造包含 n 个元素值为 val的 vector
vector(const vector& v)(重点)拷贝构造 vector,与已有 vector 一致
vector(initializer_list<T>)使用初始化列表构造 vector

代码演示:

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

int main() {
    vector<int> v1;                    // 空 vector
    vector<int> v2(5, 100);            // 5个100的元素
    vector<int> v3(v2);                // 拷贝构造
    vector<int> v4 = {1, 2, 3, 4, 5};  // 使用初始化列表

    for (int i : v4) {
        cout << i << " ";  // 输出: 1 2 3 4 5
    }
    return 0;
}

2. vector 容量与大小操作

C++ vector 提供了多种方法管理容器的容量与大小。

方法名功能描述
size()返回当前元素个数
capacity()返回分配的存储空间大小
empty()判断容器是否为空
resize(n)将容器大小调整为 n,多出的部分用默认值填充
reserve(n)预留存储空间,避免多次扩容
shrink_to_fit()收缩 capacity 以匹配 size() 的大小

代码演示:

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

int main() {
    vector<int> v = {1, 2, 3, 4, 5};

    cout << "Size: " << v.size() << endl;         // 当前元素个数
    cout << "Capacity: " << v.capacity() << endl; // 当前容量
    v.resize(10, 100);                            // 调整大小到10
    cout << "After resize: " << v.size() << endl;
    v.reserve(20);                                // 预留空间
    cout << "Capacity after reserve: " << v.capacity() << endl;
    v.shrink_to_fit();                            // 收缩容量
    cout << "Capacity after shrink_to_fit: " << v.capacity() << endl;

    return 0;
}

动态扩展与性能问题

  • capacity的代码在vs和g++下分别运行会发现,vs下capacity是按1.5倍增长的,g++是按2 倍增长的。这个问题经常会考察,不要固化的认为,vector 增容都是2倍,具体增长多少是 根据具体的需求定义的。vs是PJ版本STL,g++是SGI版本STL。
  • reserve只负责开辟空间,如果确定知道需要用多少空间,reserve 可以缓解 vector 增容的代 价缺陷问题。
  • resize在开空间的同时还会进行初始化,影响size。

通过以下代码测试 vector 的默认扩容机制

// 测试vector的默认扩容机制
void TestVectorExpand()
 {
    size_t sz;
    vector<int> v;
    sz = v.capacity();
    cout << "making v grow:\n";
    for (int i = 0; i < 100; ++i) 
    {
        v.push_back(i);
        if (sz != v.capacity()) 
        {
            sz = v.capacity();
            cout << "capacity changed: " << sz << '\n';
        }
    }
 }

g++ 运行结果示例:

making foo grow:
capacity changed: 1
capacity changed: 2
capacity changed: 4
capacity changed: 8
capacity changed: 16
capacity changed: 32
capacity changed: 64
capacity changed: 128

vs 运行结果示例:

making foo grow:
capacity changed: 1
capacity changed: 2
capacity changed: 3
capacity changed: 4
capacity changed: 6
capacity changed: 9
capacity changed: 13
capacity changed: 19
capacity changed: 28
capacity changed: 42
capacity changed: 63
capacity changed: 94
capacity changed: 141

3. vector 元素访问与修改

方法名功能
operator[]通过下标访问元素,不进行边界检查
at(n)访问指定位置元素,进行越界检查
front()返回第一个元素
back()返回最后一个元素
data()返回指向数组头部的指针

代码演示: 

通过 operator[] 或 at() 直接修改元素内容: 

v[0] = 10;
v.at(2) = 20;
#include <iostream>
#include <vector>
using namespace std;

int main() {
    vector<int> v = {1, 2, 3, 4, 5};

    cout << "First element: " << v.front() << endl; // 访问第一个元素
    cout << "Last element: " << v.back() << endl;   // 访问最后一个元素

    try {
        cout << v.at(10);  // 越界访问
    } catch (out_of_range& e) {
        cout << "Exception: " << e.what() << endl;
    }//异常捕获

    return 0;
}

4.vector 的插入、删除与修改

(1)插入操作

vector 提供多种方法用于向容器中插入元素。

方法名功能描述
push_back()在末尾插入一个元素
insert()在指定位置插入元素
emplace_back()在末尾直接构造元素,避免不必要的复制开销

代码演练:

使用 push_back() 和 insert() 插入元素

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

int main() {
    vector<int> v = {1, 2, 3};

    // 在末尾插入
    v.push_back(4);

    // 在第二个位置插入元素5
    v.insert(v.begin() + 1, 5);

    for (int val : v) {
        cout << val << " ";
    }
    return 0;
}

emplace_back()  与 push_back() 相比,直接在容器末尾构造元素,减少了不必要的临时对象生成。适用于复杂对象的插入场景。

使用 emplace_back() 插入元素

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

struct Point {
    int x, y;
    Point(int a, int b) : x(a), y(b) {}
};

int main() {
    vector<Point> points;

    // 直接在末尾构造对象
    points.emplace_back(1, 2);

    cout << "Point: " << points[0].x << ", " << points[0].y << endl;

    return 0;
}

(2) 删除操作

vector 提供了多种删除元素的方式,包括删除末尾元素和删除指定位置的元素。

方法名功能描述
pop_back()删除末尾元素
erase()删除指定位置的元素或一段范围内的元素
clear()清空整个 vector

代码演示:

使用 pop_back() 和 erase() 删除元素 

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

int main() {
    vector<int> v = {1, 2, 3, 4, 5};

    // 删除末尾元素
    v.pop_back();

    // 删除第一个元素
    v.erase(v.begin());

    for (int val : v) {
        cout << val << " ";
    }
    return 0;
}

 使用 clear() 清空 vector

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

int main() {
    vector<int> v = { 1, 2, 3, 4, 5 };
    v.clear();
    cout << "Vector size after clear: " << v.size() << endl;  // 输出:0
    return 0;
}

(3)修改元素

代码演示:

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

int main() {
    vector<int> v = {1, 2, 3, 4, 5};

    // 修改第二个元素
    v[1] = 10;

    for (int val : v) {
        cout << val << " ";
    }
    return 0;
}

三、vector 的迭代器与遍历

vector 提供了多种迭代器类型,便于对元素进行遍历、修改或访问。

迭代器类型功能
begin()(重点)返回指向容器第一个元素的迭代器
end()(重点)返回指向容器末尾的迭代器
rbegin()(重点)返回指向容器最后一个元素的反向迭代器
rend()(重点)返回指向容器第一个元素之前位置的迭代器
cbegin()常量迭代器,无法修改元素
cend()常量迭代器,返回指向末尾的常量迭代器
for_each()(STL提供)对容器中的每个元素执行指定的操作

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

int main() {
    vector<int> v = {1, 2, 3, 4, 5};

    // 使用正向迭代器遍历
    for (auto it = v.begin(); it != v.end(); ++it) {
        cout << *it << " ";
    }
    cout << endl;

    // 使用反向迭代器遍历
    for (auto rit = v.rbegin(); rit != v.rend(); ++rit) {
        cout << *rit << " ";
    }
    cout << endl;

    return 0;
}

使用 for_each() 函数遍历并输出元素

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

void print(int val) {
    cout << val << " ";
}

int main() {
    vector<int> v = {1, 2, 3, 4, 5};
    for_each(v.begin(), v.end(), print);  // 使用for_each输出
    return 0;
}

四、vector 迭代器失效问题

1. 迭代器失效的原因

vector 迭代器失效的根本原因在于底层内存的重新分配元素的移除,导致迭代器指向的内存不再有效。当发生迭代器失效时,继续使用该迭代器可能会引发未定义行为,如程序崩溃访问错误数据

2. 常见导致迭代器失效的操作

扩容相关操作:当 vector 需要扩展容量时,会分配新的内存空间并将原有元素搬移到新的位置。此时,所有的迭代器将会失效。

  • resize()
  • reserve()
  • insert()
  • push_back()
  • assign()

删除操作:删除操作会使指向被删除元素及其后续元素的迭代器失效。

(1)扩容操作导致迭代器失效

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

int main() {
    vector<int> v{1, 2, 3, 4, 5, 6};
    auto it = v.begin();
    
    // 扩容相关操作导致迭代器失效
    v.resize(100, 8);  // 扩容并填充元素
    // v.reserve(100);  // 扩容但不增加元素
    // v.push_back(7);  // 末尾插入可能引发扩容
    // v.assign(100, 8);  // 重新赋值并扩容

    // 扩容后需要重新获取迭代器
    it = v.begin();

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

在每次扩容操作后,vector 可能会分配新的内存空间,并释放原来的内存区域。这意味着之前的迭代器已指向失效的内存,因此在扩容操作后,必须重新获取迭代器

 (2)删除操作导致迭代器失效

 删除 vector 中的某些元素时,指向被删除元素及其后续元素的迭代器会失效。

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

int main() {
    vector<int> v{ 1, 2, 3, 4 };

    // 查找元素3的迭代器
    auto pos = find(v.begin(), v.end(), 3);

    // 删除元素3
    v.erase(pos);

    // 迭代器失效,继续使用将导致程序崩溃或未定义行为
    cout << *pos << endl;  // 非法访问
    return 0;
}

 

删除某个元素后,指向该元素及其后续元素的迭代器会失效。在删除操作后应重新获取有效的迭代器,以避免出现非法访问或程序崩溃。

(3)删除偶数时的正确和错误写法

错误的删除写法在删除元素后没有正确更新迭代器,会导致迭代器失效,引发未定义行为。 

#include <iostream>
#include <vector>

using namespace std;
int main() {
    vector<int> v{1, 2, 3, 4, 4};
    auto it = v.begin();
    
    // 错误的删除写法,迭代器未更新
    while (it != v.end()) {
        if (*it % 2 == 0) {
            v.erase(it);  // 迭代器失效,++it 导致未定义行为
        }
        ++it;
    }
    return 0;
}

分析:删除偶数元素时未更新迭代器,导致迭代器 “离家出走”,程序出现未定义行为。

正确示例:

#include <iostream>
#include <vector>

using namespace std;
int main() {
    vector<int> v{1, 2, 3, 4, 4};
    auto it = v.begin();
    
    // 正确的删除写法
    while (it != v.end()) {
        if (*it % 2 == 0) {
            it = v.erase(it);  // 返回新的有效迭代器,指向被删除元素的下一个元素
        } else {
            ++it;
        }
    }

    for (int num : v) {
        cout << num << " ";
    }

    return 0;
}

(4)编译器对迭代器失效的处理差异

不同编译器(如 GCC 和 MSVC)对迭代器失效的处理方式不同。GCC 在某些情况下可能会宽容处理失效迭代器,程序不会立即崩溃,但输出结果不确定;MSVC 则会直接抛出错误并导致程序崩溃。

GCC 下的宽松处理

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

int main() {
    vector<int> v{1, 2, 3, 4, 5};
    auto it = find(v.begin(), v.end(), 3);
    
    // 删除元素3
    v.erase(it);
    
    // 虽然迭代器失效,但在 GCC 下程序可能不会崩溃
    cout << *it << endl;  // 输出不确定
    return 0;
}

MSVC 下严格处理

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

int main() {
    vector<int> v{1, 2, 3, 4, 5};
    auto it = find(v.begin(), v.end(), 3);
    
    // 删除元素3
    v.erase(it);
    
    // 在 MSVC 下,使用失效迭代器会导致程序崩溃
    cout << *it << endl;  // 程序崩溃
    return 0;
}

(5)扩容后的迭代器失效问题

即使扩容后的程序在 Linux 环境下不会立刻崩溃,但输出结果仍然是不可靠的。以下代码展示了 vector 在 reserve() 扩容后的迭代器失效问题。

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

int main() {
    vector<int> v{ 1, 2, 3, 4, 5 };
    auto it = v.begin();

    cout << "扩容之前,vector的容量为: " << v.capacity() << endl;

    // 通过 reserve 扩容
    v.reserve(100);

    cout << "扩容之后,vector的容量为: " << v.capacity() << endl;

    // 迭代器失效,输出结果错误
    while (it != v.end()) {
        cout << *it << " ";  // 输出结果可能错误
        ++it;
    }
    cout << endl;
    return 0;
}

总结与建议

  1. 避免迭代器失效:进行可能引发迭代器失效的操作(如扩容、删除等)后,必须重新获取迭代器,以保证程序的稳定性。
  2. 最佳实践:对于 erase() 操作,使用函数返回的迭代器继续遍历,以避免出现迭代器失效问题。
  3. 编译器差异:不同编译器(如 GCC 和 MSVC)对迭代器失效的处理方式不同,在开发跨平台程序时应尤为注意。

五、相关文档

 C++vector构造文档:https://cplusplus.com/reference/vector/vector/vector/

 C++vector元素访问与修改文档:https://cplusplus.com/reference/vector/vector/at/

 C++vector 容量与大小操作文档:https://cplusplus.com/reference/vector/vector/capacity/

 C++vector插入、删除与修改文档:https://cplusplus.com/reference/vector/vector/erase/

标签:capacity,迭代,舞链,int,元素,C++,vector,include
From: https://blog.csdn.net/zzy985/article/details/144645223

相关文章

  • C++中管理动态内存:析构函数中的`delete`使用指南
    在C++编程中,正确管理动态分配的内存是至关重要的。不当的内存管理可能导致内存泄漏、野指针和重复释放等问题。本文将详细介绍如何在C++类中使用delete和delete[]来释放动态分配的资源,并提供一些最佳实践,以确保资源被安全、有效地管理。1.析构函数中的delete当类的成员变......
  • c++实验6
    实验任务4#include<iostream>#include<stdexcept>template<typenameT>classVector{private:T*data;intnum;public:Vector(intsize);Vector(intsize,constT&value);Vector(constVector<T>&other);......
  • 在C#中,使用 Stopwatch 比较简单粗糙的替代 WIN32 下 C++ 中调用的 QueryPerformanceCo
    C#中自带的那个CTimer看上去是通过消息事件方式的,精度上好像小于10ms就不行了。于是找了半天网络,有的方式是引用kernel32.dll的库,然后就可以在C#中调用 QueryPerformanceCounter。感觉是不那么优雅。最后居然发现这个Stopwatch。真的像一个计时器一样,按一下,开始【Sto......
  • Java转C++之模板元编程
    模板元编程(TemplateMetaprogramming)入门指南:针对Java程序员的讲解作为一个从Java转到C++的程序员,理解模板元编程(TemplateMetaprogramming,简称TMP)可能会感到有些挑战,特别是其中的语法和概念有很多与Java非常不同的地方。模板元编程是一种强大的技术,它允许我们在编译时......
  • MFC/C++学习系列之简单记录1——错误解决与Dialog移植
    MFC/C++学习系列之简单记录1前言MFCapplication已停止工作Dialog移植MFC中的去边框设置总结前言最近接触MFC,接手一个项目,基于MFC架构编写的,在跑通代码的过程中出现的问题进行简单记录。MFCapplication已停止工作在代码移植过程中,直接build代码,提示MFCapplicat......
  • MFC/C++学习系列之简单记录8——消息映射
    MFC/C++学习系列之简单记录8——消息映射前言消息映射机制DoDataExchange函数BEGIN_MESSAGE_MAP和END_MESSAGE_MAP宏与WPF对比总结前言每天学习一点MFC的小知识!消息映射机制说起来很高大上,其实就是前端界面控件和后台代码命名的绑定。当前端控件执行某些操......
  • 【C++】剖析运算符重载和赋值运算符重载
    -----------------------------------------------------begin------------------------------------------------------运算符重载:当运算符被用于类类型的对象时,C++语言允许我们通过运算符重载的形式指定新的含义。C++规定类类型对象使⽤运算符时,必须转换成调用对应运......
  • C++超市外卖小程序-计算机设计毕业源码62482
    摘要随着社会生活节奏加快和消费习惯的变化,外卖服务成为人们日常生活中不可或缺的一部分。超市外卖作为新兴业态备受关注,然而传统外卖平台在推荐精准度和用户体验方面存在挑战。本研究旨在基于协同过滤算法,结合C++语言和MySQL数据库,设计开发一款超市外卖小程序,以提升用户点......
  • C++, 模板元编程, 凭借辅助的模板结构的特化 , 从而以类型控制模板类的分支
    u++真是学无止境,遍地地雷,哦不,遍地黄金。看ue序列化中的TArray有感,特节取部分代码保存,希望能多切近ue的编码经验半分。 //...template<typenameT>structTCanBulkSerialize{enum{Value=false};};template<>structTCanBulkSerialize<unsignedint>{enum{Value......
  • C++模板--类模板
    一篇文章带你走进类模板的世界!!!前言上一篇文章的链接:https://blog.csdn.net/hujiahangdewa/article/details/144630185有了上一篇文章的铺垫,我们再来看看类模板。其实就是要看template这段代码的后面跟的是什么,如果跟的是函数的定义,那么它就是一个函数模板,如果跟的是......