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