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