一、向量的介绍 ????????向量?vector?是一种对象实体, 能够容纳许多其他类型相同的元素, 因此又被称为容器。 与string相同, vector 同属于STL(Standard Template Library, 标准模板库)中的一种自定义的数据类型, 可以广义上认为是数组的增强版。 ????????在使用它时, 需要包含头文件 vector,?#include<vector> ????????vector 容器与数组相比其优点在于它能够根据需要随时自动调整自身的大小以便容下所要放入的元素。此外, vector 也提供了许多的方法来对自身进行操作。
二、向量的声明及初始化 ?? ?vector 型变量的声明以及初始化的形式也有许多, 常用的有以下几种形式:
vector<int> a ; //声明一个int型向量a
vector<int> a(10) ; //声明一个初始大小为10的向量
vector<int> a(10, 1) ; //声明一个初始大小为10且初始值都为1的向量
vector<int> b(a) ; //声明并用向量a初始化向量b
vector<int> b(a.begin(), a.begin()+3) ; //将a向量中从第0个到第2个(共3个)作为向量b的初始值
除此之外, 还可以直接使用数组来初始化向量:
int n[] = {1, 2, 3, 4, 5} ;
vector<int> a(n, n+5) ; //将数组n的前5个元素作为向量a的初值
vector<int> a(&n[1], &n[4]) ; //将n[1] - n[4]范围内的元素作为向量a的初值
? ? ?? 三、元素的输入及访问 ?? ?元素的输入和访问可以像操作普通的数组那样, 用cin>>进行输入,?cout<<a[n]这样进行输出: ?? ?示例:
1 #include<iostream>
2 #include<vector>
3
4 using namespace std ;
5
6 int main()
7 {
8 vector<int> a(10, 0) ; //大小为10初值为0的向量a
9
10 //对其中部分元素进行输入
11 cin >>a[2] ;
12 cin >>a[5] ;
13 cin >>a[6] ;
14
15 //全部输出
16 int i ;
17 for(i=0; i<a.size(); i++)
18 cout<<a[i]<<" " ;
19
20 return 0 ;
21 }
????在元素的输出上, 还可以使用遍历器(又称迭代器)进行输出控制。在?vector<int> b(a.begin(), a.begin()+3) ;?这种声明形式中,?(a.begin()、a.begin()+3)?表示向量起始元素位置到起始元素+3之间的元素位置。(a.begin(), a.end())则表示起始元素和最后一个元素之外的元素位置。
?? ?向量元素的位置便成为遍历器, 同时, 向量元素的位置也是一种数据类型, 在向量中遍历器的类型为:?vector<int>::iterator。 遍历器不但表示元素位置, 还可以再容器中前后移动。
?? ?
?? ?在上例中讲元素全部输出部分的代码就可以改写为:
//全部输出
vector<int>::iterator t ;
for(t=a.begin(); t!=a.end(); t++)
cout<<*t<<" " ;
*t?为指针的间接访问形式, 意思是访问t所指向的元素值。
四、向量的基本操作
1>. a.size() //获取向量中的元素个数
2>. a.empty() //判断向量是否为空
3>. a.clear() //清空向量中的元素
4>. 复制
a = b ; //将b向量复制到a向量中
5>. 比较
保持 ==、!=、>、>=、<、<= 的惯有含义 ;
如: a == b ; //a向量与b向量比较, 相等则返回1
6>. 插入 - insert
a.insert(a.begin(), 1000); //将1000插入到向量a的起始位置前
a.insert(a.begin(), 3, 1000) ; //将1000分别插入到向量元素位置的0-2处(共3个元素)
vector<int> a(5, 1) ;
vector<int> b(10) ;
b.insert(b.begin(), a.begin(), a.end()); //将a.begin(), a.end()之间的全部元素插入到b.begin()前
7>. 删除 - erase
b.erase(b.begin()) ; //将起始位置的元素删除
b.erase(b.begin(), b.begin()+3) ; //将(b.begin(), b.begin()+3)之间的元素删除
8>. 交换 - swap
b.swap(a) ; //a向量与b向量进行交换
?
五、二维向量 ?? ?与数组相同, 向量也可以增加维数, 例如声明一个m*n大小的二维向量方式可以像如下形式:
vector< vector<int> > b(10, vector<int>(5)); //创建一个10*5的int型二维向量
????????在这里, 实际上创建的是一个向量中元素为向量的向量。同样可以根据一维向量的相关特性对二维向量进行操作。 例:
1 #include<iostream>
2 #include<vector>
3
4 using namespace std ;
5
6 int main()
7 {
8 vector< vector<int> > b(10, vector<int>(5, 0)) ;
9
10 //对部分数据进行输入
11 cin>>b[1][1] ;
12 cin>>b[2][2] ;
13 cin>>b[3][3];
14
15 //全部输出
16 int m, n ;
17 for(m=0; m<b.size(); m++) //b.size()获取行向量的大小
18 {
19 for(n=0; n<b[m].size(); n++) //获取向量中具体每个向量的大小
20 cout<<b[m][n]<<" " ;
21 cout<<"\n" ;
22 }
23
24 return 0;
25 }
?? ? ????????同样, 按照这样的思路我们还可以创建更多维的向量, 不过维数太多会让向量变得难以灵活控制, 三维以上的向量还需酌情使用。
_____________________________________________________________________________
1. 在C++中的详细说明 vector是C++标准模板库中的部分内容,它是一个多功能的,能够操作多种数据结构和算法的模板类和函数库。 vector之所以被认为是一个容器,是因为它能够像容器一样存放各种类型的对象,简单地说,vector是一个能够存放任意类型的动态数组,能够增加和压缩数据。
2. 使用vector, 必须在你的头文件中包含下面的代码: ? #include <vector>
vector属于std命名域的,因此需要通过命名限定,如下完成你的代码: using std::vector; vector?vInts; 或者连在一起,使用全名: ? ? ? std::vector?vInts; 建议使用全局的命名域方式: ? ? using namespace std;
3. 初始化 ?? vector?? ? ? ? ? ? ? ? // 创建一个空的vector。 vector?c1(c2) ? ? ? ? ?// 复制一个vector vector?c(n) ? ? ? ? ? ?// 创建一个vector,含有n个数据,数据均已缺省构造产生 vector?c(n, elem) ? ? ?// 创建一个含有n个elem拷贝的vector vector?c(beg,end) ? ? ?// 创建一个含有n个elem拷贝的vector
4. 析构函数 c.~vector?() ? ? ? ? ? // 销毁所有数据,释放内存
5. 成员函数 c.assign(beg,end)c.assign(n,elem) 将[beg; end)区间中的数据赋值给c。将n个elem的拷贝赋值给c。 c.at(idx) 传回索引idx所指的数据,如果idx越界,抛出out_of_range。
c.back() ? ? ?// 传回最后一个数据,不检查这个数据是否存在。 c.begin() ? ? // 传回迭代器中的第一个数据地址。 c.capacity() ?// 返回容器中数据个数。 c.clear() ? ? // 移除容器中所有数据。 c.empty() ? ? // 判断容器是否为空。 c.end() ? ? ? // 指向迭代器中末端元素的下一个,指向一个不存在元素。 c.erase(pos) ?// 删除pos位置的数据,传回下一个数据的位置。 c.erase(beg,end) ?//删除[beg,end)区间的数据,传回下一个数据的位置。 c.front() ? ? // 传回第一个数据。
get_allocator // 使用构造函数返回一个拷贝。
c.insert(pos,elem) ? ?// 在pos位置插入一个elem拷贝,传回新数据位置。 c.insert(pos,n,elem) ?// 在pos位置插入n个elem数据。无返回值。 c.insert(pos,beg,end) // 在pos位置插入在[beg,end)区间的数据。无返回值。 c.max_size() ? ? ? // 返回容器中最大数据的数量。 c.pop_back() ? ? ? // 删除最后一个数据。 c.push_back(elem) ?// 在尾部加入一个数据。 c.rbegin() ? ? ? ? // 传回一个逆向队列的第一个数据。 c.rend() ? ? ? ? ? // 传回一个逆向队列的最后一个数据的下一个位置。 c.resize(num) ? ? ?// 重新指定队列的长度。 c.reserve() ? ? ? ?// 保留适当的容量。 c.size() ? ? ? ? ? // 返回容器中实际数据的个数。 c1.swap(c2) swap(c1,c2) ? ? ? ?// 将c1和c2元素互换。同上操作。
operator[] ? ? ? ? // 返回容器中指定位置的一个引用。
6. 用法示例: 6.1. 创建一个vector vector容器提供了多种创建方法,下面介绍几种常用的。 创建一个Widget类型的空的vector对象: vector?vWidgets; 创建一个包含500个Widget类型数据的vector: vector?vWidgets(500); 创建一个包含500个Widget类型数据的vector,并且都初始化为0: vector?vWidgets(500, Widget(0)); 创建一个Widget的拷贝: vector?vWidgetsFromAnother(vWidgets); 向vector添加一个数据 vector添加数据的缺省方法是push_back()。 ? ? push_back()函数表示将数据添加到vector的尾部,并按需要来分配内存。
例如:向vector中添加10个数据,需要如下编写代码: for(int i= 0;i<10; i++) { ?vWidgets.push_back(Widget(i)); }
6.2 获取vector中指定位置的数据 vector里面的数据是动态分配的,使用push_back()的一系列分配空间常常决定于文件或一些数据源。 ? ? 如果想知道vector存放了多少数据,可以使用empty()。 ? ? 获取vector的大小,可以使用size()。
例如,如果想获取一个vector v的大小,但不知道它是否为空,或者已经包含了数据,如果为空想设置为-1, 你可以使用下面的代码实现: int nSize = v.empty() ? -1 : static_cast(v.size()); 6.3 访问vector中的数据 使用两种方法来访问vector。 1、 vector::at() 2、 vector::operator[] operator[]主要是为了与C语言进行兼容。它可以像C语言数组一样操作。 ? ? 但at()是我们的首选,因为at()进行了边界检查,如果访问超过了vector的范围,将抛出一个例外。 ? ? 由于operator[]容易造成一些错误,所有我们很少用它,下面进行验证一下: 分析下面的代码: vector?v; v.reserve(10); ? ? for(int i=0; i<7; i++) { ?v.push_back(i); } ? ? try {int iVal1 = v[7]; ?// not bounds checked - will not throw ?int iVal2 = v.at(7); ?// bounds checked - will throw if out of range }? ? ?? ? ? catch(const exception& e) { ?cout << e.what(); } 6.3 删除vector中的数据 vector能够非常容易地添加数据,也能很方便地取出数据, 同样vector提供了erase(),pop_back(),clear()来删除数据, 当删除数据时,应该知道要删除尾部的数据,或者是删除所有数据,还是个别的数据。
Remove_if()算法 如果要使用remove_if(),需要在头文件中包含如下代码:: #include?
Remove_if()有三个参数: 1、 iterator _First:指向第一个数据的迭代指针。 2、 iterator _Last:指向最后一个数据的迭代指针。 3、 predicate _Pred:一个可以对迭代操作的条件函数。 6.4 条件函数 条件函数是一个按照用户定义的条件返回是或否的结果,是最基本的函数指针,或是一个函数对象。 这个函数对象需要支持所有的函数调用操作,重载operator()()操作。 remove_if()是通过unary_function继承下来的,允许传递数据作为条件。
例如,假如想从一个vector中删除匹配的数据,如果字串中包含了一个值,从这个值开始,从这个值结束。 首先应该建立一个数据结构来包含这些数据,类似代码如下: #include? enum findmodes { ? FM_INVALID = 0, ? FM_IS, FM_STARTSWITH, FM_ENDSWITH, FM_CONTAINS };
typedef struct tagFindStr { UINT iMode; CString szMatchStr; } FindStr;
typedef FindStr* LPFINDSTR; 然后处理条件判断: class FindMatchingString : public std::unary_function<cstring, bool="">?{ public: FindMatchingString(const LPFINDSTR lpFS) : m_lpFS(lpFS) {} bool operator()(CString& szStringToCompare) const { bool retVal = false; ? ? switch (m_lpFS->iMode) { case FM_IS: { ?retVal = (szStringToCompare == m_lpFDD->szMatchStr); ?break; } case FM_STARTSWITH: { ?retVal = (szStringToCompare.Left(m_lpFDD->szMatchStr.GetLength()) ? ?== m_lpFDD->szWindowTitle); ?break; } case FM_ENDSWITH: { ?retVal = (szStringToCompare.Right(m_lpFDD->szMatchStr.GetLength()) ? ?== m_lpFDD->szMatchStr); break; } case FM_CONTAINS: { ?retVal = (szStringToCompare.Find(m_lpFDD->szMatchStr) != -1); ?break; } ?} ?return retVal; ? } private: LPFINDSTR m_lpFS; };
通过这个操作你可以从vector中有效地删除数据: ? ? FindStr fs; fs.iMode = FM_CONTAINS; fs.szMatchStr = szRemove; vs.erase(std::remove_if(vs.begin(), vs.end(), FindMatchingString(&fs)), vs.end()); Remove(),remove_if()等所有的移出操作都是建立在一个迭代范围上的,不能操作容器中的数据。 所以在使用remove_if(),实际上操作的时容器里数据的上面的。
看到remove_if()实际上是根据条件对迭代地址进行了修改,在数据的后面存在一些残余的数据, 那些需要删除的数据。剩下的数据的位置可能不是原来的数据,但他们是不知道的。 调用erase()来删除那些残余的数据。 注意上面例子中通过erase()删除remove_if()的结果和vs.enc()范围的数据。
7. 综合例子: //--------------------------------------------------------------------------- #include? #pragma hdrstop #include "Unit1.h" //---------------------------------------------------------------------------
#pragma package(smart_init) #pragma resource "*.dfm" TForm1 *Form1; #include? #include? using namespace std;
struct STResult { ? ? double Time; ? ? double Xp; ? ? double Yp; ? ? int id; };
//--------------------------------------------------------------------------- __fastcall TForm1::TForm1(TComponent* Owner) ? ? : TForm(Owner) { }
vector?ResultVector;
void __fastcall test() { ? ? //test ? ? //vector?ResultVector; ? ? STResult stritem; ? ? stritem.Time = .1; ? ? stritem.Xp = .1; ? ? stritem.Yp = .1; ? ? stritem.id = 1;
? ? ResultVector.push_back( stritem );
}
//--------------------------------------------------------------------------- void __fastcall TForm1::Button1Click(TObject *Sender) { ? ? test(); ? ? assert(ResultVector[0].id == 1); }
使用vector的注意事项(切记): 使用 vector<int> v; 声明一个容器v时,如果没有给他预定存储空间(如:vector<int> v;),则可以直接使用v.push_back(x)插入变量x,那么插入的第一个元素可以用v[0]访问到。 使用 vector<int> v(n); 声明一个容器v时,如果给他预定存储空间(如:vector<int> v(n);),则vector<int> v(n) 等价于vector<int> v(n,0); 如果要使得位置0存储元素x,则只能使用v[0]=x,如果使用v.insert(x)插入变量x,那么v的第一个元素还是0,即v[0]=0,因为v.push_back(x)是将x插入到v[n],又因为声明v时,v最多能存储n个元素,即x根本没有成功插入容器v中。 vector<int> v; v.push_back(1);//只能这样赋值,不能用v[0]=1; ? vector<int> v(n);//等价于vector<int> v(n,0); v[0]=1;//只能这样赋值,不能用v.push_back(1),因为此时的v.push_back(1)是把1插入到v[n]位置,但是v[n]越界了,实际上是无法插入的; ? vector<int> v(n,0); v[0]=1;//只能这样赋值,不能用v.push_back(1);
|