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