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