c++ vector数组详细介绍(二)

简介: c++ vector数组详细介绍(二)

c++ vector数组详细介绍(一)https://developer.aliyun.com/article/1436994


直接修改元素

你可以使用下标运算符 [] 或 at() 方法来直接访问并修改 vector 中的元素。这种方法类似于操作普通数组。

std::vector<int> v = {10, 20, 30, 40};
v[2] = 100;  // 使用下标运算符修改第三个元素
v.at(3) = 200;  // 使用 at() 方法修改第四个元素


添加元素


push_back(value): 在 vector 的末尾添加一个新元素。

emplace_back(args…): 类似于 push_back,但可以直接构造元素,避免复制或移动操作。

insert(position, value): 在指定位置插入一个或多个新元素。

std::vector<int> v = {10, 20, 30};
v.push_back(40);  // 现在 v = {10, 20, 30, 40}
v.emplace_back(50);  // 现在 v = {10, 20, 30, 40, 50}
v.insert(v.begin() + 2, 15);  // 在第三个位置插入 15,现在 v = {10, 20, 15, 30, 40, 50}

删除元素


pop_back(): 移除 vector 末尾的元素。

erase(position) 或 erase(start, end): 移除指定位置或范围内的元素。

clear(): 清空整个 vector。

std::vector<int> v = {10, 20, 30, 40, 50};
v.pop_back();  // 移除最后一个元素,现在 v = {10, 20, 30, 40}
v.erase(v.begin() + 2);  // 移除第三个元素,现在 v = {10, 20, 40}
v.clear();  // 清空整个 vector


其他修改操作

resize(n, value): 改变 vector 的大小。如果新大小大于当前大小,新元素将被初始化为 value(如果提供了的话)。


swap(vector): 与另一个 vector 交换内容。

std::vector<int> v1 = {1, 2, 3};
std::vector<int> v2 = {4, 5, 6};
v1.resize(5, 100);  // 现在 v1 = {1, 2, 3, 100, 100}
v1.swap(v2);  // 现在 v1 = {4, 5, 6}, v2 = {1, 2, 3, 100, 100}


注意事项

在使用 [] 或 at() 修改元素时,确保索引在 vector 的有效范围内。

在添加或删除元素时,vector 的大小会自动调整。

使用 emplace_back 而非 push_back 可以提高效率,尤其是对于复杂类型的对象。

insert, erase, 和 resize 等操作可能影响 vector 的性能,特别是在处理大量数据时。

2.5 迭代器

迭代器类型

正向迭代器:

允许你从 vector 的开头向末尾遍历。

反向迭代器:

允许你从 vector 的末尾向开头遍历。

常量迭代器:

不允许修改它们所指向的元素,可以是正向或反向。


获取迭代器

使用 begin() 和 end() 成员函数获得指向 vector 开头和末尾的正向迭代器。


使用 rbegin() 和 rend() 获得反向迭代器。
cbegin()、cend()、crbegin() 和 crend() 分别用于获取常量正向和反向迭代器(C++11 及以后版本)。


迭代器操作

遍历:通过递增(++)和递减(- -)迭代器来遍历 vector。
访问元素:使用解引用操作符(*)来访问迭代器所指向的元素。


比较:迭代器可以使用比较操作符(如 == 和 !=)来判断是否到达 vector 的末尾或特定位置。
#include <vector>
#include <iostream>

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

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

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

    return 0;
}
注意事项


在使用迭代器时,确保不要越界访问。例如,不要解引用 end() 或 rend() 返回的迭代器。

修改 vector(如添加或删除元素)可能会使现有迭代器失效。在进行这类操作后,应该重新获取迭代器。

常量迭代器适用于不需要修改元素值的情况,增强代码的安全性。

通过使用迭代器,你可以编写更通用的代码,这些代码可以适用于多种不同的容器类型,不仅仅是 vector。

3. vector大小和容量详解

在 C++ 中,vector 的大小(size)和容量(capacity)是两个重要但不同的概念。理解这两者之间的区别对于有效地使用 vector 来说非常关键。


大小(Size)

定义:

vector 的大小是指它当前包含的元素数量。


获取方法:

使用 size() 成员函数。

动态变化:

当你添加或删除元素时,vector 的大小会相应增加或减少。

示例:

如果你向一个空的 vector 添加了三个元素,它的大小(size())将是 3。

容量(Capacity)

定义:

vector 的容量是指在需要重新分配内存之前它可以容纳的元素数量。

获取方法:

使用 capacity() 成员函数。

预先分配:

为了减少多次分配内存的开销,vector 通常会预先分配比当前大小更多的空间。这意味着容量通常会等于或大于大小。

自动增长:

当添加新元素导致当前容量不足时,vector 会自动增加其容量(通常是当前容量的两倍,但这取决于实现)。

示例:

一个大小为 3 的 vector 可能有一个容量为 6,表示它可以在不重新分配内存的情况下增加到 6 个元素。

重要操作

resize(n): 改变 vector 的大小为 n。如果 n 大于当前大小,会添加默认初始化的元素;如果 n 小于当前大小,多余的元素会被移除。

