目录
原文章:
从C语言到C++/STL(二):vector动态数组 - 知乎
此文章为作者本人搬运至该网站。
上一章:
上一章的内容主要是串一下从c到c++的一小部分必备知识,这一章我们就开始正式学习STL!
首先第一个就是vector动态数组,下面是我个人所学习的文章:
C++ STL之vector详解_c++ stl vector-优快云博客
文中部分代码也源于这个文章,在此对原作者表示感谢!!!
一、vector是什么:
vector(又称向量类型,有个印象就行)是c++/STL中定义数组的另一种形式,为可变长度的动态数组。
我们之前肯定已经学过c语言中定义数组的方式,如:int a[5],
以及另一种定义动态数组(动态分配内存)的方式:malloc函数,
关于这一部分的知识,如果想要再巩固一下的话,可以去看下面这些文章:
而vector与它们不一样的是,vector不仅可以定义动态数组,而且还有一些对动态数组进行修改的函数(可以直接使用的那种)。
这里与学校所教的数据结构有所不同,数据结构相当于让我们自己造轮子,而STL已经摆出了车子等我们开了。_(:з」∠)_
二、vector基本用法:
1.首先也是最基础的,使用vector需要头文件:
#include <vector>
2.之后便可以用vector定义数组:
vector<int> a; //定义了一个名为a的一维数组,数组存储int类型数据
vector<double> b; //定义了一个名为b的一维数组,数组存储double类型数据
3.上面只是定义了数组的名字,长度,数据什么的都没有提到。要为数组定义长度,则需要下面的东西:
vector<int> v(n); // 定义一个长度为n的数组,初始值默认为0,数组下标可以使用的范围[0, n - 1]
vector<int> v(n, 1); // v[0] 到 v[n - 1]所有的元素初始值均为1
//注意:指定数组长度之后(指定长度后的数组就相当于正常的数组了)
第一个是定义了数组的长度,而第二个又给数组元素赋值了,应该是比较易懂的。
唯一要注意的一点就是,这里的括号为小括号!!!
4.当然也可以和c语言中定义数组一样直接通过定义数组各个元素来同时确定数组长度:
(ps:请问你还知道c语言怎么在不写数组长度的情况下用各个元素来定义数组吗?)
vector<int> a{1, 2, 3, 4, 5};
//数组a中有五个元素,分别为1,2,3,4,5,数组长度就为5
int b[] = {1, 2, 3}
//数组b中有三个元素,数组长度为3
cout << a[1] << endl;
cout << b[2] << endl;
5.数组拷贝(复制):
vector<int> a(n + 1, 0); //定义一个长度为n+1的数组,每个元素的数值都为0
vector<int> b(a); // 两个数组中的类型必须相同,a和b都是长度为n+1,初始值都为0的数组
vector<int> c = a; // 也是拷贝初始化,c和a是完全一样的数组
6.二维数组:
vector创建二维数组有三种方法:
第一个就是第一维固定,第二维可变长的二维数组:
vector<int> v[5]; //定义可变长二维数组
//注意:行不可变(只有5行), 而列可变,可以在指定行添加元素
//第一维固定长度为5,第二维长度可以改变
这个vector<int> v[5]; 可以理解为一个长度为5的一维数组v,数组中存储的是vector<int>数据类型,而vector<int>又可以继续开一维数组,合起来就是二维数组了。而且由于vector<int>只是定义了数组,并没有定义长度,因此可以用文章下面的一些vector函数(如push_back,pop_back等)给二维数组动态分配长度。
------------------
第二个就是创建行列均可变的二维数组:
vector<vector<int>> v;
之后便可以用以下方法:
vector<int> t1{1, 2, 3, 4};
vector<int> t2{2, 3, 4, 5};
v.push_back(t1);
v.push_back(t2);
v.push_back({3, 4, 5, 6}) // {3, 4, 5, 6}可以作为vector的初始化,相当于一个无名vector
如果此时运行并输出,则会出现:
说明每一次的push_back,都是对某一整行的元素进行赋值。
(对于push_back的理解,可以看后文)
------------------
第三个,行列均为固定长度:
vector<vector<int>> v( m, vector<int> (n) );
这里就是定义了一个m行n列的二维数组,初始值都为零。
而如果想要把初始值全为2的话,就在n后面改一下即可:
vector<vector<int>> v( m, vector<int> (n, 2) );
(这个n的小括号不能少)
或者用:
vector v(m, vector(n, 2));
不过是c17或者c20支持的形式,一些网站可能不支持。。。
重量级,vector定义结构体数组:
用法:
struct node{
int num;
char c;
};
bool cmp(node a, node b) { //从大到小进行排序
return a.num > b.num; //根据结构体中某个元素从而对整个结构体数组进行排序
}
int main (){
int n;
cin >> n;
vector <node> a(n);
for (int i = 0; i < n; i++) {
cin >> a[i].num;
}
sort(a.begin(), a.end(), cmp);
return 0;
}
如果不是从大到小排序,或者是要给结构体数组排序,那么就要用到cmp函数。
对结构体排序,这样就可以用结构体中另一个元素储存该结构体在数组中的位置,
因为sort是会改变数组元素的位置的,但是如果题目又需要输出元素所在的原本位置,那么另一个元素就可以派上用场了。
可以应用在下面这个题里面:
(需要用到贪心算法,不过不是很难,本人已AC)
三、vectorの一些可以直接使用的函数:
以上都是最最基础的定义和使用,而vector数组最方便的一点就是其拥有可以直接使用的函数(类似于数据结构里面的ADT,不过ADT只是个噱头,不能直接使用),在这里我放出从我所学习的文章中所贴出的函数:
乍一看这里面的函数是不是又多又杂?我来一个一个讲(所讲内容均经过本人实际代码测试):
1.前五个函数:
其中前五个应该不需要我赘述,只看含义应该就能看懂,如果不知道具体意思的话还可以参考下面的代码:
#include<iostream>
#include<vector>
using namespace std;
int main () {
vector<int> a{1, 2, 3, 4, 5};
cout << "原先数组a的各个数据:1, 2, 3, 4, 5" << endl;
int b = a.front();
int c = a.back();
cout << "数组a第一个数据:" << b << endl;
cout << "数组a最后一个数据:"<< c << endl;
a.pop_back();
c = a.back();
cout << "删除数组a最后一个数据后的最后一个数据:" << c << endl;
a.push_back(9);
c = a.back();
cout << "给数组a尾部加上一个数据'9'后的最后一个数据:" << c << endl;
c = a.size();
cout << "数组长度:" << c << endl;
return 0;
}
运行结果
2.a.clear()函数:
前五个经过本人测试都没有问题,但是到了第六个,也就是a.clear()——清除数组内所有元素时,我按照如下代码运行:
a.clear();
for (int i = 0; i < 5; i++)
cout << a[i] << " ";
却并没有出现全部元素清零的情况。
对于这个问题可以看这些文章:
C++:vector中使用.clear()函数_vector clear-优快云博客
C/C++编程:正确释放vector的内存(clear(), swap(), shrink_to_fit())_std::vector clear-优快云博客
C++ STL vector删除元素的几种方式(超级详细) - C语言中文网
简单来说,每个vector定义的数组都有size和capacity两个表示数组大小的参数,但是前者是该数组所真正有的元素的个数,而后者capacity是数组所申请的能够容纳所有元素的个数。
也就是capacity是指这个数组申请到的所有内存,而size是这个数组真正用到的内存。
打比方来说,就是capacity是瓶子能够灌满多少容量的水,而size指的是目前这个瓶子真正有多少容量的水。
这样子来说,capacity就一般会比size要大,而clear、pop_back和push_back等函数对数组内存的处理都是:
只改变size的数,而不改变capacity。
顺带一提,你也是可以直接通过a.size()和a.capacity()这两个函数来直接获得这个数组的size和capacity的大小
---------------------------------
这三篇文章分别讲了 用另一个vector或是assign函数来清除;
用shrink_to_fit函数来清除;
以及用erase或者swap函数来清除。
第一篇和第三篇都有其可取之处,感兴趣的可以自己看一看,我就不细讲了(东西太多了啊喂(´;ω;`)
3.shrink_to_fit()函数:
而对于第二篇文章中的shrink_to_fit,可以看这篇文章:
C++11:shrink_to_fit的基本作用与使用_shrink to fit-优快云博客
这个函数的功能,我目前认为是把capacity的大小改到和size的大小一样,
也就是说,如果先进行a.clear();的操作,(此时a的size大小为0,而capacity仍未改变,下一步再进行a.shrink_to_fit();的操作,那么你就会发现capacity也变成0了,此时这个数组才算是真正清除了(不是元素全部置为零,而是直接清楚内存)
如果觉得可以的话,就不妨用
v.clear();
v.shrink_to_fit();
这两个来清除vector数组。
4.resize(n):
C++中的resize()函数_c++ resize函数-优快云博客
C++ vector resize() 使用方法及示例 - 菜鸟教程
resize,由size函数就可以看出,其为改变数组的元素个数大小,并且改变的是size的个数,并非capacity的个数。
如果n要比原先size小的,那么就直接减少size,输出的也会减少。
而如果n要比size大,那么多出的部分的输出结果就是0。
而若是把函数改成resize(n, 100),那么其在n比size大的情况下就会把多出的部分的输出结果,从一开始是0变为100。
n若是比size小,那么和没有加上100的结果是一样的。
#include <iostream>
#include <vector>
using namespace std;
void cout_arr(vector<int> arr){
for (int i = 0; i < arr.size(); i++)
cout << arr[i] << ' ';
cout << endl;
return;
}
int main () {
vector<int> arr;
for (int i=1; i<10; i++)
arr.push_back(i);
arr.resize(5);
cout << "size为5时:" << endl;
cout_arr(arr);
arr.resize(8,100);
cout << "size扩大为8,并且扩大后的新元素都为100:" << endl;
cout_arr(arr);
arr.resize(12);
cout << "size扩大为12,扩大后的新元素为0:" << endl;
cout_arr(arr);
arr.resize(3, 500);
cout << "size缩小为3:" << endl;
cout_arr(arr);
return 0;
}
输出结果
5.arr.insert(it, x):
C++容器的insert()函数三种用法_c++ insert-优快云博客
C++ STL vector插入元素(insert()和emplace())详解 - C语言中文网
这个函数,从名字就可以看出,其作用为向数组中插入一个元素。而里面的 it 这个东西,指的是“迭代器”,从文章中我了解到,“迭代器”这个东西就可以看作是“地址”(也就是指针用到的那玩意)。
也就是说,这个函数的功能就是: 向it的地址所表示的元素,向前面插入一个元素x。
而既然 it 是地址,那么我们就需要找到能输出地址的函数(返回值是一个地址值),而这里我们便可以用这两个函数:
这两个函数返回的数值都是地址值,那么我们就可以用insert函数了:
如:
c.insert(c.begin(),-1);
意思就是将-1这个数插入到c[0]之前的位置,即c[0]变为-1,其余元素均向后移动一位。
而如果将c.begin()后面加上个2,即:
c.insert(c.begin() + 2,-1);
意思就是将-1这个数插入到c[2]之前的位置,就是c[1],c[2]及其后面的元素均向后移动一位。
而和和resize一样,insert更改的也是size的数值。
6.begin()和end():
(*)注意!对于上面的begin函数,其地址显然为第一个元素的地址,但是end函数返回的是最后一个元素的后一个位置的地址,不是最后一个元素的地址,所有STL容器均是如此。
对于上面这句话来说,可以用以下代码来解释:
#include <iostream>
#include <vector>
using namespace std;
void cout_arr(vector<int> arr) { //按顺序输出数组的各个数字
for (int i = 0; i < arr.size(); i++)
cout << arr[i] << ' ';
cout << endl;
return;
}
int main () {
vector<int> arr;
for (int i = 0; i < 5; i++)
arr.push_back(i + 1);
cout << "原数组:" << endl;
cout_arr(arr);
arr.insert(arr.begin(), 250);
cout << "在arr第一个元素 之前 插入一个元素250:" << endl;
cout_arr(arr);
arr.insert(arr.end(), 99);
cout << "在arr最后一个元素 的后一个位置 之前 插入一个元素99:" << endl;
cout_arr(arr);
return 0;
}
如果end函数返回的地址是最后一个元素,而并非最后一个元素的后一个位置,依据insert总是插在元素之前的位置的道理来说,那么最后的输出结果就应该是:
250 1 2 3 4 99 5
但是显然并非如此。
这一点感觉还是需要注意一下。
7.arr.erase(first, last):
C/C++语言中erase()函数的用法_c++erase函数用法-优快云博客
这个函数的功能为,删除从first到last的所有元素(包括first和last本身),
但是有一点要注意的就是,first 和 last 和第5个中的 insert 函数中的 it 一样,都是地址类型的。
比如:
arr.erase(arr.begin() + 1, arr.end() - 1);
这一步就是删除从第二个元素,直至倒数第二个的所有元素(包括这两个),并改变size的数值。
(这里为什么end减去一但还是
8.arr.empty():
C++ Vector 库 - empty() 函数 | C++ 标准库
STL:调用empty()而不是检查size()是否为0 - 知乎
这个是用来判断数组是否为空,即判断size是否为0(不是把数组变成空,只是用来判断的)如果是空,则返回真,反之返回假。
注意,由于empty只看的是size,而非capacity,所以,此”空“非彼”空“啊 (〜 ̄△ ̄)〜
既然empty只看size,而clear函数又只是把size置为零,那么就可以用clear和empty完成下面这个”组合技“:
arr.clear();
bool flag = arr.empty();
if (flag) {
cout << "清除完成!" << endl;
}
四、画外音——sort函数:
sort()函数为STL中自带的函数,其原理类似于快速排序。其方便之处就是我们不需要再像数据结构一样写出它的原理了。
使用方法:sort(c.begin(), c.end());
这样就也清楚明白了,sort接入的也是地址,对指定区间内的元素进行从小到大的排序。
这个很简单,应该不用多说。
(ps:本人在我们学校的oj进行测试时,发现编译错误,遂去网上寻找,发现有的网站在使用sort函数时,需要再加上 #include <algorithm> 这个头文件,不过dev c++里面不需要,反正注意一下也好)
五、访问并使用vector数组:
1.和普通数组一样的下标法:
很简单,就是a[0], a[1]什么的,应该不用解释吧?
2.迭代器访问(指针访问):
虽然不知道为什么叫迭代器,但就这样凑合用吧!
用迭代器访问法时,需要先声明一个迭代器变量(和声明指针变量一个道理):
vector<int> vi; //定义一个vi数组
vector<int>::iterator it = vi.begin(); //声明一个迭代器指向vi的初始位置
第二行中的vector<int>::iterator,都是固定格式,而真正声明的名字则是后面的 it,
于是便可以有以下法子:
vector<int> vi; //定义一个vi数组
vector<int>::iterator it = vi.begin(); //声明一个迭代器指向vi的初始位置
for(int i = 0; i < 5; i++)
cout << *(it + i) << " ";
cout << endl;
里面cout也符合指针的格式。
3.使用auto:
C++ auto用法及应用详解_c++ auto&-优快云博客
auto貌似是个大东西,不过我所学习的文章中的使用方法为:
vector<int> vi;
auto it = vi.begin();
while (it != vi.end()) {
cout << *it << "\n";
it++;
}
相当于把vector<int>::iterator it = vi.begin(); 给替换为了 auto it = vi.begin();
不过auto应该深藏不露,应该不止有这一个用法,其余与vector关联不大的我就先省略了。