|
|
作者:wangtianxing
+ _- v5 G6 z9 P4 D% t( j# h, L- _# P$ g6 ~2 h
原文出处:http://www.cpphelp.net/issue/vector.html; v9 ?5 b7 ]5 f( ~$ L; i
1 v) r( n0 C, v
4 E; M4 F" J( g8 d q: c
8 z, ^, _5 N7 a2 ^/ a摘要: 本文介绍了C++标准库中的容器类vector,分析了它的优点,并且建议在应用程序中使用它作为动态数组的优先选择,而不是MFC的CArray<>等其他类模板。最后介绍了vector的接口和使用时的注意事项。! V* ^% B/ n2 e- M9 P' z
- U( [0 c% A1 r5 i在一些使用 MFC 的程序中,经常看到许多程序使用 CArray<>,由于 CArray<>的设计问题,造成使用它的代码的复杂化,增加了维护难度。因此建议使用 ::std::vector<> 代替 CArray<>。, J+ L, e4 d9 I
; D; T% P& R4 M& c8 o& `
另外,也看到一些程序在用 malloc/realloc/free/new[]/delete[] 等手工管理内存。在应用程序中,手工管理内存是容易导致错误的,应该用 ::std::vector<> 之类的对象来管理动态数组。& Z# u# q3 \" T
! }' Q- m6 Z* N( u! j0 C: T1 C# t由于 MSDN 中关于 ::std::vector 的内容较少,我们在这里做一些介绍,供参考。; G: |( j4 i& d2 r
& p# C3 d$ t8 r% Z, H/ m不熟悉 CArray<>/WIN32 也没关系,这里提到它们的地方并不太多。
- p' l) y9 e2 y6 l
, G1 }3 U+ }2 W9 ]' K% W# p" K$ H1. CArray<> VS ::std::vector<> ?0 T" o, Y0 ~, |0 b K! ^6 M
CArray<> 和 ::std::vector<> 一样,都是模板类,用于管理任意类型的对象的动态数组。都在解构时释放所管理的动态内存。因此都可以用于代替手工动态数组管理。' V2 N) d% J# _- N% @! M* v
3 l. Q: S/ N, Y" W: k4 r7 j
但是,CArray<> 是在 C++ 标准化之前很多年(VC++2.0时代)设计的,当时对 C++程序设计,面向对象程序设计,模板程序设计等技术认识严重不足,尤其是当时对面向对象技术的错误信仰与宣传,造成 CArray<> 的设计有重大错误。/ f& L% h& `! R6 K4 b' n# u
, |( u& n4 H( P; y/ }, k' ]
在 C++ 语言标准化以后(1998),以及 VC++ 6.0 出世以后,提供了标准的::std::vector<> 模板,基本上在任何方面都要优于 CArray<>。Microsoft 由于要支持老的程序,因此一直保留了 CArray<>,但显然并没有打算按照新的思想去发展它(至少应该提供operator=(CArray const&)吧)。# J. N3 }% [3 ~
1 ` D7 ?; L* Y概括起来,CArray<> 与 ::std::vector<> 有以下不同:3 r& Z# Q) w$ B! m" y7 a2 T$ ?
7 D7 H$ W, }' x! V& R. a
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<> 没有继承任何东西,只是实现了管理一个动态数组该做的事。& l$ {* W% H( m+ { S7 U
: Z; A. t+ P% w- ^6 ^6 r' ~
2) CArray<> 不是一个恰当的值类型,例如下列操作都是不合法的:
d6 f9 ~- ?$ c0 @' ]: e5 x1 L0 q& p* I* d: e! ?# `( ?
CArray<int,int> a;
- J0 {2 o' F" e: p4 p4 U2 z0 VCArray<int,int> b(a); // error, must use Copy().
# K$ A4 }; o* l/ H8 y% a" xb = a; // error, must use Copy(). c: A2 R: w, W. h) K9 R
b == a; // error, you must write your own.* C* @5 c& L( e9 W* O% |
b < a; // error, you must write your own.6 f w( T# @5 V% Q L% u9 i
与 CArray<> 相反,::std::vector<> 是一个认真设计的值类型,天生是可以拷贝构造和可赋值的。如果 T 是可比较的,那么 ::std::vector<T> 将自动地是可以比较的。
1 ~' }, f6 Z4 o+ n3 d5 l3 g7 R5 x' n% y) d0 p- r
此外,由于涉及到四个特殊成员函数;
3 @3 a6 W0 n4 u( y2 F7 @; t
0 E* D# o; L( E3 E2 P8 FT(); // 缺省构造函数(default constructor)
. X$ ^1 Z+ r& H~T(); // 解构函数(destructor)
Z; q' f( j% HT( T const& ); // 拷贝构造函数
( J' T4 ]; T0 m3 P# u' ZT& operator=( T const& ); // 拷贝赋值函数
( J2 I; T& q5 g' Y; P8 R" e3 H3 N的自动生成,如果使用 CArray() 作为 T 的成员变量,那么上述的四个特殊函数中的后两个将无法自动生成,需要手工写:* W+ R/ ?/ N, o3 A }: |% Q
$ @5 G% L* S# _( F# y) |, z
struct T
; k8 j4 \. ?( b. v' m; V( X. l+ R! d{1 @+ U. g" c" k+ K
T() {}. v3 N. m% u- M& i: g
T( T const& t )/ t1 l' M% }& J
{
# F" Z( Z5 D7 A* r a_.Copy( t.a_ );
+ M. _; A& B; W8 G/ Y5 l i_ = t.i_;" O8 R9 k, ^8 k7 e' E. n9 X
d_ = t.d_;
% b5 R( C2 Q! ^% T s_ = t.s_;
& v, i: B: N, [& x+ U3 F7 V }3 @& Y* X0 ?( |3 P% i9 d
T& operator = ( T const& t )
" l3 G; Y2 W- ~1 l( u# C* L+ w8 V {8 n @1 b6 K- R' `0 Q* h
if( this != &t )9 o _, L3 _) \$ ^ ] w# E. p
{; h0 T( D/ Q4 i1 w( E
a_.Copy( t.a_ );
% K( F' B/ j/ S i_ = t.i_;
/ j+ C. b8 k k ~ d_ = t.d_;
7 p, f/ x; Y: N w8 ?% w s_ = t.s_;* }5 e2 V: ]- N$ i: r( p9 j& r
}6 c% _ Z; i# p
return *this;5 }" S0 e& `7 W. n) M" L" j$ n( g
}) u: b. _. M v: T s
private:
2 a. e; r) O# Z P+ Y! S CArray<int,int> a_;
$ X6 w( \2 O' o6 G5 V$ X" Y, N int i_;
: z( Z8 `: F2 @+ o# G8 z! K double d_;
$ u7 A5 N! q7 x' V ::std::string s_;
" n E+ x, x& ]4 F8 H0 {9 Q};
h" W+ o9 r+ T3 o' t如果使用 ::std::vector<>:
e$ z; m& c* V2 A2 U; F
/ O0 g7 g: P" A/ o! i- s8 b/ u- Qstruct T
! s) j4 X: b1 ?{
# x1 @" k) l9 a2 {# W( wprivate:
2 |( i9 b0 C. M+ | ::std::vector<int> a_;
8 F( F1 i' h+ \. [6 N) } int i_;' J6 ?5 ]9 M% j* h
double d_;' w" O% n" _; D2 L) u4 f
::std::string s_;
) {! b; `5 O/ e/ W0 l- w};
8 m7 @; G' w$ z4 L( u" ?( N; H. c上面列出的三个特殊成员函数都不需要写。好处是明显的:当你增减 T 的成员变量时,你不必到/ @- ^$ ]3 ~/ d+ I% f8 n
T(T const&) 和 operator=() 中去相应地增减。: y- i+ Z- K- m! ^. C
+ n. ^/ o+ f ~& J x) d$ y3) 没有现成的算法可以对 CArray<> 进行操作,而标准 C++ 里的标准算法大多都可以直接在 R8 x7 Z! {4 G4 o) Z5 U
::std::vector<> 上运行。例如:: F, O- t, U( s' c
+ T2 W( j3 W+ Y+ ?3 ~' f
static int const init_vals[] = { 3, 1, 4, 1, 6, 9 };
( q- T" e8 f# o; u: W. r* zvector<int> a( init_vals, init_vals + 6 );
( m( J+ D) k3 x3 f7 a*find( a.begin(), a.end(), 6 ) = 5; // 把6改成5
# B+ ^& y( j( Lsort( a.begin(), a.end() ); // 排序。
9 G+ ~/ T& A' w" d& `可以说,CArray<> 的主要设计错误是把一个本来应该是一个简单的“值”类型的东西设计成一个难用的“对象”类型了。所有的“值”的好特性都丧失了,但那些从CArray<>继承的派生类呢?
1 o/ u+ s$ t" c/ p" B4 x3 r$ d$ x9 Q& F/ r
CByteArray等的问题与 CArray<> 的问题一样,甚至更多(例如,CPtrArray,永远不要用)。
; F# i7 l8 ^8 \8 g- K$ b; F, o# ^" n1 l' A
同样,其他的 MFC container 模板,象 CMap<>, CList<> 等,都有类似问题,都应该用
" E" ^3 O( `9 m& k! {" b- B::std::map<>,::std::list<> 等设计更好的东西代替。
% o) P; V2 \/ H- A( \3 r. V$ f" b% d" _! e
2. ::std::vector<> 在哪里?% D& `1 k2 i& i1 x5 O# |
::std::vector<> 在头文件 <vector> 中定义:
: i8 s7 s0 }8 `7 c) i% `/ ]1 }# T3 l$ n$ G
(注意,标准的 C++ 头文件都没有 .h 后缀,有 .h 的文件是与 C 兼容的,或支持老的不标准的东西,象 <iostream.h>。)0 G' A t* d; O( Z6 V3 a7 s
. `$ H; P4 n5 E: i2 [, I8 y
namespace std 6 n) F1 ^/ D: ]3 F$ g( {8 Y
{
8 |5 ]; v, K5 j- Q0 u/ }/ m# @2 V template<typename T, typename A = allocator<T> >5 e) K+ ]4 F1 b- I! l/ [- n
struct vector) g, j% a9 [6 X M( I9 w) B0 v
{( V6 D9 Y" T7 s) ]4 Q
// 具体内容稍后讨论
R$ ~" @! L. O( ~5 e; q0 U };
. q' e2 ^, q2 d: `- |6 g+ n' p% k( D+ d: m
: P3 i9 P) o4 W% H: j, a/ [
template<typename T, typename A>1 O& u: a0 b, S5 E
bool operator == ( vector<T,A> const& a, vector<T,A> const& b );
$ }" b8 U7 c) _$ y! e$ B" f template<typename T, typename A>
: z" p* w& I ^" l; z6 E bool operator != ( vector<T,A> const& a, vector<T,A> const& b );4 }7 [1 A9 E' K) s+ m) j
template<typename T, typename A>
. U& n r4 P* D. Y6 p( } _- G9 K bool operator < ( vector<T,A> const& a, vector<T,A> const& b );! K9 n: m; `2 S8 L9 z7 b
template<typename T, typename A>
+ Y) b( v' i& l6 L' Y8 p+ a bool operator >= ( vector<T,A> const& a, vector<T,A> const& b );
/ _. [8 r% b0 Y: e+ U& K template<typename T, typename A>
5 }* v8 y1 B0 \. ^, e bool operator > ( vector<T,A> const& a, vector<T,A> const& b );1 b! ?3 N; ]+ V) j: q4 A
template<typename T, typename A>
. S5 Z4 c: t1 y' L) l bool operator >= ( vector<T,A> const& a, vector<T,A> const& b );! c6 L1 u! a" P, s/ C
}9 s) I' z) r: M& j; c1 y |
vector<> 定义在 namespace std 中,使用时为了减少击键次数,通常使用一个类型定义缩短类型名称:
9 T4 A2 D' w a6 L: ^
q! z4 `* p( U' _% V6 }* |#include <vector> f0 ~( ~* W. \& T# e- Y
typedef ::std::vector<int> IntVector;
& n" s N# g: r3 i \IntVector a;9 j# [" B/ r _* y Q0 ] C1 M
IntVector b( a );7 D0 K# s1 {9 P7 o8 f" n
IntVector c;
! o t- C, m4 h+ _. v' V- W, f7 s0 ic = b;6 j* H7 T3 B/ Y& X
assert( a == c );/ ]1 w" a$ S$ X# e- k3 S" Z" V% \- ~
请注意 <vector> 中定义了六个 vector<T,A> 的比较函数。这些函数只在真的用到时才会被实例化,才会要求 T 也提供 operator==() 和 operator<()。
9 b3 t) d+ h, A/ L3 w# D8 Z' c8 E/ [1 w @# B$ N( b
另外,A = alloctor<T>:用于提供一个用户定义的存储管理类。由于这个参数很少用到,而且在 VC++6 的实现中有问题,不能用,因此以下的讨论忽略这一部分的内容。. G8 F8 i1 ^: e) G. O. u1 Z4 ~( [: z
, l3 K' _: Q+ X/ C ?
3. ::std::vector<> 中的类型定义 m5 a' a3 W- A+ q
vector<> 中定义了一些类型,下面只列出常用的:) [3 w; d( [4 E2 p9 `3 h* c
1 T; | ? N+ Z3 y* H' K
typedef T value_type;
; W' |6 ~9 ~+ B9 ~typedef T0 iterator;
5 ]0 l# b: @- G: |+ b( g; V# b5 `typedef T1 const_iterator;
7 Q4 q' d9 m. q9 U2 ], I' Itypedef T2 reverse_iterator;
* e3 C: U/ T2 J0 n, ]typedef T3 const_reverse_iterator;
& ~/ x8 @4 m6 L" N0 R1 Y7 w$ s. Z0 ?6 [) ?+ ~
value_type 就是 vector<T> 的元素类型,也就是 T。当写通用的算法处理任意类型的 vector<> 或其他容器类型时是很有用的。 L/ A' I8 o( E# P
& \$ G) e6 d* H4 ?iterator/const_iterator 是两个 vector<> 的实现定义的未知类型,用于访问vector<> 中的元素,类似于 T*/T const* 指针,他们的区别是一个指向的元素可被修改,另一个只可以读:4 i0 |7 L1 \) s! p+ }) o" o
+ z0 x' D! r8 R% o+ G- n* X
typedef ::std::vector<int> IntVector;
, X" W# j$ G0 |: \: \+ eIntVector::iterator iter;: ?: W5 {9 z% }8 ~
IntVector::const_iterator c_iter;! A/ t0 z5 q5 X& Z3 r0 D# ~& `
// ...7 ~9 _( g4 T I2 ]" m
++iter; iter++; // ok: increment, post-increment." q. ?% B& L5 _
--iter; iter--; // ok: decrement, post-decrement.
5 o; X+ ? m& V++c_iter; c_iter++; // ok: increment, post-increment.8 m) F* b! L2 [/ |7 z! A
--c_iter; c_iter--; // ok: decrement, post-decrement.! P+ r; v) |: S* n$ B6 L4 f p C
*iter = 123; // ok.
/ a8 m) f1 j7 i/ d4 A8 p+ t0 Mint k = *iter; // ok.
9 L) q( ~1 N/ M* k) s2 l lk = *--c_iter; // ok.
) C: u* Y( K! g5 p: \*c_iter = k; // error.: a3 K. A9 t' X" n6 q! X& n5 F7 T
c_iter = iter; // ok: iterator is convertible to const_iterator.+ T( p* i, X2 X7 _! c4 P$ \$ F
iter = c_iter; // error: can't convert const_iterator to iterator.
$ A; C, [) Z- o$ P在使用上 iterator/const_iterator 和 T*/T const* 基本相同,事实上有些vector<> 的实现里就是用 T*/T const* 实现 iterator/const_iterator 的,但又不可以把 iterator/const_iterator 当作真正的 T*/T const*:
" F6 n) s: ?4 W/ F2 @- M) i x& V: o) C7 G$ a- G" R
T* p = iter; // may fail to compile.- w |/ V. @: r% Z0 v r
T const* q = c_iter; // may fail to compile.( s K+ N2 j# z
reverse_iterator/const_reverse_iterator 与 iterator/const_iterator 类似,但以相反的次序(从尾至头)访问 vector 中的元素。8 R( K0 P! j' i& A- w
: C! t" @6 `% c+ B2 v各种各样的 iterator 在 STL 中有特别重要的意义,但这里我们不做具体介绍。只要理解通过 iterator 可以访问 vector 中的元素,大概相当于一个指示位置的指针就行了。. c: v- o$ F/ H8 X# i
. P) W" d( Z! i$ {7 K
4. ::std::vector<> 的构造
9 \3 ]4 U2 ]; P* Z4 ~9 R8 Avector<> 提供了以下构造函数:(忽略 allocator 参数)5 |( Y- S4 Q6 p) f9 O
0 c( Q$ B; s3 vvector();
6 s" @1 w ]2 Xvector( size_t n, T const t=T() );
# T" R( L9 U3 u& o4 F- @% v5 c$ ovector( vector const & );
; z0 p! N$ O/ x" J. pvector( const_iterator first, const_iterator last );
% w* E% G: Z" p1) vector();: S! u( x! m: Q1 }8 R; U. o# |
/ E( _" g+ C/ {; G' @$ w构造一个空的 vector,不包含任何元素。
2 a) |9 g4 A# V
1 J {0 A" ]/ V' T& iIntVector v1; // 空的整数向量。9 O/ X) ?0 x; t. J6 j7 m0 W
2) vector( size_t n, T const t=T() );
' y- v/ x" z# ]9 ?1 N2 |2 G
' L; X3 H# y4 Q构造一个 n 个相同元素 t 组成的 vector。如果不给出 t,那么将用 T() 做缺省值:
% i# Z# G9 C( j$ }* p, i' X7 k. V1 w: T5 @, {
IntVector v2( 100, 1234 ); // 100 个 1234.2 \- i8 [$ r {6 ^+ R7 C
IntVector v3( 100 ); // 100 个 0。' L. N$ a3 {% v; N/ O. k1 z
3) vector( vector const& other );
" o2 w y, K- L T& M3 q. W/ @
" x [, d+ b1 Y- w1 }' i5 r复制构造函数,复制 other 中的内容:
6 r2 u5 @2 {* y) L3 `# i( v7 N3 f& ]8 i, V' I( ~
IntVector v4( v2 ); // 100 个 1234。
- y$ w1 }1 F# L5 W- ]4) vector( const_iterator first, const_iterator last );4 y6 k% ^% N. P# Z- [, v% X; W8 n" s
6 U! R4 z* ~% `1 @$ k) t事实上,这个构造函数应该为) T/ E% H3 j+ |2 t! @$ w/ G# l' G
( p1 H/ ?( ~4 p7 G9 C. \) q
template<typename Iter>8 x4 y; _3 m, k- ]0 l* U
vector( Iter first, Iter last );
0 T* i) d) E' t* p, S( m0 R9 z即拷贝任意的序列 [first,last) 到 vector 中。由于 VC++6sp0 编译程序的限制, Iter 被换为 const_iterator 了。不过,碰巧 const_iterator就是 T const*,所以可以如下使用:( e! B5 T+ c* Z9 z( f C
) k1 B# }0 @8 s
int a[] = { 1, 2, 3, 4, 5 };( O: m/ \3 S" h( f9 v$ v$ p0 F4 n+ L
IntVector v5( a, a + 5 ); // {1,2,3,4,5}3 H: M6 q8 m5 d# E* \1 }* i1 r+ s
IntVector v6( v5.begin() + 2, v5.end() ); // {3,4,5}
3 b+ d. I) K2 |/ L, o; R' ~5. 访问 vector<> 中的元素
( a9 J |3 o3 P/ T以下成员函数/运算符用于访问 vector 中的一个元素:5 l( B! ~% M6 X! W
& Z2 r$ h& l* v6 K+ @
T& at( size_t n );
: b% l$ [- b# G- o- [T const& at( size_t n ) const;
+ ? h% }" G$ \) |5 TT& operator [] ( size_t n );
( W( }8 a* }( B" t; p; ]- dT const& operator [] ( size_t n ) const;4 h, Q7 P5 S+ `0 q M
T& front();7 F$ }, Z' }9 x1 v5 ^- D
T const& front() const;
5 [, K/ W$ w, m5 zT& back();
" @+ e" G" U2 a, { }' a8 DT const& back() const;
% x/ d. X* S& W8 m请注意,由于 vector 是一个“值”语义的对象,所有的操作函数都必须严格保证 const 的正确性。所以,所有的元素访问方法都有 const 和非 const两个版本。8 c$ O0 x" z a1 E6 C2 x
, v) {3 k& x- Z3 `
at(n) 和 operator [] (n) 都返回下标为 n 的元素的引用,他们的区别是,at() 进行下标越界检查,若发现越界,抛出 range_error 异常,operator[]不进行下标检查。
" S( m3 V' f: Z) `3 i+ k
# k' x& [4 I n" N1 j1 ^+ c+ s/ Hfront() 返回下标为 0 的元素的引用,back() 返回最后一个元素的引用。0 g1 ~2 q; y" v& D$ U
/ u( L7 f/ R. [6 {, J% r: ^1 A/ K! x
int a[] = { 4, 1, 4, 1, 5, 8 };
7 v# k" e( N, [& BIntVector v( a, a + 6 );5 W W1 O" m1 ]. h* R! v! `; M
// 使用 front(), back():
4 G# N3 a( X1 e% X+ g3 \9 Y; Q( hv.front() = 3; t! s8 d" a3 }' f; J4 O
v.back() = 9;8 }! c {* [4 I( I5 U# e
// 使用 operator [] ():# s! P. x# t- s1 S
for( size_t i = 0; i < v.size(); ++i ), `0 W G) R5 e3 Q- P3 g" `2 h: W
::std::cout << v[i] << '\n';
$ j: Q; W* ?, }+ I" Q$ G! U6. ::std::vector<> 的存储管理
, m8 S3 x7 P6 V7 s( c0 S8 q: G w以下成员函数用于存储管理:
3 z& T& V3 Z5 d- ~. l
- \4 z( R3 I( _. {void reserve( size_t n );' J! g, p( g& N8 @, h
size_t capacity() const;! x+ @3 n7 v8 N- h/ B
void resize( size_t n, T t=T() );
4 s/ ~ [$ ?: Zvoid clear();
% {. _8 l y: ]: ~. Csize_t size() const;- {* U) j. N/ a$ {9 U7 z8 P
bool empty() const { return size() == 0; }
( M& d$ J! c7 W' asize_t max_size() const;
3 ^3 A) b4 P; y% V/ S# [6 D- J3 _7 G' f8 T$ z- ]/ _
另外,push_back(), insert() 等也涉及到存储管理,后面另行介绍。+ j d" E2 u$ x/ r% g- W
1 g6 c( h# x5 h- x5 a1) max_size()
/ p2 g( V; ?% t7 L& W& R
% l3 r: o8 r: ]$ D返回 vector<T> 理论上可以装的最多 T 的个数。这只是一个理论上的数字, 大概是 4GB/sizeof(T),没有多大实用价值。在程序中不要用。
) c4 O/ @+ ?# H+ i$ Y a$ [
0 P& ^ p5 c2 g2 u' Y- D- J2) size()
# P* I$ _2 Q4 w1 c1 q+ o7 y. o {
9 O/ g) w+ X2 g返回 vector<T> 中实际装的 T 的个数。相当于 CArray<>::GetSize()。/ X8 k; Y9 d4 Y" z; a6 v+ h
X7 R! l; f) @6 C' G) B1 A3) empty()
! v: }3 W4 e( z! h$ T
f/ Z9 }" K7 O6 t( T) e如果 vector<T> 中没有任何 T 对象,返回 true。也就是返回 size() == 0。
& }5 T" P; a* d W& X* L
$ Z) K$ c: E! F6 H2 V/ d( X4 u9 v4) clear();" T+ K5 O: `1 i* q, g( ]* z
2 P8 \( e& Y9 D% z! w" M清除 vector<T> 中的所有 T 对象。执行后 empty() 返回 true。大致相当于 resize(0),但不要求 T 可被缺省构造。相当于 CArray<>::RemoveAll()。1 M3 Y; R, `' V6 v4 K" y7 N
1 \0 t3 E3 n+ d8 m$ l5) resize( size_t n, T t = T() );/ u/ D- Y8 e) r+ L3 ]" a
5 _1 @5 V" p% X8 x
将 vector 中的元素个数设置为 n,n 可以大于 size() 也可以小于 size。如果 n 小于 size(),那么 vector 中下标为 n..size()-1 的元素都将被解构。如果 n > size(),那么将在 vector 的后面新增加
5 u3 y% N& h& R3 o( in - size() 个相同的元素 t。在增大 vector 时,可能发生存储再次分配。总之,调用resize( n, t ) 后,(size() == n) 成立。. I; M9 o& Q$ r
7 P$ t: P- J. r7 F1 p8 N请注意,如果调用 resize( n ) 不带参数 t ,那么 T 必须可以缺省构造。
% x; |5 E1 o- l2 R9 Y' ^- I9 n4 h/ o7 i
6) reserve( size_t n );1 g* M* t. q( q2 w4 p5 K E; d
& i7 P: F: D0 ]6 W/ w
事先分配至少可以保存 n 个 T 对象的空间。调用后 (capacity() >= n)成立。; r/ t4 Z3 F* {( U* ^
, v4 u2 J/ { s: e B7) capacity();2 [# T: P! f# a' t0 `. W, W% I2 N
- ^, _3 M5 v6 m
返回已经分配的存储空间够容纳的 T 类型对象的个数。后续的增加元素操作(如 push_back(), insert())如果增加元素后 vector 中的总元素个数不超过 capacity(),那么 vector 的实现保证不重新分配存储空间。
: B2 _* j3 Y9 Q* a9 C, ?. Q# i
* a9 g% S, ]3 z$ i0 L3 o4 ~6 |3 Rvector 管理的动态存储空间是连续的。执行操作
0 @5 _- X0 j. v" q. {1 Y. H
) r0 j1 f6 X1 q+ `, c. F1 _. [IntVector v(7, 1); // seven ones.
8 W2 y6 S2 U9 S/ y pv.reserve( 12 );
* v2 ]; V$ o- {+ r. S后,v 的状态可以用下图表示:
5 W' [- Q; b E8 e# ]4 d& A4 y& C; W% w9 D( x5 g
/--size()---\* [' B8 a8 ?0 a/ \
|1|1|1|1|1|1|1|-|-|-|-|-|- `! N( H9 q/ l5 O6 m
\--capacity()---------/$ v- w7 m0 @# B0 k" x* t
其中,1 是已经构造的 int 类型的对象,- 是可以构造一个 int 类型的对象,但还没有构造的原始空间。再执行
) i w! C, T* H, { t5 Z- O4 M9 {# F+ S4 p) K+ j
v.push_back( 2 );
1 l9 S! h$ P% Z) |5 }v.push_back( 3 );
! z2 \2 F0 g7 L后,v 的状态可用下图表示:" a6 }7 M" a- w1 A p5 t. m* n! i
5 U9 a) X! f* r( ?; y6 D+ m /----size()-----\
; |& M) r0 ]! f) s8 V|1|1|1|1|1|1|1|2|3|-|-|-|
2 t" N1 F( W- h% V k! K \----capacity()-------/
0 n1 A- \! | z执行 resize( 11, 4 ); 后:) ~& g4 i5 N2 a3 U) `2 S6 w" F6 D
2 f% l0 L% u9 H5 g0 f! h /----size()---------\" g& x& m3 e X! n# W3 p9 Q9 @
|1|1|1|1|1|1|1|2|3|4|4|-|
9 r" ?7 E: |$ b3 I& \- c \----capacity()-------/
% }* w! N; u3 d+ I& B% Gcapacity() >= size() 总是成立的。对于下标为 [size()..capacity()-1]的未构造对象的存储空间,是不可以访问的:8 S/ k/ `9 i; ?( _/ f& D* Z
4 ~. }: A7 n$ ^" R3 U( I
v[11] = 5; // undefined behavior - anything can happen.
( @8 }# m- [7 `- @7. 添加元素到 vector 中
\/ S/ W* B8 D! B. l4 @下列操作添加元素到 vector 中,并可能引起存储分配:
- W" V1 V* x# A
& q1 c8 j6 L5 Kvoid push_back( T const& t );
. H; v* ^+ h& w; m6 y! uvoid insert( iterator pos, T const& t=T() );
" B: H$ [8 O9 t: lvoid insert( iterator pos, size_t n, T const& t );
& c! @/ [1 N! n) B1 |9 C" xtemplate<typename Iter>7 h) r- S9 m: `5 y. N" C9 |+ _! {: E
void insert( iterator pos, Iter first, Iter last );! ?9 h" o/ Q2 I# T
push_back() 是把一个元素添加到 vector 的末尾。insert() 是把一个 t,或 n 个 t,或从 first 开始到 last 结束的一个序列插入到 pos 指示的位置之前。* {4 p) f6 M7 ]7 x& c8 n& z0 e8 B* h
. U! V" A3 ?$ P$ n
当插入元素后 size() 将会大于 capacity() 时,将引起自动存储分配。vector 将会分配一个比需要的存储区大若干倍(通常是1.5到2)的新的存储区,把老的元素拷贝过去,同时完成添加或插入,然后释放老的存储区。) e+ Z6 e( }, o2 `
/ E& z6 H& C+ @# m
这就是说,vector 自动存储分配的空间大小是指数式增长的,这可以保证多次添加元素到 vector 中时,平均用时是接近于常数的。) E1 n, `# E& v7 d* s r3 l4 ]5 o
% I/ `" Z8 d1 @# j: g' C q' P
IntVector v;" |$ s$ q' I* Q. W5 [
* I9 I* i# T% F/ y3 p. _/ v// add 0, 1, ..., 99 to v:
/ t$ w3 { ~, {+ Wfor( int i = 0; i < 100; ++i )
) V! v3 A; y6 h8 L% c; A; Yv.push_back( i );
5 j. ]3 y3 }7 Z- l
/ H0 F8 f$ a s* q! {// append 9, 8, 7,..., 0 to the end:
1 E7 q) j P2 J2 t' \! F9 i% j0 bint a[] = { 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 };
9 j* _- x% |; G. Z$ Hv.insert( v.end(), a, a + 10 );
h+ a# J8 z, k3 o3 U2 {' R! ~8. 删除元素
2 |+ A: L$ |+ N) b8 V4 D' K2 j下列成员函数完成元素删除:
/ {4 m% S9 g2 u. K: A. b% w/ d3 b+ h$ }6 e/ X; P9 u' p
void erase( iterator );; s$ k9 Q3 I3 _+ N5 b
void erase( iterator first, iterator last );
2 M: B2 N& `) `% H# Z6 [0 p* ]) C3 Xvoid pop_back();$ n2 q! f$ C6 V, q( D7 {+ z: W0 P
void clear();
( |% T( t% m' Y1 T4 m: m: e这些函数分别删除一个,一串,最后一个,或全部元素。
% @" G) U. s; y1 R% O" ?' h8 `7 H4 h" X3 y$ @1 O! d! I! L
IntVector v;% U+ E, D! l. V7 e& \4 S8 Y: W
for( int i = 0; i < 100; ++i )
. {* [4 K( H8 m) c& U/ V v.push_back( i );7 e( r# p" X# H7 n: F+ U
; ]$ X X$ k! ~7 }* d& h' T5 N
// 删除 50, 51, ..., 89:
/ V" l- ]/ J2 l! J9 ^6 y* A/ Tv.erase( v.begin() + 50, v.end() - 10 );
w5 p# K1 R0 @( J5 k # i/ R* F- k R( J: n
// 删除 49, 48:
: i2 A$ x5 Q) ^3 J: I* cv.pop_back();: Q5 S ^! f4 c- {
v.pop_back();
( i* e$ J; F- L3 x5 f' J6 M 9 M! X6 j9 M7 O3 m+ x
// 全部删除:
% _. }" Z8 k2 N7 `. `9 lv.clear();
9 F2 x0 D- j- w; \注意,删除操作不会引起存储分配,因此 capacity() 不变。% d4 m: ?: I) q" v3 G# e- U
, \: M. @% |1 ~3 \( \9. 作为序列访问 vector 中的元素
1 a/ U% x5 ^5 r! d8 T$ e9 ^3 @序列(sequence)在 STL 中是一个非常重要的概念,所有的容器类型和算法都涉及到,而且所有的算法都是建立在“序列”这个概念之上的。
2 A" L1 x! l" `; c1 _9 z. z) I! |$ w# ` N4 l; T6 `
“序列”是一个线性结构,由一个指示其起始和一个指示结束的叠代子(iterator)来决定。如果 first 和 last 是某种类型的叠代子,那么经常用[first, last) 来表示一个序列。注意,first 指向的元素是这个序列的一个元素,而 last 指示的是这个序列最后一个元素之后的位置,可能根本没有元素可以访问。这种半闭半开的区间表示是整个 C++ 标准中的约定,而且确实可以简化程序。" n* H4 h' a( ?
W3 Y9 G& B5 @6 M* |- r* Q叠代子是传统的 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 的要求。
0 k6 z5 L. `# ~" `
6 Z6 p) i1 t% V1 Z, B7 I @vector<> 中定义了以下函数用于获取被控制(管理的)序列(动态数组)的各种叠代子:3 G" M2 C [+ f0 h, J& y: @! V$ F. p+ G
: r& l; u2 p# f( Diterator begin();* h5 t% y" \8 `- z s/ x' d
iterator end();
2 X2 K( P. u( T" a/ ^const_iterator begin() const; I* t0 l: _. n0 |- ^5 N( q
const_iterator end() const;% I* H7 x* b6 S- Y7 n9 S
reverse_iterator rbegin();. }. E/ s( n# B9 v5 t
reverse_iterator rend();0 f. c9 t: g) F
const_reverse_iterator rbegin() const;
1 {7 M2 a$ v* z% J8 y7 f& yconst_reverse_iterator rend() const;5 p# Z# a1 M& b% M g2 o6 s' A
这里我们不讨论叠代子的一般概念,只举几个 random access iterator 的例子:7 H- b. n4 ?0 |* H8 l+ ~% Z0 x
( g( n9 [4 D" H; b7 J# J/ z
int a[] = { 1, 2, 3, 4, 5, 6 };* ]( L, C! o' R9 @* q) i$ g
[a, a + 6) 是一个随机访问序列,指示了 a[] 中的所有元素。这里叠代子的类型为 int*。
6 I6 \ g5 S6 Z" C
% ^/ {% }( D8 o V; o1 `1 P+ _& M[a + 2, a + 4) 也是一个序列,指示了 a[] 中的 3, 4 两个元素。叠代子的类型仍然是 int*。; t9 L ^" J; S( w
2 J: Q! T1 z9 ]$ l8 t& V# _( xIntVector v( 100, 1 ); // 100 个 1。3 v6 i# G. Q7 U- B" h" N
[v.begin(), v.end()) 是一个随机访问序列,指示了 v 中的所有元素,叠代子的类型是 IntVector::iterator。6 Z' G0 O; u, }: n, I4 k
$ Q" d! L$ \* I3 [# m+ t& T; O
[v.begin() + 10, v.end() - 20 ) 也是一个随机访问序列,指的是 v 中除了头 10 个和尾 20 个元素外的其它元素。+ O# a! A4 ]$ j1 _: ?
" z0 ]: p* o- A+ G
[v.rbegin(), v.rend() ) 是一个随机访问序列,指的是 v 中的所有元素,但与 [v.begin(), v.end() ) 不同,这个序列是从尾到头遍历所有元素。4 m# U' _$ Z. O" @" n
+ _/ d" F1 u! M/ n[v.rbegin() + 20, v.rend() - 10) 与 [v.begin() + 10, v.end() - 20 )指示的元素相同,但遍历顺序相反。
# S0 M4 f/ P* b, M+ U# R. E- b2 \$ w- F, @2 v5 K
下图是有十个元素的 vector 的 begin()/end()/rbegin()/end() 的示意:. }1 u- p$ m% y
+ H3 f2 @" [( u- `
begin() ----------> end()3 l% b7 m9 J! K! ?" z3 I2 U, P
| |
2 m% _3 D3 k/ f! T+ p# ] v v+ W# ~% d1 P8 [# [8 O# I0 d
|0|1|2|3|4|5|6|7|8|9|; f5 Y7 {" W' ?5 `# M' G" ~
^ ^
1 R5 ?+ s& k4 s+ Y8 j* @| |
: N2 X# H; S- |( X* }" Rrend() <---------- rbegin()
3 h- L$ y' I2 F9 Y- R. y5 l) H9 |2 j % z; R7 I! E" R# f# i0 |
IntVector v;
. z; O7 ?# A4 E; M5 U3 Pfor( int i = 0; i < 10; ++i )% C7 d$ c4 D8 p6 l
v.push_back( i );
" D- ~9 U% _% r* | + r% p( d, ?" O9 m. Y3 a. u8 L6 l, v
// print 0, 1, 2, ..., 9:
4 F- G; T* ]$ L& @$ w$ rfor( IntVector::iterator i = v.begin(); i != v.end(); ++i )+ p; p& b) ]9 i- p2 h# ~* {$ Z3 K
::std::cout << *i << '\n';, ^( m/ o1 F0 V5 n; ~/ O
7 I1 k5 d7 [+ a
// print 9, 8, ..., 0:: P" r: ?" f" r8 E9 B
for( IntVector::reverse_iterator i = v.rbegin(); i != v.rend(); ++i )
" Q2 E% x6 M$ W4 L' o::std::cout << *i << '\n'; A; m9 ~8 |3 d6 S) J
除了使用 begin()/end()/rbegin()/rend() 来遍历 vector 中的元素外,由于 vector 管理的空间是连续的,因此可以直接取地址进行处理:
1 s) r8 v% ~) F
5 D) f& _: {5 }::std::vector<HANDLE> handles;; U) E" u5 H# j5 |) n b/ b
handles.push_back( handle1 );
+ d# j9 ?6 q5 whandles.push_back( handle2 );
) Q9 |. n, ]7 d t; i
4 F- F! x& ?0 `' D! ]
" ~0 Z; W- A( ?$ D::WaitForMultipleObjects(handles.size(), &handles[0],TRUE, INFINITE);
3 n' G" L1 L) S/ _这在与 C 库函数接口时尤其有用。. ?1 V: g- l- E$ _
2 ~5 F# V: n0 k& e
10. 赋值和交换2 W1 c8 ?8 l) R) j1 d; e: J# L1 [% C
vector<> 是可以赋值的,这也是一般的“值”类型必须提供的操作:% ]- T1 A+ ~/ d5 n
# v1 {1 U. h+ o( o' [5 r! {IntVector v( 100, 123 );% ^ |8 ~. g* f- g8 h
IntVector v1;- h& \# L0 M0 z
v1 = v;2 S5 S+ L9 m+ V- k G
vector 另外还提供了/ C+ T& Y4 ^; ^8 ^! ^3 \+ r1 m
: E& P( d. R! Y* v; s: R. o
template<typename Iter>% R% [, E& `6 b1 H& [/ j* x
void assign( Iter first, Iter last );
) T h' ~5 f* D6 E' Svoid assign( size_t n, T const& t = T() );6 G$ l. i4 g: N
用于赋值:6 f- C: I. u0 I q) `
A# M. C6 r! a4 V, I, \, r
int a[] = { 1, 3, 5, 7 };* m; G4 U6 o2 J. y* i7 b
v.assign( a, a + 4 ); // v 将包含 1, 3, 5, 7.
" `1 [( Q+ ^( ?- r. Jv.assign( 100 ); // 100 个 0。
; k0 C' Q0 V: U, i6 w4 F# N. w还有一个很重要的操作:
0 Z# k' k x9 o# V
8 Q3 y x" {% l1 [- @5 ^% jvoid swap( vector& v ) throw();
$ O4 U3 |! V. N0 M0 [用于交换两个同类型的 vector 的值。它的特点是快速(只需要交换内部的三个指针),不产生异常。这在写一些保证异常安全的程序时非常有用。
6 j' @8 f+ t8 b. K+ U" ^) c( Y- H
+ | b) |/ ]# u' N7 |) U事实上,swap() 基本上已经被当作类似于 operator=() 的一个“值”类型应该提供的基本操作,::std::swap() 也应该为用户定义的类型进行特例化,调用相应的类的成员 swap() 函数:
* Y- o! _# p2 A3 x L# x$ S8 o; v7 b" _& e# W
struct MyVal) Y6 V- s4 [* m* Q% G
{
$ t* j9 e. m0 z7 f // blah blah.+ v0 v; O6 N P( l8 @1 z0 ]
void swap( MyVal& ) throw();
& E, B3 j6 L& d! s9 F. u};- e1 {# q/ Z( o
% d* w0 @1 a3 |. F1 m( s& ^
namespace std {. K/ b6 s9 o+ ^% D
template<>
% L/ U7 Y* c" Z, K% X void swap( MyVal& a, MyVal& b )8 y) k& A4 O6 X- P& ~, `
{ a.swap( b ); }
; U: q) u+ Z5 ?, W4 Y}
/ o3 p5 D n; _4 D. n# O* p关于 swap(),值得专文讨论。这里我们只指出,vector<T>::swap() 是快速的,不抛出异常的,很有价值。
: ?/ Z" l4 u9 a4 U5 W7 u" j6 X" U0 w* K0 T1 M! F3 [
11. 使用 vector 时的存储管理策略
+ n7 ^) W' _: G" k8 O: C从前面的介绍中可以看到,vector 的自动存储分配是指数式的增加存储空间,而且永不缩小已经分配的空间。这在大多数情况下是合适的。 如果应用程序事先知道要用到的元素个数,可以先调用 reserve() 来保留(分配)空间,这样可以避免以后增加元素时不必要的重新分配和元素拷贝:
3 H* X" r; C$ n5 {% G2 i( R5 ^$ K* y7 J
IntVector v; R1 g& v( }9 X- H) y
v.reserve( 100 );
1 o% V4 W& X" O& `7 r1 Q7 E0 bfor( int i = 0; i < 100; ++i )
3 c5 |) I* a Z, a0 q v.push_back( i );" O& ?) t2 E. e2 E4 H
请注意,reserve() 和 resize() 是本质上完全不同的。reserve(n) 保留的是未使用而能够使用的原始空间,而 resize(n) 是真的创建了 n 个对象:
8 e* _6 R' K& ~8 C1 b# t0 |/ q5 B! \6 u- R6 M3 y
IntVector v;
2 s1 A' k3 S' Z7 vv.resize( 100 ); // v 已经包含 100 个 0.* O3 r0 F# t2 W; b4 q
for( int i = 0; i < 100; ++i )8 a4 n+ S% T. G! L
v[i] = i; // 可以赋值
) T# R# U- Y/ H& i有时候,一个 vector 可能增长到较多个元素,然后又减少到较少的元素个数,这时,可能希望缩小 vector 分配的空间以节约内存。CArray<> 中提供了 FreeExtra(),但 vector<> 并没有提供相应的函数。这时必须进行复制:
9 `6 h" r4 W N$ `8 T, s3 }0 P
+ l3 ], B3 F! U. P; `& KIntVector(v).swap( v );
0 h. F* H1 t$ {: a1 h$ ^$ n+ h有一种看法认为拷贝构造函数同时也复制了capacity(),而标准中并没有很明确地指出这一点,因此更安全的方法是
6 c0 { O5 H1 K* J: ]. {5 z% y; G0 Q
IntVector(v.begin(),v.end()).swap(v);3 W2 ^4 L0 X; r) J& Y& C( K
如果一个 vector 中可能要存储的元素个数较多(例如,超过100个),而且事先无法确定其个数(因此无法调用 reserve()),那么通常 vector 不是一个恰当的数据结构,应该考虑用 ::std::deque<>。与 vector<> 相比,deque<>不保证背后的存储空间是连续的(因此象上面的WaitForMultipleObjects()中的应用不能用 deque<HANDLE> 代替),但有较好的伸缩性,还可以在数组的前端用 push_front()/pop_front() 增减元素(hence its name, doubly endedqueue)。 |
|