C++:STL(四)之vector的基本介绍与使用方式|容器接口

白乐天_ξ( ✿>◡❛) 2024-10-05 13:35:01 阅读 75

Blog’s 主页: 白乐天_ξ( ✿>◡❛)

🌈 个人Motto:他强任他强,清风拂山冈!

🔥 所属专栏:C++深入学习笔记

💫 欢迎来到我的学习笔记!

一、C/C++中的字符串

1.1. C语言中的数组

在C语言中,数组是一组相同类型元素的有序集合。与字符串类型,它的大小在编译时就已经确定无法更改了。

<code>// 大小为5的整形数组

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

// 大小为5的浮点型数组

double arr2[5] = { 3.14 };

1.2. C++中的数组

与<code>string类似,C++为了更加方便就引入了一个支持可动态数组大小数组的序列容器vectorvector的特点如下:

基本特性

vector是可变大小的序列容器,像数组一样使用连续存储空间存储元素,能通过下标高效访问元素。与数组的区别在于vector的大小可动态改变,并且这种大小的改变由容器自动处理。 存储机制

本质上,vector使用动态分配数组存储元素。当插入新元素时,可能需要重新分配数组大小,这一过程代价相对较高,但vector不会每次插入都重新分配。 空间分配策略

vector会分配额外空间以应对可能的增长,不同库在空间使用和重新分配上有不同策略。重新分配的间隔大小应是对数增长,使得在末尾插入元素的操作能在常数时间复杂度内完成。为了获得管理存储空间和有效动态增长的能力,vector会占用更多存储空间。 与其他容器比较

相较于dequelistforward_list等动态序列容器,vector访问元素更高效。在末尾添加和删除元素相对高效。对于不在末尾的删除和插入操作,vector的效率更低。vector具有比listforward_list更好的统一迭代器和引用。 还要注意:

每次使用vector都需要包含头文件<vector>vector是一个模板类,所以在使用时需要显示实例化。

//整型数组

vector<int> v1;

//浮点型数组

vector<double> v2;

二、vector的接口

vector的接口和string的接口很相似。

2.1 vector类对象的默认成员函数

构造函数声明 接口说明
<code>vector() 构造函数
<code>vector(size_type n,const value_type& val = value_type()) 构造并初始化n个val
<code>vector(Inputlterator first, Inputilerator last) 使用迭代器进行初始化构造
<code>vector(const vector& x) 拷贝构造
<code>vector& operator=(const vector& x) 赋值重载

2.1.1 构造函数

<code>explicit vector(const allocator_type& alloc = allocator_type());

explicit vector(size_type n, const value_type& val = value_type(),

const allocator_type& alloc = allocator_type());

template <class InputIterator>

vector(InputIterator first, InputIterator last,

const allocator_type& alloc = allocator_type());

vector(const vector& x);

// 其中的 allocator 是空间配置器,只是用于分配空间,目的为增加申请释放空间的效率

// 对于vector来说是一个模板类,所以我们在实例化时要声明其内置类型。

无参构造函数

示例代码:

// 构造函数创建一个空的vector,初始大小为0

vector<int> v1;// 无参(没有传入任何的参数)

这种构造函数创建一个空的vector,其初始化大小为0。

指定个数和初始值构造函数

示例代码:

vector<int> v2(3, 2);

for (int i = 0; i < v2.size(); i++)

{

cout << v2[i] << " ";

}

// 2 2 2

这里构造了一个包含3个元素每个元素初始值为2的vector

使用迭代器区间构造函数

示例代码

string s("abcd");

vector<int> v3(s.begin(), s.end());

这里使用迭代器区间来构造vector。需要注意的是,如果vector的内置类型与迭代器所指向的元素类型不匹配(如上述代码中v3vector<int>,而sstring类型,会发生隐式类型转换,将字符的 ASCII 值存储到vector中)。如果要正确存储字符,应声明为vector<char>。此外,还可以利用指针(其底层基于迭代器思维)来初始化vector,例如:

// 利用天然的迭代器 —— 指针进行初始化

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

vector<int> v2(a, a + 4);

vector是模板类,在实例化时需要声明其内置类型,如template <class T, class Alloc = allocator<T>> class vector;

2.1.2 拷贝构造函数

示例代码:

vector<int> v1(3, 2);

vector<int> v2(v1);// 拷贝构造

// 2 2 2

拷贝构造函数用于创建一个与已有vector完全相同的新vector

2.1.3 赋值重载

示例代码:

vector<int> v1(3, 2);// 创建3个2

vector<int> v2;

v2 = v1;// 赋值重载

//v3 = v1;//error:要求是同一类型的vector类

// 2 2 2

赋值重载操作将一个vector的值复制到另一个已存在的vector中。(值之间的拷贝)要求两个vector的类型相同,不同类型的vector之间无法进行赋值操作。

2.2 vector类对象的访问及遍历操作

2.2.1 opeartor[] 和 at()

