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