std::vector<int> v = {1, 2, 3};
v.resize(5);  // 大小改为 5,添加了两个值为 0 的元素
reserve(n): 保证 vector 至少有 n 的容量。如果 n 大于当前容量,会进行内存分配以增加容量;否则,该方法不会改变 vector 的容量。
std::vector<int> v;
v.reserve(10);  // 容量至少为 10,但大小仍为 0

注意事项


对于性能敏感的应用,合理管理 vector 的大小和容量是很重要的。频繁的内存分配和复制可能会导致性能问题。

使用 reserve() 预分配内存可以减少因添加新元素而导致的多次内存重新分配。

size() 和 capacity() 的值可以相同,但也可以不同。capacity() 永远不会小于 size()。

在处理大量数据时,了解和合理利用 vector 的大小和容量可以帮助优化程序的性能和内存使用。

4. 算法操作

C++ 中的 vector 容器与标准模板库(STL)提供的算法高度兼容。这种兼容性是通过迭代器实现的,使得 vector 可以与大多数通用算法一起使用。


算法概述

STL 算法库包括一系列标准化的函数,用于执行范围内的操作,如排序、搜索、转换、计数等。

这些算法通常通过一对迭代器来指定作用的范围(例如,begin() 和 end())。


常用算法示例

排序 (sort):

对 vector 中的元素进行排序。

std::vector<int> v = {4, 1, 3, 5, 2};
std::sort(v.begin(), v.end());  // 升序排序
查找 (find):

查找 vector 中的特定元素。

auto it = std::find(v.begin(), v.end(), 3);
if (it != v.end()) {
    std::cout << "Element found!" << std::endl;
}
反转 (reverse):

反转 vector 中的元素顺序。

std::reverse(v.begin(), v.end());
累加 (accumulate):

计算 vector 中所有元素的总和。

int sum = std::accumulate(v.begin(), v.end(), 0);


唯一化 (unique):

移除连续且相同的元素。

std::sort(v.begin(), v.end());  // unique 需要先排序
auto last = std::unique(v.begin(), v.end());
v.erase(last, v.end());  // 移除重复之后的多余元素

注意事项


在使用这些算法时,确保传递正确的迭代器范围。

某些算法(如 sort 和 unique)可能改变容器中元素的位置或数量。在使用这些算法之后,之前获取的迭代器可能会失效。

许多算法需要容器中的元素已经按照某种顺序排列,例如 unique 和 binary_search,在使用这些算法之前,需要确保容器已经正确排序。

accumulate 等算法可能需要额外的头文件(如 )。

通过将 vector 与 STL 算法结合使用,你可以编写出既简洁又高效的代码来处理复杂的数据集操作。这也是 C++ STL 强大和灵活的一个重要体现。

5. 性能考量

当使用 C++ 的 vector 容器时,考虑其性能特点是非常重要的。虽然 vector 提供了便利性和灵活性,但在某些情况下,它的行为可能对程序的性能产生影响。以下是需要考虑的主要性能方面:


5.1 动态数组的性能影响

内存分配和重新分配:


当向 vector 添加新元素而当前容量不足以容纳它们时,vector 需要进行内存重新分配。这个过程涉及分配新的更大的内存块、复制现有元素到新内存、并释放旧内存。这可能是一个相对昂贵的操作,特别是对于大型 vector 或包含复杂对象的 vector。


预分配容量:

通过 reserve() 方法预先分配足够的容量可以避免或减少内存重新分配的次数,从而提高性能。

5.2 元素访问和修改


随机访问:

vector 提供了快速的随机访问(即直接通过索引访问),这在性能方面是一个优势,特别是与一些其他容器(如 list 或 forward_list)相比。


插入和删除:

在 vector 的末尾添加或移除元素(例如使用 push_back() 和 pop_back())是非常快速的。但是在 vector 的开始或中间位置插入或删除元素可能会导致现有元素的移动,这可能是一个较慢的操作。


5.3 大小与容量管理

容量与大小:


vector 的容量可能大于其实际大小。这意味着 vector 可能会占用比实际需要的更多内存。使用 shrink_to_fit() 可以请求减少 vector 的容量以匹配其大小,尽管这种请求不一定总会被满足。

大小变更操作:


resize() 和 reserve() 可以用来管理 vector 的大小和容量。合理使用这些操作可以避免不必要的内存重新分配。

5.4 迭代器和算法

迭代器遍历:

使用迭代器遍历 vector 通常是高效的。但是,如果在遍历过程中修改 vector(例如添加或删除元素),可能会使迭代器失效,导致未定义行为。


5.5 总体性能

整体效率:


总的来说,vector 是一个高效的容器,特别是在需要快速随机访问元素时。对于大多数用途,它提供了良好的性能和方便的接口。

选择合适的容器:


虽然 vector 在很多情况下都是一个好的选择,但根据具体需求选择合适的容器类型是很重要的。例如,如果你需要频繁在容器中间插入或删除元素,list 或 deque 可能是更好的选择。

6. 高级特性