operator[]

示例代码

#include <vector>

#include <iostream>

int main()

{

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

for (size_t i = 0; i < v.size(); i++)

{

std::cout << v[i] << " ";

}

std::cout << std::endl;

return 0;

}

// 1 2 3 4 5

operator[]是一种常见的访问vector元素的方式,通过下标来获取元素。下标越界会导致断言错误。

at()

示例代码

#include <vector>

#include <iostream>

int main()

{

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

for (size_t i = 0; i < v.size(); i++)

{

std::cout << v.at(i) << " ";

}

std::cout << std::endl;

return 0;

}

at()函数也用于访问元素,与operator[下标]不同的是,如果下标越界,at(下标)会抛出异常。

2.2.2 迭代器

begin() 和 end()(正向迭代器)

示例代码

#include <vector>

#include <iostream>

int main()

{

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

std::vector<int>::iterator it = v.begin();

while (it != v.end())

{

std::cout << *it << " ";

it++;

}

std::cout << std::endl;

return 0;

}

// 1 2 3 4 5

begin()返回指向vector第一个元素的迭代器,end()返回指向vector最后一个元素位置之后的迭代器。通过迭代器遍历vector中的元素。

rbegin() 和 rend()(反向迭代器)

示例代码

#include <vector>

#include <iostream>

int main()

{

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

std::vector<int>::reverse_iterator rit = v.rbegin();

while (rit != v.rend())

{

std::cout << *rit << " ";

rit++;

}

std::cout << std::endl;

return 0;

}

// 5 4 3 2 1

rbegin()返回指向vector最后一个元素的反向迭代器,rend()返回指向vector第一个元素之前的反向迭代器,用于反向遍历vector

2.2.3 范围for

- 示例代码

#include <vector>

#include <iostream>

int main()

{

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

for (auto e : v)

{

std::cout << e << " ";

}

std::cout << std::endl;

return 0;

}

// 1 2 3 4 5

范围for是一种简洁的遍历vector元素的方式吗,基于迭代器实现。

2.2.4 sort

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

sort(v.begin(), v.end());

for(auto e : v)

{

cout<< e << " ";

}

cout << endl;

迭代器与sort概述

迭代器与algorithm 头文件中的sort函数

在涉及迭代器时,algorithm头文件中的sort函数是一个重要的库函数。它有两个重载形式。 sort函数的第一个重载形式:升序排列。

// 传入的仿函数对象为less<int>(),升序排列

sort(v.begin(), v.end());

sort函数的第二个重载形式:默认升序排列

最后一个参数是 STL 六大组件中的仿函数。默认传入的仿函数对象为less<int>(),这是一个小堆,代表升序。如果要进行降序排序,需要传入的仿函数对象为greater<int>()(这是一个大堆,代表降序),如sort(v.begin(), v.end(), greater<int>());,运行结果可观察到确实进行了降序排序。

// 传入的仿函数对象为greater<int>() ,降序排列

sort(v.begin(), v.end(), greater<int>());

反向迭代器与sort函数:

反向迭代器传递的可行性

除了正向迭代器,反向迭代器也可以传递给sort函数。

sort(v.rbegin(), v.rend());

反向迭代器排序的特性

当传递反向迭代器并默认按照升序排序时,从后往前进行升序排序就相当于从前往后进行降序排序。

总结:

升序排序(正向迭代器 - 第一个重载形式)

sort(v.begin(), v.end());

- 效果:按照默认的升序规则对`v`容器中的元素进行排序。

降序排序(正向迭代器 - 第二个重载形式)

sort(v.begin(), v.end(), greater<int>());

- 效果:通过传入`greater<int>()`仿函数对象,实现对`v`容器中的元素进行降序排序。

反向迭代器排序

sort(v.rbegin(),v.rend());

- 效果:从后往前进行升序排序,等同于从前往后进行降序排序。

2.3 vector类对象的常见容量操作

2.3.1 size 和 capacity

size

示例代码

#include <vector>

#include <iostream>

int main()

{

std::vector<int> v(10, 1);

std::cout << "v的大小:" << v.size() << std::endl;

return 0;

}

// v的大小:10

size函数返回vector中当前元素的个数。

capacity

示例代码:

#include <vector>

#include <iostream>

int main()

{

std::vector<int> v;

std::cout << "初始容量: " << v.capacity() << std::endl;

for (int i = 0; i < 10; i++)

{

v.push_back(i);

}

std::cout << "添加10个元素后的容量: " << v.capacity() << std::endl;

return 0;

}

// 初始容量: 0

// 添加10个元素后的容量: 13

capacity函数返回vector当前分配的存储空间能够容纳的元素数量。在不同的 STL 版本中,vector的扩容机制有所不同。例如在VS下的扩容机制是呈现 1.5 进行增长的,其STL是P.J.版本;在 Linux 下却始终是呈现的一个2倍的扩容机制,其STL是SGI版本

