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