【C++要笑着学】vector 常用接口介绍 | 遍历操作 | 空间操作 | 增删查改(二)

简介: 本章开始讲解 vector,首先对 vector 进行介绍,然后讲解 vector 常用的接口。像 emplace 等涉及右值引用的接口,我们等后期讲C++11的时候再作讲解。话不多说,直接开讲。

Ⅲ. vector 空间


容量空间 接口说明
size 获取数据个数
capacity 获取容量大小
empty 判断是否为空
resize     (重点) 改变 vector 的 size
reserve   (重点)

改变 vector 放入 capacity


0x00 获取数据个数的 size()

💬 和 string 里的一样,是用来获取数据的个数的。

void test_vector2() {
  vector<int> v(6, 6);        // 生成6个6
  cout << v.size() << endl;
}

🚩 运行结果如下:

dbfcdec54707ae94da55a2888596ac51_a890dbff56c447bca808ea181e99e5e1.png


0x01 获取 vector 最大存储的 max_size()

💬 我们来用 max_size 看看有多大:

void test_vector3() {
  vector<int> v(6, 6);   // 生成6个6
  cout << v.max_size() << endl;
}

🚩 运行结果如下:

e72ecce6d964bc3d9dd9f40b69b955c0_7e0a418e097548e9b5d9c3b045ea4e99.png


0x02 改变 vector 容量的 reserve()

b94f41ff549676372aed1e69dc9e1e44_af21b5ba4ad443dca840bedef4dc4853.png

💬 reserve:


void test_vector3() {
  vector<int> v;
  v.push_back(1);
  v.push_back(2);
  v.push_back(3);
  v.push_back(4);
  v.reserve(100);
}

🐞 调试结果如下:

a54335bb2ea09567243402d53931b803_775528e274d543a590e8879182cc2385.png

reserve 会扩容,但是不会影响数据个数。[capacity] 4  → [capacity]100


0x03 改变 vector 大小的 resize()

1ab8e880882d92be7ea14aee0021c4f3_80f79539414149a9b2ae33c1834a2a00.png


" reserve 和 resize 都是卖房子的(开空间的),reserve 只是把房子卖给你(开空间),而 resize 是一条龙服务,房子卖给你(开空间)之后还帮你搞装修(初始化),你还可以指定装修风格(初始化内容),如果不指定会按默认的简约风格装修(按类型的缺省值初始化)"


💬 resize:

void test_vector4() {
  vector<int> v;
  v.push_back(1);
  v.push_back(2);
  v.push_back(3);
  v.push_back(4);
  v.resize(100);
}

a967a3918e0d1015c435140b96812c4d_132a6a3b8ee744ceaaa08f19ec5244b4.png

string 的 resize 如果不指定 "填充值" ,默认给的是 \0


而 vector 的 resize 如果不指定,默认给的是其对应类型的缺省值作为 "填充值"


这里是 int 就是 0,如果是指针,对应的缺省值就是空指针。


💬 我们来试着给 resize 提供指定 "填充值":

void test_vector5() {
  vector<int> v;
  v.push_back(1);
  v.push_back(2);
  v.push_back(3);
  v.push_back(4);
  v.resize(100, 6);   // 开100个空间,用6补
}

848b4cd8fb01cd64afa7dd1fcb9870f3_0a21d00b99144b6390358f224f5fe962.png


📌 注意事项:如果开的数据比之前更小,还会删除数据!

void test_vector4() {
  vector<int> v;
  v.push_back(1);
  v.push_back(2);
  v.push_back(3);
  v.push_back(4);
  v.resize(2);
}

31f3cbf262c604d119b4ed00db334ed5_61cb34a8cec34411a4a4021ae62b9ff4.png


当然,正如我们 string 章节所说,它的容量并不会因此改变。


这里虽然大小变成 2,数据也只有 [0]1 和 [1]2 了,但是容量仍然为 4。


0x04 vector 空间增长问题

① capacity 的代码在 VS 和 g++下分别运行会发现:VS下 capacity 是按 1.5 倍增长的,而 g++ 下 capacity 是按 2 倍增长的。 这个问题经常会考察,不要固化的认为,顺序表增容都是2倍,具体增长多少是根据具体的需求定义的。VS 是 PJ 版本 STL,g++ 是 SGI 版本 STL。


