如何在C++中从vector中删除一个元素_C++ vector元素删除操作详解

C++中删除vector元素需注意迭代器失效,推荐使用erase配合remove或remove_if实现高效删除,避免直接遍历删除导致未定义行为。

如何在c++中从vector中删除一个元素_c++ vector元素删除操作详解

在C++中,从

std::vector

中删除元素并非简单地按个键就能完成,它涉及几种不同的策略,核心在于理解迭代器失效和容器底层机制。最常见且推荐的方法是利用

vector::erase

函数,通常会配合

std::remove

std::remove_if

来高效地移除特定值,或者直接通过迭代器移除特定位置的元素。这其中,对迭代器生命周期的把握是关键,否则很容易掉进各种“坑”里。

解决方案

std::vector

中删除元素,我们通常会用到以下几种主要方法:

通过迭代器或位置删除单个或一段元素:

vector

erase

方法是直接删除元素的利器。它可以接受一个迭代器来删除单个元素,或者接受一对迭代器来删除一个范围内的元素。当

erase(iterator)

被调用时,它会删除

iterator

指向的元素,并将该元素之后的所有元素向前移动一位,然后返回一个指向被删除元素之后的新位置的迭代器。所有指向被删除元素之后位置的迭代器都会失效。而

erase(first, last)

则会删除从

first

last

(不包含

last

)范围内的所有元素,同样会返回一个指向新范围末尾之后位置的迭代器,并使后续迭代器失效。

#include #include #include  // for std::findvoid print_vector(const std::vector& vec, const std::string& msg = "") {    std::cout << msg;    for (int x : vec) {        std::cout << x << " ";    }    std::cout << std::endl;}int main() {    std::vector nums = {10, 20, 30, 40, 50};    print_vector(nums, "原始vector: "); // 10 20 30 40 50    // 删除特定位置的元素 (例如,删除第三个元素 30)    // 注意:vector的索引从0开始,所以第三个元素是索引2    auto it_to_erase = nums.begin() + 2; // 指向30    nums.erase(it_to_erase);    print_vector(nums, "删除索引2元素后: "); // 10 20 40 50    // 删除一段范围的元素 (例如,删除 20 和 40)    // 找到20的位置    auto it_start = std::find(nums.begin(), nums.end(), 20);    // 找到40的位置 (如果40存在且在20之后)    auto it_end = std::find(nums.begin(), nums.end(), 40);    if (it_start != nums.end() && it_end != nums.end()) {        nums.erase(it_start, it_end); // 删除从20到40(不含40)    }    print_vector(nums, "删除20到40(不含40)后: "); // 10 40 50 (如果之前是10 20 40 50,这里会删除20)                                                // 实际上,由于40是下一个元素,它会删除20                                                // 让我们重新演示一个更清晰的范围删除    nums = {10, 20, 30, 40, 50, 60};    print_vector(nums, "重新初始化vector: "); // 10 20 30 40 50 60    // 删除从索引1 (20) 到索引4 (50) 之间的元素,不包含索引4 (即删除 20, 30, 40)    nums.erase(nums.begin() + 1, nums.begin() + 4);    print_vector(nums, "删除索引1到4(不含4)后: "); // 10 50 60}

通过值删除(“remove-erase”惯用法):如果你想删除所有值为特定

X

的元素,直接遍历并用

erase

删除效率不高,而且容易出错。C++标准库提供了一个更优雅、高效的惯用法:

std::remove

配合

vector::erase

std::remove

并不会真正删除元素,它会将所有不等于目标值的元素“移动”到

vector

的前部,并返回一个迭代器,指向新的逻辑“末尾”。这个新的逻辑末尾之后的所有元素都是“待删除”的冗余元素。

vector::erase

接着被用来删除从这个新的逻辑末尾到

vector

实际末尾之间的所有元素。

