找回密码
 注册
搜索
查看: 5664|回复: 0

使用::std::vector<>作为管理动态数组的优先选择

[复制链接]
发表于 2008-2-26 13:29:18 | 显示全部楼层 |阅读模式
作者:wangtianxing# s! q) P. q5 O
6 h# O' f, C3 E& z
原文出处:http://www.cpphelp.net/issue/vector.html4 I2 {6 s  g2 F! G: x
- V) G9 p2 L! u" {' A8 d
! w- @5 j1 E. {& O- v* B7 J

$ l7 j* r( I7 p" G摘要: 本文介绍了C++标准库中的容器类vector,分析了它的优点,并且建议在应用程序中使用它作为动态数组的优先选择,而不是MFC的CArray<>等其他类模板。最后介绍了vector的接口和使用时的注意事项。
8 V5 \8 L* l& L/ ]- z2 G+ X  \/ K6 c/ a, I8 Q9 s9 U
在一些使用 MFC 的程序中,经常看到许多程序使用 CArray<>,由于 CArray<>的设计问题,造成使用它的代码的复杂化,增加了维护难度。因此建议使用 ::std::vector<> 代替 CArray<>。- d/ x' s) j. C. S
! a& I3 z# c/ A+ W" y7 u# l
另外,也看到一些程序在用 malloc/realloc/free/new[]/delete[] 等手工管理内存。在应用程序中,手工管理内存是容易导致错误的,应该用 ::std::vector<> 之类的对象来管理动态数组。
9 r: ~" ^& ]1 `" d6 M) `
0 M6 Y( o7 f; H由于 MSDN 中关于 ::std::vector 的内容较少,我们在这里做一些介绍,供参考。
" e* V1 I5 V1 \; U7 ?, v
! x; e+ H# x, x( y- ~' u+ Y不熟悉 CArray<>/WIN32 也没关系,这里提到它们的地方并不太多。; _2 [% ^1 x) F; e9 d+ M8 [

5 o% f6 o3 j% `1 B7 R& q" [3 |7 s1. CArray<> VS ::std::vector<> ?+ m' {/ J* Q! m6 |1 W' F
CArray<> 和 ::std::vector<> 一样,都是模板类,用于管理任意类型的对象的动态数组。都在解构时释放所管理的动态内存。因此都可以用于代替手工动态数组管理。
8 v# p6 m$ P7 d" X/ w& E" r
' Q- D" \. K4 r但是,CArray<> 是在 C++ 标准化之前很多年(VC++2.0时代)设计的,当时对 C++程序设计,面向对象程序设计,模板程序设计等技术认识严重不足,尤其是当时对面向对象技术的错误信仰与宣传,造成 CArray<> 的设计有重大错误。
/ ?  q2 D% d2 {- x
, o5 c+ \0 c1 n) E7 B2 K" y5 m在 C++ 语言标准化以后(1998),以及 VC++ 6.0 出世以后,提供了标准的::std::vector<> 模板,基本上在任何方面都要优于 CArray<>。Microsoft 由于要支持老的程序,因此一直保留了 CArray<>,但显然并没有打算按照新的思想去发展它(至少应该提供operator=(CArray const&)吧)。
( V3 ], K* S4 R% J) h. Q* [- v0 S
) Q' B* K- k3 N7 D概括起来,CArray<> 与 ::std::vector<> 有以下不同:! L4 K, i5 L; e' a( V0 z- Y. l

' V' @  ~) @, {8 f4 S1) 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<> 没有继承任何东西,只是实现了管理一个动态数组该做的事。5 ^+ `9 a6 O( r0 B% T4 l+ B

" ?  \" D0 g9 J. Q' ]0 N) \% q0 W2) CArray<> 不是一个恰当的值类型,例如下列操作都是不合法的:
$ ~) c6 I4 N2 l5 p" \' A6 d; C, _# x* M* O- ?7 ?
CArray<int,int> a;
6 G- o- r1 B- v1 l1 VCArray<int,int> b(a);  // error, must use Copy().$ `# ]# r7 B: m
b = a;        // error, must use Copy().1 f' J* S- h& [- U! n
b == a;       // error, you must write your own.
( {0 T  e3 Z* f" qb < a;        // error, you must write your own.
7 {5 _5 t/ g) c, [与 CArray<> 相反,::std::vector<> 是一个认真设计的值类型,天生是可以拷贝构造和可赋值的。如果 T 是可比较的,那么 ::std::vector<T> 将自动地是可以比较的。
$ w& L" P& b+ ~: s& L( q* y. J
1 H4 |. c  n& t1 p此外,由于涉及到四个特殊成员函数;
& S' s9 r% C7 L, `0 }8 }5 o. U' `" P* b6 \8 f% Q* z! ]3 i; m
T(); // 缺省构造函数(default constructor)
9 G2 Z. `# G$ }0 R+ a$ j; S* O~T(); // 解构函数(destructor)9 k+ t& {5 W7 [
T( T const& ); // 拷贝构造函数
2 {4 H" T; _8 [8 ]% ?T& operator=( T const& ); // 拷贝赋值函数/ q8 f& d# d: t! z) |
的自动生成,如果使用 CArray() 作为 T 的成员变量,那么上述的四个特殊函数中的后两个将无法自动生成,需要手工写:
* W. K' F7 f! G& P- u, i4 `5 G1 @! _7 [0 U2 j) j4 j: ]0 q
struct T, M1 x7 K8 c3 H+ {
{
! L5 M( }7 P% Q3 R0 I. m+ y, E   T() {}+ U8 s0 z3 k5 [. d" N
   T( T const& t )
4 Y# t4 j6 _! K* |   {0 N/ {6 \- D* `  `" W: t
       a_.Copy( t.a_ );8 R) @' A  z5 C5 k. ^; I
       i_ = t.i_;
4 Q, _0 m+ u* `. k# t       d_ = t.d_;5 u" h) r, b; n. H
       s_ = t.s_;
- C& u- }$ D  e7 ^: B   }
; r& u" v7 E5 p9 O/ Y# d/ |   T& operator = ( T const& t )
  k3 _3 x6 T0 M6 p: a$ k   {
  m4 _* K, Y7 H       if( this != &t )
" u' F( D) B- T4 H0 v; l8 b4 j2 d       {
3 M/ L; w; L$ z  E5 K8 y           a_.Copy( t.a_ );% t3 b2 S  Z) A$ c7 J
           i_ = t.i_;
6 {* U( Y+ _7 ?9 {' Q           d_ = t.d_;
6 l6 p9 k6 Q  N* x/ @& O' [0 O           s_ = t.s_;5 I( d# w# C# A, P+ G2 M
       }% g. G6 b" [1 D. y& h/ v
       return *this;9 T& U' z; V7 t" G' }# L
   }; M4 [( Y* l, C' J1 Y
private:2 t8 ?+ K$ g; [- ~2 x
   CArray<int,int> a_;
9 ^3 P% V3 @5 O; D3 ^, t/ l   int i_;% j9 K" i3 N  C1 D
   double d_;
+ L, N( k( U" N* g: y, i  N   ::std::string s_;
4 d7 y( n& V" g  w8 X& z};# C% ]9 A+ ]8 u. `2 p9 L
如果使用 ::std::vector<>:7 [& H3 i; m) T& H5 ]4 W" r
5 c; f4 U9 k! y
struct T1 @% R) y" N0 l7 b5 W
{
& r% J- z! S; N2 t% Tprivate:2 B' K0 h2 B1 E3 X; r8 B: V) q
   ::std::vector<int> a_;
4 s2 |" C. ]; e, Y  D: m" @   int i_;/ D. q, v3 g$ _. e& v! n
   double d_;& w9 }% g9 @: V7 {' K4 x9 o8 ^
   ::std::string s_;. ^" v  ]+ V$ z
};
8 D0 E: u" Z. ^3 Y* T1 Y4 e9 s上面列出的三个特殊成员函数都不需要写。好处是明显的:当你增减 T 的成员变量时,你不必到( W. B+ V9 ]$ |1 g' [$ U
T(T const&) 和 operator=() 中去相应地增减。( b. O1 N' Y& N+ f
6 D) c5 j6 S# U$ T" c* o* W- ]
3) 没有现成的算法可以对 CArray<> 进行操作,而标准 C++ 里的标准算法大多都可以直接在
$ t$ \& f( R4 l) A( C7 W::std::vector<> 上运行。例如:
1 D4 Q: F, `: h
- G' X* W% g* J* b4 p) M  ~static int const init_vals[] = { 3, 1, 4, 1, 6, 9 };
8 D" i7 Q8 w" T- |vector<int> a( init_vals, init_vals + 6 );; W$ o; ?* t6 i* c' [
*find( a.begin(), a.end(), 6 ) = 5;    // 把6改成5
* }0 N1 E$ `, t8 jsort( a.begin(), a.end() );    // 排序。; H' s. n, q7 G* N/ a2 ~, @( o
可以说,CArray<> 的主要设计错误是把一个本来应该是一个简单的“值”类型的东西设计成一个难用的“对象”类型了。所有的“值”的好特性都丧失了,但那些从CArray<>继承的派生类呢?" h0 K3 I4 ?4 P: f2 Q2 @# ?3 X
3 \" G9 R) u, U. {. @- H3 o& z# O
CByteArray等的问题与 CArray<> 的问题一样,甚至更多(例如,CPtrArray,永远不要用)。2 }- M* Z% \( i& l6 Z. q

- Z# L: V. O6 `) [同样,其他的 MFC container 模板,象 CMap<>, CList<> 等,都有类似问题,都应该用
/ y% k$ L: H" h3 C2 w' m::std::map<>,::std::list<> 等设计更好的东西代替。
: J7 A, M7 T( @% p
1 ?2 ^8 J& q" a7 ^0 u8 y, ^2. ::std::vector<> 在哪里?" X# ]3 p5 U  O
::std::vector<> 在头文件 <vector> 中定义:
* ~% R/ v. n8 A$ m8 d, u  Q$ V0 T& w$ e1 s* C4 P
(注意,标准的 C++ 头文件都没有 .h 后缀,有 .h 的文件是与 C 兼容的,或支持老的不标准的东西,象 <iostream.h>。)
# K5 [5 V9 l: s& V5 K$ ^2 |
$ d  Z, Y& ^  ^/ r6 ~' Qnamespace std
# l8 g2 `+ w6 H! U9 E: B. [{
6 H7 p; N- h; c) }9 `    template<typename T, typename A = allocator<T> >% o! q' J2 \; l  t
    struct vector
/ y. i- |  H3 ~4 v    {
7 |1 Q" O( P$ H0 E        // 具体内容稍后讨论
! a8 w: ?. E7 S/ K    };
* g7 x. j# }/ h* V  A4 ~' Q4 |. J# B( `( Y4 |
( x" y5 X0 V# j
    template<typename T, typename A>
  `: C" ^& R/ ?        bool operator == ( vector<T,A> const& a, vector<T,A> const&    b );6 t+ W% \8 U9 `# Y: O+ U
    template<typename T, typename A>
/ |$ A( C% U; L& Z1 E0 r        bool operator != ( vector<T,A> const& a, vector<T,A> const&    b );
- Y# a- I( y& ^6 n! T    template<typename T, typename A>
! ~. J! x8 T# P  c. {* z) x        bool operator < ( vector<T,A> const& a, vector<T,A> const&    b );
5 Z. p# T: D% s- }$ G    template<typename T, typename A>
/ c. y' H6 y! b8 ^        bool operator >= ( vector<T,A> const& a, vector<T,A> const&    b );" M. `- M2 ?" m7 {1 b& V, J
    template<typename T, typename A>
2 I, _3 q8 w- a* b        bool operator > ( vector<T,A> const& a, vector<T,A> const&    b );1 v& n9 Q" P* M* r% T$ B; H' N
    template<typename T, typename A>
* Q1 t8 D) w6 {, \% t        bool operator >= ( vector<T,A> const& a, vector<T,A> const&    b );
" }! W. I: b, O2 _8 Z, _* l}* N9 K; t! T/ a2 P7 z
vector<> 定义在 namespace std 中,使用时为了减少击键次数,通常使用一个类型定义缩短类型名称:
$ _4 p, j* u) z& |( Z3 o; i/ A
8 D& H$ Z$ T5 @& T3 Z3 {/ U/ @#include <vector>/ d3 s' c' Q" K& V
typedef ::std::vector<int> IntVector;
( a, V# S1 m' YIntVector a;2 k! m! b. `# g, E# b, c' K
IntVector b( a );% h  q1 [/ T) z& r% g# o0 r
IntVector c;  [  ?* a$ w& l7 r2 y  f
c = b;% Y$ P3 Y% ~) G/ A. n8 C& X
assert( a == c );
- \" d( [" `; e4 O请注意 <vector> 中定义了六个 vector<T,A> 的比较函数。这些函数只在真的用到时才会被实例化,才会要求 T 也提供 operator==() 和 operator<()。# {' _2 K9 o0 ?' ^+ v& `
* p' b+ h& ]( v6 Z; ~/ [+ z
另外,A = alloctor<T>:用于提供一个用户定义的存储管理类。由于这个参数很少用到,而且在 VC++6 的实现中有问题,不能用,因此以下的讨论忽略这一部分的内容。
" _% m2 ^) E0 p: v+ [1 P' p0 A7 a& e+ U( {* D( y
3. ::std::vector<> 中的类型定义& F8 M" ?  q4 n" Y, I# }1 G3 c- C
vector<> 中定义了一些类型,下面只列出常用的:: s% O/ w& a; x# [

% z8 L& F9 e' v0 ltypedef T value_type;( T0 [, M2 k( d. P0 {
typedef T0 iterator;
& V: _& d7 u1 k9 E8 z, rtypedef T1 const_iterator;4 j/ f7 d$ O" j1 _: L5 U
typedef T2 reverse_iterator;+ E0 _, f$ M. @, d" s3 y3 \
typedef T3 const_reverse_iterator;6 P) B5 N& k. p/ L
+ N7 q9 T8 W, C! p0 x/ e. J
value_type 就是 vector<T> 的元素类型,也就是 T。当写通用的算法处理任意类型的 vector<> 或其他容器类型时是很有用的。9 G+ y) d) v) M- x  B  |
( e. @0 \+ c% h. [% C
iterator/const_iterator 是两个 vector<> 的实现定义的未知类型,用于访问vector<> 中的元素,类似于 T*/T const* 指针,他们的区别是一个指向的元素可被修改,另一个只可以读:3 @' I7 ]* ?& U0 d# Z% V
9 T9 d# m2 {7 [# j: ^8 G) r2 B. U
typedef ::std::vector<int> IntVector;+ [0 k2 s% _- Z
IntVector::iterator iter;
$ F# M5 a3 T+ d. c- Z/ Q8 IIntVector::const_iterator c_iter;
2 X3 I2 @( O. l// ...
3 H/ X; |7 [% T: B# W8 l++iter; iter++; // ok: increment, post-increment.6 s5 z7 y4 B8 D' Z
--iter; iter--; // ok: decrement, post-decrement., b3 O+ D1 ~$ \9 T
++c_iter; c_iter++; // ok: increment, post-increment.
5 w- ?' c9 E' n/ d' j, j--c_iter; c_iter--; // ok: decrement, post-decrement.2 H' K: c( X" P/ [* G. J1 J
*iter = 123; // ok.4 v: Y2 t6 f( E5 u- N' \9 K' n
int k = *iter; // ok.
, R/ v1 S3 ]- e0 f' H5 v/ sk = *--c_iter; // ok.$ H  \- M5 G1 R$ q4 X0 Y( U
*c_iter = k; // error.4 J. R. ^* _) s0 U* h
c_iter = iter; // ok: iterator is convertible to const_iterator.
, Z% i$ r- A, B5 W3 Yiter = c_iter; // error: can't convert const_iterator to iterator.0 `* ~/ G$ h: Z4 y
在使用上 iterator/const_iterator 和 T*/T const* 基本相同,事实上有些vector<> 的实现里就是用 T*/T const* 实现 iterator/const_iterator 的,但又不可以把 iterator/const_iterator 当作真正的 T*/T const*:$ a( W* m. o" i, \/ x4 q/ M$ K
, ]: l' Y% J2 w9 L7 v/ I/ U
T* p = iter; // may fail to compile.0 B+ I9 p0 G: l: E0 |9 p
T const* q = c_iter; // may fail to compile.
" s4 p, \* q3 D( e! \reverse_iterator/const_reverse_iterator 与 iterator/const_iterator 类似,但以相反的次序(从尾至头)访问 vector 中的元素。+ b8 u3 K6 N4 c! Z1 U

- J2 b8 }. Y5 V1 T- i, S$ ^各种各样的 iterator 在 STL 中有特别重要的意义,但这里我们不做具体介绍。只要理解通过 iterator 可以访问 vector 中的元素,大概相当于一个指示位置的指针就行了。2 [# Q; M* N) `. t9 u4 m
8 G% ~2 Z9 e0 T1 j
4. ::std::vector<> 的构造
5 u1 B4 A% F. K6 _3 M# Nvector<> 提供了以下构造函数:(忽略 allocator 参数)/ A& m, U$ `0 D; S- F

: I# o: D* q. ?! l+ j! i$ O9 qvector();
' y6 N1 d% A0 u6 {vector( size_t n, T const t=T() );" g% }  z9 J$ o' O/ I4 L
vector( vector const & );/ m; E' u+ k6 r
vector( const_iterator first, const_iterator last );
4 R% W1 p' j$ u% K! f+ j1) vector();9 A* R2 D, `2 t* x. y. F
4 ?1 l; p8 p* q8 ]9 v# [
构造一个空的 vector,不包含任何元素。
( z3 G$ R6 |1 r
$ z$ f+ R" t7 z7 {7 m% \7 ~6 |IntVector v1; // 空的整数向量。
/ N( ]1 ?4 C" O7 J2) vector( size_t n, T const t=T() );1 y$ S0 b- f7 u# M8 S  M7 G

8 R: Z/ `; H' v( X9 O; H构造一个 n 个相同元素 t 组成的 vector。如果不给出 t,那么将用 T() 做缺省值:, C" k+ t" r7 S* \
. G/ }2 {" H9 {3 ~  ~1 N
IntVector v2( 100, 1234 ); // 100 个 1234.
; s% ^( }/ `4 b% e2 qIntVector v3( 100 ); // 100 个 0。
1 M# f# T+ `3 Z3) vector( vector const& other );
+ Y1 Z! Y- ?0 n# p  M6 j+ W& Y" V; E2 V  H/ F' H  [$ ~+ _
复制构造函数,复制 other 中的内容:& J2 q, h$ S3 g4 N: |

) l. u7 L5 B; i: f4 IIntVector v4( v2 ); // 100 个 1234。: {2 j  ?% {. ?1 o4 \* q
4) vector( const_iterator first, const_iterator last );) G# m7 ^2 ]. C+ ~  n

# p0 B- E0 \1 j# o4 P事实上,这个构造函数应该为$ V& U2 ~: d: Q4 w' f2 @7 c* g+ m
/ g, j/ {! j4 f
template<typename Iter>
7 r& `4 g# K9 z$ h4 d    vector( Iter first, Iter last );
5 K' I* |) I' n9 t- O6 k: [即拷贝任意的序列 [first,last) 到 vector 中。由于 VC++6sp0 编译程序的限制, Iter 被换为 const_iterator 了。不过,碰巧 const_iterator就是 T const*,所以可以如下使用:$ U& d: s6 m9 P$ p8 S. t' N

) f8 `4 v) \: b# \. d" Oint a[] = { 1, 2, 3, 4, 5 };. T: @2 O1 z( B) h, U& c
IntVector v5( a, a + 5 ); // {1,2,3,4,5}
  C- G- I+ _- l& S9 [IntVector v6( v5.begin() + 2, v5.end() ); // {3,4,5}2 f- O: @2 y1 v+ D  x* m
5. 访问 vector<> 中的元素% B  G" q2 G% t& l$ c/ F. U* f
以下成员函数/运算符用于访问 vector 中的一个元素:
* q: D% E% A( f/ g
+ @- P; d+ G, F* q% |0 t  pT& at( size_t n );
, A2 ^1 b( ?8 k+ f) b% C, ET const& at( size_t n ) const;
4 x2 i' X7 \5 b+ ?T& operator [] ( size_t n );
4 f' k5 f5 n9 C1 _: ^T const& operator [] ( size_t n ) const;. b$ W1 B& u5 [
T& front();5 k$ W- S% g1 `8 Q4 z
T const& front() const;: g# B( U8 w" `7 n* A9 X; u
T& back();
" k: c1 M, e4 J; I, rT const& back() const;
. s3 ]+ M$ F  \: q" l- c8 h# M$ @请注意,由于 vector 是一个“值”语义的对象,所有的操作函数都必须严格保证 const 的正确性。所以,所有的元素访问方法都有 const 和非 const两个版本。
* R5 O* c  N; |* T; C/ e6 P( g5 r* t
at(n) 和 operator [] (n) 都返回下标为 n 的元素的引用,他们的区别是,at() 进行下标越界检查,若发现越界,抛出 range_error 异常,operator[]不进行下标检查。! h" W+ X- ^) }5 P) f5 ^; G

+ j) i! _/ x! j' K: Gfront() 返回下标为 0 的元素的引用,back() 返回最后一个元素的引用。
- t( t& G2 [/ x. ?0 i' j2 H/ Q# q2 b  O2 a) v9 A
int a[] = { 4, 1, 4, 1, 5, 8 };
# @7 s* a$ t! V7 hIntVector v( a, a + 6 );2 A! h5 o4 |" y: H+ _$ n$ \
// 使用 front(), back():, [: h7 U2 O. ?% _
v.front() = 3;4 g% H& p/ f7 R" Y
v.back() = 9;3 u7 n% s! ?8 m# D% W3 S
// 使用 operator [] ():* W. y6 |8 m- i0 G0 w/ n
for( size_t i = 0; i < v.size(); ++i )' M0 q& k' i3 s1 Q4 a
::std::cout << v[i] << '\n';, u: i+ C$ J0 D$ s
6. ::std::vector<> 的存储管理6 B# H8 G" o  d# k) d1 t
以下成员函数用于存储管理:/ n% d; G. ?/ j% m8 p
8 o$ ]* |6 T: c
void reserve( size_t n );
; W$ ~) l0 J4 K. osize_t capacity() const;, Y1 g, G. W7 Q: D% _5 H
void resize( size_t n, T t=T() );
4 I7 {* S" ], r, I+ y5 i' n6 \void clear();
' F9 x' X, F5 F- l( W/ P- qsize_t size() const;3 W9 V% h- {  p$ d* j$ P
bool empty() const { return size() == 0; }$ g* Q/ J& L  {& c! W
size_t max_size() const;  D; I) D) ^* o; m9 S

* E6 p5 L3 O7 W3 o  B2 r7 F) W7 d( M另外,push_back(), insert() 等也涉及到存储管理,后面另行介绍。
  H1 p. k' Q! R  Q7 J
3 t, ?2 `& ^$ x$ L; r7 [& q# c1) max_size()1 ]" B5 ~! F: |* y' S% O

4 L1 s# t) f6 ]+ f返回 vector<T> 理论上可以装的最多 T 的个数。这只是一个理论上的数字, 大概是 4GB/sizeof(T),没有多大实用价值。在程序中不要用。
2 g( L. a. @: }2 ^7 k' M6 \+ P. B, f4 a# B5 G: y4 ]5 T$ F3 D3 `% z
2) size()
( [6 B  l5 d# U  G9 \) @$ _8 x! i" S/ t
返回 vector<T> 中实际装的 T 的个数。相当于 CArray<>::GetSize()。
' \( B. B- x5 ~6 P9 d
9 p* D# O% m4 M' L, o3) empty()2 `( j: O9 j# D& A2 G

) M, V7 {8 y* r# D# K; r( f6 Z, D如果 vector<T> 中没有任何 T 对象,返回 true。也就是返回 size() == 0。6 J; z; s5 e" d+ h7 S
9 M6 J9 n; ?: w) ~. [
4) clear();
, H- z: a- y2 f, O# W! I. ]5 ?1 h7 m- O- a
清除 vector<T> 中的所有 T 对象。执行后 empty() 返回 true。大致相当于 resize(0),但不要求 T 可被缺省构造。相当于 CArray<>::RemoveAll()。
! [% {1 ^; {' T% b' g$ H+ C5 N. U4 s8 _# I, ^6 Q
5) resize( size_t n, T t = T() );
; j  c: H; q" g. m
1 Q# G! d# j; L. c将 vector 中的元素个数设置为 n,n 可以大于 size() 也可以小于 size。如果 n 小于 size(),那么 vector 中下标为 n..size()-1 的元素都将被解构。如果 n > size(),那么将在 vector 的后面新增加
6 X' e. e5 e+ E: N" a- J1 {n - size() 个相同的元素 t。在增大 vector 时,可能发生存储再次分配。总之,调用resize( n, t ) 后,(size() == n) 成立。9 S% y. t) L6 q" N: g8 x; d+ ?, g
: D; h) ^9 u* d6 y0 a
请注意,如果调用 resize( n ) 不带参数 t ,那么 T 必须可以缺省构造。7 P7 e$ F* B5 {! H
" y, W% }/ k5 {) F9 x  N9 H0 I
6) reserve( size_t n );
! k3 L# e, H4 a- B5 L5 N% _& x8 |
9 r5 Z; _8 x  t' j$ O事先分配至少可以保存 n 个 T 对象的空间。调用后 (capacity() >= n)成立。2 K) J* a3 [. F. s
; n6 i" p0 s3 {$ U' _2 C
7) capacity();) J5 u5 W( X6 O7 L
' C1 p. `* \9 ]  n# j' I
返回已经分配的存储空间够容纳的 T 类型对象的个数。后续的增加元素操作(如 push_back(), insert())如果增加元素后 vector 中的总元素个数不超过 capacity(),那么 vector 的实现保证不重新分配存储空间。
; ~6 H/ a5 C  a$ X5 A( z0 e6 ~9 F( Y/ ?7 R$ g( w/ W, Q2 z; ?
vector 管理的动态存储空间是连续的。执行操作; ~0 _! k& Q) g9 N# K! h! _9 z8 `
( X; a" I) _) C, _1 ~2 p7 g6 E
IntVector v(7, 1); // seven ones.
3 p7 E3 ^' _7 E1 [/ b! Qv.reserve( 12 );! d& G  x/ N2 D6 w# Z4 z
后,v 的状态可以用下图表示:
3 G! q# A. Z6 e* D8 ?; G  v4 _' E6 r* o/ _2 r' V6 ~
/--size()---\
& k) ^) m1 d$ \! i% a8 A|1|1|1|1|1|1|1|-|-|-|-|-|
4 X0 p- k" H; @8 B& L+ Q2 P \--capacity()---------/
4 C" v8 Q' c" H其中,1 是已经构造的 int 类型的对象,- 是可以构造一个 int 类型的对象,但还没有构造的原始空间。再执行
. G; e& X% |. `/ Y; Y5 b; d/ K, G' V4 ?0 O" f/ ]
v.push_back( 2 );6 Z  I: q1 b9 {7 _7 h9 P
v.push_back( 3 );
3 U5 m5 D! w0 ?$ k后,v 的状态可用下图表示:
4 A; _+ h$ \! N6 a& C8 l
, g4 `8 A9 ^' l' N /----size()-----\0 w0 K/ O2 X/ {
|1|1|1|1|1|1|1|2|3|-|-|-|
( i( q  E' g* e! R7 ?5 b2 s \----capacity()-------/, f, M# |# l7 z
执行 resize( 11, 4 ); 后:
% e' r) y7 Z7 a" B' W5 Q3 L
: I9 O  i8 J& _# O& K$ J, _( C1 d /----size()---------\
% h. X/ ~8 W+ j2 ||1|1|1|1|1|1|1|2|3|4|4|-|
# s- D+ X4 s8 E& w3 w) F! v6 ? \----capacity()-------/% L8 x: l, e5 s5 v; g+ K
capacity() >= size() 总是成立的。对于下标为 [size()..capacity()-1]的未构造对象的存储空间,是不可以访问的:
9 L; M& V$ u2 Q! j
- o1 [* y$ A- Tv[11] = 5; // undefined behavior - anything can happen.+ S1 S) G1 _5 D+ f9 C) U
7. 添加元素到 vector 中, V' C1 I- G8 F  D0 ~- x
下列操作添加元素到 vector 中,并可能引起存储分配:9 d( U: X: |5 E: {9 [# a0 e6 q

) J: m# `9 u- P) Gvoid push_back( T const& t );
- `4 E( W' E, G, U+ Q6 \void insert( iterator pos, T const& t=T() );2 u" f, _# v1 ^0 p
void insert( iterator pos, size_t n, T const& t );. p; ^- C  q) {
template<typename Iter>
6 c, L' H2 M7 V; l    void insert( iterator pos, Iter first, Iter last );
9 q. C1 p/ F1 b% J/ U6 P1 t4 Q) K; Gpush_back() 是把一个元素添加到 vector 的末尾。insert() 是把一个 t,或 n 个 t,或从 first 开始到 last 结束的一个序列插入到 pos 指示的位置之前。
$ [- r: C% i: L& l5 J7 V9 m3 B9 Z/ z$ d  p4 E" j0 g4 p
当插入元素后 size() 将会大于 capacity() 时,将引起自动存储分配。vector 将会分配一个比需要的存储区大若干倍(通常是1.5到2)的新的存储区,把老的元素拷贝过去,同时完成添加或插入,然后释放老的存储区。2 k: \! u8 L$ ~/ m1 |6 N6 [/ X. }& [

! I8 [: f% o- D, L这就是说,vector 自动存储分配的空间大小是指数式增长的,这可以保证多次添加元素到 vector 中时,平均用时是接近于常数的。
- m6 }* L" @& }4 L, w% D) ?8 G% t: F/ i
IntVector v;
# D# E8 D7 u; m2 e4 V8 Z5 i   8 m+ m1 @7 H8 g) [) I
// add 0, 1, ..., 99 to v:
, `  `+ t0 X( k) qfor( int i = 0; i < 100; ++i )
; E' y' d6 @+ G6 W/ ]* hv.push_back( i );
' t; i2 m& F7 F. P8 D   " Q2 H1 R. F: P( Q8 M' v8 L# Z
// append 9, 8, 7,..., 0 to the end:
4 n5 S5 _# Q* R  W% M  c) v9 j  Tint a[] = { 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 };
9 B$ V0 `. |2 j1 t9 H# av.insert( v.end(), a, a + 10 );. `4 c  t! K, Z! K/ ?8 o+ j5 \
8. 删除元素
- N6 ^# l0 }5 }. c下列成员函数完成元素删除:
+ ~$ b/ c" V2 E9 ^1 j5 M$ H( V. |5 k/ {; U3 U6 b
void erase( iterator );
& R; a9 A' R2 j& Z1 v% b" Svoid erase( iterator first, iterator last );
$ r6 e5 G4 ]. wvoid pop_back();* g, ^# m4 c1 d. x
void clear();
# ^1 P8 V1 G& Q' g" ?$ ?这些函数分别删除一个,一串,最后一个,或全部元素。7 w/ c# x/ X! U2 F# a+ `9 H" v8 {& m

$ B$ p: D* C0 @: NIntVector v;
9 g7 ^1 Z3 v3 B' C, n5 @for( int i = 0; i < 100; ++i )- _# g. j6 Y7 y. {/ }( z% w
    v.push_back( i );+ d$ G* e* D& f0 j8 t2 v# p) H! j
   
  |% ~, Z. _0 d5 q; j, z// 删除 50, 51, ..., 89:
' w) `; O5 y7 o$ G2 ov.erase( v.begin() + 50, v.end() - 10 );3 E% h! n+ b9 a1 L- d4 C( P0 `7 N
   
+ L9 e& P0 R0 d! C7 n# `( X) d$ g// 删除 49, 48:
. \. K. F  j8 J' Jv.pop_back();$ p; a  r" ]+ e  k( j" Q
v.pop_back();
3 `- B- U, ^) }1 I* Y% r   
$ k! N, Q( j) T" \- I// 全部删除:
& f6 Y+ j. @* H" z& U! hv.clear();
$ E6 s: z" \8 s8 x0 U注意,删除操作不会引起存储分配,因此 capacity() 不变。
) r' z, s0 k3 i2 H
) Z. e/ l/ i9 X7 r9. 作为序列访问 vector 中的元素
* T# a3 ?. j. a序列(sequence)在 STL 中是一个非常重要的概念,所有的容器类型和算法都涉及到,而且所有的算法都是建立在“序列”这个概念之上的。; L; g2 U4 h! o+ f4 c5 R. @
9 `! {- q+ p7 V7 a
“序列”是一个线性结构,由一个指示其起始和一个指示结束的叠代子(iterator)来决定。如果 first 和 last 是某种类型的叠代子,那么经常用[first, last) 来表示一个序列。注意,first 指向的元素是这个序列的一个元素,而 last 指示的是这个序列最后一个元素之后的位置,可能根本没有元素可以访问。这种半闭半开的区间表示是整个 C++ 标准中的约定,而且确实可以简化程序。
; a, j% _+ l1 r% M
- @( F6 |" g4 x) ]+ c. n* a叠代子是传统的 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 的要求。) v8 U2 I1 l4 _7 W9 ^3 a
6 X$ {' f1 I0 W2 a5 U
vector<> 中定义了以下函数用于获取被控制(管理的)序列(动态数组)的各种叠代子:
- L) ^# h6 g, l2 g0 h4 @; R1 I' X* y5 X# J9 L/ {7 ?; N
iterator begin();- x% [; k* g3 {+ }5 D4 i
iterator end();; i0 q( c6 t/ k; S
const_iterator begin() const;& [. H& X* Q' O! \) n8 s1 ~
const_iterator end() const;
$ O1 o$ K. H* D9 H+ L9 greverse_iterator rbegin();* ?' p8 z+ b: i; {" m/ [
reverse_iterator rend();
: ]3 m: y. N* ]6 Econst_reverse_iterator rbegin() const;
' p( v  r% j+ f  ^+ X2 O' |* Kconst_reverse_iterator rend() const;, I5 J4 I# R  `( ^" w
这里我们不讨论叠代子的一般概念,只举几个 random access iterator 的例子:0 t7 W: I  C% v& j

! e0 P7 p. T: ~$ w. M! eint a[] = { 1, 2, 3, 4, 5, 6 };1 `% n* J, {# K) s  ?
[a, a + 6) 是一个随机访问序列,指示了 a[] 中的所有元素。这里叠代子的类型为 int*。6 ^+ @+ P- g2 {4 |

3 F) P% c9 R, I: {/ X5 {. u3 y[a + 2, a + 4) 也是一个序列,指示了 a[] 中的 3, 4 两个元素。叠代子的类型仍然是 int*。
) h# C$ N/ @2 S- x6 i& H; f3 }; X$ @* c, m; N" O! w
IntVector v( 100, 1 ); // 100 个 1。4 Y  M" v. `/ ^1 ^4 B( a: F, V
[v.begin(), v.end()) 是一个随机访问序列,指示了 v 中的所有元素,叠代子的类型是 IntVector::iterator。
& X, v) z  t& x2 w8 S
: N# m5 J  e1 K2 E; J" G, X[v.begin() + 10, v.end() - 20 ) 也是一个随机访问序列,指的是 v 中除了头 10 个和尾 20 个元素外的其它元素。% z5 [% D( X4 S; h# @+ ]8 v5 t

0 T3 `* O  n' h( l6 \[v.rbegin(), v.rend() ) 是一个随机访问序列,指的是 v 中的所有元素,但与 [v.begin(), v.end() ) 不同,这个序列是从尾到头遍历所有元素。  j# Q* Q6 W4 U7 I+ h. l
$ \: j( ^% ?+ i& u; @9 z$ l
[v.rbegin() + 20, v.rend() - 10) 与 [v.begin() + 10, v.end() - 20 )指示的元素相同,但遍历顺序相反。% p: |; M% ]$ |5 W
3 P  w; N" ?  E1 E" x8 V8 u
下图是有十个元素的 vector 的 begin()/end()/rbegin()/end() 的示意:
6 p* E" N4 g( Z9 b. W( j! X( K/ T# I
begin() ----------> end()
& k% C' I" ~+ Z6 S  |                   |
1 P) E8 k2 \0 ^6 O" U. ]0 C  v                   v
4 z& Q0 @' a0 G2 G9 ?+ ~( u6 j |0|1|2|3|4|5|6|7|8|9|: R$ b! H' g* k5 w
^                   ^0 H- j# A7 I, w
|                   |
/ G9 C3 `" k% l; Mrend() <---------- rbegin()1 _+ D* v0 U( g0 G1 Z
   
+ S- O9 u7 n" h* e. O( @) EIntVector v;
: V" R% F8 @7 @6 U' I8 Wfor( int i = 0; i < 10; ++i )
% v* C6 Q! v1 Y. T3 dv.push_back( i );7 r  T" j( s8 Q' N7 @
     ]5 _) r, X' m% r% j8 j
// print 0, 1, 2, ..., 9:
; D4 N' G1 n& j/ s/ c4 f' m- Ifor( IntVector::iterator i = v.begin(); i != v.end(); ++i )& s4 W6 x! V! Y' }
::std::cout << *i << '\n';: o" |& }# o2 I: n( z( w4 b2 k
   
0 l  `+ k+ r+ ~2 e/ Y" P// print 9, 8, ..., 0:
6 ^! d. Q) |& O* H" ?for( IntVector::reverse_iterator i = v.rbegin(); i != v.rend(); ++i )( s2 B% O5 r6 c* j
::std::cout << *i << '\n';. A, P+ d3 g  r- x3 G1 V0 c# P
除了使用 begin()/end()/rbegin()/rend() 来遍历 vector 中的元素外,由于 vector 管理的空间是连续的,因此可以直接取地址进行处理:: W  E& J9 f2 j" w

( D: C! R0 q5 j0 s::std::vector<HANDLE> handles;) A# s: L2 r% j
handles.push_back( handle1 );6 |% V) ^1 K. Q
handles.push_back( handle2 );5 {* A$ |8 `; l8 u7 P  L- J
& z* ~# s; c8 H5 B4 B/ Y. |

: T. q) i0 n4 n::WaitForMultipleObjects(handles.size(), &handles[0],TRUE, INFINITE);
$ E- P) V% V( h+ K# z/ f3 s. ~这在与 C 库函数接口时尤其有用。
3 C' [' V8 v* |, \* u/ `$ p9 u. Y8 B$ i$ b+ `  R" \: u  y
10. 赋值和交换
8 n9 O- B2 R1 B7 g$ _! D2 V; b3 g9 @vector<> 是可以赋值的,这也是一般的“值”类型必须提供的操作:
( v( g, s7 s: r: F" ]6 `) ~( ^2 e4 n$ \; v
IntVector v( 100, 123 );; }/ U2 r" p2 j8 ^+ L* p
IntVector v1;
2 Y! i4 v$ x8 X5 L; z  [v1 = v;4 ?! `# T& ^# ]! q+ i
vector 另外还提供了
0 F% q8 L. B# ^3 @; m1 z7 Q2 n
4 H+ y8 e3 C% H' w/ [template<typename Iter>
7 m* B, @$ H$ k2 ^+ |void assign( Iter first, Iter last );4 w7 \) m  m! Z' Y$ N5 K8 F
void assign( size_t n, T const& t = T() );
1 u) F/ R  P# e8 C7 _$ v2 F用于赋值:
  h4 w; e, M3 F
0 j# @6 ^( D% ^8 C7 x( P5 Z: ^int a[] = { 1, 3, 5, 7 };
/ U% b: M, s( C% g* Ev.assign( a, a + 4 ); // v 将包含 1, 3, 5, 7.: ~( u8 e2 t' L" y  G; ?. k
v.assign( 100 ); // 100 个 0。
. S7 L* ]9 P8 Y: L% t8 J还有一个很重要的操作:
# ?; P4 L; l- \3 K- U  M  @  {" y+ R5 b$ i6 H: A
void swap( vector& v ) throw();
9 V2 I) Z7 Q# g用于交换两个同类型的 vector 的值。它的特点是快速(只需要交换内部的三个指针),不产生异常。这在写一些保证异常安全的程序时非常有用。
9 I% Z4 W/ o) V0 h( z% [
* q2 W! t, x% c事实上,swap() 基本上已经被当作类似于 operator=() 的一个“值”类型应该提供的基本操作,::std::swap() 也应该为用户定义的类型进行特例化,调用相应的类的成员 swap() 函数:8 o+ f- E. w) F) C/ K, Y/ N( h
) U* T1 l5 D. l' S2 ^. u
struct MyVal
. z$ J& L. f; [! P{; h' ]/ }1 Y8 y# Z4 k
  // blah blah.6 S8 i3 K' i& \" C  b7 G
  void swap( MyVal& ) throw();
! Y) @4 r% L$ v$ K};% N) b6 Z: t/ I  _% v. v
   , U  t9 W) k: _
namespace std {
! k+ U% {; Z  B3 |$ }  template<># E# f$ R) K; T4 s7 O& k1 ]
    void swap( MyVal& a, MyVal& b )* H, _1 p- U2 C+ x7 p5 F4 L& U
    { a.swap( b ); }! J, S  Y+ o' J- {; t1 V0 j
}7 o  I1 g$ R4 P5 A* d
关于 swap(),值得专文讨论。这里我们只指出,vector<T>::swap() 是快速的,不抛出异常的,很有价值。
* I$ |- C: m! @- N) C9 n# e
3 b  C8 G1 N- P3 f" i: _; K11. 使用 vector 时的存储管理策略
: G- _: u: C, d6 C- P) p5 i! c& d" C9 V从前面的介绍中可以看到,vector 的自动存储分配是指数式的增加存储空间,而且永不缩小已经分配的空间。这在大多数情况下是合适的。 如果应用程序事先知道要用到的元素个数,可以先调用 reserve() 来保留(分配)空间,这样可以避免以后增加元素时不必要的重新分配和元素拷贝:
" e* R% o/ p& i; i& d, g- e1 c2 Y/ }  u1 _$ u
IntVector v;
# V+ N  s7 [2 ?2 B. Rv.reserve( 100 );$ E3 j1 b, P2 E6 l1 L
for( int i = 0; i < 100; ++i )
" L4 v$ `8 \$ g% v+ C& p    v.push_back( i );& Y# }. h: t2 A
请注意,reserve() 和 resize() 是本质上完全不同的。reserve(n) 保留的是未使用而能够使用的原始空间,而 resize(n) 是真的创建了 n 个对象:
: N2 ^' j3 j2 h9 e2 T& |2 ]& a: S4 q( H7 g0 S# C) S8 ?/ _. s% j$ Y
IntVector v;
. i$ V" L( `1 y8 H5 T' {9 Ev.resize( 100 ); // v 已经包含 100 个 0.
4 ?/ I% F9 z  r3 \for( int i = 0; i < 100; ++i )
; f" v$ ]$ y% h+ s3 \) g# ~    v[i] = i; // 可以赋值: R' m- w& f. J) R
有时候,一个 vector 可能增长到较多个元素,然后又减少到较少的元素个数,这时,可能希望缩小 vector 分配的空间以节约内存。CArray<> 中提供了 FreeExtra(),但 vector<> 并没有提供相应的函数。这时必须进行复制:4 |* s9 K5 A. Y& V, ~

9 `# |5 ^, C4 B8 AIntVector(v).swap( v );/ T- ~0 J* V" h2 o: v3 G
有一种看法认为拷贝构造函数同时也复制了capacity(),而标准中并没有很明确地指出这一点,因此更安全的方法是2 {# p7 H, C) F4 U+ Z- w- E6 x4 V3 {

+ `7 k# t* }% k$ aIntVector(v.begin(),v.end()).swap(v);
3 l  g1 l2 C: a) r  ~如果一个 vector 中可能要存储的元素个数较多(例如,超过100个),而且事先无法确定其个数(因此无法调用 reserve()),那么通常 vector 不是一个恰当的数据结构,应该考虑用 ::std::deque<>。与 vector<> 相比,deque<>不保证背后的存储空间是连续的(因此象上面的WaitForMultipleObjects()中的应用不能用 deque<HANDLE> 代替),但有较好的伸缩性,还可以在数组的前端用 push_front()/pop_front() 增减元素(hence its name, doubly endedqueue)。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

Archiver|手机版|小黑屋|宁德市腾云网络科技有限公司 ( 闽ICP备2022007940号-5|闽公网安备 35092202000206号 )

GMT+8, 2025-11-15 04:51 , Processed in 0.020313 second(s), 15 queries .

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

快速回复 返回顶部 返回列表