② reserve 只负责开辟空间,如果确定知道需要用多少空间,reserve 可以缓解 vector 增容的代价缺陷问题。


③ resize 在开空间的同时还会进行初始化,影响 size。


💬 测试:

// vector::capacity
#include <iostream>
#include <vector>
int main(void)
{
  size_t sz;
  std::vector<int> foo;
  sz = foo.capacity();
  std::cout << "making foo grow:\n";
  for (int i = 0; i < 100; ++i) {
  foo.push_back(i);
  if (sz != foo.capacity()) {
    sz = foo.capacity();
    std::cout << "capacity changed: " << sz << '\n';
  }
  }
}


🚩 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

🚩 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

Ⅳ. vector 增删查改


vector 增删查改 接口说明
push_back(重点) 尾插
pop_back  (重点) 尾删
find            (#include algorithm) 查找(注意这个是算法模块实现,不是 vector 的成员接口)
insert 在 pos 之前插入 val
erase 删除 pos 位置的数据
swap 交换两个 vector 的数据空间
operator[]  (重点) 像数组一样访问

 刚才讲 "下标 + 方括号" 的遍历方式的时候已经讲过 push_back 了,


也顺便解释了为什么不提供 push_front 和 pop_front ,所以这里就不多bb了。


0x00 pop_back() 尾删

81aa121b941de563827fb0479483e784_0f23f72e357c487aa34d72fda29f5c3d.png


现在我们把 pop_back 简单介绍一下,pop_back 就是尾删,可用来删除 vector 中的数据。


💬 pop_back:

void test_vector5() {
  vector<int> v;
  v.push_back(1);
  v.push_back(2);
  v.push_back(3);
  for (auto e : v) cout << e << " "; cout << endl;
  v.pop_back();   // 尾删:3
  for (auto e : v) cout << e << " "; cout << endl;
  v.pop_back();   // 尾删:2
  for (auto e : v) cout << e << " "; cout << endl;
}

🚩 运行结果如下:

3cfb8bdd63027090ab3e37e82663fa7f_8df1954c811a4b35a630ee3b9ca6f216.png


0x01  assign() 赋值

513797056e162fc58e44d425bf93e1b8_028b91e725ae48138f1857facbc888c5.png

assign 可以把 vector 的内容覆盖掉。允许给一段区间覆盖,也可以给  个 value 去覆盖。


💬 用  个 value 覆盖:

void test_vector6() {
  vector<int> v;
  v.push_back(1);
  v.push_back(2);
  v.push_back(3);
  v.push_back(4);
  v.assign(10, 5);   // 原来是1到4,现在改成10个5
}

🐞 调试结果如下:

a6118b662c47f6d83b5b8c2754a79756_1edc0688577b47b9a45e4ff9ca56a572.png


0x02 探讨:为什么 vector 不提供 find 接口

string、map、set 都有 find() 用,凭什么 vector 和 list 没有?


太不公平了吧!歧视这是赤裸裸的容器歧视??


其实,我们应该考虑的是 —— 为什么 string、map、set 能有 find 操作。


而 vector 之所以不提供 find ,是因为如果去查找元素效率就会是  ……


呵呵,凭什么不让我 vector 用 find() ,我偏要用!


可以的,"algorithm库" 里有通用的 find 操作,我们来一睹其芳容 ——

#include <algorithm>

该 find 内部是从 begin 到 end 进行一次遍历,其复杂度是  


值得一提的是,在C++中,凡是使用迭代器区间,都是左闭右开的 ——  


(map 和 set 接下来的章节会讲,以下部分可先作了解)


再去思考 map、set 为什么有 find() 通过迭代器从头到尾遍历 map 与 set 时,


得到的结果是按 key 排序的结果,而不是插入时的顺序,所以这两个容器没有 push_back 操作。


其实,插入到 map 与 set 中的元素会被组织到一颗红黑树上,红黑树是一颗平衡二叉树,


平衡二叉树是一颗二叉排序树,对一颗二叉排序树的查找有点像二分查找,复杂度是 ,


由于 map 与 set 的数据结构能有更快的查找方法,所以在容器内提供了 find 方法。


0x03 通用查找 find()

如果非要在 vector 里用 find() ,我们可以用通用 find。


找到了:返回一个迭代器区间那个值的位置;


没找到:返回的是  ,即右边开区间的位置。


💬 使用方法演示:

void test_vector7() {
  vector<int> v;
  v.push_back(1);
  v.push_back(2);
  v.push_back(3);
  v.push_back(4);
  vector<int>::iterator ret = find(v.begin(), v.end(), 3);
    // auto ret = find(v.begin(), v.end(), 3);
  if (ret != v.end()) {
  cout << "找到了" << endl;
  }
}

0x04 insert() 插入

d2de2040f1b173ba0dfedef0e9f0ead0_90bcde83f5f741dd8fad57574e0de40e.png


比如我们刚才用通用 find 找到了 3 的位置,


我们想在这个位置前面插入一个数据,就可以使用 insert() 插入。


💬 在 3 前面用 insert 插入一个 30:


void test_vector7() {
  vector<int> v;
  v.push_back(1);
  v.push_back(2);
  v.push_back(3);
  v.push_back(4);
  vector<int>::iterator ret = find(v.begin(), v.end(), 3);
  if (ret != v.end()) {
  cout << "找到了" << endl;
  v.insert(ret, 30);         // 在ret位置前面插入一个30
  }
  for (auto e : v) cout << e << " "; cout << endl;
}

🚩 运行结果如下:

dcde836206ae54b9d3cac1bcf14f1297_22215629c1d348fcaffd2b87c80ddda2.png


我们刚才说过,虽然没有 vector 没有提供 push_front,但是我们也可以用 insert 去头插。


💬 用 insert 去头插:

void test_vector8() {
  vector<int> v;
  v.push_back(1);
  v.push_back(2);
  v.push_back(3);
  v.push_back(4);
    for (auto e : v) cout << e << " "; cout << endl;
  v.insert(v.begin(), -1);   // 在起始位置插入一个-1
  for (auto e : v) cout << e << " "; cout << endl;
}

🚩 运行结果如下:

8ac575e7c5da4c9c92636ee36be0838f_23ba8e7623f749f382bef1d125f79be8.png


0x05 erase() 删除

272e5dccdf4d76e89bd66133cafb52ac_adf3ce07e198444287bd26837febdb6d.png


我们我们想删除数据,我们就可以用 erase 去删除。


💬 使用 erase 的时要判断一下有没有找到要删除的目标:

void test_vector9() {
  vector<int> v;
  v.push_back(1);
  v.push_back(2);
  v.push_back(3);
  v.push_back(4);
  for (auto e : v) cout << e << " "; cout << endl;
  vector<int>::iterator pos = find(v.begin(), v.end(), 5);
  if (pos != v.end()) {   // 判断pos是否存在
  v.erase(pos);       // 删除pos
  }
  for (auto e : v) cout << e << " "; cout << endl;
}

🚩 运行结果如下:

89933394d609ca2e6b91604c24c441b8_4bef9369628e4e25819b05900a920d0f.png


❓ 如果没有判断会怎么样?

void test_vector9() {
  vector<int> v;
  v.push_back(1);
  v.push_back(2);
  v.push_back(3);
  v.push_back(4);
  for (auto e : v) cout << e << " "; cout << endl;
  vector<int>::iterator pos = find(v.begin(), v.end(), 3);
  v.erase(pos);
  for (auto e : v) cout << e << " "; cout << endl;
}

🚩 运行结果如下:

8caefb3706745c8bbfa9efc4525b4feb_4ec1adf29c244f998e6e23d750bca889.png


如果要删除的目标存在,不会怎么样。


怕的就是要删的目标不存在!比如我要删个 5,但是 vector 里只有 1,2,3,4 。


vector<int>::iterator pos = find(v.begin(), v.end(), 5);
v.erase(pos);

d7fd2b4f2522e4fdb4a32883de1f6412_a9d272c00c0d47b39f6e25b5c7df3174.png

 (待删目标不存在导致)


 如果有了判断,就不会翻车了,如果待删目标不存在,就不会去走 erase()  。


因为 pos 如果找不到就会等于 end() 上的值,我们利用这一点进行 if 判断,

vector<int>::iterator pos = find(v.begin(), v.end(), 5);
if (pos != v.end()) {   // 检查!
  v.erase(pos);   
}

fe66827e6a5a1dc5cd959862f01e4a11_7efc98c7768d48d0b1e7c4930df37b99.png


0x04 clear() 清空数据

3a6768d93105ef6dfd3cd2de48768f02_8b506111846d4fbabc48da3dd6d85fb1.png

💬 clear:

void test_vector10() {
  vector<int> v;
  v.push_back(1);
  v.push_back(2);
  v.push_back(3);
  v.push_back(4);
  printf("清空前:");
  for (auto e : v) cout << e << " "; cout << endl;
  v.clear();
  printf("清空后:");
  for (auto e : v) cout << e << " "; cout << endl;
}

🚩 运行结果如下:

995485cc9fe5c8c3bed92979b50c8c23_070838b1e8b744188f2166d9b9d1901f.png

03984531e28b02ee03063c39b1928483_723c8d831064410c8bbe15e42e624f77.png




相关文章
|
2月前
|
存储 C++ 索引
【C++打怪之路Lv9】-- vector
【C++打怪之路Lv9】-- vector
23 1
|
2月前
|
人工智能 前端开发 JavaScript
前端大模型入门(二):掌握langchain的核心Runnable接口
Langchain.js 是 Langchain 框架的 JavaScript 版本,专为前端和后端 JavaScript 环境设计。最新 v0.3 版本引入了强大的 Runnable 接口,支持灵活的执行方式和异步操作,方便与不同模型和逻辑集成。本文将详细介绍 Runnable 接口,并通过实现自定义 Runnable 来帮助前端人员快速上手。
|
2月前
|
编译器 C++
【C++】—— vector模拟实现
【C++】—— vector模拟实现
|
2月前
|
算法 C++ 容器
C++之打造my vector篇(下)
C++之打造my vector篇(下)
29 0
|
2月前
|
存储 编译器 C++
C++之打造my vector篇(上)
C++之打造my vector篇(上)
29 0
|
2月前
|
算法 C++ 容器
【C++】—— vector使用
【C++】—— vector使用
|
7天前
|
存储 编译器 C语言
【c++丨STL】string类的使用
本文介绍了C++中`string`类的基本概念及其主要接口。`string`类在C++标准库中扮演着重要角色,它提供了比C语言中字符串处理函数更丰富、安全和便捷的功能。文章详细讲解了`string`类的构造函数、赋值运算符、容量管理接口、元素访问及遍历方法、字符串修改操作、字符串运算接口、常量成员和非成员函数等内容。通过实例演示了如何使用这些接口进行字符串的创建、修改、查找和比较等操作,帮助读者更好地理解和掌握`string`类的应用。
22 2
|
13天前
|
存储 编译器 C++
【c++】类和对象(下)(取地址运算符重载、深究构造函数、类型转换、static修饰成员、友元、内部类、匿名对象)
本文介绍了C++中类和对象的高级特性,包括取地址运算符重载、构造函数的初始化列表、类型转换、static修饰成员、友元、内部类及匿名对象等内容。文章详细解释了每个概念的使用方法和注意事项,帮助读者深入了解C++面向对象编程的核心机制。
41 5
|
19天前
|
存储 编译器 C++
【c++】类和对象(中)(构造函数、析构函数、拷贝构造、赋值重载)
本文深入探讨了C++类的默认成员函数,包括构造函数、析构函数、拷贝构造函数和赋值重载。构造函数用于对象的初始化,析构函数用于对象销毁时的资源清理,拷贝构造函数用于对象的拷贝,赋值重载用于已存在对象的赋值。文章详细介绍了每个函数的特点、使用方法及注意事项,并提供了代码示例。这些默认成员函数确保了资源的正确管理和对象状态的维护。
49 4
|
21天前
|
存储 编译器 Linux
【c++】类和对象(上)(类的定义格式、访问限定符、类域、类的实例化、对象的内存大小、this指针)
本文介绍了C++中的类和对象,包括类的概念、定义格式、访问限定符、类域、对象的创建及内存大小、以及this指针。通过示例代码详细解释了类的定义、成员函数和成员变量的作用,以及如何使用访问限定符控制成员的访问权限。此外,还讨论了对象的内存分配规则和this指针的使用场景,帮助读者深入理解面向对象编程的核心概念。
47 4