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