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