#include #include #include  // for std::removeint main() {    std::vector nums = {10, 20, 30, 20, 40, 50, 20};    print_vector(nums, "原始vector: "); // 10 20 30 20 40 50 20    // 删除所有值为20的元素    // std::remove 将所有非20的元素移到前面,并返回新逻辑末尾的迭代器    auto new_end = std::remove(nums.begin(), nums.end(), 20);    // erase 删除从 new_end 到 nums.end() 之间的元素    nums.erase(new_end, nums.end());    print_vector(nums, "删除所有20后: "); // 10 30 40 50}

条件删除(

std::remove_if

配合

vector::erase

):如果你想根据某个条件来删除元素,

std::remove_if

std::remove

的泛化版本。它接受一个谓词(一个返回

bool

的函数或lambda表达式),删除所有使谓词返回

true

的元素。

#include #include #include  // for std::remove_ifint main() {    std::vector nums = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};    print_vector(nums, "原始vector: "); // 1 2 3 4 5 6 7 8 9 10    // 删除所有偶数    auto new_end = std::remove_if(nums.begin(), nums.end(), [](int n){        return n % 2 == 0; // 谓词:如果是偶数则返回true    });    nums.erase(new_end, nums.end());    print_vector(nums, "删除所有偶数后: "); // 1 3 5 7 9}

当顺序不重要时,更高效的删除单个元素:如果你只需要删除一个特定元素,并且

vector

中元素的相对顺序不重要,那么可以采用一个非常高效的技巧:将要删除的元素与

vector

的最后一个元素交换,然后

pop_back

删除最后一个元素。这种方法的时间复杂度是O(1),而

erase

通常是O(N)。

#include #include #include  // for std::findint main() {    std::vector nums = {10, 20, 30, 40, 50};    print_vector(nums, "原始vector: "); // 10 20 30 40 50    // 假设要删除元素30,但顺序不重要    auto it = std::find(nums.begin(), nums.end(), 30);    if (it != nums.end()) {        *it = nums.back(); // 将最后一个元素的值赋给要删除的元素        nums.pop_back();   // 删除最后一个元素    }    print_vector(nums, "删除30(顺序不重要)后: "); // 10 20 50 40 (或 10 20 40 50,取决于具体实现,但30肯定没了)                                                // 实际输出会是 10 20 50 40}

为什么直接遍历并删除元素会导致问题?

这几乎是所有C++新手在处理

vector

删除时会踩的第一个“坑”。当我刚开始学习C++时,也曾天真地以为,像其他语言那样,一个简单的循环加上删除操作就能搞定。然而,

std::vector

的底层机制决定了这种做法是危险且错误的。

核心问题在于迭代器失效。当

vector::erase()

被调用时,它会删除指定位置的元素,并将其后的所有元素向前移动以填补空缺。这意味着:

立即学习“C++免费学习笔记(深入)”;

被删除元素之后的所有迭代器都会失效。 它们不再指向原来的元素,甚至可能指向无效的内存地址。

erase()

函数会返回一个指向被删除元素之后的新位置的迭代器。这是关键!

如果你在一个

for

循环中,例如

for (auto it = vec.begin(); it != vec.end(); ++it)

,然后在这个循环体内部调用

vec.erase(it)

,那么

it

erase

操作后就失效了。接着,

++it

尝试递增一个失效的迭代器,这会导致未定义行为(Undefined Behavior)。程序可能会崩溃,或者在某些情况下看似正常运行,但结果是错误的,这比直接崩溃更难调试。

错误示例:

#include #include int main() {    std::vector nums = {1, 2, 3, 4, 5};    std::cout << "原始vector: ";    for (int n : nums) std::cout << n << " ";    std::cout << std::endl;    // 尝试删除所有偶数(错误的方式)    for (auto it = nums.begin(); it != nums.end(); ++it) {        if (*it % 2 == 0) {            nums.erase(it); // 此时it失效            // 问题:下一个循环迭代会尝试递增一个失效的it,导致未定义行为            // 如果不加处理,甚至可能跳过下一个元素        }    }    // 实际运行可能会崩溃,或者输出错误结果    std::cout << "删除偶数后(错误方式): ";    for (int n : nums) std::cout << n << " ";    std::cout << std::endl; // 结果通常不正确或崩溃    return 0;}

