1.array
有了前面一章的基础,我们就可以正式来学习使用各个容器了,首先是序列式容器。
注意这些容器会用到最基础的数据结构,如果你对此还不慎了解,那么可以先看看这篇文章:数据结构与算法。
这个容器可能我们平时用的较少,它是一种定长的容器,看名字就知道,基本和我们平时使用的数组没有什么区别。
声明的方式为:
#include<array>
#include<iostream>
using namespace std;
int main() {
array<int, 100> arr;
}
其模板参数, 第一个为要存储的类型,第二个为大小。
其特点是一旦声明了,这个数组的大小就不可更改了。
你可以像普通数组那样赋值与获取值:
array<int, 100> arr;
arr[0] = 1;
cout << arr[0]<<endl;
但通过下标获取值容易出现访问越界,比如这里为100
个元素,范围为0-99
,你却访问下标为100
的元素,就超界了,有时就会出现获取的值不对,或者程序直接崩溃。
为了解决这个问题,我们可以通过它提供的函数来获取值:
array<int, 100> arr;
try
{
cout<<arr.at(100);
}
catch (const std::exception& e)
{
cout << e.what();
}
然后我们在最外层套一个异常处理,如果越界了,它就会抛出异常,然后我们这个try catch
块就会捕获到。
不熟悉异常用法的可以参考文章:异常处理。
我们还可以使用get
函数,在编译期间获取这个值:
array<int, 100> arr;
cout << get<100>(arr);
如果越界, 就会直接编译失败:
注意这个get
函数, 是std
里面的,它的模板参数就是你想要获取元素的下标值,而参数就是哪一个对象。
同时要注意,由于它是编译期间完成取值,所以其模板参数不能为变量,只能为常量。
你也可以获取这个数组指针:
array<int, 100> arr;
arr[0] = 1;
int *num=arr.data(); //获取数组指针
cout << num[0]<<endl; //通过指针来获取值
也可以为它所有的元素都填充数据,并遍历输出:
array<int, 10> arr;
arr.fill(100);
for (auto i = arr.begin(); i != arr.end(); i++)
{
cout << *i << endl;
}
当然你也可以使用反向迭代器来逆向输出:
array<int, 10> arr;
for (int i = 0; i < arr.size(); i++)
{
arr[i] = i;
}
for (auto i = arr.rbegin(); i < arr.rend(); i++)
{
cout << *i << endl;
}
begin
函数前面添加一个r
,即反向的意思,rbegin
函数返回指向最后一个元素的迭代器,而rend
则返回第一个元素之前一个位置的迭代器。
注意这里同样使用的是++
,这就是反向迭代器。
还有更加简洁的遍历方式:
for (auto& i : arr)
{
cout << i << endl;
}
所有支持迭代器的容器,均支持这种遍历方式
auto
为自动推断arr
中元素的数据类型,这里可以写为int
,&
代表引用,如果不添加,那么就是拷贝过来赋值给i
变量,对于对象元素来说会造成更大的消耗。
除了上面的普遍用法外,它对字符数组的支持也非常好,我们完全可以用它来代替普通的char[]
字符数组:
array<char, 100> arr;
strcpy_s(arr.data(),100,"https;//www.kucoding.com");
cout << arr.data() << endl;
2.vector
这个容器相信大家应该是最熟悉的,因为它长度可变,几乎可无限追加数据,并且可以像普通数组那样访问,使用起来非常方便,使用频率也非常高。
但使用它也需要注意效率影响,它底层同样是一个有限的连续内存块,它和string
一样, 一旦这个内存块不够了,那么它就会重新开辟一块更大的内存,并将原有的数据拷贝到新内存块中。
理解连续内存块这个概念很重要, 因为这意味着string
不能做的事,就可以用vector<char>
来做。
比如string
默认是以\0
结尾的,这在处理网络数据的时候非常不便利,你不能保证别人发来的数据字节里面没有\0
,比如给你发个图片字节数据,里面肯定或多或少存在\0
字节。
但用vector
就完全没有这个问题,因为它可以将\0
作为一般字符处理,最后可以通过它的data
函数来获取这个内存块地址,非常方便。
初始化的方式有很多:
#include<vector>
#include<iostream>
using namespace std;
int main() {
vector<int> v; //最常见的,声明一个空的vector
vector<int> v1(20); //初始化容器的内存大小,可以容纳20个
vector<int> v2(20,100); //初始化容器的内存大小20,并全部初始化为100
vector<int> v3{1,2,3,4}; //初始化容器的初始值
}
添加元素的方式:
vector<int> v;
v.push_back(1); //尾部添加
v.emplace_back(2); //尾部添加
auto beg = v.begin(); //获取开始迭代器
beg=v.emplace(beg, 200); //在指定位置插入一个数,其它元素后移,并返回插入元素的迭代器
beg = v.insert(beg, 20); //在指定位置插入一个数,其它元素后移,并返回插入元素的迭代器
//这里通过它们的返回值来更新开始迭代器,保证它始终指向第一个元素
v.insert(beg + 1, 40);//通过开始迭代器加一个数,实现任意位置插入
//或v.emplace(beg+1, 40)
beg = v.begin(); //重新获取开始迭代器
v.insert(beg, 10, 100); //在开始迭代器的位置,插入10个100
这里注意一下push_back
与emplace_back
函数的区别,它们的功能都是在尾部添加元素。
但push_back
在添加前会先创建,然后再拷贝或移动到尾部,而emplace_back
则是直接在尾部创建这个元素,效率更高, emplace
与insert
也是同理。
特别注意:一旦你在头部插入元素,那么先前的开始迭代器指向的就不再是第一个元素了,需要重新获取。
访问元素的方式:
vector<int> v{1,2,3,4,5};
cout << v[0]<<endl; //直接通过下标访问
cout << v.at(1) << endl; //通过at函数访问,如果越绝会抛出异常,可用try catch 语句进行捕获处理
cout << v.front() << endl; //访问头部元素
cout << v.back() << endl; //访问尾部元素
修改方式: