目录
前言
vector是什么?
在C++中,存在着容器这一概念,vector,list,string都可以算作容器,今天要介绍的vector,是一个可以表示可变大小数组的序列容器,通俗一点的理解,vector是一个可以动态增长的数组实现的顺序容器,他和数组一样,通过下标访问元素,使用连续的存储空间存储元素,但是,他又和数组不同,他的空间是动态的,使用容器会自动判断扩容
关于vector的定义?
vector的定义,我们可以在cplusplus.com - The C++ Resources Network上查看:
我们可以看到,vector在定义时使用了类模板,这是为了让vector能够存储多种类型的数据,使用模板进行实例化操作,而后面的Alloc则是创建一个内存池,本意为加快效率,在这里不做了解
关于vector的构造函数?
我们可以看到,vector设计了四个构造函数:无参,带参构造,迭代器初始化,拷贝构造
第一个:无参构造
我们看到这个函数中给了一个模板参数,并且给了一个缺省值,这意味着我们可以进行无参构造
例如
void test1()
{
vector<int> v1;//无参构造
for (auto e : v1)
{
cout << e << " ";
}
cout << endl;
}
int main()
{
test1();
return 0;
}
可以发现打印空值,因为我们的无参构造并没有给里面的元素进行赋值
第二个:n个val构造
size_type:表示无符号整型,value_type:表示模板类型,也就是说这个构造是用n个元素去初始化
void test1()
{
vector<int> v2(10, 0);//10个0
for (auto e : v2)
{
cout << e << " ";
}
cout << endl;
}
int main()
{
test1();
return 0;
}
关于这个构造函数,我们发现,如果初始化类型为int,就会变成const int& val = int(),要知道,在C++中,自定义类型都有自己的默认的构造函数,内置类型本来是没有的,于是,模板的概念引入了,模板使得内置类型也能支持构造或默认构造函数,且初始化的值为0,让我们回到这里,那我们在这里使用T& val = 0可不可以?答案是否定的,要知道,vector使用模板的意义就在于:能够支持容纳多种类型的数据,如果缺省值给成0,显然这里的类型就不能改变,所以,我们可以传一个匿名对象T(),让他调用默认构造函数创建一个匿名对象
第三种:迭代器初始化
这里使用了模板,注意,在类模板中也可以写模板函数
void test1()
{
vector<int> v3(v2.begin(), v2.end());//迭代器初始化
string str("hello");
vector<int> v4(str.begin(), str.end());
//vector<int>::iterator it = v4.begin();
auto it = v4.begin();
while (it != v4.end())
{
cout << *it << " ";
++it;
}
cout << endl;
}
int main()
{
test1();
return 0;
}
这里vector构造函数是一个模板,任何迭代器都可以用来初始化,但是数据类型要匹配,那为什么str可以?这里的<int>,用了隐式类型转换
第四种:拷贝构造
void test1()
{
vector<int> v4;
v4.push_back(1);
v4.push_back(1);
v4.push_back(1);
v4.push_back(1);
v4.push_back(1);
vector<int> v5(v4);//拷贝构造
for (auto e : v5)
{
cout << e << " ";
}
cout << endl;
}
int main()
{
test1();
return 0;
}
vector的使用
reserve,resize函数
reserve,resize函数,是vector中的扩容函数,两者不同的点在于:reserve函数是只对capacity进行扩容,size保持不变,而resize则可以变化size的值,以及对扩容后的数据插入数据
reserve:
resize:
对于这两个函数的使用,可以看一下下面的例子:
void test_vector()
{
vector<int> v1;
//v1.reserve(100);
v1.resize(100);
//for (size_t i = 0; i < v1.size(); i++)
for (size_t i = 0; i < 100; i++)
{
v1[i] = i;
}
for (auto e : v1)
{
cout << e << " ";
}
cout << endl;
}
在这段代码中,当我们使用注释中的reserve和for循环进行运行,我们会发现程序能运行,但是不会打印任何数据,这是因为现在使用的for循环里的条件,是size(),而这里用size()能运行但不打印,因为reserve是提前开空间,不会改变size的值,size还是0,造成了for循环直接结束,那我们把for循环改为注释外的for循环能否正确运行呢?我们会发现,程序会直接报错,会发现不能访问开出来的空间,为什么呢?因为当我们使用for循环遍历打印时,用到了 [ ] ,[ ]里加了一个断言,断言访问的下标必须是size-1,顺序表规定数据访问必须在0到size-1,而现在size是0,这就造成了size-1越界访问,那我们就想用这种方式 进行遍历打印该怎么搞?这时候就要用到resize函数了,我们将v1 resize到100,这时他的capacity和size都被扩容到了100,这时候使用for循环遍历就可以了。总之,eserve 提前开空间,size没变,可以避免扩容,不用resize,让数据插入时从0开始插入,如resize不给值,默认初始化成\0,即0
shrink_to_fit接口
这个函数并不常用,所以只是简单提一下:这是缩容接口,比如如果capacity比size大,使用该接口通过异地的方式新建一个小的空间并释放掉原来的
使用举例:vector中的clear会清除数据,但不会清除capacity,那如果想要清除的时候怎么搞?我们不能显示的调用析构函数,所以这里可以用shrink_to_fit即缩容接口,size在clear后是0,使用缩容,让capacity也变成0
vector中的find函数?
很不幸,vector中并没有find函数,而且不止vector,list和其他容器也没有find函数,因为大佬们在设计这些容器的时候发现,这些容器使用的find函数基本上都一样,所以他们直接在算法库中写了一个通用的find函数,(逻辑都一样就直接写一个通用),传一个迭代器过去就能用,通用的find函数返回last迭代器的值,但是为什么string中就有一个find函数?这是因为string是对应的字符类型,string其实也可以用这个通用的,但是string的需求不同,string中有查找字符和查找字符串的find函数,而通用的find函数不能够查找字符串
vector中的流插入和流提取?
容器只有string提供流插入流提取。
swap函数
vector中写了一个swap函数,因为vector如果用算法库中的swap进行交换会涉及到深拷贝的问题,所以直接用vector中的swap函数,两个指针进行交换。
vector中的数据操作函数
push_back,pop_back函数
这两个函数原理基本相同,即尾插和尾删vector中的数据
push_back:
pop_back:
void test_vector()
{
vector<int> val;
val.push_back(1);
val.push_back(2);
val.push_back(3);
val.push_back(4);
val.push_back(5);
val.pop_back();
for (auto e : val)
{
cout << e << " ";
}
cout << endl;
}
insert,erase函数
这两个函数允许用户在任意位置添加,删除一个或一段数据
insert:
通过图中的重载函数可以看到,insert通过传入位置,并在位置处进行插入,同时他也支持迭代器传入。
void test_vector3()
{
vector<int> val;
val.push_back(1);
val.push_back(2);
val.push_back(3);
val.push_back(4);
val.push_back(5);
val.insert(val.begin(), 0);
auto it = find(val.begin(), val.end(), 3);
if (it != val.end())
{
val.insert(it, 30);
}
for (auto e : val)
{
cout << e << " ";
}
cout << endl;
}
erase:
与insert不同的点在于:erase函数只需要传入一个或一段位置,找到后将传入的位置的数据进行删除。
void test_vector3()
{
vector<int> val;
val.push_back(1);
val.push_back(2);
val.push_back(3);
val.push_back(4);
val.push_back(5);
val.insert(val.begin(), 0);
auto it = find(val.begin(), val.end(), 3);
if (it != val.end())
{
val.insert(it, 30);
}
for (auto e : val)
{
cout << e << " ";
}
if(it != val.end())
{
val.erase(it);
}
for (auto e : val)
{
cout << e << " ";
}
cout << endl;
}
值得注意的是,这两个函数在使用时会有一个迭代器失效的问题,这个问题我们会在模拟实现的时候展开说说
assign函数
assign函数也是一个插入函数,不过他干的事情是:将传入的一个或一段位置的值清空,再填入输入的值,可以通过迭代器传入一段位置进行清空填入,也可以传入n和内容,让该内容填充n次
void test_vector3()
{
vector<int> val;
vector<int> v1;
v1.push_back(1);
v1.push_back(1);
v1.push_back(1);
v1.push_back(1);
v1.push_back(1);
val.push_back(1);
val.push_back(2);
val.push_back(3);
val.push_back(4);
val.push_back(5);
val.assign(v1.begin(), v1.end());
val.assign(3, 123);
for (auto e : val)
{
cout << e << " ";
}
cout << endl;
for (auto e : val)
{
cout << e << " ";
}
cout << endl;
}
vector中的数据查看函数
下面这些函数很常用但理解起来并不困难,所以选择略讲:D
size,capacity,empty函数
三个函数,前两个返回类中size和capacity的值,通过调用可以查看size和capacity的大小。empty函数则是判断是否为0
size:
capacity:
empty:
operator[]函数
operator[]函数返回对向量容器中位置n处元素的引用,也就是vector中查看元素的方法之一,可以通过for循环遍历用[]进行打印元素
operator[]:
总结
到这里位置,初学阶段中vector相对重要的函数就差不多介绍完了,其实对于容器类的学习,要学会举一反三,各类容器的函数设计和实现其实都大致相同,正因如此,当我们学会一个后,其他的也就能简单起来!