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