C++ vector 容器提供了一些高级特性,特别是在 C++11 和后续版本中。这些特性不仅增加了灵活性,还改善了性能和使用方便性。以下是一些重要的高级特性:


初始化列表 (C++11)

允许使用花括号 {} 初始化 vector,使代码更简洁。

例如:

std::vector<int> v = {1, 2, 3, 4, 5};

emplace_back 和 emplace (C++11)



这些函数在 vector 中直接构造元素,而不是先构造然后复制或移动到容器中。

这可以提高性能,特别是对于包含复杂对象的 vector。

例如:

v.emplace_back(3, "three"); //在 vector 的末尾直接构造一个元素。


范围构造器 (C++11)

允许使用两个迭代器作为参数来构造 vector,从而复制或移动另一个容器中的元素范围。

例如:

std::vector<int> v1 = {1, 2, 3}; 
std::vector<int> v2(v1.begin(), v1.end());


shrink_to_fit (C++11)

请求减少 vector 的容量以匹配其大小,有助于节省内存。

不保证容量一定会减少,但大多数实现都会尽量满足这个请求。


data 方法 (C++11)


提供对 vector 内部数组的直接访问。

可以用于与 C 风格的函数接口交互,需要一个指向数组的指针。

例如:

int* p = v.data();

移动语义 (C++11)


vector 支持移动构造和移动赋值,这意味着在某些情况下可以避免复制整个 vector 的内容,而是直接移动它。

对于包含大量元素的 vector,这可以大大提高性能。


noexcept 修饰符 (C++11)


一些 vector 操作被标记为 noexcept,表明它们不会抛出异常。

这对于某些需要异常安全保证的场景非常有用。


可以在 vector 上使用的 Lambda 表达式 (C++11)

结合 STL 算法,可以用于强大的元素处理,如 std::for_each 或 std::transform。

支持非标准分配器


vector 可以使用自定义分配器,这对于特殊的内存管理需求很有用。

这些高级特性使得 vector 更加强大和灵活,同时也提高了其与现代 C++ 语言特性的兼容性。这些特性的使用可以使代码更加高效、简洁和功能强大。


7. vector动态大小详解


vector 可以根据需要自动调整大小,这意味着你可以在运行时向 vector 添加或删除元素,而它的大小会自动增长或缩减。

vector 的动态大小特性是其最重要的特点之一。这个特性允许 vector 在运行时根据需要自动调整其存储的元素数量。以下是对这一特性的详细介绍:

自动调整大小


当新元素被添加到 vector 中,而当前的存储空间不足以容纳更多元素时,vector 会自动扩展其容量。

通常,vector 会将其容量增加到当前大小的两倍,这种增长策略旨在平衡内存使用和重新分配的次数。不过,具体的增长策略可能因实现而异。


内存管理


vector 在内部管理一个动态数组,用于存储其元素。当 vector 的大小改变时,它可能需要分配一个更大的数组并将现有元素复制到新数组中,然后释放旧数组的内存。

这个过程是自动的,对使用者来说是透明的。


c++ vector数组详细介绍(三)https://developer.aliyun.com/article/1437019?spm=a2c6h.13262185.profile.51.5bba685cuSQkDD

目录
相关文章
|
4天前
|
C++
c++的学习之路:13、vector(2)
c++的学习之路:13、vector(2)
24 0
|
4天前
|
编译器 C++ Windows
【C++】vector问题解决(非法的间接寻址,迭代器失效 , memcpy拷贝问题)
不使用memcpy函数不就可以了,然后我们使用简单粗暴的赋值拷贝,这样就不会发生浅拷贝问题了!!!
17 1
|
4天前
|
算法 C++ 容器
【C++/STL】vector(常见接口、模拟实现、迭代器失效)
【C++/STL】vector(常见接口、模拟实现、迭代器失效)
11 0
|
4天前
|
存储 C++
【C++模板】模板实现通用的数组
【C++模板】模板实现通用的数组
|
4天前
|
存储 人工智能 C++
【重学C++】【指针】详解让人迷茫的指针数组和数组指针
【重学C++】【指针】详解让人迷茫的指针数组和数组指针
34 1
|
4天前
|
存储 缓存 编译器
【C++进阶(五)】STL大法--list模拟实现以及list和vector的对比
【C++进阶(五)】STL大法--list模拟实现以及list和vector的对比
|
4天前
|
编译器 C++
【C++进阶(三)】STL大法--vector迭代器失效&深浅拷贝问题剖析
【C++进阶(三)】STL大法--vector迭代器失效&深浅拷贝问题剖析
|
4天前
|
存储 C语言 C++
【C++进阶(二)】STL大法--vector的深度剖析以及模拟实现
【C++进阶(二)】STL大法--vector的深度剖析以及模拟实现
|
4天前
|
存储 C++ 容器
c++的学习之路:12、vector(1)
c++的学习之路:12、vector(1)
14 0
|
4天前
|
存储 算法 编译器
【C++初阶】STL详解(三)vector的介绍与使用
【C++初阶】STL详解(三)vector的介绍与使用
37 0