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