在C语言程序开发中,若是遇到了不能确定长度的数据类型结构,链表常常是一个不错的选择,但是C语言并未提供原生的链表结构,程序员不得不自己设计一套链表管理机制。虽说开源社区也提供了不少好用的通用设计,但毕竟不是C语言原生的语法,统一性有所欠缺。C++大概是察觉到了这一点,提供了一些不错的模板类,例如 STL 中的 Vector 就是一个很好用的模板类。
怎样定义 Vector?
C++语言中的 Vector 是一个动态的序列容器,随着元素的插入删除,它的 size 会自动的改变。Vector 在内存分布上有些类似于数组,其内部存储的元素是连续存储的,因此可以像数组那样通过下标访问各个元素。
Vector 属于标准命名空间 std
,使用它需要包含头文件<vector>
:
#include <vector>
然后便可定义 vector 对象了,因为 vector 是一个模板类,因此在定义对象时,应指明数据结构,例如:
std::vector<int> myvec;
上面这行C++语言代码便定义了一个 int 类型的 vector 对象 myvec,因此 myvec 可以存储若干 int 类型的数据。
初始化 Vector
在C++11标准之后,初始化 vector 可以在定义时直接执行,例如下面这段C++语言代码:
#include <vector>
int main()
{
std::vector<int> myvec = {1, 1, 2, 3, 5};
return 0;
}
在上面这段代码中,我们定义了一个 vector 对象 myvec,并在其中存储了 5 个 int 型的元素,按照前面讨论的,vector 容器内部的元素在内存上是连续的,因此此时 myvec 的内存模型大致如下,请看:
此时的 myvec 看起来和数组没什么两样,但是数组的长度一旦确定下来,就不能再改变了,而 vector 却可以根据实际需要动态的改变长度,此时 vector 又有些像“链表”。。
Vector 的迭代器
前面几节讨论其他几种STL容器时,都提到了相应的迭代器,vector 容器类也不例外,它也有这些通用的设计,通过迭代器,我们能够轻易的访问容器中的各个元素。
- begin:返回指向vector中第一个元素的迭代器指针。
- end:返回最后一个元素接下来位置(也即结束位置)的迭代器指针。
- rbegin 和 rend:类似于 begin 和 end,只不过是反向(reverse)的
- cbegin 和 cend:类似于 begin 和 end,只不过返回的迭代器指针是只读的(const)。
- crbegin 和 crend:前缀 cr 分别是 reverse 和 const。
下面是一段使用这几个迭代器的C++语言代码示例,请看:
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> v1;
for (int i = 1; i <= 5; i++)
v1.push_back(i+1);
cout << "Output of Vector with begin and end: ";
for (auto i = v1.begin(); i != v1.end(); ++i)
cout << *i << " ";
cout << "\nOutput of Vector with rbegin and rend: ";
for (auto itr = v1.rbegin(); itr != v1.rend(); ++itr)
cout << *itr << " ";
cout << "\nOutput Vector of with cbegin and cend: ";
for (auto itc = v1.cbegin(); itc != v1.cend(); ++itc)
cout << *itc << " ";
cout << "\nOutput Vector of with crbegin and crend : ";
for (auto icr = v1.crbegin(); icr != v1.crend(); ++icr)
cout << *icr << " ";
return 0;
}
编译这段C++语言代码时记得指定-std=c++11
选项,执行编译后的程序,得到如下输出:
# g++ t2.cpp -std=c++11
# ./a.out
Output of Vector with begin and end: 2 3 4 5 6
Output of Vector with rbegin and rend: 6 5 4 3 2
Output Vector of with cbegin and cend: 2 3 4 5 6
Output Vector of with crbegin and crend : 6 5 4 3 2
上述C++语言代码定义了一个 vector 对象并使用 push_back() 函数插入了若干元素,接着,我们使用了每一个迭代器函数遍历了 vector 容器中的各个元素,结合前文的解释,输出应该很好理解。
vector 元素排序
vector 容器中的元素顺序和它们被插入的顺序是一致的,不过得到一个排序后的元素序列是常有的需求,此时可使用 sort() 函数,例如下面这段C++语言代码示例:
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main()
{
vector<int> myvec = {10,50,30,20,60,40};
cout<<"Original Vector"<<endl;
for(auto i=myvec.begin();i<myvec.end();++i)
{
cout<<*i<<" ";
}
cout<<endl;
sort(myvec.begin(),myvec.end());
cout<<"Sorted Vector"<<endl;
for(auto i=myvec.begin();i<myvec.end();++i)
{
cout<<*i<<" ";
}
cout<<endl;
return 0;
}
编译并执行之,得到如下输出:
# g++ t3.cpp -std=c++11
# ./a.out
Original Vector
10 50 30 20 60 40
Sorted Vector
10 20 30 40 50 60
sort() 函数本身是一个位于 std
命名空间的模板函数,它的部分代码如下图所示:
可见,sort() 函数接收两个迭代器指针,它会将两个迭代器之间的元素排序。并且通过注释能够看出,sort() 函数排序的基本判断是 *(i+1)<*i
,程序员应自己把握这一点。
vector 的容量
成员函数 size() 可以返回当前 vector 容器内部存储的元素个数,按照前文讨论的,vector 容器类对象可以像数组那样访问元素,二者结合使用也是非常顺手的操作,例如下面这段C++语言代码示例:
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> myvec = {1, 1, 2, 3, 5, 8};
cout << "Vector Size : " << myvec.size() << endl;
for (size_t i=0; i<myvec.size(); i++)
cout << myvec[i] << " ";
cout << endl;
return 0;
}
编译这段代码并执行,得到如下输出:
# g++ t4.cpp -std=c++11
# ./a.out
Vector Size : 6
1 1 2 3 5 8
这么看来,将元素插入到 vector 容器的随机位置也是极其方便的,只需要指定下标就可以了:
myvec[random] = val;
但是这么做要确保 random 的值不超过 myvec.size(),否则就会导致错误。幸好C++语言中的 vector 提供了 resize() 函数,它可以将 vector 的容量也即 myvec.size() 改变到期望大小,下面是一段C++语言代码示例:
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> myvec;
myvec.resize(4);
cout << "\nVector Size after resize: " << myvec.size();
for (size_t i=0; i<myvec.size(); i++)
myvec[i] = i*3;
cout << "\nVector elements are: ";
for (auto it = myvec.begin(); it != myvec.end(); it++)
cout << *it << " ";
cout << endl;
return 0;
}
编译并执行这段代码,得到如下输出:
# g++ t5.cpp -std=c++11
# ./a.out
Vector Size after resize: 4
Vector elements are: 0 3 6 9
删除元素
类似于其他 STL 容器类,vector 也提供了 erase() 函数用于删除指定的元素。erase() 函数接收一个迭代器指针参数,用于指定被删除元素的位置,因此期望删除第 i 个元素的C++语言代码可以是下面这样的:
myvec.erase(myvec.begin() +i);
下面是一段完整的C++语言代码示例,请看:
#include <iostream>
#include <vector>
using namespace std;
int main()
{
// Initialize vector
vector<int> myvec = {1,1,2,3,5};
cout << "\nVector elements:";
for (int i = 0; i < myvec.size(); i++)
cout << myvec[i] << " ";
// remove the first element
myvec.erase(myvec.begin());
cout<<"\nVector size after erase: "<<myvec.size();
cout << "\nVector after erase operation: ";
for (int i = 0; i < myvec.size(); i++)
cout<<myvec[i]<<" " ;
cout << endl;
return 0;
}
编译并执行这段C++语言代码,可以得到如下输出,请看:
# g++ t6.cpp -std=c++11
# ./a.out
Vector elements:1 1 2 3 5
Vector size after erase: 4
Vector after erase operation: 1 2 3 5
另外一个需要说明的 vector 容器类的成员是 clear() 函数,它可以将容器内部的所有元素都删除:
myvec.clear();
因此要慎重使用 clear() 函数。
插入元素
C++语言中的 vector 容器类还提供了 insert() 函数用于插入元素到指定位置,下面是一段C++语言代码示例,请看:
#include <iostream>
#include <vector>
using namespace std;
int main()
{
// Assign vector
vector<int> myvec = {2,3,4};
cout << "\nInitial vector: ";
for (int i = 0; i < myvec.size(); i++)
cout << myvec[i] << " ";
// inserts 20 at the beginning, 30 after that
myvec.insert(myvec.begin(), 20);
myvec.insert(myvec.begin()+1,30);
cout << "\nNew vector after insert: ";
for (int i = 0; i < myvec.size(); i++)
cout << myvec[i] << " ";
cout << endl;
return 0;
}
编译并执行这段C++语言代码,可以得到如下输出,请看:
# g++ t7.cpp -std=c++11
# ./a.out
Initial vector: 2 3 4
New vector after insert: 20 30 2 3 4
程序一开始定义了 vector 对象 myvec,并且给了 3 个初值,稍后使用 insert() 函数将 20 和 30 两个元素插入到容器开头,结合借助于下标的随机访问,应该能够发现 vector 是一个非常好用的容器类。