一、引言
在C语言中,不论是数组,还是结构体定义的数组,功能都比较欠缺,不是单纯的添加几个变量就能够解决的。缺少增删查改的功能,为了解决这个问题,C++决定填上C语言这个坑,但是填过坑的人都知道,想填一个坑必定需要在其他地方挖一个坑。这些坑呢,零零碎碎,等遇到我们再一一揭晓。C++的祖师爷本贾尼博士创造了vector这一容器,既然vector是容器,那它必然可以包含各种各样的数据。很多东西都具有相似性,容器本身就是要将相似的物品归位一类。这里需要用到一个模板的工具。
二、 vector的大概介绍(相当于数组,依照先后顺序排列)
vector是STL的六大组件之一,头文件:<vector>,与之前string不同的是vector的增删查改使用迭代器特别频繁。主要是基于迭代器的灵活性、适配性、安全。vector还有个别名是顺序表(sequence list),至于它为什么叫vector(向量),就不得而知了。既然有顺序表这个别名,那vector肯定与顺序表有着千丝万缕的联系,实际上vector的底层就是顺序表。在使用过程中记得按需实例化(模版的内容)。
三、模版(template)
模版本身可以理解为一个模具,分为函数模版和类模版,只要符合的内置类型(计算机规定的类型)或自定义类型都可以套用模版,编译器会根据函数模版或是类模版生成一个对应的函数或类(有多少种不同的类型,就会生成多少个),模版就像方程组中的未知量,直到编译时模板才会被编译器替换为准确类型,就像我们求方程时求x的大小一样,算一遍才会得出x的值。编译器也会根据接受到的变量类型或根据使用者的按需实例化将模版类型替换。遇到不同类型使用模版,编译器也会生成不同类型的函数和类,这样我们就可以将不同的类型复用在同一段代码上了,大大增加了代码的复用性,节省了时间。
//class和typename都可以用来定义模版类型
template<class + 自定义的类型名,typename + 自定义的类型名>
//vector的使用
std::vector<int> /*按需实例化,编译器自动将模版类型替换该类型*/ v(初始化值);
//没有初始化值时,不需要加(),不然编译器分不清这是函数,还是类对象。
//最好的初始化是使用{}(花括号),这样编译器默认只是一个变量
//当然{}中可以像数组那样写出多个值。
std::vector<int> v{ };
std::cout << v[0] << std::endl;
当然模版类型也可以给缺省参数。也就是给定一个确定的类型。如果没有类型的传入,编译器根据缺省参数生成一个确定的类型。
//class和typename在tmeplate的声明中是一样的,混着用都没关系
template<class T,class T1 = int>
//
template<typename T,typename T2 = double>
3.1函数模版
函数模版即在函数的形参的类型,与类模版不同的是他可以不需要按需实例化,因为函数只有在执行可执行文件时才会建立栈帧。而类模版则不同,在没有缺省参数时,需要按需实例化。
template<class T>
void swap(T& tmp1,T& tmp2)
{
T tmp = tmp1;
tmp1 = tmp;
tmp2 = tmp;
}
int main()
{
int a = 11 , b = 13;
swap<int>(a , b);
return 0;
}
3.2类模版
类的声明就和内置类型一样需要开好空间,如果连类型都不确定,还怎么计算内存开空间呢?哪怕是一个空类也有一字节的空间。因为编译器还会根据类的地址来查找类中的函数。这就需要类的声明的做好按需实例化。
四、 vector的详细介绍
4.1 vector中的迭代器
vector类中虽然,的很多函数都会使用迭代器。即是因为迭代器方便实用,又是因为要与其他的STL组件接轨。迭代器与指针相似,但有些迭代器可能不是指针,可能是用类封装,这个以后有机会再讲。
在vector中的迭代器,分为正向迭代器和反向迭代器。正向迭代器(iterator)、反向迭代器(reverse_iterator)。顾名思义正向迭代器是由顺序表起始位置(begin())到最后一个位置的的下一个位置(end()),反向迭代器就是从最后一个位置(rbegin())到顺序表的起始位置的下一个位置(rend()),可以通过解引用操作符访问,自然也可以通过解引用操作符修改值。(除const迭代器以外)
#include<iostream>
//vector的头文件
//为了与C语言作区分,不加.h后缀
#include<vector>
int main()
{
std::vector<int> v;
//尾插一个数据
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
v.push_back(5);
std::vector<int>::reverse_iterator it = v.rbegin();
while (it != v.rend())
{
std::cout << *it << " ";
++it;
}
//不可访问,end()、rend(()函数都是从
//std::cout << *(v.end()) << std::endl;
//不论是reverse_iterator还是iterator
//都是从正向或是反向的位置相加、减,前置后置加加、减减。
std::vector<int>::reverse_iterator it = v.rbegin();
while (it != v.rend())
{
std::cout << *it << " ";
++it;
}
//std::cout << *(v.end()) << std::endl;
return 0;
}
const迭代器只能访问,不能修改值。const迭代器直接往迭代器前加const_,直接const在迭代器前加const则会报错,不管迭代器的底层都是否为指针,它都是一个对访问方式的封装,并不是一个单纯的类型。
//初始化,当然也可以不加小括号,直接就大括号
std::vector<int> v({1,2,3,4,5,6});
std::vector<int>::const_iterator it = v.begin();
while (it != v.end())
{
std::cout << *it << " ";
++it;
}
4.2 vector中的尾插函数
push_back()和string中的push_back()效果相差无几,都是往最后的位置插入类型元素。至于是什么类型就看声明时按需实例化的是什么类型。当然它也可以用来存储char类型。也可以按需实例化成string。
//这里用int类型做个示范
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
v.push_back(5);
//这里用string做一个示范
vector<string> vs;
vs.push_back("ABC");
vs.push_back("ABC");
vs.push_back("ABC");
vs.push_back("ABC");
4.3vector中的insert和erase函数
vector中的在指定位置插入和在删除指定位置。这里需要注意的是此insert、erase非string中的insert、erase。需要用迭代器,这里用迭代器自然是为了和STL的其他容器保持一致。但是至少也要重载一个下标插入、删除,所以C++某些设计还是不完善。
C++11的官网:cplusplus.com - The C++ Resources Network
C++11vector中的insert:vector::insert - C++ Reference (cplusplus.com)
C++11vector中的erase:vector::erase - C++ Reference (cplusplus.com)
std::vector<int> v{ 1,3,4 };
v.insert(v.begin() + 1, 2);
//注意迭代器失效的问题
for (auto e : v)
{
std::cout << e << " " ;
}
std::cout << std::endl;
//注意end()、rbegin()都是顺序表之外的位置
//end()是末尾位置的下一个位置。
//rbegin()是开头的上一个位置。
v.erase(v.end() - 1);
for (auto e : v)
{
std::cout << e << " ";
}
用迭代器访问时,注意insert()后迭代器位置就已经失效了,需要更新一下,这里原因是扩容导致的。因为内存上的堆区也并不是所有位置都可以访问的,也有些是无法访问的,我们要保证顺序表的连续性,只能另寻他处再复制拷贝下来。
//迭代器更新,insert会自动返回插入位置的迭代器
std::cout << std::endl;
std::vector<int> v1{ 1,3,4 };
std::vector<int>::iterator it = v1.begin() + 1;
it = v1.insert(it, 2);
while (it != v1.end())
{
std::cout << *it << " ";
++it;
}
//错误示例:
std::cout << std::endl;
std::vector<int> v1{ 1,3,4 };
std::vector<int>::iterator it = v1.begin() + 1;
v1.insert(it, 2);
while (it != v1.end())
{
std::cout << *it << " ";
++it;
}
4.4vector的下标访问
和string一样,vector也可以用下标来访问。这里size()和capacity()就不讲了,string中讲过了。
std::vector<int> v{ 1,3,4 };
for(int i = 0;i < v.size();++i)
{
std::cout << v[i] << " ";
}
4.5vector拷贝构造和operator =
这是我们比较容易忽略的。顺嘴提醒一下。
std::vector<int> v{ 1 , 2 , 3 , 4 }
std::vector<int> v1 = v;