C++ 容器模版类 - vector
一、概述
vector 称为容器模板类,是同一种类型的对象的集合,每个对象都有一个对应的整数索引值。vector 不是一种数据类型,而只是一个类模板,可用来定义任意多种数据类型。vector 类型的每一种都指定了其保存元素的类型,如vector<int> 和vector<string> 都是数据类型。vector是一个能够存放任意类型的动态数组,能够增加和压缩数据。
#include <vector>
using namespace std;
二、vector定义与初始化
vector<typeName> vt(length);
//length可以是整型常量,也可以是整型变量,也可以为空;当为空时,表示创建一个0长度的vector。
vector <T> vt1; //vector 保存类型为 T 的对象。默认构造函数 vt1 为空。
vector <T> vt2(vt1); //vt2 是 vt1 的一个副本,深拷贝。
vector <T> vt3(n, i); //vt3 包含有 n 个 i 元素。
vector <T> vt4(n); //vt4 包含有 n 个元素。
vector <T> vt5(vt4.begin(), vt4.begin()+3); //将向量 vt4 的 0 - 2 索引的值初始化为vt5;vt4.end() 表示结束位置。
//使用数组初始化向量
int n[] = {1, 2, 3, 4, 5};
vector <int> vt5(n, n+5); //将数组的前5个元素作为向量 vt5 的初值。
vector <int> vt6(&n[1], &n[4]); //将 n[1] 到 n[4] 范围的元素作为向量 vt6 的初始值。
1 #include <iostream> 2 #include <vector> 3 using namespace std; 4 5 template <typename T> 6 void Showelements(vector <T>vec, int n); 7 8 int main(void) 9 { 10 //虽然可以对给定元素个数的 vector 对象预先分配内存, 11 //但更有效的方法是先初始化一个空 vector 对象, 12 //然后再动态地增加元素 13 14 //定义,且长度为 0 的空vector对象 15 vector <int> vt1; 16 17 //定义,初始化每一个元素为0 18 vector <int> vt2(10); 19 Showelements(vt2, 10); 20 21 //定义,初始化每一个元素为单字符 ‘*’ 22 vector <char> vt3(10, '*'); 23 Showelements(vt3, 10); 24 25 vector <string> vt4(10, "hi!"); 26 Showelements(vt4, 10); 27 28 //定义,深拷贝初始化 29 vector <int> vt5(vt2); 30 Showelements(vt5, 10); 31 32 } 33 34 template <typename T> 35 void Showelements(vector <T> vec, int n) 36 { 37 cout << "-----------------------" << endl; 38 int i = 0; 39 for (i = 0; i < n; i++) 40 { 41 cout << "\t#" << i << ": " << vec[i] << endl; 42 } 43 return; 44 }
二、vector对象作为参数
常用的vector容器作为参数时,有三种传参数方式,分别如下:
> function1(vector <T> vec); //传值
> function2(vector <T> & vec); //传引用
> function3(vector <T> * vec); //传指针
// 分别可对应const形式
三种方式分别对应的调用方式:
function1(vec); //传值
function2(vec); //传引用
function3(&vec); //传地址
三种方式的效果:
发生拷贝构造;
不发生拷贝构造;
不发生拷贝构造;
1 #include <iostream> 2 #include <vector> 3 4 using namespace std; 5 6 void function1(std::vector<std::vector<int> > vec) 7 { 8 cout<<"-----------------------------------------"<<endl; 9 //打印vec的地址 10 cout<<"function1.&vec:"<<&vec<<endl; 11 //打印vec[i]的地址(即第一层vector的地址) 12 cout<<"function1.&vec[i]:"<<endl; 13 for(int i=0;i<2;i++) 14 cout<<&vec[i]<<endl; 15 //打印vec的各元素地址 16 cout<<"function1.&vec[i][j]:"<<endl; 17 for(int i=0;i<2;i++) 18 { 19 for(int j=0;j<3;j++) 20 cout<<&vec[i][j]<<" "; 21 cout<<endl; 22 } 23 cout<<"---------------------------"<<endl; 24 //打印vec的各元素值 25 cout<<"function1.vec[i][j]:"<<endl; 26 for(int i=0;i<2;i++) 27 { 28 for(int j=0;j<3;j++) 29 cout<<vec[i][j]<<" "; 30 cout<<endl; 31 } 32 } 33 void function2(std::vector<std::vector<int> >& vec) 34 { 35 cout<<"-----------------------------------------"<<endl; 36 //打印vec的地址 37 cout<<"function2.&vec:"<<&vec<<endl; 38 //打印vec[i]的地址(即第一层vector的地址) 39 cout<<"function2.&vec[i]:"<<endl; 40 for(int i=0;i<2;i++) 41 cout<<&vec[i]<<endl; 42 //打印vec的各元素地址 43 cout<<"function2.&vec[i][j]:"<<endl; 44 for(int i=0;i<2;i++) 45 { 46 for(int j=0;j<3;j++) 47 cout<<&vec[i][j]<<" "; 48 cout<<endl; 49 } 50 cout<<"---------------------------"<<endl; 51 //打印vec的各元素值 52 cout<<"function2.vec[i][j]:"<<endl; 53 for(int i=0;i<2;i++) 54 { 55 for(int j=0;j<3;j++) 56 cout<<vec[i][j]<<" "; 57 cout<<endl; 58 } 59 60 } 61 void function3(std::vector<std::vector<int> > *vec) 62 { 63 cout<<"-----------------------------------------"<<endl; 64 //打印vec的地址 65 cout<<"function3.&vec:"<<vec<<endl; 66 //打印vec[i]的地址(即第一层vector的地址) 67 cout<<"function3.&vec[i]:"<<endl; 68 for(int i=0;i<2;i++) 69 cout<<&(*vec)[i]<<endl; 70 //打印vec的各元素地址 71 cout<<"function3.&vec[i][j]:"<<endl; 72 for(int i=0;i<2;i++) 73 { 74 for(int j=0;j<3;j++) 75 cout<<&(*vec)[i][j]<<" "; 76 cout<<endl; 77 } 78 cout<<"---------------------------"<<endl; 79 //打印vec的各元素值 80 cout<<"function3.vec[i][j]:"<<endl; 81 for(int i=0;i<2;i++) 82 { 83 for(int j=0;j<3;j++) 84 cout<<(*vec)[i][j]<<" "; 85 cout<<endl; 86 } 87 } 88 89 int main() 90 { 91 //创建2*3的vector容器v,初始值均初始化为0 1 2 1 2 3 92 std::vector<std::vector<int> > v(2,std::vector<int>(3,0)); 93 for(int i=0;i<2;i++) 94 { 95 for(int j=0;j<3;j++) 96 v[i][j]=i+j; 97 } 98 99 //打印v的地址 100 cout<<"&v:"<<&v<<endl; 101 //打印v[i]的地址(即第一层vector的地址) 102 cout<<"&v[i]:"<<endl; 103 for(int i=0;i<2;i++) 104 cout<<&v[i]<<endl; 105 //打印v的各元素地址 106 cout<<"&v[i][j]:"<<endl; 107 for(int i=0;i<2;i++) 108 { 109 for(int j=0;j<3;j++) 110 cout<<&v[i][j]<<" "; 111 cout<<endl; 112 } 113 114 cout<<"---------------------------"<<endl; 115 //打印v的各元素值 116 cout<<"v[i][j]:"<<endl; 117 for(int i=0;i<2;i++) 118 { 119 for(int j=0;j<3;j++) 120 cout<<v[i][j]<<" "; 121 cout<<endl; 122 } 123 124 function1(v); 125 function2(v); 126 function3(&v); 127 128 return 0; 129 }
三、vector 容器函数
vector(向量): C++中的一种数据结构,确切的说是一个类.它相当于一个动态的数组,当程序员无法知道自己需要的数组的规模多大时,用其来解决问题可以达到最大节约空间的目的.
它是一个多功能的,能够操作多种数据结构和算法的模板类和函数库。vector之所以被认为是一个容器,是因为它能够像容器一样存放各种类型的对象,简单地说,vector是一个能够存放任意类型的动态数组,能够增加和压缩数据。使用vector库,必须在文件首#include<vector>
常用函数:
1.push_back 在数组的最后添加一个数据
2.pop_back 去掉数组的最后一个数据
3.at 得到编号位置的数据
4.begin 得到数组头的指针
5.end 得到数组的最后一个单元+1的指针
6.front 得到数组头的引用
7.back 得到数组的最后一个单元的引用
8.max_size 得到vector最大可以是多大
9.capacity 当前vector分配的大小
10.size 当前使用数据的大小
11.resize 改变当前使用数据的大小,如果它比当前使用的大,者填充默认值
12.reserve 改变当前vecotr所分配空间的大小
13.erase 删除指针指向的数据项
14.clear 清空当前的vector
15.rbegin 将vector反转后的开始指针返回(其实就是原来的end-1)
16.rend 将vector反转构的结束指针返回(其实就是原来的begin-1)
17.empty 判断vector是否为空
18.swap 与另一个vector交换数据
使用说明:
==================================
1. 构造函数
语法:
vector();
vector( size_type num, const TYPE &val );
vector( const vector &from );
vector( input_iterator start, input_iterator end );
C++ Vectors可以使用以下任意一种参数方式构造:
1. 无参数 - 构造一个空的vector,
2. 数量(num)和值(val) - 构造一个初始放入num个值为val的元素的Vector
3. vector(from) - 构造一个与vector from 相同的vector
4. 迭代器(start)和迭代器(end) - 构造一个初始值为[start,end)区间元素的Vector(注:半开区间).
举例,下面这个实例构造了一个包含5个值为42的元素的Vector
vector<int> v1( 5, 42 );
实现2维数组:
如果一个向量的每一个元素是一个向量,则称为二维向量,例如
vector<vector<int> >vv(3, vector<int>(4)); //这里,两个“>”间的空格是不可少的
vector<vector<int> >vv(3, vector<int>(4)); //这里,两个“>”间的空格是不可少的将构造一个二维向量vv,它含有三个元素,每个元素含有4个int型元素的向量。编译器两次调用vector的构造函数构造对象vv,第一次调用构造函数构造了一个无名的含有4个0的vector<int>对象:
[0] | [1] | [2] | [3] |
0 | 0 | 0 | 0 |
第二次调用构造函数,以这个无名向量为初值初始化它的三个元素,结果是:
vv | [0] | [1] | [2] | [3] |
[0] | 0 | 0 | 0 | 0 |
[1] | 0 | 0 | 0 | 0 |
[2] | 0 | 0 | 0 | 0 |
vv[i]表示第i(i=0,1,2)行的元素组成的向量。vv.size()的值是3,vv[1].size()的值是4.
一个二维向量每个元素的长度可以不同,例如
-
vector<vector<int> >vv;
-
for(int c = 1; c <= 3; c++)
-
vv.push_back(vector<int>(c,0));
vector<vector<int> >vv;
for(int c = 1; c <= 3; c++) vv.push_back(vector<int>(c,0));
上面代码产生一个长度不同的二维向量,第一行只有1个元素,第二行有两个,第三行三个。
运算符
语法:
v1 == v2
v1 != v2
v1 <= v2
v1 >= v2
v1 < v2
v1 > v2
v[]
C++ Vectors能够使用标准运算符: ==, !=, <=, >=, <, 和 >. 要访问vector中的某特定位置的元素可以使用 [] 操作符.
两个vectors被认为是相等的,如果:
它们具有相同的容量
所有相同位置的元素相等.
vectors之间大小的比较是按照词典规则.
===================================================
assign函数
语法:
void assign( input_iterator start, input_iterator end );
void assign( size_type num, const TYPE &val );
assign() 函数要么将区间[start, end)的元素赋到当前vector,或者赋num个值为val的元素到vector中.这个函数将会清除掉为vector赋值以前的内容.
==========================
at函数
语法:
TYPE at( size_type loc );
at() 函数 返回当前Vector指定位置loc的元素的引用. at() 函数 比 [] 运算符更加安全, 因为它不会让你去访问到Vector内越界的元素.
例如, 考虑下面的代码:
vector<int> v( 5, 1 );
for( int i = 0; i < 10; i++ ) {
cout << "Element " << i << " is " << v[i] << endl;
}
这段代码访问了vector末尾以后的元素,这将可能导致很危险的结果.以下的代码将更加安全:
vector<int> v( 5, 1 );
for( int i = 0; i < 10; i++ ) {
cout << "Element " << i << " is " << v.at(i) << endl;
}
取代试图访问内存里非法值的作法,at() 函数能够辨别出访问是否越界并在越界的时候抛出一个异常out_of_range.
====================
back 函数
语法:
TYPE back(),front();
front() 函数返回当前vector最首一个元素的引用
back() 函数返回当前vector最末一个元素的引用.例如:
vector<int> v;
for( int i = 0; i < 5; i++ ) {
v.push_back(i);
}
cout << "The first element is " << v.front()
<< " and the last element is " << v.back() << endl;
这段代码产生如下结果:
The first element is 0 and the last element is 4
===============================
begin 函数
语法:
iterator begin();
begin()函数返回一个指向当前vector起始元素的迭代器(似乎是指向元素的指针) .例如,下面这段使用了一个迭代器来显示出vector中的所有元素:
vector<int> v1( 5, 789 );
vector<int>::iterator it;
for( it = v1.begin(); it != v1.end(); it++ )
cout << *it << endl;
end 函数
语法:
iterator end();
end() 函数返回一个指向当前vector末尾元素的下一位置的迭代器.注意,如果你要访问末尾元素,需要先将此迭代器自减1.
capacity 函数
语法:
size_type capacity();
capacity() 函数 返回当前vector在重新进行内存分配以前所能容纳的元素数量.
clear 函数
语法:
void clear();
clear()函数删除当前vector中的所有元素.
empty 函数
语法:
bool empty();
如果当前vector没有容纳任何元素,则empty()函数返回true,否则返回false.
例如,以下代码清空一个vector,并按照逆序显示所有的元素:
vector<int> v;
for( int i = 0; i < 5; i++ ) {
v.push_back(i);
}
while( !v.empty() ) {
cout << v.back() << endl;
v.pop_back();
}
erase 函数
语法:
iterator erase( iterator loc );
iterator erase( iterator start, iterator end );
erase函数要么删作指定位置loc的元素,要么删除区间[start, end)的所有元素.
返回值是指向删除的最后一个元素的下一位置的迭代器.例如:
// 创建一个vector,置入字母表的前十个字符
vector<char> alphaVector;
for( int i=0; i < 10; i++ )
alphaVector.push_back( i + 65 );
int size = alphaVector.size();
vector<char>::iterator startIterator;
vector<char>::iterator tempIterator;
for( int i=0; i < size; i++ )
{
tartIterator = alphaVector.begin();
alphaVector.erase( startIterator );
// Display the vector
for( tempIterator = alphaVector.begin(); tempIterator != alphaVector.end(); tempIterator++ )
cout << *tempIterator;
cout << endl;
}
这段代码将会显示如下输出:
BCDEFGHIJ
CDEFGHIJ
DEFGHIJ
EFGHIJ
FGHIJ
GHIJ
HIJ
IJ
J
pop_back
语法:
void pop_back();
pop_back()函数删除当前vector最末的一个元素,例如:
vector<char> alphaVector;
for( int i=0; i < 10; i++ )
alphaVector.push_back( i + 65 );
int size = alphaVector.size();
vector<char>::iterator theIterator;
for( int i=0; i < size; i++ ) {
alphaVector.pop_back();
for( theIterator = alphaVector.begin(); theIterator != alphaVector.end(); theIterator++ )
cout << *theIterator;
cout << endl;
}
这段代码将显示以下输出:
ABCDEFGHI
ABCDEFGH
ABCDEFG
ABCDEF
ABCDE
ABCD
ABC
AB
A
push_back 函数
语法:
void push_back( const TYPE &val );
push_back()添加值为val的元素到当前vector末尾
===============================
get_allocator 函数
语法:
allocator_type get_allocator();
get_allocator() 函数返回当前vector的内存分配器.在STL里面一般不会调用new或者alloc来分配内存,而且通过一个allocator对象的相关方法来分配.
示例:vector <int> v3( 3, 1, v2.get_allocator( ));//把V2的内存分配器作为一个参数参与构造V3。这样,它们两个用一个内存分配器了。
=============================
insert 函数
语法:
iterator insert( iterator loc, const TYPE &val );
void insert( iterator loc, size_type num, const TYPE &val );
void insert( iterator loc, input_iterator start, input_iterator end );
insert() 函数有以下三种用法:
在指定位置loc前插入值为val的元素,返回指向这个元素的迭代器,
在指定位置loc前插入num个值为val的元素
在指定位置loc前插入区间[start, end)的所有元素 .
举例:
//创建一个vector,置入字母表的前十个字符
vector<char> alphaVector;
for( int i=0; i < 10; i++ )
alphaVector.push_back( i + 65 );
//插入四个C到vector中
vector<char>::iterator theIterator = alphaVector.begin();
alphaVector.insert( theIterator, 4, 'C' );
//显示vector的内容
for( theIterator = alphaVector.begin(); theIterator != alphaVector.end(); theIterator++ )
cout << *theIterator;
这段代码将显示:
CCCCABCDEFGHIJ
===============================
max_size 函数
语法:
size_type max_size();
max_size() 函数返回当前vector所能容纳元素数量的最大值(译注:包括可重新分配内存).
rbegin 函数
语法:
reverse_iterator rbegin();
rbegin函数返回指向当前vector末尾的逆迭代器.(译注:实际指向末尾的下一位置,而其内容为末尾元素的值,详见逆代器相关内容)
示例:
vector<int>v1;
for(int i=1;i<=5;i++)
{
v1.push_back(i);
}
vector<int>::reverse_iterator pos;
pos=v1.rbegin();
cout<<*pos<<" ";
pos++;
cout<<*pos<<endl;
输出结果为:5 4
rend 函数
语法:
reverse_iterator rend();
rend()函数返回指向当前vector起始位置的逆迭代器.
示例:
vector<int>v1;
for(int i=1;i<=5;i++)
{
v1.push_back(i);
}
vector<int>::reverse_iterator pos;
pos=v1.rend();
pos--;
cout<<*pos<<" ";
pos--;
cout<<*pos<<endl;
输出结果为:1 2
reserve 函数
语法:
void reserve( size_type size );
reserve()函数为当前vector预留至少共容纳size个元素的空间.(译注:实际空间可能大于size)
resize 函数
语法:
void resize( size_type size, TYPE val );
resize() 函数改变当前vector的大小为size,且对新创建的元素赋值val
resize 与reserve的区别
reserve是容器预留空间,但并不真正创建元素对象,在创建对象之前,不能引用容器内的元素,因此当加入新的元素时,需要用push_back()/insert()函数。
resize是改变容器的大小,并且创建对象,因此,调用这个函数之后,就可以引用容器内的对象了,因此当加入新的元素时,用operator[]操作符,或者用迭代器来引用元素对象。再者,两个函数的形式是有区别的,reserve函数之后一个参数,即需要预留的容器的空间;resize函数可以有两个参数,第一个参数是容器新的大小,第二个参数是要加入容器中的新元素,如果这个参数被省略,那么就调用元素对象的默认构造函数。
初次接触这两个接口也许会混淆,其实接口的命名就是对功能的绝佳描述,resize就是重新分配大小,reserve就是预留一定的空间。这两个接口即存在差别,也有共同点。下面就它们的细节进行分析。
为实现resize的语义,resize接口做了两个保证:
一是保证区间[0, new_size)范围内数据有效,如果下标index在此区间内,vector[indext]是合法的。
二是保证区间[0, new_size)范围以外数据无效,如果下标index在区间外,vector[indext]是非法的。
reserve只是保证vector的空间大小(capacity)最少达到它的参数所指定的大小n。在区间[0, n)范围内,如果下标是index,vector[index]这种访问有可能是合法的,也有可能是非法的,视具体情况而定。
resize和reserve接口的共同点是它们都保证了vector的空间大小(capacity)最少达到它的参数所指定的大小。
因两接口的源代码相当精简,以至于可以在这里贴上它们:
void resize(size_type new_size) { resize(new_size, T()); }
void resize(size_type new_size, const T& x) {
if (new_size < oldsize)
erase(oldbegin + new_size, oldend); // erase区间范围以外的数据,确保区间以外的数据无效
else
insert(oldend, new_size - oldsize, x); // 填补区间范围内空缺的数据,确保区间内的数据有效
示例:
#include<iostream>
#include<vector>
using namespace std;
void main()
{
vector<int>v1;
for(int i=1;i<=3;i++)
{
v1.push_back(i);
}
v1.resize(5,8);//多出的两个空间都初始化为8,
for(i=0;i<v1.size();i++)//resize与reserver并不会删除原先的元素以释放空间
{
cout<<v1[i]<<" ";
}
cout<<endl;
v1.reserve(7);// 新元素还没有构造,
for(i=0;i<7;i++)
{
cout<<v1[i]<<" ";//当i>4,此时不能用[]访问元素
}
cout<<endl;
cout<<v1.size()<<endl;
cout<<v1.capacity()<<endl;
}
输出结果为:
1 2 3 8 8
1 2 3 8 8 -842150451 -842150451
5
7
size函数
语法:
size_type size();
size() 函数返回当前vector所容纳元素的数目
swap 函数
语法:
void swap( vector &from );
swap()函数交换当前vector与vector from的元素
示例:
vector<int>v1,v2;
for(int i=1;i<=3;i++)
{
v1.push_back(i);
v2.push_back(i);
}
v2.push_back(4);
v2.push_back(5);
v1.swap(v2);
for(int j=0;j<v1.size();j++)
{
cout<<v1[j]<<" ";
}
cout<<endl;
for(int k=0;k<v2.size();k++)
{
cout<<v2[k]<<" ";
}
cout<<endl;
输出结果为:
1 2 3 4 5
1 2 3
======================
======================
4.内存管理与效率
1》使用reserve()函数提前设定容量大小,避免多次容量扩充操作导致效率低下。
关于STL容器,最令人称赞的特性之一就是是只要不超过它们的最大大小,它们就可以自动增长到足以容纳你放进去的数据。(要知道这个最大值,只要调用名叫max_size的成员函数。)对于vector和string,如果需要更多空间,就以类似realloc的思想来增长大小。vector容器支持随机访问,因此为了提高效率,它内部使用动态数组的方式实现的。在通过 reserve() 来申请特定大小的时候总是按指数边界来增大其内部缓冲区。当进行insert或push_back等增加元素的操作时,如果此时动态数组的内存不够用,就要动态的重新分配当前大小的1.5~2倍的新内存区,再把原数组的内容复制过去。所以,在一般情况下,其访问速度同一般数组,只有在重新分配发生时,其性能才会下降。正如上面的代码告诉你的那样。而进行pop_back操作时,capacity并不会因为vector容器里的元素减少而有所下降,还会维持操作之前的大小。对于vector容器来说,如果有大量的数据需要进行push_back,应当使用reserve()函数提前设定其容量大小,否则会出现许多次容量扩充操作,导致效率低下。
reserve成员函数允许你最小化必须进行的重新分配的次数,因而可以避免真分配的开销和迭代器/指针/引用失效。但在我解释reserve为什么可以那么做之前,让我简要介绍有时候令人困惑的四个相关成员函数。在标准容器中,只有vector和string提供了所有这些函数。
(1) size()告诉你容器中有多少元素。它没有告诉你容器为它容纳的元素分配了多少内存。
(2) capacity()告诉你容器在它已经分配的内存中可以容纳多少元素。那是容器在那块内存中总共可以容纳多少元素,而不是还可以容纳多少元素。如果你想知道一个vector或string中有多少没有被占用的内存,你必须从capacity()中减去size()。如果size和capacity返回同样的值,容器中就没有剩余空间了,而下一次插入(通过insert或push_back等)会引发上面的重新分配步骤。
(3) resize(Container::size_type n)强制把容器改为容纳n个元素。调用resize之后,size将会返回n。如果n小于当前大小,容器尾部的元素会被销毁。如果n大于当前大小,新默认构造的元素会添加到容器尾部。如果n大于当前容量,在元素加入之前会发生重新分配。
(4) reserve(Container::size_type n)强制容器把它的容量改为至少n,提供的n不小于当前大小。这一般强迫进行一次重新分配,因为容量需要增加。(如果n小于当前容量,vector忽略它,这个调用什么都不做,string可能把它的容量减少为size()和n中大的数,但string的大小没有改变。在我的经验中,使用reserve来从一个string中修整多余容量一般不如使用“交换技巧”,那是条款17的主题。)
这个简介表示了只要有元素需要插入而且容器的容量不足时就会发生重新分配(包括它们维护的原始内存分配和回收,对象的拷贝和析构和迭代器、指针和引用的失效)。所以,避免重新分配的关键是使用reserve尽快把容器的容量设置为足够大,最好在容器被构造之后立刻进行。
例如,假定你想建立一个容纳1-1000值的vector<int>。没有使用reserve,你可以像这样来做:
vector<int> v;
for (int i = 1; i <= 1000; ++i) v.push_back(i);
在大多数STL实现中,这段代码在循环过程中将会导致2到10次重新分配。(10这个数没什么奇怪的。记住vector在重新分配发生时一般把容量翻倍,而1000约等于210。)
把代码改为使用reserve,我们得到这个:
vector<int> v;
v.reserve(1000);
for (int i = 1; i <= 1000; ++i) v.push_back(i);
这在循环中不会发生重新分配。
在大小和容量之间的关系让我们可以预言什么时候插入将引起vector或string执行重新分配,而且,可以预言什么时候插入会使指向容器中的迭代器、指针和引用失效。例如,给出这段代码,
string s;
...
if (s.size() < s.capacity()) {
s.push_back('x');
}
push_back的调用不会使指向这个string中的迭代器、指针或引用失效,因为string的容量保证大于它的大小。如果不是执行push_back,代码在string的任意位置进行一个insert,我们仍然可以保证在插入期间没有发生重新分配,但是,与伴随string插入时迭代器失效的一般规则一致,所有从插入位置到string结尾的迭代器/指针/引用将失效。
回到本条款的主旨,通常有两情况使用reserve来避免不必要的重新分配。第一个可用的情况是当你确切或者大约知道有多少元素将最后出现在容器中。那样的话,就像上面的vector代码,你只是提前reserve适当数量的空间。第二种情况是保留你可能需要的最大的空间,然后,一旦你添加完全部数据,修整掉任何多余的容量。
2》使用“交换技巧”来修整vector过剩空间/内存
有一种方法来把它从曾经最大的容量减少到它现在需要的容量。这样减少容量的方法常常被称为“收缩到合适(shrink to fit)”。该方法只需一条语句:vector<int>(ivec).swap(ivec);
表达式vector<int>(ivec)建立一个临时vector,它是ivec的一份拷贝:vector的拷贝构造函数做了这个工作。但是,vector的拷贝构造函数只分配拷贝的元素需要的内存,所以这个临时vector没有多余的容量。然后我们让临时vector和ivec交换数据,这时我们完成了,ivec只有临时变量的修整过的容量,而这个临时变量则持有了曾经在ivec中的没用到的过剩容量。在这里(这个语句结尾),临时vector被销毁,因此释放了以前ivec使用的内存,收缩到合适。
3》用swap方法强行释放STL Vector所占内存
template < class T> void ClearVector( vector<T>& v )
{
vector<T>vtTemp;
vtTemp.swap( v );
}
如
vector<int> v ;
nums.push_back(1);
nums.push_back(3);
nums.push_back(2);
nums.push_back(4);
vector<int>().swap(v);
/* 或者v.swap(vector<int>()); */
/*或者{ std::vector<int> tmp = v; v.swap(tmp); }; //加大括号{ }是让tmp退出{ }时自动析构*/
5.Vector 内存管理成员函数的行为测试
C++ STL的vector使用非常广泛,但是对其内存的管理模型一直有多种猜测,下面用实例代码测试来了解其内存管理方式,测试代码如下:
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> iVec;
cout << "容器 大小为: " << iVec.size() << endl;
cout << "容器 容量为: " << iVec.capacity() << endl; //1个元素, 容器容量为1
iVec.push_back(1);
cout << "容器 大小为: " << iVec.size() << endl;
cout << "容器 容量为: " << iVec.capacity() << endl; //2个元素, 容器容量为2
iVec.push_back(2);
cout << "容器 大小为: " << iVec.size() << endl;
cout << "容器 容量为: " << iVec.capacity() << endl; //3个元素, 容器容量为4
iVec.push_back(3);
cout << "容器 大小为: " << iVec.size() << endl;
cout << "容器 容量为: " << iVec.capacity() << endl; //4个元素, 容器容量为4
iVec.push_back(4);
iVec.push_back(5);
cout << "容器 大小为: " << iVec.size() << endl;
cout << "容器 容量为: " << iVec.capacity() << endl; //5个元素, 容器容量为8
iVec.push_back(6);
cout << "容器 大小为: " << iVec.size() << endl;
cout << "容器 容量为: " << iVec.capacity() << endl; //6个元素, 容器容量为8
iVec.push_back(7);
cout << "容器 大小为: " << iVec.size() << endl;
cout << "容器 容量为: " << iVec.capacity() << endl; //7个元素, 容器容量为8
iVec.push_back(8);
cout << "容器 大小为: " << iVec.size() << endl;
cout << "容器 容量为: " << iVec.capacity() << endl; //8个元素, 容器容量为8
iVec.push_back(9);
cout << "容器 大小为: " << iVec.size() << endl;
cout << "容器 容量为: " << iVec.capacity() << endl; //9个元素, 容器容量为16
/* vs2005/8 容量增长不是翻倍的,如
9个元素 容量9
10个元素 容量13 */
/* 测试effective stl中的特殊的交换 swap() */
cout << "当前vector 的大小为: " << iVec.size() << endl;
cout << "当前vector 的容量为: " << iVec.capacity() << endl;
vector<int>(iVec).swap(iVec);
cout << "临时的vector<int>对象 的大小为: " << (vector<int>(iVec)).size() << endl;
cout << "临时的vector<int>对象 的容量为: " << (vector<int>(iVec)).capacity() << endl;
cout << "交换后,当前vector 的大小为: " << iVec.size() << endl;
cout << "交换后,当前vector 的容量为: " << iVec.capacity() << endl;
return 0;
}
6.vector的其他成员函数
c.assign(beg,end):将[beg; end)区间中的数据赋值给c。
c.assign(n,elem):将n个elem的拷贝赋值给c。
c.at(idx):传回索引idx所指的数据,如果idx越界,抛出out_of_range。
c.back():传回最后一个数据,不检查这个数据是否存在。
c.front():传回地一个数据。
get_allocator:使用构造函数返回一个拷贝。
c.rbegin():传回一个逆向队列的第一个数据。
c.rend():传回一个逆向队列的最后一个数据的下一个位置。
c.~ vector <Elem>():销毁所有数据,释放内存。
7.备注:在用vector的过程中的一些问题,特此列出讨论:
1)
vector <int > a;
int b = 5;
a.push_back(b);
此时若对b另外赋值时不会影响a[0]的值
2)
vector <int*> a;
int *b;
b= new int[4];
b[0]=0;
b[1]=1;
b[2]=2;
a.push_back(b);
delete b; //释放b的地址空间
for(int i=0 ; i <3 ; i++)
{
cout<<a[0][i]<<endl;
}
此时输出的值并不是一开始b数组初始化的值,而是一些无法预计的值.
分析:根据1) 2)的结果,可以想到,在1)中, 往a向量中压入的是b的值,即a[0]=b,此时a[0]和b是存储在两个不同的地址中的.因此改变b的值不会影响a[0];而在2)中,因为是把一个地址(指针)压入向量a,即a[0]=b,因此释放了b的地址也就释放了a[0]的地址,因此a[0]数组中存放的数值也就不得而知了.