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