正确的处理方式: 始终使用

erase

返回的新迭代器。

#include #include int main() {    std::vector nums = {1, 2, 3, 4, 5, 6};    std::cout << "原始vector: ";    for (int n : nums) std::cout << n << " ";    std::cout << std::endl;    // 正确删除所有偶数的方式 (虽然效率不如remove-erase,但可以这样操作)    for (auto it = nums.begin(); it != nums.end(); ) { // 注意这里没有 ++it        if (*it % 2 == 0) {            it = nums.erase(it); // erase返回指向下一个元素的有效迭代器        } else {            ++it; // 如果没有删除,则正常前进        }    }    std::cout << "删除偶数后(正确方式): ";    for (int n : nums) std::cout << n << " ";    std::cout << std::endl; // 1 3 5    return 0;}

即便如此,这种在循环中频繁调用

erase

的方式,对于删除大量元素而言,效率依然不高,因为它每次删除都会导致后续元素的移动。这正是“remove-erase”惯用法存在的价值。

std::remove

vector::erase

组合使用的精髓是什么?

std::remove

vector::erase

的组合,也就是我们常说的“remove-erase idiom”,是C++标准库中最优雅、最惯用也通常是最有效率的删除

vector

中特定值(或满足特定条件)元素的策略。它的精髓在于将元素的“逻辑删除”与容器的“物理删除”分离开来,从而优化性能并避免迭代器失效的复杂性。

std::remove

的本质:移动而非删除

首先要明确,

std::remove

(或

std::remove_if

)本身不会改变容器的大小。它做的事情是:

遍历指定范围内的元素。将所有“不满足删除条件”的元素(即需要保留的元素)移动到范围的前部。返回一个迭代器,指向新的逻辑“尾部”。这个迭代器以及它之后的所有元素,都是“待删除”的冗余元素,它们的值是不确定的,但它们占据着原来的内存空间。

想象一下,你有一排书,想把所有红色的书扔掉。

std::remove

不会真的扔掉书,它会把所有非红色的书挪到书架的前面,然后告诉你:“看,这些是你要留下的书,从这里开始,后面都是你要扔的。”那些“要扔的书”还在书架上,只是被推到了后面,而且可能被其他书的内容覆盖了。

vector::erase

的收尾工作:物理删除

std::remove

返回的迭代器,正是

vector::erase

所需要的起点。

vector::erase(new_end, nums.end())

会真正地:

销毁从

new_end

nums.end()

之间的所有元素对象。调整

vector

的实际大小(

size()

会减小)。释放这部分元素所占用的内存(如果需要)。

通过这种分工,

std::remove

只需要进行一次遍历和移动操作(线性时间复杂度O(N)),而

vector::erase

则负责一次性地截断容器。相比于在循环中频繁调用

erase

(每次

erase

都可能导致大量元素的移动,最坏情况下是O(N^2)),这种组合方式通常效率更高。

示例再次强调:

#include #include #include  // for std::removeint main() {    std::vector data = {1, 5, 2, 5, 3, 5, 4};    print_vector(data, "原始数据: "); // 1 5 2 5 3 5 4    // 假设我们要删除所有值为5的元素    // 步骤1: std::remove    // 它会将 {1, 2, 3, 4, ?, ?, ?} 这样的结构,并返回指向第一个?的迭代器    auto new_end_it = std::remove(data.begin(), data.end(), 5);    print_vector(data, "std::remove后 (注意大小不变,但内容已重排): "); // 1 2 3 4 3 5 4 (后面的值是未定义的,取决于实现)                                                                       // 这里只是一个示例,实际值可能是任何东西,但前四个是正确的    // 步骤2: vector::erase    // 它会删除从 new_end_it 到 data.end() 的所有元素    data.erase(new_end_it, data.end());    print_vector(data, "std::remove + vector::erase 后: "); // 1 2 3 4}

这种模式不仅清晰,而且对于

vector

这类连续存储的容器来说,其性能优势是显而易见的。它避免了在中间频繁插入或删除元素所带来的昂贵开销。

删除元素后,对vector的性能和内存有什么影响?

删除

vector

中的元素,其对性能和内存的影响是值得深思的,这不仅仅是“删掉就没了”那么简单。理解这些影响,能帮助我们写出更高效、更内存友好的C++代码。

性能影响:元素的移动开销当你在

vector

的中间删除一个元素(或一段元素)时,

vector

需要将所有被删除元素之后的所有元素向前移动,以保持其连续存储的特性。这个移动操作,在底层通常是内存块的拷贝(例如使用

memmove

)。

单个元素删除 (

erase(iterator)

): 如果

vector

有N个元素,你在索引

i

处删除一个元素,那么

N - 1 - i

个元素需要被移动。最坏情况下(删除第一个元素),需要移动N-1个元素,时间复杂度是O(N)。范围删除 (

erase(first, last)

): 如果删除了

k

个元素,那么

N - k - (first_index)

个元素需要被移动。同样,时间复杂度是O(N)。

remove-erase

惯用法:

std::remove

会进行一次遍历和元素的移动,时间复杂度是O(N)。

vector::erase

则是一次性截断,时间复杂度也是O(N)。因此,整个操作的复杂度依然是O(N),但通常比多次调用

erase

的效率更高,因为元素移动的次数更少。

pop_back

swap

+

pop_back

: 这是最高效的删除方式,时间复杂度是O(1),因为它不涉及任何元素的移动(或者只移动一个元素到末尾)。但前提是元素的顺序不重要。

频繁地在

vector

中间删除元素,会导致大量的元素移动,这会显著降低程序的性能。如果你的应用场景需要频繁地在中间位置进行插入和删除,那么

std::vector

可能不是最佳选择,

std::list

std::deque

可能更合适。

内存影响:容量(Capacity)与大小(Size)

vector

size()

(实际元素数量)和

capacity()

(底层分配的内存能容纳的元素数量)两个概念。

erase

操作只会减少

size()

,通常不会减少

capacity()

