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