|
作者:wangtianxing* C0 I* w, Q* e
8 _, f# D" {. [3 r0 y/ Z
原文出处:http://www.cpphelp.net/issue/vector.html) Y+ B. u2 l- S; {
/ G5 Y8 E; t! X( E2 n
0 D6 W/ x5 t6 U# o* n# y7 J
2 P$ j; l: {" @% s& _6 `; K [摘要: 本文介绍了C++标准库中的容器类vector,分析了它的优点,并且建议在应用程序中使用它作为动态数组的优先选择,而不是MFC的CArray<>等其他类模板。最后介绍了vector的接口和使用时的注意事项。
- Q) y7 s$ i7 U, D3 _/ \1 K( b4 h
在一些使用 MFC 的程序中,经常看到许多程序使用 CArray<>,由于 CArray<>的设计问题,造成使用它的代码的复杂化,增加了维护难度。因此建议使用 ::std::vector<> 代替 CArray<>。
5 N- V' ]# d# v# i5 T
6 {) M, N4 m/ y2 C& E- W" E" Y另外,也看到一些程序在用 malloc/realloc/free/new[]/delete[] 等手工管理内存。在应用程序中,手工管理内存是容易导致错误的,应该用 ::std::vector<> 之类的对象来管理动态数组。
* F8 p7 S: M2 I0 M4 l* q/ v ~& E" F+ r0 W/ @
由于 MSDN 中关于 ::std::vector 的内容较少,我们在这里做一些介绍,供参考。( Z2 `+ n2 e# ^# _
( A8 p+ U/ v1 z7 v ^% _. n
不熟悉 CArray<>/WIN32 也没关系,这里提到它们的地方并不太多。$ U: D/ D6 y& A( D2 D$ t+ Q$ H
+ v8 u$ j4 R4 D3 o* t% y0 b; b4 @1. CArray<> VS ::std::vector<> ?
& z8 z) U' g# O1 O% ^CArray<> 和 ::std::vector<> 一样,都是模板类,用于管理任意类型的对象的动态数组。都在解构时释放所管理的动态内存。因此都可以用于代替手工动态数组管理。7 D9 @. ~% M5 \
" D3 v5 b% x F E" M" S
但是,CArray<> 是在 C++ 标准化之前很多年(VC++2.0时代)设计的,当时对 C++程序设计,面向对象程序设计,模板程序设计等技术认识严重不足,尤其是当时对面向对象技术的错误信仰与宣传,造成 CArray<> 的设计有重大错误。
1 f& T" B9 E4 t7 s) d- u4 S6 `5 _( Y( }& r+ L! ?
在 C++ 语言标准化以后(1998),以及 VC++ 6.0 出世以后,提供了标准的::std::vector<> 模板,基本上在任何方面都要优于 CArray<>。Microsoft 由于要支持老的程序,因此一直保留了 CArray<>,但显然并没有打算按照新的思想去发展它(至少应该提供operator=(CArray const&)吧)。
+ M. U e7 |1 l# B- {8 L* ^6 t1 L' G
概括起来,CArray<> 与 ::std::vector<> 有以下不同:
5 `- i% C: O( R1 i$ g: e# F A+ @2 T4 _. O' N. N8 u) R% o
1) CArray<> 是 MFC 中的,::std::vector<> 存在于任何标准的 C++ 实现中。因此,你用熟了 CArray<> 也只能在 MFC 中用,若用熟了 ::std::vector<>,你可以在任何平台的任何 C++ 编译器下使用。使用标准的部件也有利于别人理解你的程序。 . CArray<> 继承了 CObject,仅仅为了实现 serialization,这是不恰当的, 违反了 "You don't pay for what you don't use." 的 C++ 设计原则。::std::vector<> 没有继承任何东西,只是实现了管理一个动态数组该做的事。
/ S; e) o& j% e( j1 k2 e8 @1 T/ \2 F
2) CArray<> 不是一个恰当的值类型,例如下列操作都是不合法的:/ J4 G- }/ T/ J7 \ {8 W
+ w, Z a- \: A) w( [; FCArray<int,int> a;: X6 U* E, h$ u
CArray<int,int> b(a); // error, must use Copy().$ d) Z# Q& b" ~4 K8 [* j1 e
b = a; // error, must use Copy().
. s8 h/ o# w& S; q8 g, t, Cb == a; // error, you must write your own.1 `. [' q, B4 T$ J/ f
b < a; // error, you must write your own.
7 E2 m$ l4 c g. h3 N与 CArray<> 相反,::std::vector<> 是一个认真设计的值类型,天生是可以拷贝构造和可赋值的。如果 T 是可比较的,那么 ::std::vector<T> 将自动地是可以比较的。
* K- P, c1 ~4 A( d( f
' X+ G2 T- X1 Q* Z此外,由于涉及到四个特殊成员函数;
: i. I3 @9 h! \ F2 v, s7 S# u# e: v. E4 v0 r& ?8 }/ U
T(); // 缺省构造函数(default constructor)/ O* y9 O( E( X3 b
~T(); // 解构函数(destructor): I) ~ ^5 x! Q& q7 x5 x8 C
T( T const& ); // 拷贝构造函数
' B4 J3 \! W* U1 v, P/ fT& operator=( T const& ); // 拷贝赋值函数 P4 M* m& Z# v% f% P
的自动生成,如果使用 CArray() 作为 T 的成员变量,那么上述的四个特殊函数中的后两个将无法自动生成,需要手工写:% F& H0 J: d% x& \' \+ F! k
( G- g# K: X1 }0 w struct T
6 a# x, D3 x4 g7 k1 r* x{- N/ b% N/ l: o" V
T() {}
) i2 m F3 S r3 V T( T const& t )
, S2 p) G! _1 B6 e) @ {% E4 h. A# g) D/ _7 N; F
a_.Copy( t.a_ );3 e8 H5 V0 A8 E% q& U: _" z) v
i_ = t.i_;9 }7 ]* `6 Y& f( L4 N' Q3 e8 m
d_ = t.d_;
% ]+ k* j) f' J, [, t8 j b s_ = t.s_;
5 J. c6 _) g6 E5 t }. q) B0 D9 H6 ?0 Y( G& O; G7 x9 h
T& operator = ( T const& t )! L5 T3 _8 v+ a5 l0 E
{
/ U" s: q3 I1 l# ]/ { if( this != &t ): k3 z% @" C' u# z4 g. _0 S( W2 @/ c
{9 \' K3 \9 P" v3 M: F% d) z, h
a_.Copy( t.a_ );5 i$ X" ]% t {) J. D+ ?' z$ l
i_ = t.i_;' K! G8 [2 d6 [- i8 z# K7 h$ u
d_ = t.d_;/ {0 z* K4 G3 z2 O. T
s_ = t.s_;
6 ]2 U- C( m% ~/ ?" g }
# N) z% q2 Y D1 f- @ return *this;
, z' C! A" V. K+ V& Q3 N! a }
* Q( @. f3 e$ Xprivate:2 h) s7 F) e8 Z# {6 [
CArray<int,int> a_;4 r6 H9 c- J' ` J& i
int i_;
. X9 G, L: `- P9 z double d_;
- ] Z+ ?1 n& |6 b ::std::string s_;2 h. K4 I" R6 t2 t1 E' N3 i
};& I! e0 H4 _8 u+ B% L" g7 r
如果使用 ::std::vector<>:" o2 E% W6 p+ k. }8 F. c8 j/ g
- j* ^# E& b) Z& Y; v' _! k. L
struct T* t+ x. |1 z' n% p l
{
* n# Y2 ^; m( s- C2 f3 ^private:
8 j+ X9 B4 }6 }0 C ::std::vector<int> a_;
- ~) g8 Z5 M# g! f+ `! T int i_;
+ K1 Z/ u' i! J( Q double d_;% z. u( }: D) L" D
::std::string s_;
" h: v6 _( s v. U8 Z4 N};4 l! a/ X6 Z4 F9 H: T0 j" L% A
上面列出的三个特殊成员函数都不需要写。好处是明显的:当你增减 T 的成员变量时,你不必到 k$ X* M% Z6 W' g, J
T(T const&) 和 operator=() 中去相应地增减。: T8 Z! R( u" m: T
5 K7 j1 c% x- B
3) 没有现成的算法可以对 CArray<> 进行操作,而标准 C++ 里的标准算法大多都可以直接在
" h7 ~; l2 {& p7 B3 K::std::vector<> 上运行。例如:) t! F3 I- w2 ]: P7 F
2 C3 J e8 e% a: f3 |2 \: D
static int const init_vals[] = { 3, 1, 4, 1, 6, 9 };# G$ O2 N: A2 E& R( p- |
vector<int> a( init_vals, init_vals + 6 );
, l0 g& x" }7 h8 J* V. B" t- b) V. I*find( a.begin(), a.end(), 6 ) = 5; // 把6改成5. g- y5 [! L7 q; Q
sort( a.begin(), a.end() ); // 排序。$ Y7 N2 E" W8 Z
可以说,CArray<> 的主要设计错误是把一个本来应该是一个简单的“值”类型的东西设计成一个难用的“对象”类型了。所有的“值”的好特性都丧失了,但那些从CArray<>继承的派生类呢?* H8 X. k! v" c: `4 q
) J1 M, t( W# `$ t& _
CByteArray等的问题与 CArray<> 的问题一样,甚至更多(例如,CPtrArray,永远不要用)。- L6 U( `6 G* B/ g$ |0 @
6 G" R1 r# l1 e同样,其他的 MFC container 模板,象 CMap<>, CList<> 等,都有类似问题,都应该用
2 p* i4 o8 d0 }+ b0 u7 l::std::map<>,::std::list<> 等设计更好的东西代替。
% r* j2 f- ~, M/ t% S1 r
3 E& g- q) r: @9 _2. ::std::vector<> 在哪里?. B5 c8 ^5 {7 r+ d8 V
::std::vector<> 在头文件 <vector> 中定义:
) |' Z( l/ h6 i* @* W3 n
8 \- P: h2 Q |9 z/ Z# g/ y6 U- D5 X(注意,标准的 C++ 头文件都没有 .h 后缀,有 .h 的文件是与 C 兼容的,或支持老的不标准的东西,象 <iostream.h>。)$ I( V: C/ [! ]2 ]& E+ `9 O
. ~' ?/ N; O8 t# O0 Hnamespace std
3 _, V! c" Q0 K3 N{
* i+ |8 a9 J' @% l( p template<typename T, typename A = allocator<T> >( p) I0 Y2 p' N5 g
struct vector. v. \" T* w# |3 ^; Y
{
; i6 b( @6 R" z3 t7 D$ d3 u // 具体内容稍后讨论 f( v0 Y" u# M
};" {$ b# `0 T5 k" s
C: ~. Y- R2 s5 g0 q: u. N, j
2 |8 F; U1 G$ O template<typename T, typename A>. K, M; a1 L, N2 P- T
bool operator == ( vector<T,A> const& a, vector<T,A> const& b );
6 R9 O0 d- o) N: d' e6 m template<typename T, typename A>! `* O' w3 t- T0 d5 s3 z; L
bool operator != ( vector<T,A> const& a, vector<T,A> const& b );! N1 c, \, z, \6 C* {
template<typename T, typename A>
8 J8 W* f+ C( {, A" h$ ?, K bool operator < ( vector<T,A> const& a, vector<T,A> const& b );
v0 L- e' ?$ ` template<typename T, typename A>- V. `8 P2 N3 R
bool operator >= ( vector<T,A> const& a, vector<T,A> const& b );
( S g" j! G+ ]' u template<typename T, typename A>
! z/ a9 F( \- q) P bool operator > ( vector<T,A> const& a, vector<T,A> const& b );9 b ~& v! `6 b
template<typename T, typename A>/ S, b- X: ^! R/ T
bool operator >= ( vector<T,A> const& a, vector<T,A> const& b );8 R! Z/ S) X4 d M# F9 @( k( l
}& g6 \. Z* W1 Y5 p+ v5 O- v0 v- \7 E# f+ _
vector<> 定义在 namespace std 中,使用时为了减少击键次数,通常使用一个类型定义缩短类型名称:
: m! p: D% }, q- m7 j+ w0 e
q! w* H0 W$ |( O) Q+ F#include <vector>
+ M: t) `% | q0 q- R# q1 s8 p" Wtypedef ::std::vector<int> IntVector;5 @( b/ B: h$ A! J; X
IntVector a;4 \: Y/ \% Q+ ?: M0 X8 s- m1 [
IntVector b( a );. I! d2 f' a: F
IntVector c;
* w0 }% {. V3 lc = b;) U7 a e, s U/ o+ F& c+ Y- h' J
assert( a == c );
6 ~8 W* W8 o% ~' Z g* e请注意 <vector> 中定义了六个 vector<T,A> 的比较函数。这些函数只在真的用到时才会被实例化,才会要求 T 也提供 operator==() 和 operator<()。
5 H) m1 a: I. Q0 c0 O$ A$ P/ T* I7 L' Y
另外,A = alloctor<T>:用于提供一个用户定义的存储管理类。由于这个参数很少用到,而且在 VC++6 的实现中有问题,不能用,因此以下的讨论忽略这一部分的内容。4 v9 n# D% e) J# i
B' h! C6 \( L+ L) d2 N* n3. ::std::vector<> 中的类型定义
% x$ W# ^# c" Q3 a( b& Pvector<> 中定义了一些类型,下面只列出常用的:7 h0 T. x& T5 e8 W j& F, d
+ g% b8 X Y4 t/ h. o3 s: U
typedef T value_type;* Z: D2 i% y& X! A
typedef T0 iterator;4 w) D7 b. f a1 {
typedef T1 const_iterator;
3 r, `( Q. g9 B, A( ?; ^1 C0 Stypedef T2 reverse_iterator;) s6 W" M; o& k
typedef T3 const_reverse_iterator;- M; o. P' \8 c' ^$ C# t2 O
. x" b: I/ ~$ m4 Hvalue_type 就是 vector<T> 的元素类型,也就是 T。当写通用的算法处理任意类型的 vector<> 或其他容器类型时是很有用的。
6 q( n$ H; Q, E# N: j8 `; f3 h6 ?1 O7 ?# R6 I
iterator/const_iterator 是两个 vector<> 的实现定义的未知类型,用于访问vector<> 中的元素,类似于 T*/T const* 指针,他们的区别是一个指向的元素可被修改,另一个只可以读:
% X5 f+ Z# b7 l. H, p/ D; M! L4 m: j* ^4 @) U
typedef ::std::vector<int> IntVector;! _( e! [9 N" c/ g U
IntVector::iterator iter;
: I0 A; R7 l' K2 RIntVector::const_iterator c_iter;
& I7 H, C. \ u) A, q8 I// ...% M5 j7 c T' m1 |
++iter; iter++; // ok: increment, post-increment.2 i# V* p _3 E
--iter; iter--; // ok: decrement, post-decrement.4 F! L! _- G8 i: a
++c_iter; c_iter++; // ok: increment, post-increment.
+ F) f ^* D- |- g/ W% k1 g--c_iter; c_iter--; // ok: decrement, post-decrement.
8 ~& X7 Q2 T: |' R' M; _1 S*iter = 123; // ok.5 v2 }, `' j4 H. w
int k = *iter; // ok.
7 [" v: a- y9 o2 Zk = *--c_iter; // ok.
: j3 F4 B, h; U* B% w0 o*c_iter = k; // error.
2 S+ h- Q8 L5 W% Bc_iter = iter; // ok: iterator is convertible to const_iterator.( \2 U' B- H9 N6 a
iter = c_iter; // error: can't convert const_iterator to iterator.& s2 M' h9 g- f3 \# I: ~
在使用上 iterator/const_iterator 和 T*/T const* 基本相同,事实上有些vector<> 的实现里就是用 T*/T const* 实现 iterator/const_iterator 的,但又不可以把 iterator/const_iterator 当作真正的 T*/T const*:
\; W1 T0 a) c" D L, i% Z( m! m! j' `2 M/ `9 S' A
T* p = iter; // may fail to compile.
( C5 [0 X4 U7 b9 }T const* q = c_iter; // may fail to compile.
3 A/ K* }! m& p# x' p+ t1 v+ b8 preverse_iterator/const_reverse_iterator 与 iterator/const_iterator 类似,但以相反的次序(从尾至头)访问 vector 中的元素。
- U9 `" d: E, m% t2 x: w; R% U
0 `: `. D0 n1 L各种各样的 iterator 在 STL 中有特别重要的意义,但这里我们不做具体介绍。只要理解通过 iterator 可以访问 vector 中的元素,大概相当于一个指示位置的指针就行了。8 f- R7 X1 b6 X
: D% a6 d7 j/ y8 |; h0 X a% s6 @
4. ::std::vector<> 的构造
! n4 i4 ~ w1 Qvector<> 提供了以下构造函数:(忽略 allocator 参数)0 A9 t) i. A3 ?" |& L% }3 q8 u# J; x
( n, M$ q8 S' W' D0 d/ q
vector();
( ~5 W- e) i8 X/ |vector( size_t n, T const t=T() );9 y w+ {7 q: Q9 b% d
vector( vector const & );
* j1 r0 P* A! \5 w/ [- n# [vector( const_iterator first, const_iterator last );
7 G( [8 T- X- L% x/ o) r1) vector();
5 @% m, K% N/ R4 d$ g7 P0 }5 g o6 ?4 p% p) F7 o
构造一个空的 vector,不包含任何元素。2 R/ F6 p$ j' P( g) v
# c% {& \3 x5 s9 j) z) q: {( zIntVector v1; // 空的整数向量。6 V5 k1 [; Y0 e, e0 K) n
2) vector( size_t n, T const t=T() );, Z5 ?: k4 ^8 [5 W4 T- `( k7 O* F" ~) @
9 b7 M6 a0 V. b
构造一个 n 个相同元素 t 组成的 vector。如果不给出 t,那么将用 T() 做缺省值:
9 r. ^$ y; E* t6 Y* a* y9 Y: h' a# r' ]! M
IntVector v2( 100, 1234 ); // 100 个 1234.
) d6 ]: c! d; m! v* e( G0 UIntVector v3( 100 ); // 100 个 0。
( _+ f$ W- O3 M& {3) vector( vector const& other );
9 r7 e5 z2 \9 n! F# p
8 h4 c4 b! ]3 V) ^, i( N( T5 o复制构造函数,复制 other 中的内容:& k+ m) z4 g7 A8 x
7 X i5 P5 g# H, P0 G
IntVector v4( v2 ); // 100 个 1234。
$ K% V5 e% k2 f- @" W4) vector( const_iterator first, const_iterator last );6 n" f3 e& t+ H/ w* k' J
8 U- c3 e `* u2 b ?事实上,这个构造函数应该为- M. k* |, }8 k! x- _0 B
) F! e# h2 b2 `" W* U' P. P
template<typename Iter>! {+ E# B! j2 `; i
vector( Iter first, Iter last );. m# ?7 J3 r( I6 n
即拷贝任意的序列 [first,last) 到 vector 中。由于 VC++6sp0 编译程序的限制, Iter 被换为 const_iterator 了。不过,碰巧 const_iterator就是 T const*,所以可以如下使用:1 P# t" v, j6 g' M; ^2 E+ D! f
# G# z& t8 x" j; {
int a[] = { 1, 2, 3, 4, 5 };0 [8 @# u& ^! K8 A
IntVector v5( a, a + 5 ); // {1,2,3,4,5}: C6 Z" I( Q3 Y8 \# M- W$ I
IntVector v6( v5.begin() + 2, v5.end() ); // {3,4,5}0 f0 c/ k: D9 [2 C3 \) N
5. 访问 vector<> 中的元素
$ Z( A) }2 a+ ?; `! V以下成员函数/运算符用于访问 vector 中的一个元素:
w7 i- e9 B* R+ d) e6 B2 F2 j! c- W3 P. m0 n
T& at( size_t n );$ s1 P8 Q( `; ?+ E7 { D
T const& at( size_t n ) const;) ]& E2 Y% U1 k3 ~7 O
T& operator [] ( size_t n ); \0 _8 X0 L6 u* ~4 A# k' s5 Y( s
T const& operator [] ( size_t n ) const;; j: V. M4 o( M
T& front();
: Y3 H, M7 }/ B4 v0 k& ST const& front() const;$ h; u# F( y, q A
T& back();* J+ x' m: ~" d x, L
T const& back() const;
# C- M+ \% n S* |' w请注意,由于 vector 是一个“值”语义的对象,所有的操作函数都必须严格保证 const 的正确性。所以,所有的元素访问方法都有 const 和非 const两个版本。" `4 A" }! R$ L" \
J e+ E9 T* \
at(n) 和 operator [] (n) 都返回下标为 n 的元素的引用,他们的区别是,at() 进行下标越界检查,若发现越界,抛出 range_error 异常,operator[]不进行下标检查。7 a. L' N1 R, ?+ C, s8 ?
$ Q- _% c! K) sfront() 返回下标为 0 的元素的引用,back() 返回最后一个元素的引用。# P; W0 h" D% `8 x
. K9 k" W% C7 _. x9 g' C1 U. S
int a[] = { 4, 1, 4, 1, 5, 8 };8 v, V, l# V% i) J/ E# _! N& h
IntVector v( a, a + 6 );
' c% d5 U' j `* U7 M// 使用 front(), back():
9 z- f$ b1 ~7 U: P+ zv.front() = 3;& d1 ~# f/ U3 J) _$ r
v.back() = 9;" N1 N5 u5 t7 S( u
// 使用 operator [] ():! U* x! Y" a6 I1 Z8 ?
for( size_t i = 0; i < v.size(); ++i )
0 O t& h3 Y! R# Y7 a6 }::std::cout << v[i] << '\n';4 t* e9 A$ P6 C2 l/ s- @
6. ::std::vector<> 的存储管理4 M7 l6 g% e1 e5 V t
以下成员函数用于存储管理:4 h' l! ]6 T' r/ W& y) @) I
) X) a; Z+ E8 B3 k) t+ R7 svoid reserve( size_t n );
4 t4 m9 P" T7 t9 g$ T7 I, Bsize_t capacity() const;: E% s2 b+ ~/ E+ Z
void resize( size_t n, T t=T() );, ^" W/ w0 N: t# L* ?9 I) D
void clear();
) E" t& {3 f0 }: Fsize_t size() const;
5 K9 G8 \$ G- q( w8 d: q+ ?, hbool empty() const { return size() == 0; }4 U4 H1 _( b. l* a
size_t max_size() const;9 X E. `* J0 Z3 {
& t& B, ~0 _* A4 P. T% p另外,push_back(), insert() 等也涉及到存储管理,后面另行介绍。0 _/ ~! w( ~9 D G y1 G/ }$ H
. X+ y$ H: Q a/ J: X. u1) max_size()* C7 I# k" J! a- t9 `7 S! M
2 O- t- X. E# k4 \) r+ m8 k
返回 vector<T> 理论上可以装的最多 T 的个数。这只是一个理论上的数字, 大概是 4GB/sizeof(T),没有多大实用价值。在程序中不要用。3 g# e. P7 g( n! f/ y
8 r: l$ ^) _" r; U0 K& V+ [2) size()- _3 f. L" U0 k8 b
5 E" S5 Z( ?9 d: [/ c* _
返回 vector<T> 中实际装的 T 的个数。相当于 CArray<>::GetSize()。
3 W$ p4 s) ~) }: d
4 E6 j3 |: o- K) W* _4 t% O3) empty()# l- i" t3 T5 A: B% c$ w4 o6 u
* P* X2 E6 `, w- @, V3 L$ S如果 vector<T> 中没有任何 T 对象,返回 true。也就是返回 size() == 0。
4 \/ O; D. P4 {+ ~4 {. X, U$ b3 I% ~- G* o" D# l/ Y
4) clear();" `! C4 s/ K, n
* q' c; `2 p& B1 W* w/ f G* R
清除 vector<T> 中的所有 T 对象。执行后 empty() 返回 true。大致相当于 resize(0),但不要求 T 可被缺省构造。相当于 CArray<>::RemoveAll()。
& P2 k/ C0 o! U" p* I' p6 m n
9 x& Z! |6 E- D5) resize( size_t n, T t = T() );) n& B, Z, t. f
( Q0 p, E) N. {; `" A8 Z- \将 vector 中的元素个数设置为 n,n 可以大于 size() 也可以小于 size。如果 n 小于 size(),那么 vector 中下标为 n..size()-1 的元素都将被解构。如果 n > size(),那么将在 vector 的后面新增加2 ?' ~: L, J8 O9 a
n - size() 个相同的元素 t。在增大 vector 时,可能发生存储再次分配。总之,调用resize( n, t ) 后,(size() == n) 成立。
# X/ P) C/ `: Y7 n* |$ c
# g4 b' s3 ?5 |请注意,如果调用 resize( n ) 不带参数 t ,那么 T 必须可以缺省构造。! z1 s7 ^9 ^" K7 U6 r$ |# ]8 O
+ t( O; L7 h, \" b9 o' F
6) reserve( size_t n );
1 f, j! _/ I- Y& t* Z, I# G6 X1 F$ x$ C
事先分配至少可以保存 n 个 T 对象的空间。调用后 (capacity() >= n)成立。4 j9 @) i" f, l* W, V
8 X& u3 \. s1 _* L# z7) capacity();- Q- B/ N* M& F& V/ I' ]
9 d/ N9 Q) b9 |# Z2 E8 T
返回已经分配的存储空间够容纳的 T 类型对象的个数。后续的增加元素操作(如 push_back(), insert())如果增加元素后 vector 中的总元素个数不超过 capacity(),那么 vector 的实现保证不重新分配存储空间。* O% a5 v* ~& i
% m* V5 d! |4 G, b6 Hvector 管理的动态存储空间是连续的。执行操作
* D: `) W7 B+ k$ \
( O! X2 `1 \6 o% V+ {$ h3 d" OIntVector v(7, 1); // seven ones.
5 D) |! H) K& G- }/ O/ D6 Lv.reserve( 12 );0 K: B% p. Y1 F, d! d( S
后,v 的状态可以用下图表示:4 g K- s: B7 ?" ]
" R3 s4 p, @0 v' P& h0 E y
/--size()---\
0 u4 Q! e5 U& H+ a1 ?. D|1|1|1|1|1|1|1|-|-|-|-|-|2 q3 T+ y h4 K5 B
\--capacity()---------/! U8 z7 F- x+ Z F5 ?+ T, b
其中,1 是已经构造的 int 类型的对象,- 是可以构造一个 int 类型的对象,但还没有构造的原始空间。再执行! Q! G- j- O* H, d
7 l% a8 Y+ ?0 I. av.push_back( 2 );0 P+ B3 p) R* m6 D
v.push_back( 3 );
" M* P3 A' J w9 E' |/ B; A6 n后,v 的状态可用下图表示:
; H& L, u% ~5 W4 k, {: Z0 t" u
, e; `) b7 G" Z; Z5 c /----size()-----\
8 O! c- |/ q6 j: z5 w. L|1|1|1|1|1|1|1|2|3|-|-|-|
8 W% }) P- I1 l/ B3 f7 y \----capacity()-------/ C y- \) M' M6 b4 Y5 a
执行 resize( 11, 4 ); 后:8 s; T* w* `+ e
0 a- k: ^' M5 |# Z" G
/----size()---------\% }2 ?( s. W w/ j& k! m8 d
|1|1|1|1|1|1|1|2|3|4|4|-|! A7 A' o9 F* r: }( X9 \* f
\----capacity()-------/3 r0 t, h# y. k' _
capacity() >= size() 总是成立的。对于下标为 [size()..capacity()-1]的未构造对象的存储空间,是不可以访问的:3 n8 a/ r v8 b; Z
3 ]5 e- x! f- {1 B; D' v; l% w" wv[11] = 5; // undefined behavior - anything can happen.7 ?. A- ?+ J# b) m2 Q$ Z" H1 P4 _
7. 添加元素到 vector 中
! i4 {, ^ d7 l( |% k7 x$ f7 r4 m; P下列操作添加元素到 vector 中,并可能引起存储分配:
* {2 L2 s1 v3 U4 X% b/ v3 [3 I4 i! q0 j
void push_back( T const& t );
3 `+ O- |" G S5 x# Gvoid insert( iterator pos, T const& t=T() );% t& M+ u' g' A0 {- M' |, K* W
void insert( iterator pos, size_t n, T const& t );& g4 L; F% V$ G1 n0 G- c/ q$ g
template<typename Iter>2 |: f! b9 L Z% s1 c
void insert( iterator pos, Iter first, Iter last );
, \9 T$ i" `* A, ^; v7 N1 v0 d rpush_back() 是把一个元素添加到 vector 的末尾。insert() 是把一个 t,或 n 个 t,或从 first 开始到 last 结束的一个序列插入到 pos 指示的位置之前。9 G6 y" a- s4 C% ^' r8 I
/ G2 \+ S/ z. m7 G% h, w0 Q1 @
当插入元素后 size() 将会大于 capacity() 时,将引起自动存储分配。vector 将会分配一个比需要的存储区大若干倍(通常是1.5到2)的新的存储区,把老的元素拷贝过去,同时完成添加或插入,然后释放老的存储区。
* o7 d9 w1 P6 J5 f, y8 X$ E. \# n
- I) p+ @ H7 z a4 W9 e这就是说,vector 自动存储分配的空间大小是指数式增长的,这可以保证多次添加元素到 vector 中时,平均用时是接近于常数的。( Y/ ]- `& `9 u* {7 S6 J* n
: c) m( G" r+ M3 O- q6 mIntVector v;1 {) o- Q& K [& N; d9 \& k
9 D& F9 Z& P1 a
// add 0, 1, ..., 99 to v:$ b3 V0 L d3 x
for( int i = 0; i < 100; ++i )
: [( U. z3 g& H" f* }* k2 Iv.push_back( i );* H4 O+ R, |2 v: B
! p" n# Z+ P, U. f// append 9, 8, 7,..., 0 to the end:7 |4 r& Y# l! u+ ~$ ?: q$ D
int a[] = { 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 };
# h, P% Y9 a- S7 ov.insert( v.end(), a, a + 10 );
1 i9 C% t% a5 {! A9 L8. 删除元素
& X& o- u! ]; W t, J6 |# P3 X下列成员函数完成元素删除:; e' o; U5 n8 d2 J/ i
- J7 f7 s; s8 R5 C
void erase( iterator );6 G5 k, P% N; K& ^' {
void erase( iterator first, iterator last );
3 q0 q w4 q6 Q. E# _( |$ ovoid pop_back();
- N" N4 v5 I \! @void clear();% e1 S; g, q+ P) v6 c
这些函数分别删除一个,一串,最后一个,或全部元素。
* x* c* g" Z2 W) A
, q: ?" G7 U* [8 BIntVector v;
) P: O1 X/ b2 D" v3 q( W s' f Xfor( int i = 0; i < 100; ++i )+ a( [5 H9 W9 p* J J
v.push_back( i );
; _3 l0 ]- K: v- {: Q8 D, k8 `
& G4 D' K# W6 H5 T" |// 删除 50, 51, ..., 89:, [# O9 I' f% b8 A% B' a/ Y2 A
v.erase( v.begin() + 50, v.end() - 10 );
# l/ l* M+ n& }' \7 \ ; N* V% R$ a. O; E
// 删除 49, 48:
( g) c" n7 j& }/ Bv.pop_back();
, x1 y3 H+ U5 {$ d0 h, `$ |v.pop_back();
! M8 Y& T$ [( z0 x; _1 O- n 9 W0 i9 ~4 @' h w+ q8 Y& I- _; L
// 全部删除:- j: ~* e [- g
v.clear();+ X1 ^, Q1 G: v3 B- x7 ]2 f
注意,删除操作不会引起存储分配,因此 capacity() 不变。
+ ]5 l2 J3 ?1 x$ c$ `, O1 V5 {1 i) H& S A9 g9 ~- h
9. 作为序列访问 vector 中的元素7 P; ?; W# C/ \" L( i l. }+ @
序列(sequence)在 STL 中是一个非常重要的概念,所有的容器类型和算法都涉及到,而且所有的算法都是建立在“序列”这个概念之上的。' |, k$ t) f; z1 u% y9 |! y
7 j1 u l; {+ C; M; I“序列”是一个线性结构,由一个指示其起始和一个指示结束的叠代子(iterator)来决定。如果 first 和 last 是某种类型的叠代子,那么经常用[first, last) 来表示一个序列。注意,first 指向的元素是这个序列的一个元素,而 last 指示的是这个序列最后一个元素之后的位置,可能根本没有元素可以访问。这种半闭半开的区间表示是整个 C++ 标准中的约定,而且确实可以简化程序。
+ p8 m% k. a5 r! [2 ?3 ~ }& j5 j' D5 S# T5 e
叠代子是传统的 C/C++ 中指针的抽象和进一步分类。在 C++ 中把 iterator划分为 input iterator, output iterator, forward iterator,bidirectional iterator, random access iterator 五类。其中的 randomaccess iterator 是最强的一类,即允许的操作最多。C++ 中的指针类型以及vector<>/deque<> 的 iterator/const_iterator/reverse_iterator/const_reverse_iterator 都满足 random access iterator 的要求。 o' k- a7 d; _0 w7 y X
5 g- V2 d! j F( Ovector<> 中定义了以下函数用于获取被控制(管理的)序列(动态数组)的各种叠代子:
1 S$ o4 t3 V2 t% V. e8 b' G' Q6 \4 {0 P! f' b
iterator begin();
4 e) l. ]2 ^7 B P1 aiterator end();
5 d# U+ U# d9 D! M. J/ X, aconst_iterator begin() const;; U+ z$ a6 w2 d8 S
const_iterator end() const;4 ]/ v/ C1 T; h" |+ `
reverse_iterator rbegin();
+ Q j! M% |7 l! J8 G# \reverse_iterator rend();$ F( t- m6 `0 o$ t( i4 o2 h
const_reverse_iterator rbegin() const;* \! A/ H/ r% G" \, j' f, e
const_reverse_iterator rend() const;0 D g) |! J4 T0 v
这里我们不讨论叠代子的一般概念,只举几个 random access iterator 的例子:/ a9 X! P$ T7 \9 ^. j; G3 H
2 V$ |# Z4 F8 l7 z0 {7 lint a[] = { 1, 2, 3, 4, 5, 6 };
9 \6 ]! x- n, k) I5 ~0 J |[a, a + 6) 是一个随机访问序列,指示了 a[] 中的所有元素。这里叠代子的类型为 int*。
; ?4 [' ?/ A8 N* f, P4 |
& B2 r; f+ B3 h$ `! \: i1 x4 N[a + 2, a + 4) 也是一个序列,指示了 a[] 中的 3, 4 两个元素。叠代子的类型仍然是 int*。7 M8 m/ g9 N% y! @
! I9 e- W- q4 Y2 N) H7 F1 y
IntVector v( 100, 1 ); // 100 个 1。3 S+ a( K" P, J# M# E' I7 o
[v.begin(), v.end()) 是一个随机访问序列,指示了 v 中的所有元素,叠代子的类型是 IntVector::iterator。. \% o& e" k( Q6 C& V& C8 F) y$ ~
9 i3 X% H4 E- x" R6 T[v.begin() + 10, v.end() - 20 ) 也是一个随机访问序列,指的是 v 中除了头 10 个和尾 20 个元素外的其它元素。
1 G, J: p2 O8 x) b, Y g i
0 ^4 o& w& d9 U" h[v.rbegin(), v.rend() ) 是一个随机访问序列,指的是 v 中的所有元素,但与 [v.begin(), v.end() ) 不同,这个序列是从尾到头遍历所有元素。
* I% b8 I$ G, j$ y& F p0 }1 j/ `4 E) m9 h; Z- A& @
[v.rbegin() + 20, v.rend() - 10) 与 [v.begin() + 10, v.end() - 20 )指示的元素相同,但遍历顺序相反。; j9 z! x- _: x" @
/ \; T# {* p* T/ O
下图是有十个元素的 vector 的 begin()/end()/rbegin()/end() 的示意:/ U* j Y4 C* q: s# o9 M e
1 H- z* m. H. R7 Q- I' Gbegin() ----------> end()- {( t- l, T- [/ n7 ~3 ~. K; y3 ?
| |# H2 W+ _% [9 k8 l$ W2 z9 `0 _& P" j( V
v v
: P8 @6 a+ k- h! X |0|1|2|3|4|5|6|7|8|9| F5 T* J4 ^, m3 q
^ ^2 w1 p6 e- u' X- r4 q8 a' W+ K
| |1 Y: ?/ ]: [& z
rend() <---------- rbegin()
9 v6 Y0 I' R: ~
( f" w, p" n- yIntVector v;" \7 y I+ [* R4 h
for( int i = 0; i < 10; ++i )0 s5 B+ V/ `8 E, m3 {. |! b( |
v.push_back( i );
% C0 y: G4 e4 u$ p& ^. {# d, x% u9 L
6 W; {- d$ X* V/ d9 A2 k// print 0, 1, 2, ..., 9:
% \/ h' r" j+ p4 T. _for( IntVector::iterator i = v.begin(); i != v.end(); ++i )
( y$ u8 z) y' R# G::std::cout << *i << '\n';- Z4 ], B1 f4 y* I; |. }
$ C* y* f: X, _4 K' j \3 R1 s) [// print 9, 8, ..., 0:
( Z5 M9 p& Y3 s$ I G& gfor( IntVector::reverse_iterator i = v.rbegin(); i != v.rend(); ++i )
, F% }3 }- G' R, l9 B::std::cout << *i << '\n';
; P. P8 }: Y- J除了使用 begin()/end()/rbegin()/rend() 来遍历 vector 中的元素外,由于 vector 管理的空间是连续的,因此可以直接取地址进行处理:
6 ~0 x2 i4 f+ B5 m3 h
@$ E9 x1 g/ P V::std::vector<HANDLE> handles;$ y- M7 a! k D6 g9 D/ h2 O3 }5 C
handles.push_back( handle1 );& H, @8 f, Q* `
handles.push_back( handle2 );
& G+ |. @: C9 e: e, K% ?. [( p- W0 i+ Z- I ~
& }" P. R9 `* o# X::WaitForMultipleObjects(handles.size(), &handles[0],TRUE, INFINITE);
k0 ^/ w4 R1 L$ Q9 e这在与 C 库函数接口时尤其有用。9 J) f( f ^9 j$ W3 x U, B( \
& a$ w% p! l2 J4 }' w3 g+ Q2 ?5 d0 S
10. 赋值和交换
- U6 E9 l, J+ z, P; j3 c$ z/ O6 h/ Jvector<> 是可以赋值的,这也是一般的“值”类型必须提供的操作:* Z7 d! ], I: k1 G. w1 O. r
4 [+ A) X" ^. {: lIntVector v( 100, 123 );# l ?* ]8 X# c' x
IntVector v1;) ^9 a4 j( _5 y; O4 j$ M8 S4 V5 \
v1 = v;
8 t7 s: r1 H, \5 J) I3 z& ^3 ?1 Bvector 另外还提供了$ j6 E4 |6 {/ r+ s; ~/ n7 A
9 K0 ?% }8 ~3 l; itemplate<typename Iter>! L- a" D) D. _7 \1 x+ i
void assign( Iter first, Iter last );' {, t: R& ?, X0 U% D1 t6 L
void assign( size_t n, T const& t = T() );
# c0 l' V& ~ O用于赋值:9 v4 u' Q( G! o, E( w8 M3 I
( q+ T( Y8 e% d/ K
int a[] = { 1, 3, 5, 7 };
" B0 r5 V7 C) M% ~: ~3 Nv.assign( a, a + 4 ); // v 将包含 1, 3, 5, 7.
( x2 Q0 i% n r7 B, ]v.assign( 100 ); // 100 个 0。! [% u3 s* H, F) ~ m/ a! ]9 Z
还有一个很重要的操作:
$ D, U/ f8 P/ m/ i& l0 m& k* z3 I; A# b
void swap( vector& v ) throw();
! ^# L% A( t1 i% Q用于交换两个同类型的 vector 的值。它的特点是快速(只需要交换内部的三个指针),不产生异常。这在写一些保证异常安全的程序时非常有用。% B# ?2 j. l3 U- a+ _ e& R% L/ s
1 p" H5 U' w4 P事实上,swap() 基本上已经被当作类似于 operator=() 的一个“值”类型应该提供的基本操作,::std::swap() 也应该为用户定义的类型进行特例化,调用相应的类的成员 swap() 函数:
: P5 `" F- T a! i" s% d. @3 k
3 g- Y W, h' \' |. G5 `struct MyVal
( Q P; G% Z! {5 L5 f R- Q{
+ h9 u9 ?, U) z8 Z // blah blah.
+ q, _% z8 B' e void swap( MyVal& ) throw();. t+ ?+ ^9 ~- P$ ]# v6 f' Z; N
};8 T$ w1 L3 u' C! L$ w. P4 y$ `
2 B$ J) L1 F. v0 U- I: e
namespace std {$ V- f6 X2 X4 J- m" d& z* J
template<>% V4 S' G% D: [& X3 @: X
void swap( MyVal& a, MyVal& b )# U* `" R# x l) u" m
{ a.swap( b ); }
( u1 @; d9 V ^}0 W9 V& H+ k7 h1 j
关于 swap(),值得专文讨论。这里我们只指出,vector<T>::swap() 是快速的,不抛出异常的,很有价值。& w2 o W* b4 f" v9 K1 ?' m
$ B7 w1 q$ R$ j- k, ?/ E11. 使用 vector 时的存储管理策略
+ {" J6 [# J6 u+ A7 l! s从前面的介绍中可以看到,vector 的自动存储分配是指数式的增加存储空间,而且永不缩小已经分配的空间。这在大多数情况下是合适的。 如果应用程序事先知道要用到的元素个数,可以先调用 reserve() 来保留(分配)空间,这样可以避免以后增加元素时不必要的重新分配和元素拷贝:- i0 y" Y7 l5 _1 Y9 R4 ~. q3 |
8 a( s+ W) C5 @8 s4 p+ B: Q/ ]7 EIntVector v;
( D! X; T$ ~+ ~0 S5 P6 Yv.reserve( 100 );7 {$ ]) X3 q# P: i
for( int i = 0; i < 100; ++i ), O; d" ~! _4 X) ^! T
v.push_back( i );
0 J) S: h6 Z; ]) J6 q9 q5 z) L请注意,reserve() 和 resize() 是本质上完全不同的。reserve(n) 保留的是未使用而能够使用的原始空间,而 resize(n) 是真的创建了 n 个对象:
) a) ?4 x4 V1 i1 {& Y6 D3 r
3 @+ p" M5 j6 pIntVector v;
6 H9 l7 l, Q: a3 K$ L" [v.resize( 100 ); // v 已经包含 100 个 0." X5 w. h. F6 H
for( int i = 0; i < 100; ++i )
# K& D) o2 d U: S9 w0 u v[i] = i; // 可以赋值: Z f8 b' y& T! y# G
有时候,一个 vector 可能增长到较多个元素,然后又减少到较少的元素个数,这时,可能希望缩小 vector 分配的空间以节约内存。CArray<> 中提供了 FreeExtra(),但 vector<> 并没有提供相应的函数。这时必须进行复制:
+ f5 [3 O. G" \3 ?0 d0 n$ q( C- ~: Y4 ]
IntVector(v).swap( v );# E" l% c4 r' _1 R! C
有一种看法认为拷贝构造函数同时也复制了capacity(),而标准中并没有很明确地指出这一点,因此更安全的方法是
0 r; j: h: N. V9 I) q6 {. V |1 `; t8 f2 o8 d4 O0 Y( c# {
IntVector(v.begin(),v.end()).swap(v);: T& y0 d+ V0 x/ u4 S3 j4 u! s
如果一个 vector 中可能要存储的元素个数较多(例如,超过100个),而且事先无法确定其个数(因此无法调用 reserve()),那么通常 vector 不是一个恰当的数据结构,应该考虑用 ::std::deque<>。与 vector<> 相比,deque<>不保证背后的存储空间是连续的(因此象上面的WaitForMultipleObjects()中的应用不能用 deque<HANDLE> 代替),但有较好的伸缩性,还可以在数组的前端用 push_front()/pop_front() 增减元素(hence its name, doubly endedqueue)。 |
|