这意味着,即使你删除了`

以上就是如何在C++中从vector中删除一个元素_C++ vector元素删除操作详解的详细内容,更多请关注创想鸟其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/1476284.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2025年12月19日 00:07:26
下一篇 2025年12月19日 00:07:42

相关推荐

  • CSS mask属性无法获取图片:为什么我的图片不见了?

    CSS mask属性无法获取图片 在使用CSS mask属性时,可能会遇到无法获取指定照片的情况。这个问题通常表现为: 网络面板中没有请求图片:尽管CSS代码中指定了图片地址,但网络面板中却找不到图片的请求记录。 问题原因: 此问题的可能原因是浏览器的兼容性问题。某些较旧版本的浏览器可能不支持CSS…

    2025年12月24日
    900
  • Uniapp 中如何不拉伸不裁剪地展示图片?

    灵活展示图片:如何不拉伸不裁剪 在界面设计中,常常需要以原尺寸展示用户上传的图片。本文将介绍一种在 uniapp 框架中实现该功能的简单方法。 对于不同尺寸的图片,可以采用以下处理方式: 极端宽高比:撑满屏幕宽度或高度,再等比缩放居中。非极端宽高比:居中显示,若能撑满则撑满。 然而,如果需要不拉伸不…

    2025年12月24日
    400
  • 如何让小说网站控制台显示乱码,同时网页内容正常显示?

    如何在不影响用户界面的情况下实现控制台乱码? 当在小说网站上下载小说时,大家可能会遇到一个问题:网站上的文本在网页内正常显示,但是在控制台中却是乱码。如何实现此类操作,从而在不影响用户界面(UI)的情况下保持控制台乱码呢? 答案在于使用自定义字体。网站可以通过在服务器端配置自定义字体,并通过在客户端…

    2025年12月24日
    800
  • 如何在地图上轻松创建气泡信息框?

    地图上气泡信息框的巧妙生成 地图上气泡信息框是一种常用的交互功能,它简便易用,能够为用户提供额外信息。本文将探讨如何借助地图库的功能轻松创建这一功能。 利用地图库的原生功能 大多数地图库,如高德地图,都提供了现成的信息窗体和右键菜单功能。这些功能可以通过以下途径实现: 高德地图 JS API 参考文…

    2025年12月24日
    400
  • 如何使用 scroll-behavior 属性实现元素scrollLeft变化时的平滑动画?

    如何实现元素scrollleft变化时的平滑动画效果? 在许多网页应用中,滚动容器的水平滚动条(scrollleft)需要频繁使用。为了让滚动动作更加自然,你希望给scrollleft的变化添加动画效果。 解决方案:scroll-behavior 属性 要实现scrollleft变化时的平滑动画效果…

    2025年12月24日
    000
  • 如何为滚动元素添加平滑过渡,使滚动条滑动时更自然流畅?

    给滚动元素平滑过渡 如何在滚动条属性(scrollleft)发生改变时为元素添加平滑的过渡效果? 解决方案:scroll-behavior 属性 为滚动容器设置 scroll-behavior 属性可以实现平滑滚动。 html 代码: click the button to slide right!…

    2025年12月24日
    500
  • 为什么设置 `overflow: hidden` 会导致 `inline-block` 元素错位?

    overflow 导致 inline-block 元素错位解析 当多个 inline-block 元素并列排列时,可能会出现错位显示的问题。这通常是由于其中一个元素设置了 overflow 属性引起的。 问题现象 在不设置 overflow 属性时,元素按预期显示在同一水平线上: 不设置 overf…

    2025年12月24日 好文分享
    400
  • 网页使用本地字体:为什么 CSS 代码中明明指定了“荆南麦圆体”,页面却仍然显示“微软雅黑”?

    网页中使用本地字体 本文将解答如何将本地安装字体应用到网页中,避免使用 src 属性直接引入字体文件。 问题: 想要在网页上使用已安装的“荆南麦圆体”字体,但 css 代码中将其置于第一位的“font-family”属性,页面仍显示“微软雅黑”字体。 立即学习“前端免费学习笔记(深入)”; 答案: …

    2025年12月24日
    000
  • 如何选择元素个数不固定的指定类名子元素?

    灵活选择元素个数不固定的指定类名子元素 在网页布局中,有时需要选择特定类名的子元素,但这些元素的数量并不固定。例如,下面这段 html 代码中,activebar 和 item 元素的数量均不固定: *n *n 如果需要选择第一个 item元素,可以使用 css 选择器 :nth-child()。该…

    2025年12月24日
    200
  • 使用 SVG 如何实现自定义宽度、间距和半径的虚线边框?

    使用 svg 实现自定义虚线边框 如何实现一个具有自定义宽度、间距和半径的虚线边框是一个常见的前端开发问题。传统的解决方案通常涉及使用 border-image 引入切片图片,但是这种方法存在引入外部资源、性能低下的缺点。 为了避免上述问题,可以使用 svg(可缩放矢量图形)来创建纯代码实现。一种方…

    2025年12月24日
    100
  • 如何让“元素跟随文本高度,而不是撑高父容器?

    如何让 元素跟随文本高度,而不是撑高父容器 在页面布局中,经常遇到父容器高度被子元素撑开的问题。在图例所示的案例中,父容器被较高的图片撑开,而文本的高度没有被考虑。本问答将提供纯css解决方案,让图片跟随文本高度,确保父容器的高度不会被图片影响。 解决方法 为了解决这个问题,需要将图片从文档流中脱离…

    2025年12月24日
    000
  • 为什么我的特定 DIV 在 Edge 浏览器中无法显示?

    特定 DIV 无法显示:用户代理样式表的困扰 当你在 Edge 浏览器中打开项目中的某个 div 时,却发现它无法正常显示,仔细检查样式后,发现是由用户代理样式表中的 display none 引起的。但你疑问的是,为什么会出现这样的样式表,而且只针对特定的 div? 背后的原因 用户代理样式表是由…

    2025年12月24日
    200
  • inline-block元素错位了,是为什么?

    inline-block元素错位背后的原因 inline-block元素是一种特殊类型的块级元素,它可以与其他元素行内排列。但是,在某些情况下,inline-block元素可能会出现错位显示的问题。 错位的原因 当inline-block元素设置了overflow:hidden属性时,它会影响元素的…

    2025年12月24日
    000
  • 为什么 CSS mask 属性未请求指定图片?

    解决 css mask 属性未请求图片的问题 在使用 css mask 属性时,指定了图片地址,但网络面板显示未请求获取该图片,这可能是由于浏览器兼容性问题造成的。 问题 如下代码所示: 立即学习“前端免费学习笔记(深入)”; icon [data-icon=”cloud”] { –icon-cl…

    2025年12月24日
    200
  • 为什么使用 inline-block 元素时会错位?

    inline-block 元素错位成因剖析 在使用 inline-block 元素时,可能会遇到它们错位显示的问题。如代码 demo 所示,当设置了 overflow 属性时,a 标签就会错位下沉,而未设置时却不会。 问题根源: overflow:hidden 属性影响了 inline-block …

    2025年12月24日
    000
  • 如何利用 CSS 选中激活标签并影响相邻元素的样式?

    如何利用 css 选中激活标签并影响相邻元素? 为了实现激活标签影响相邻元素的样式需求,可以通过 :has 选择器来实现。以下是如何具体操作: 对于激活标签相邻后的元素,可以在 css 中使用以下代码进行设置: li:has(+li.active) { border-radius: 0 0 10px…

    2025年12月24日
    100
  • 为什么我的 CSS 元素放大效果无法正常生效?

    css 设置元素放大效果的疑问解答 原提问者在尝试给元素添加 10em 字体大小和过渡效果后,未能在进入页面时看到放大效果。探究发现,原提问者将 CSS 代码直接写在页面中,导致放大效果无法触发。 解决办法如下: 将 CSS 样式写在一个单独的文件中,并使用 标签引入该样式文件。这个操作与原提问者观…

    2025年12月24日
    000
  • 如何模拟Windows 10 设置界面中的鼠标悬浮放大效果?

    win10设置界面的鼠标移动显示周边的样式(探照灯效果)的实现方式 在windows设置界面的鼠标悬浮效果中,光标周围会显示一个放大区域。在前端开发中,可以通过多种方式实现类似的效果。 使用css 使用css的transform和box-shadow属性。通过将transform: scale(1.…

    2025年12月24日
    200
  • 为什么我的 em 和 transition 设置后元素没有放大?

    元素设置 em 和 transition 后不放大 一个 youtube 视频中展示了设置 em 和 transition 的元素在页面加载后会放大,但同样的代码在提问者电脑上没有达到预期效果。 可能原因: 问题在于 css 代码的位置。在视频中,css 被放置在单独的文件中并通过 link 标签引…

    2025年12月24日
    100
  • 为什么我的 Safari 自定义样式表在百度页面上失效了?

    为什么在 Safari 中自定义样式表未能正常工作? 在 Safari 的偏好设置中设置自定义样式表后,您对其进行测试却发现效果不同。在您自己的网页中,样式有效,而在百度页面中却失效。 造成这种情况的原因是,第一个访问的项目使用了文件协议,可以访问本地目录中的图片文件。而第二个访问的百度使用了 ht…

    2025年12月24日
    000

发表回复

登录后才能评论
关注微信