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