2.3.2 empty

- 示例代码:

#include <vector>

#include <iostream>

int main()

{

std::vector<int> v;

std::cout << "初始时是否为空: " << (v.empty() ? "是" : "否") << std::endl;

v.push_back(1);

std::cout << "添加元素后是否为空: " << (v.empty() ? "是" : "否") << std::endl;

return 0;

}

// 初始时是否为空: 是

// 添加元素后是否为空: 否

empty函数用于判断vector是否为空,如果vector中没有元素,则返回true,否则返回false

2.3.3 reserve 和 resize

reserve

示例代码:

#include <vector>

#include <iostream>

int main()

{

std::vector<int> v;

v.reserve(10);

std::cout << "预留空间后的容量: " << v.capacity() << std::endl;

return 0;

}

// 预留空间后的容量: 10

示例代码2:

void TestVectorExpandOP()

{

vector<int> v;

size_t sz = v.capacity();

v.reserve(100); // 提前将容量设置好,可以避免一遍插入一遍扩容

cout << "making bar 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';

}

}

}

reserve函数用于预先分配指定大小的存储空间,以避免在后续添加元素时频繁扩容。需要注意的是,reserve只改变vector的容量,不改变元素个数(size)。

resize

示例代码:

#include <vector>

#include <iostream>

int main()

{

std::vector<int> v;

v.resize(3);

std::cout << "调整大小后的元素个数: " << v.size() << std::endl;

for (int i = 0; i < v.size(); i++)

{

std::cout << v[i] << " ";

}

std::cout << std::endl;

return 0;

}

// 调整大小后的元素个数: 3

// 0 0 0

resize函数用于改变vector的元素个数。

如果新的大小大于当前大小,则会在末尾添加默认值(对于基本类型,默认值为 0)的元素;如果新的大小小于当前大小,则会删除多余的元素。

2.4 vector类对象的修改操作

函数名称 功能
push_back 在数组后追加元素
insert 在指定位置追加元素
assign 使用指定数组替换原数组
pop_back 删除数组最后一个元素
erase 删除数组指定部分区间
swap 交换两个数组

2.4.1 push_back 和 pop_back

push_back

示例代码:

<code>#include <vector>

#include <iostream>

#include <string>

int main()

{

std::vector<std::string> v;

std::string name1("张三");

v.push_back(name1);

v.push_back(std::string("李四"));

v.push_back("王五");

for (auto str : v)

{

std::cout << str << " ";

}

std::cout << std::endl;

return 0;

}

// 张三 李四 王五

push_back函数用于在vector的末尾添加一个元素。它支持多种方式添加元素,如添加已有的对象、匿名对象或者利用单参数构造函数引发的隐式类型转换来添加元素。

pop_back

示例代码:

#include <vector>

#include <iostream>

int main()

{

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

v.pop_back();

for (auto num : v)

{

std::cout << num << " ";

}

std::cout << std::endl;

return 0;

}

// 1 2 (没有3)

pop_back函数用于删除vector末尾的一个元素。

2.4.2 insert 和 erase

insert

示例代码:

#include <vector>

#include <iostream>

int main()

{

std::vector<std::string> v;

v.insert(v.begin(), "刘琦");

v.push_back("赵六");

for (auto str : v)

{

std::cout << str << " ";

}

std::cout << std::endl;

return 0;

}

// 刘琦 赵六

insert函数用于在指定位置之前插入一个元素。它有多种重载形式,可以根据不同的需求在vector中的不同位置插入元素。

erase

示例代码:

#include <vector>

#include <iostream>

int main()

{

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

v.erase(v.begin() + 1);

for (auto num : v)

{

std::cout << num << " ";

}

std::cout << std::endl;

return 0;

}

// 1 3 5

erase函数用于删除指定位置的元素。它有两种重载形式,一种是传递单个迭代器来删除指定位置的元素,另一种是传递迭代器区间来删除多个元素。在想要删除容器内指定数据时,如果仅依靠vector本身的接口是无法直接做到的,需要借助<algorithm>头文件中的find函数。

2.4.3 find函数与迭代器失效

vector中,查找指定元素通常使用<algotithm>头文件中的find函数。示例代码:

#include <vector>

#include <iostream>

#include <algorithm>

int main()

{

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

auto pos = std::find(v.begin(), v.end(), 1);

while (pos != v.end())

{

v.erase(pos);

pos = std::find(pos, v.end(), 1);

}

for (auto num : v)

{

std::cout << num << " ";

}

return 0;

}

在这个例子中,当我们使用erase函数删除元素后,vector的内部结构可能会发生改变,导致之前获取的迭代器pos失效。如果继续使用失效的迭代器,会导致未定义行为,如程序崩溃或者产生错误的结果。为了避免这种情况,在删除元素后,需要重新获取有效的迭代器,如代码中在每次删除元素后重新调用find函数获取下一个匹配元素的迭代器。



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。