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

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

[复制链接]
发表于 2008-2-26 13:29:18 | 显示全部楼层 |阅读模式
作者:wangtianxing+ s" c+ j& z0 r, {+ ?
# u3 p* X0 n5 W( `% @; a) S
原文出处:http://www.cpphelp.net/issue/vector.html
, o, w7 H7 x  f* `; w, s& L* |. N( P1 p5 l9 ]! }" r* T7 ]
8 U( `4 w, r  a

3 L- M) }# A& h6 b8 @& v/ E摘要: 本文介绍了C++标准库中的容器类vector,分析了它的优点,并且建议在应用程序中使用它作为动态数组的优先选择,而不是MFC的CArray<>等其他类模板。最后介绍了vector的接口和使用时的注意事项。
; P# z: p" G" j8 K/ M( F& d0 H: |) C' w$ z
在一些使用 MFC 的程序中,经常看到许多程序使用 CArray<>,由于 CArray<>的设计问题,造成使用它的代码的复杂化,增加了维护难度。因此建议使用 ::std::vector<> 代替 CArray<>。) h. R0 A6 j$ U$ S# N0 \9 L, n. j

* R, x  X+ H+ {- W另外,也看到一些程序在用 malloc/realloc/free/new[]/delete[] 等手工管理内存。在应用程序中,手工管理内存是容易导致错误的,应该用 ::std::vector<> 之类的对象来管理动态数组。5 `7 D: {+ }, |( }. n! m2 z3 Q" j! A

6 m' H5 e' E8 v( ]5 Q- @由于 MSDN 中关于 ::std::vector 的内容较少,我们在这里做一些介绍,供参考。3 i0 a! x, y/ R: E4 Z; B! g

" a& t7 F$ h( K$ B! l  M" H不熟悉 CArray<>/WIN32 也没关系,这里提到它们的地方并不太多。5 ~8 p7 s# \" Q6 K! m

# \4 o: q3 t1 L1. CArray<> VS ::std::vector<> ?: T0 _4 U5 g, O2 }0 l
CArray<> 和 ::std::vector<> 一样,都是模板类,用于管理任意类型的对象的动态数组。都在解构时释放所管理的动态内存。因此都可以用于代替手工动态数组管理。
' z; _9 r" N- [  F5 _5 K; }- |; v. h2 K
但是,CArray<> 是在 C++ 标准化之前很多年(VC++2.0时代)设计的,当时对 C++程序设计,面向对象程序设计,模板程序设计等技术认识严重不足,尤其是当时对面向对象技术的错误信仰与宣传,造成 CArray<> 的设计有重大错误。! v' h% W. q9 S# y( X) ~

  \, m( [1 w" w- W- m在 C++ 语言标准化以后(1998),以及 VC++ 6.0 出世以后,提供了标准的::std::vector<> 模板,基本上在任何方面都要优于 CArray<>。Microsoft 由于要支持老的程序,因此一直保留了 CArray<>,但显然并没有打算按照新的思想去发展它(至少应该提供operator=(CArray const&)吧)。
$ Z1 v/ v" V' Q8 u+ |
9 o& k( J, d7 Z& k9 C6 ~: ]概括起来,CArray<> 与 ::std::vector<> 有以下不同:3 n6 f4 x& m1 ^# }7 b/ Y
* _& G7 ^& }& J) X8 [
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<> 没有继承任何东西,只是实现了管理一个动态数组该做的事。+ C9 b5 s$ e. M% ]7 y

$ o* ?( |) r/ c! c( L8 W2) CArray<> 不是一个恰当的值类型,例如下列操作都是不合法的:+ W* ]1 K6 D7 Q# ?$ w! s9 T+ Z; Y
7 I& Z- l( `2 G0 g- d7 t) I
CArray<int,int> a;1 ^7 }5 }+ k8 C5 ?6 h2 J/ o" B
CArray<int,int> b(a);  // error, must use Copy().9 A0 z0 D, w( Y" i! D7 J6 Z1 b% g
b = a;        // error, must use Copy().
! q& y4 g( W. O2 tb == a;       // error, you must write your own.! N( r2 o( d& ?7 m( r; n% m' X" i
b < a;        // error, you must write your own.' M8 N4 w4 c' h/ F& m2 z  o# V* l- t9 F
与 CArray<> 相反,::std::vector<> 是一个认真设计的值类型,天生是可以拷贝构造和可赋值的。如果 T 是可比较的,那么 ::std::vector<T> 将自动地是可以比较的。2 [* [0 [9 p# I/ Z

8 Y# j$ h0 T: w8 u' J2 K- W此外,由于涉及到四个特殊成员函数;! u5 A6 E, T" r! ?: g2 K
( {; v* D+ W! d
T(); // 缺省构造函数(default constructor)
9 O. a  u, s1 A& c. o4 u~T(); // 解构函数(destructor)
# u& p. v% O; ?, V* jT( T const& ); // 拷贝构造函数2 |  ~4 ]; i* y- h) s9 |
T& operator=( T const& ); // 拷贝赋值函数7 H+ Y$ N' j" x6 n7 o+ v: }
的自动生成,如果使用 CArray() 作为 T 的成员变量,那么上述的四个特殊函数中的后两个将无法自动生成,需要手工写:
# G# K5 p4 |' Q& ]% [1 w! |* U% G) o9 c2 S* `: ]
struct T
% |$ i0 A* C+ W+ Q0 o{+ \" y* m% l* z7 t( t1 B
   T() {}# a% y# n9 R; b$ ^* e: J7 E
   T( T const& t )% Z. {+ B8 X& W4 `- v& h
   {3 K: u) `2 J4 G& p3 {  D. m
       a_.Copy( t.a_ );( [8 |  A& B% _2 T; p  G$ t/ d
       i_ = t.i_;$ X# Z4 J3 U+ @. p1 I
       d_ = t.d_;2 F7 [* w6 X2 ?; R
       s_ = t.s_;
0 L$ a0 o8 @; \: m+ C   }
0 u1 H) g6 w+ f, D5 C3 S   T& operator = ( T const& t )
$ W% L3 a+ C8 [   {( M& g( b0 r/ {. f. m$ {- y
       if( this != &t )" e* D" L9 ?9 p' [# U$ ?
       {
6 i0 E& `' t- O* t+ p0 r           a_.Copy( t.a_ );
0 B5 t. q0 v, @6 k9 [           i_ = t.i_;0 N% L& w8 W, R  q) s0 \) e8 [  O
           d_ = t.d_;
6 u+ M" |( T$ v" }. p           s_ = t.s_;9 v8 o# O* k& B5 k& V5 n/ X, Q9 j! F
       }
) H  r& _: Q* z% ?* G2 w+ w4 E% {" n       return *this;+ O3 n& I) v# f! m, @3 g
   }( h+ K) D, k0 G* b' J
private:1 p: V5 F2 z$ X3 \& u7 l. ~$ M/ h
   CArray<int,int> a_;
9 A1 r/ W) X" i; M   int i_;8 a/ U0 l3 V# {3 h
   double d_;
. O' l  i+ q5 \   ::std::string s_;
7 f9 e  r: R2 c: ?};. y, f5 O; s9 Y5 c1 P- y
如果使用 ::std::vector<>:3 R  H" ~7 E" ~- @# C
, k! {! }2 h$ R" }8 @8 y
struct T+ W( s8 o9 i# `0 r
{6 h  F' Z+ [# x. R
private:7 p7 {# ]2 F+ v  i6 O2 J
   ::std::vector<int> a_;
  U8 `. u" z5 S$ S( O) C   int i_;* R1 \9 _7 \7 k$ A$ f6 j
   double d_;, F/ ?" h; E1 q; P& h# T' k
   ::std::string s_;8 A, }/ h: E' @8 m6 b: ^/ T- J' e3 o
};# V: H( s- `' Q; r- f
上面列出的三个特殊成员函数都不需要写。好处是明显的:当你增减 T 的成员变量时,你不必到
+ D: |; Y) b1 ~& e" b8 lT(T const&) 和 operator=() 中去相应地增减。
/ U4 D" x7 Y! P, c4 r6 N6 s/ o- g! ^* g+ R; B/ ~( F
3) 没有现成的算法可以对 CArray<> 进行操作,而标准 C++ 里的标准算法大多都可以直接在
# ~% @7 C! e& }7 d8 Q::std::vector<> 上运行。例如:8 J$ h8 s' s2 W1 \  I
; i' K  @/ I& n* s6 j$ N
static int const init_vals[] = { 3, 1, 4, 1, 6, 9 };
* Y- o$ Y& b) U2 C! L' wvector<int> a( init_vals, init_vals + 6 );+ y, Y, Y4 t. V! N0 c: S
*find( a.begin(), a.end(), 6 ) = 5;    // 把6改成5
8 O: L$ l- p1 F/ f/ l% Vsort( a.begin(), a.end() );    // 排序。
! k2 @1 b6 u2 G& Q% x8 N, o8 G可以说,CArray<> 的主要设计错误是把一个本来应该是一个简单的“值”类型的东西设计成一个难用的“对象”类型了。所有的“值”的好特性都丧失了,但那些从CArray<>继承的派生类呢?
5 t4 o4 `$ e% P8 C1 z+ h$ X1 I
7 d( D, X( @2 ~" l9 LCByteArray等的问题与 CArray<> 的问题一样,甚至更多(例如,CPtrArray,永远不要用)。9 s: q8 \" Z1 P! M) i
8 J. J- t9 ?1 @, ]* F& |& a
同样,其他的 MFC container 模板,象 CMap<>, CList<> 等,都有类似问题,都应该用
0 y: V1 `" F0 \) E3 I) Q::std::map<>,::std::list<> 等设计更好的东西代替。# c) z/ U; J7 C+ o& k! u+ Q+ n
" D+ H; Q: l6 O- J: b4 _5 X
2. ::std::vector<> 在哪里?# }0 j, C1 G1 s6 ^' c- t
::std::vector<> 在头文件 <vector> 中定义:
/ u- W6 ~# E( }! q! U( w6 u# h
; a1 @3 B7 E; R(注意,标准的 C++ 头文件都没有 .h 后缀,有 .h 的文件是与 C 兼容的,或支持老的不标准的东西,象 <iostream.h>。)" _: o' j' [0 g( l' z% w
% T/ V# Q; C9 s& e
namespace std
$ f5 P, v2 J6 {1 J# z9 i{
8 e4 H1 X1 t/ H6 Z    template<typename T, typename A = allocator<T> >
5 x+ \/ |5 F+ X9 ^6 Y; @8 r: J    struct vector
1 ~2 p9 E' H' l+ R# C3 ]    {7 ^' m0 I" p+ f- E  M! x2 v/ h+ o5 N* V
        // 具体内容稍后讨论
1 _$ z  l  @; R    };
; X  W2 z- K! P3 z9 n4 Z6 p
5 \& z/ A, S7 |2 m
% ]) Y( u% b; U    template<typename T, typename A>
+ n8 `) a. X9 q/ ~        bool operator == ( vector<T,A> const& a, vector<T,A> const&    b );' I$ n; l* d1 v! S0 `
    template<typename T, typename A>, y8 a8 n/ A: s4 ]) E4 h  e8 |) B
        bool operator != ( vector<T,A> const& a, vector<T,A> const&    b );
) G, X' o( C. N7 q1 I    template<typename T, typename A>& \! D( O) P! W
        bool operator < ( vector<T,A> const& a, vector<T,A> const&    b );- i, _# w6 `/ ?0 ~; T3 a6 z
    template<typename T, typename A>3 T/ T4 X5 v2 X  z$ P4 ]
        bool operator >= ( vector<T,A> const& a, vector<T,A> const&    b );
  m) [( ]! r: Q7 o& ~    template<typename T, typename A>
- T  ?2 t4 f# ]; K' W( c& w1 ]& N        bool operator > ( vector<T,A> const& a, vector<T,A> const&    b );
( z. J% m4 g) ]/ U2 z+ F: F2 c    template<typename T, typename A>
4 R4 n; k% ]$ W' h" c        bool operator >= ( vector<T,A> const& a, vector<T,A> const&    b );+ [/ D$ s0 [# f* F' L
}4 ?6 k- J" g1 U( B
vector<> 定义在 namespace std 中,使用时为了减少击键次数,通常使用一个类型定义缩短类型名称:3 \# z8 n+ y4 L# F. ~
) s+ u$ a" M. Q' ^. u$ [0 b
#include <vector>
- _/ t* p/ }- F* d0 c- D! Ftypedef ::std::vector<int> IntVector;
' N$ N9 ?4 v) c& S2 b, qIntVector a;
+ A& a  z1 Q0 B3 dIntVector b( a );
* }* f# d0 d- n+ t6 G- WIntVector c;
! A' A+ Y* X$ }* m3 ~c = b;
: O" H; W; h) m* ~4 Iassert( a == c );5 n4 A. g/ Z2 \" \
请注意 <vector> 中定义了六个 vector<T,A> 的比较函数。这些函数只在真的用到时才会被实例化,才会要求 T 也提供 operator==() 和 operator<()。) Z, e4 S  Z5 i% [( G* ?$ |9 S
4 N. [6 u% @" _4 ^( M/ a6 O
另外,A = alloctor<T>:用于提供一个用户定义的存储管理类。由于这个参数很少用到,而且在 VC++6 的实现中有问题,不能用,因此以下的讨论忽略这一部分的内容。8 \% }% ~1 U9 M! r0 c/ S" x( j
" k. D  o) Z" T- e; z4 A3 F
3. ::std::vector<> 中的类型定义0 o! ?% o: u8 c% @  l0 ?8 W
vector<> 中定义了一些类型,下面只列出常用的:
3 \4 n9 j6 s; T) O! s3 M9 e" C& H+ x$ i, ?1 k7 B1 f- O1 O
typedef T value_type;
5 d# E5 d0 L& ]typedef T0 iterator;" _9 g& C2 z' n9 h2 T- h: _
typedef T1 const_iterator;
% j9 ?$ U# l, w( \3 jtypedef T2 reverse_iterator;
) @* u0 ]% N7 W+ xtypedef T3 const_reverse_iterator;4 d# I7 K2 V* |! |
- E5 h1 G7 X/ `
value_type 就是 vector<T> 的元素类型,也就是 T。当写通用的算法处理任意类型的 vector<> 或其他容器类型时是很有用的。$ K! c, [3 I+ |

: P: {; c# q: W, _! Miterator/const_iterator 是两个 vector<> 的实现定义的未知类型,用于访问vector<> 中的元素,类似于 T*/T const* 指针,他们的区别是一个指向的元素可被修改,另一个只可以读:
8 e* `/ q+ U: x1 V; f8 y  a) k$ k! J" x7 L1 r6 S& w
typedef ::std::vector<int> IntVector;- j1 l. R! w! }' m' b+ L* }8 p9 _
IntVector::iterator iter;( ]/ S0 [/ P/ P4 G
IntVector::const_iterator c_iter;
. [# x7 ]7 W! v: C" o$ v# h// ...
- Z8 z' _: a6 E" W# p6 H# `++iter; iter++; // ok: increment, post-increment.
& ~- \5 b( Y2 Q( t--iter; iter--; // ok: decrement, post-decrement.! y8 n/ J6 J1 G
++c_iter; c_iter++; // ok: increment, post-increment.# a  Z% {; ]' K9 b3 W) Q
--c_iter; c_iter--; // ok: decrement, post-decrement.
1 U. j- J( a8 ~+ Q* ~, f5 C0 N*iter = 123; // ok.
! i5 @) |; r# U  }$ M/ bint k = *iter; // ok.
) C' ?0 [: R/ [% O) A* g0 qk = *--c_iter; // ok.; |* t: z# Y! X. H2 ^% P
*c_iter = k; // error." x4 V- t5 m2 n* K$ }
c_iter = iter; // ok: iterator is convertible to const_iterator.
" F% U2 k; a  P8 l6 qiter = c_iter; // error: can't convert const_iterator to iterator.
# H# W$ ~, O" v5 a1 W# [" W1 O在使用上 iterator/const_iterator 和 T*/T const* 基本相同,事实上有些vector<> 的实现里就是用 T*/T const* 实现 iterator/const_iterator 的,但又不可以把 iterator/const_iterator 当作真正的 T*/T const*:
" r. ]1 l6 I/ K; f! o1 y- _
) _5 ~' _  J( h7 h4 H  FT* p = iter; // may fail to compile./ e, X& r. K2 I% Z+ B; b
T const* q = c_iter; // may fail to compile.7 Q. s* x6 r( j" g- @
reverse_iterator/const_reverse_iterator 与 iterator/const_iterator 类似,但以相反的次序(从尾至头)访问 vector 中的元素。/ P4 d. d* P! [3 T  B- f8 t4 ~8 x

4 J0 S' N( f9 ?* x! d) o5 C各种各样的 iterator 在 STL 中有特别重要的意义,但这里我们不做具体介绍。只要理解通过 iterator 可以访问 vector 中的元素,大概相当于一个指示位置的指针就行了。
+ y6 y- p. t- @3 g4 K4 X
) x$ [. U" n% S, H4. ::std::vector<> 的构造1 I% |/ d6 \# j  V& r
vector<> 提供了以下构造函数:(忽略 allocator 参数)
' h4 X, w# |$ E2 y0 u5 v6 K& v) K6 h
vector();1 R/ r; M' ^8 q8 b" v
vector( size_t n, T const t=T() );& t$ z  B/ M: N* b
vector( vector const & );: ?+ J% F6 a- E$ H5 U4 C0 Q+ F
vector( const_iterator first, const_iterator last );
5 w9 L9 S4 W7 D" M1) vector();
& h- x6 V* M4 h- k; O7 B6 a) k2 a2 I- t) A& s( P5 O" U
构造一个空的 vector,不包含任何元素。% l' a3 m% h+ p$ ]
7 C7 a7 ]9 \: O9 W5 P
IntVector v1; // 空的整数向量。. O3 Y/ y" e  o
2) vector( size_t n, T const t=T() );5 e& W! u- L: e) n2 z" V

8 f3 c8 u% A- B( x% p构造一个 n 个相同元素 t 组成的 vector。如果不给出 t,那么将用 T() 做缺省值:% X9 r6 I" T- c6 J
0 W/ H. j/ C7 R) Q: _
IntVector v2( 100, 1234 ); // 100 个 1234.: m  _+ H& B* O  T& q$ X; J
IntVector v3( 100 ); // 100 个 0。
& {" m4 l5 b) l5 N1 x% m3) vector( vector const& other );
- I+ w6 a+ |' ~) K- W, q+ |
/ w' u2 n6 D; i: F复制构造函数,复制 other 中的内容:
0 M& v% f0 R- J7 K) t7 V  W! k- m7 u: [, T! A3 e7 w/ O
IntVector v4( v2 ); // 100 个 1234。) u. x5 Y! R( y6 t: m9 Q* `
4) vector( const_iterator first, const_iterator last );' [& u) ]) Z4 L2 G1 ?- F9 r6 ?
& @5 M  h. _- I9 a; v7 s
事实上,这个构造函数应该为  z& t; w9 T/ x" x
, W: _7 q% s* Z# }, ?2 A, i. Y
template<typename Iter>
. ^7 f6 m7 l& \- `2 X$ t, ]$ }, p. n0 F    vector( Iter first, Iter last );
3 v$ s+ a% {) T8 S即拷贝任意的序列 [first,last) 到 vector 中。由于 VC++6sp0 编译程序的限制, Iter 被换为 const_iterator 了。不过,碰巧 const_iterator就是 T const*,所以可以如下使用:
0 w! `8 M  M' O9 B7 O4 X# m$ D- w% ^* o
int a[] = { 1, 2, 3, 4, 5 };' n; w% N: t* r8 w# r, a
IntVector v5( a, a + 5 ); // {1,2,3,4,5}
+ o/ C9 \; `6 d! hIntVector v6( v5.begin() + 2, v5.end() ); // {3,4,5}: S  E- m4 s( v( c0 q- p
5. 访问 vector<> 中的元素
' Y: U& L) k8 X以下成员函数/运算符用于访问 vector 中的一个元素:
4 l$ \9 J" M4 T# }* q$ y6 }' ]& f" U0 q# c. L$ ^# r
T& at( size_t n );
) g: s- Z5 |9 k$ _1 C. @( OT const& at( size_t n ) const;
( I8 P2 x" W% R# O  {T& operator [] ( size_t n );; u6 K' Q" v" S" y/ B7 Q( o" _
T const& operator [] ( size_t n ) const;( Y3 Q% L/ `0 o+ a1 T2 i4 k
T& front();0 R  w- M( g% b# A
T const& front() const;9 @- K8 h2 `3 D6 I2 K  F, N
T& back();( P9 W1 P& k* i4 _1 C; y# z
T const& back() const;3 o% D/ j9 h- l8 L% E2 n
请注意,由于 vector 是一个“值”语义的对象,所有的操作函数都必须严格保证 const 的正确性。所以,所有的元素访问方法都有 const 和非 const两个版本。* Y  K! Q5 R' ?1 g- D
3 O1 @' g) }6 F' \7 D
at(n) 和 operator [] (n) 都返回下标为 n 的元素的引用,他们的区别是,at() 进行下标越界检查,若发现越界,抛出 range_error 异常,operator[]不进行下标检查。. A0 T% I# w' b  S% \+ O$ a
( ^( R! V; {6 ~
front() 返回下标为 0 的元素的引用,back() 返回最后一个元素的引用。
# ]# D9 e0 J( H% J( \
+ c3 G/ R( a: j: Bint a[] = { 4, 1, 4, 1, 5, 8 };
7 |- f+ O5 W% x: g8 W. FIntVector v( a, a + 6 );6 N& u2 y- z' q- @* N8 w
// 使用 front(), back():. {7 W" Y+ y) G8 Q, e" q, ?9 E; d0 O
v.front() = 3;
; ]' m  i! L. ]/ ov.back() = 9;' K0 q% V% O6 D3 {
// 使用 operator [] ():3 P( V- E2 G, Y0 O
for( size_t i = 0; i < v.size(); ++i )8 G  {/ g2 Z$ g4 j  |
::std::cout << v[i] << '\n';
2 s# {6 T2 l" @8 g" B( M, Y6. ::std::vector<> 的存储管理
7 z( F  P% |' D: b# c1 C( ~+ j以下成员函数用于存储管理:
# ~- Z' w" T% x! Q6 _; r5 b+ X) z  l
void reserve( size_t n );! F3 d! V9 U1 M' q2 P
size_t capacity() const;) Z. [* X$ W  E8 m
void resize( size_t n, T t=T() );
8 Z" A6 U8 F* a( }3 Q. r) f6 o: S6 tvoid clear();
6 d" h. m$ A* _size_t size() const;
3 W4 m/ [" e4 Y/ q' z2 m' Xbool empty() const { return size() == 0; }
" b; N, i1 }9 ^' Y1 y( wsize_t max_size() const;( O4 v* u# B7 D) Q' S+ k# F

3 }4 ]1 w& j! _  u$ ]# B另外,push_back(), insert() 等也涉及到存储管理,后面另行介绍。
" h. W" e: u" H0 w4 ?5 P3 ?9 m: W0 |( K4 i, U' m4 y
1) max_size()% s! f6 {, T2 t
" H. h7 N& @) N, p
返回 vector<T> 理论上可以装的最多 T 的个数。这只是一个理论上的数字, 大概是 4GB/sizeof(T),没有多大实用价值。在程序中不要用。3 e' r1 x) {* v- Q- `$ I
5 ~# ]' D0 s, {6 r
2) size()
4 ~7 h3 k" }4 _; G/ i+ K% S5 X0 `- E, l$ f7 J% A4 V
返回 vector<T> 中实际装的 T 的个数。相当于 CArray<>::GetSize()。! H+ ~1 X2 ^- P) p- v

7 `/ C, k% z& d: L; p3) empty()2 x7 s7 B' S, P

1 N; O' T0 r$ @$ r8 {$ Z# G( H如果 vector<T> 中没有任何 T 对象,返回 true。也就是返回 size() == 0。5 b, D3 M0 U% ?; w' @* s
+ Y. V! O4 y8 _
4) clear();# d: J7 }) r' b

* L  X6 M0 _! f: V6 {" x清除 vector<T> 中的所有 T 对象。执行后 empty() 返回 true。大致相当于 resize(0),但不要求 T 可被缺省构造。相当于 CArray<>::RemoveAll()。
9 P; D; h0 F8 }! v" t: u+ u% Z7 T- E; b( Y# j
5) resize( size_t n, T t = T() );
) }1 [) n$ @) C1 m3 _" y/ o6 f9 R) }$ v0 N6 H
将 vector 中的元素个数设置为 n,n 可以大于 size() 也可以小于 size。如果 n 小于 size(),那么 vector 中下标为 n..size()-1 的元素都将被解构。如果 n > size(),那么将在 vector 的后面新增加9 |) K0 |- F' i! G- p
n - size() 个相同的元素 t。在增大 vector 时,可能发生存储再次分配。总之,调用resize( n, t ) 后,(size() == n) 成立。
" q. Y; F+ x- A" q/ ^. G* y/ W# O5 r; Z- E! W
请注意,如果调用 resize( n ) 不带参数 t ,那么 T 必须可以缺省构造。4 I( u9 [$ e8 W4 \& Q) Y2 z
! I; S. b3 ]) s1 g6 l% w
6) reserve( size_t n );5 ]* b9 z2 G4 U

7 k" F0 B  o" u! V2 |0 C, \( o% y事先分配至少可以保存 n 个 T 对象的空间。调用后 (capacity() >= n)成立。( Z+ l. i3 S  r! r& _
7 p5 N8 E; R9 b0 b& n' p
7) capacity();
+ y* _, F/ Q6 R" s. F* R. X3 H2 m% @2 z3 q% t/ W: t; a
返回已经分配的存储空间够容纳的 T 类型对象的个数。后续的增加元素操作(如 push_back(), insert())如果增加元素后 vector 中的总元素个数不超过 capacity(),那么 vector 的实现保证不重新分配存储空间。
2 J) M0 q/ R/ s6 J% a8 Q% z% {( t' I9 W+ x) s% T
vector 管理的动态存储空间是连续的。执行操作
; N9 e& t1 K; `. `1 v* l' A$ N  B" ^" L' ^) T
IntVector v(7, 1); // seven ones.
7 }+ m' I4 V2 }/ x3 R0 I5 xv.reserve( 12 );
6 L8 l$ }: H$ M- @2 S4 B后,v 的状态可以用下图表示:0 W- l! i2 Z8 h+ G

/ B1 f0 z+ k- P2 N/ s /--size()---\+ G3 N; i, a: L% O8 C# e5 Z
|1|1|1|1|1|1|1|-|-|-|-|-|3 n) O& _9 p: A
\--capacity()---------/
: g5 x0 ~+ [; }! C+ N3 [- X# `) a& {其中,1 是已经构造的 int 类型的对象,- 是可以构造一个 int 类型的对象,但还没有构造的原始空间。再执行) V) H+ C5 L" Y: s( O  D) L
* W  a7 c  U; M) l% n/ L0 @+ A
v.push_back( 2 );
  D9 r; M* h( B$ c4 R1 r/ F. N. ev.push_back( 3 );
6 o) n1 S3 Z3 l4 l  q' O后,v 的状态可用下图表示:
) [- ], S0 _. x! Y9 G$ d- F+ y- J# F
/----size()-----\, V; Z9 u# p. w; _: m
|1|1|1|1|1|1|1|2|3|-|-|-|) `+ |1 S* p' n$ k
\----capacity()-------/$ D1 }6 H- V- z- K+ ?+ Z( t
执行 resize( 11, 4 ); 后:5 o  i8 ?+ V! e- U+ [/ n% W
* b- `# A7 N+ |( z2 g
/----size()---------\
/ G: q5 C# P, X6 F. u* ^5 k3 t" _|1|1|1|1|1|1|1|2|3|4|4|-|
0 }% O- n" r& p2 n1 n9 y \----capacity()-------/
% u5 R: j4 T+ A/ }/ H" @capacity() >= size() 总是成立的。对于下标为 [size()..capacity()-1]的未构造对象的存储空间,是不可以访问的:
% N6 `2 E* a4 m" M8 |+ u# U4 Z9 y& B+ i- R
v[11] = 5; // undefined behavior - anything can happen.
1 y: ^( U9 K  ~8 U8 f; B7. 添加元素到 vector 中
4 K, }0 A8 I7 N. F3 v5 N0 y下列操作添加元素到 vector 中,并可能引起存储分配:
! j- h6 ^# _( z6 i- R& f6 S( b! z: c1 Z  Z2 l
void push_back( T const& t );
, ^# }9 u5 N2 J2 k( gvoid insert( iterator pos, T const& t=T() );
& {; {) }% L: q" O3 K) z9 nvoid insert( iterator pos, size_t n, T const& t );; e, r2 [9 A4 X8 p% v& q
template<typename Iter>5 X3 f5 I4 M# M5 {) V* q( t2 M
    void insert( iterator pos, Iter first, Iter last );7 J9 E2 L# s8 I; x
push_back() 是把一个元素添加到 vector 的末尾。insert() 是把一个 t,或 n 个 t,或从 first 开始到 last 结束的一个序列插入到 pos 指示的位置之前。8 F5 O4 p/ Q% n# n4 d$ N; p! L
! U( d. [8 Y6 p2 }. ~
当插入元素后 size() 将会大于 capacity() 时,将引起自动存储分配。vector 将会分配一个比需要的存储区大若干倍(通常是1.5到2)的新的存储区,把老的元素拷贝过去,同时完成添加或插入,然后释放老的存储区。* u7 d6 s) Z. I# h# t) Y

- R. y- W0 `" B+ Y( ?这就是说,vector 自动存储分配的空间大小是指数式增长的,这可以保证多次添加元素到 vector 中时,平均用时是接近于常数的。
) ^7 [+ o6 F% h& Y% _
8 n+ q7 J7 \3 l& w! v" K! EIntVector v;8 \1 k- U% @/ n9 ]+ _
   6 g' K( Q) c, ]
// add 0, 1, ..., 99 to v:( \4 W4 ^& T* q
for( int i = 0; i < 100; ++i )
$ v( ]3 J& q) S8 @v.push_back( i );! ^' c. j/ M0 [/ X
   5 K8 h  v" q3 e! J5 b( ]
// append 9, 8, 7,..., 0 to the end:
9 f& Q, `0 U! G1 e6 Q. Lint a[] = { 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 };2 @! D* c9 J0 U+ o, {
v.insert( v.end(), a, a + 10 );
( h- `- G" m& |! w9 V8. 删除元素
2 V+ ~: K  Z# q( U下列成员函数完成元素删除:% A/ D/ |  j3 Z7 \6 c# a9 [

6 s3 I/ V$ ~5 k5 D7 Pvoid erase( iterator );5 q7 T& _9 O. X% P8 t2 x# ^( B
void erase( iterator first, iterator last );
. _- W$ p% ^9 |3 i& o2 Ivoid pop_back();
# M) U0 ?( p: |. y) Svoid clear();
4 c0 k$ Y7 K. h; c这些函数分别删除一个,一串,最后一个,或全部元素。
8 l; a+ D3 h8 X- e" f6 o; _5 a8 W0 o, h
IntVector v;
6 W% ]  I$ m7 k7 }/ ?9 Sfor( int i = 0; i < 100; ++i )8 [" n8 G% k; ~2 V" w8 S* U
    v.push_back( i );% ^" n- F9 R& d/ `! p1 M; x
   ) l7 L. D( ?2 ^# D. `# F2 e& W6 @) w
// 删除 50, 51, ..., 89:
# x: C8 ]( V4 Q/ M6 {# ?9 [v.erase( v.begin() + 50, v.end() - 10 );5 ]. |5 ?4 d+ @
   " D/ W( o, C  E7 ?$ m/ U; L
// 删除 49, 48:
& ~3 M% E; {. U- J; Tv.pop_back();
! {2 a0 `7 j2 r: T% f# ~0 h" d1 iv.pop_back();
% y; n0 S' l3 i! K! a" m   3 q" K7 @3 v5 {0 {7 X) ~
// 全部删除:
* a% C) \& x) b7 R! b# E. P4 H+ [v.clear();" M7 r% b$ `( Q
注意,删除操作不会引起存储分配,因此 capacity() 不变。
% J& d' b/ @, c! h, \2 F7 E
1 S! j0 ]" H" |4 R9. 作为序列访问 vector 中的元素
" f+ A& }* d! |$ B( l序列(sequence)在 STL 中是一个非常重要的概念,所有的容器类型和算法都涉及到,而且所有的算法都是建立在“序列”这个概念之上的。
% {% T  }# E7 l" F5 C5 o
$ z; h+ X+ _' P( v, T“序列”是一个线性结构,由一个指示其起始和一个指示结束的叠代子(iterator)来决定。如果 first 和 last 是某种类型的叠代子,那么经常用[first, last) 来表示一个序列。注意,first 指向的元素是这个序列的一个元素,而 last 指示的是这个序列最后一个元素之后的位置,可能根本没有元素可以访问。这种半闭半开的区间表示是整个 C++ 标准中的约定,而且确实可以简化程序。* e2 [- {, k' C$ D- V% v; G2 v

8 e  ?& ~* X$ E7 {& A- y叠代子是传统的 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 的要求。0 q# E1 `2 H+ l% n; T/ a5 a$ _
3 l7 W& @, u# X3 n/ _
vector<> 中定义了以下函数用于获取被控制(管理的)序列(动态数组)的各种叠代子:& C' `3 j, w+ q4 B
% z4 N& n/ j$ a7 \7 i
iterator begin();# `* ?4 W9 n4 k5 H
iterator end();* C. f" w5 k# V6 l; D
const_iterator begin() const;
+ x$ b6 |8 n$ q0 c2 [9 e' b. hconst_iterator end() const;
& a) y& e3 f9 ireverse_iterator rbegin();
8 F* ]- H' S( b! Q6 o) @4 ^; greverse_iterator rend();) y  |  m0 F. N- O. s+ S# h
const_reverse_iterator rbegin() const;
$ H. p( D" Q- T+ a% xconst_reverse_iterator rend() const;
- Y* Z. y* ~; t2 `/ c* J这里我们不讨论叠代子的一般概念,只举几个 random access iterator 的例子:
, b/ }/ ~9 w8 r# P5 X8 K1 h+ U. n! ?% N  s  v$ \
int a[] = { 1, 2, 3, 4, 5, 6 };# u9 y3 u, |$ p  c2 G
[a, a + 6) 是一个随机访问序列,指示了 a[] 中的所有元素。这里叠代子的类型为 int*。
0 g. ~! t/ Y# c1 K6 B% I/ V  [8 _
1 G5 B  _& r3 O& n[a + 2, a + 4) 也是一个序列,指示了 a[] 中的 3, 4 两个元素。叠代子的类型仍然是 int*。
6 J5 l6 T3 A& d" h- W( K+ b2 r8 w  w' O/ `3 u. E
IntVector v( 100, 1 ); // 100 个 1。
4 z( h+ G4 z2 {8 ]6 N3 H[v.begin(), v.end()) 是一个随机访问序列,指示了 v 中的所有元素,叠代子的类型是 IntVector::iterator。
0 H! R5 V0 ^! ^, m# l$ `
8 m1 y, \, a) K) ^5 \[v.begin() + 10, v.end() - 20 ) 也是一个随机访问序列,指的是 v 中除了头 10 个和尾 20 个元素外的其它元素。
$ ?5 J4 h' G  V' h& L* w/ P0 F2 N
[v.rbegin(), v.rend() ) 是一个随机访问序列,指的是 v 中的所有元素,但与 [v.begin(), v.end() ) 不同,这个序列是从尾到头遍历所有元素。1 @* }, U4 W& t6 \. U: A2 K

2 n4 i( y+ R1 w[v.rbegin() + 20, v.rend() - 10) 与 [v.begin() + 10, v.end() - 20 )指示的元素相同,但遍历顺序相反。
" k! n$ V2 D9 n4 U2 S; R5 [, @! e3 d  }2 R
下图是有十个元素的 vector 的 begin()/end()/rbegin()/end() 的示意:
2 B7 `: O: Y- V( u5 T. v8 Y/ `9 h% i" J* n
begin() ----------> end()8 x0 S* p$ k$ n
  |                   |
. @( S5 [$ g: d3 g& `2 L  v                   v
# w: T3 U5 u" B( z3 z  R |0|1|2|3|4|5|6|7|8|9|
# b% h; J4 O' A, ]8 o% p^                   ^8 M% B' S) A/ ]* w. h0 J
|                   |) {/ [' T7 N- h6 P- k4 _5 ^+ \9 {
rend() <---------- rbegin()" T  n+ l- ?- }( ~7 Z% x, y4 t6 c
   0 T9 ~* }4 B' Q& l& R9 ^1 f( H3 S
IntVector v;
  s" `( t* [, o) h2 S1 xfor( int i = 0; i < 10; ++i )  S" [# L% E0 E0 o
v.push_back( i );
$ J4 r/ t9 ]8 H' X   3 T5 _4 O/ y9 p) @- b: t9 A3 e4 z
// print 0, 1, 2, ..., 9:
1 Q) I3 y1 w5 c4 t% L6 {for( IntVector::iterator i = v.begin(); i != v.end(); ++i ); y) {- B- }2 s: }' B
::std::cout << *i << '\n';
* N1 S+ P( Y5 t   7 O1 x3 \' a" y8 w3 U
// print 9, 8, ..., 0:
, p3 f6 K. b: i% `9 |1 jfor( IntVector::reverse_iterator i = v.rbegin(); i != v.rend(); ++i )' }6 a& G3 e: \- U+ k
::std::cout << *i << '\n';# n+ b' U0 |$ ^( R, }/ J3 O
除了使用 begin()/end()/rbegin()/rend() 来遍历 vector 中的元素外,由于 vector 管理的空间是连续的,因此可以直接取地址进行处理:! ~, D: x" W) }8 X
7 m1 [9 r: Y, [! x0 H( X% @
::std::vector<HANDLE> handles;
$ H4 J3 h3 n7 e& e7 U' |( {; [handles.push_back( handle1 );0 \$ O' `, G. J: _2 o
handles.push_back( handle2 );9 g. k# A* ]0 I& E+ o
1 h( b8 B- ?" n

$ r$ O' H% x3 j' h, w: B! J::WaitForMultipleObjects(handles.size(), &handles[0],TRUE, INFINITE);; L6 s: w( D! D0 P" A- T
这在与 C 库函数接口时尤其有用。5 t/ e2 J9 b8 h0 a- c2 [* d

# J0 L3 j( |/ g2 t+ p10. 赋值和交换7 q5 ~+ Z; ~8 T: H1 `% D. F  \
vector<> 是可以赋值的,这也是一般的“值”类型必须提供的操作:9 U' N( M: R9 d5 @
$ q, ~2 ^' x) S7 z! R
IntVector v( 100, 123 );
6 w$ @9 x7 o) P' r9 _" r: Y$ _1 FIntVector v1;
( }" h3 t" ^( B6 [* a7 uv1 = v;
/ D' a: S+ q6 ~9 ~9 n! l% yvector 另外还提供了: G5 o: }. {1 v- W2 o- `0 _) z5 Z
- g% s1 j, h4 w1 p+ g! P. J# q
template<typename Iter>
. F7 j; m7 j6 Y' [  r  J5 Zvoid assign( Iter first, Iter last );
9 u) u1 H# R8 x& X9 tvoid assign( size_t n, T const& t = T() );  ^  {$ k/ Z9 K5 z9 |3 [- K) _, E
用于赋值:
& {9 ?" P/ P" \" B% L; L! \0 h1 q: W3 O9 c; b* C, ]
int a[] = { 1, 3, 5, 7 };& O0 P) j7 f8 O9 G4 `; j& [% Z6 ?
v.assign( a, a + 4 ); // v 将包含 1, 3, 5, 7.
7 ?" p( k- H2 [6 w, I0 f+ F$ sv.assign( 100 ); // 100 个 0。4 ?9 E2 z8 r" i
还有一个很重要的操作:& n! J( G! G' Y9 {5 \) c5 A
4 s2 b) @2 y+ E! e
void swap( vector& v ) throw();
0 \3 O1 y* h( d! B8 i( [1 {5 n用于交换两个同类型的 vector 的值。它的特点是快速(只需要交换内部的三个指针),不产生异常。这在写一些保证异常安全的程序时非常有用。
- h' Q7 ?# l4 w+ L; c$ U0 t3 h6 ~; @" y6 v
事实上,swap() 基本上已经被当作类似于 operator=() 的一个“值”类型应该提供的基本操作,::std::swap() 也应该为用户定义的类型进行特例化,调用相应的类的成员 swap() 函数:' j) ]* Y& ?. e- y3 R, x

: U. U9 P8 \1 m# ?struct MyVal$ V  U9 z: A5 M( t9 p5 {
{
# I% W2 M5 D5 l: }; h* ]  // blah blah.; E0 X6 ?% U: U3 n. u' I8 k
  void swap( MyVal& ) throw();4 }4 v: `6 \0 a  ]& t
};
/ Y# |7 ?3 ]6 J7 p+ t$ Q8 b   - r0 B# a  A. x4 U1 f
namespace std {
& \3 H8 v" |2 m. P  template<>
. o) G( K: G1 S    void swap( MyVal& a, MyVal& b )
! ]# g# B7 J- v0 `    { a.swap( b ); }
* N) ]! C' L+ C9 G/ K% h/ S3 D}9 ^5 t, t) S6 T4 O% N0 g# u
关于 swap(),值得专文讨论。这里我们只指出,vector<T>::swap() 是快速的,不抛出异常的,很有价值。7 \" R3 m! K7 y6 K: w
3 U3 l' _1 G) b9 u- j& R
11. 使用 vector 时的存储管理策略3 T8 t. t1 |$ w& D  {5 _1 B5 v
从前面的介绍中可以看到,vector 的自动存储分配是指数式的增加存储空间,而且永不缩小已经分配的空间。这在大多数情况下是合适的。 如果应用程序事先知道要用到的元素个数,可以先调用 reserve() 来保留(分配)空间,这样可以避免以后增加元素时不必要的重新分配和元素拷贝:( X2 F+ P3 L6 D' a( K  p6 D) `

/ w5 ?8 t9 k6 _! W% e9 E7 ]IntVector v;
; D* n1 T9 i9 b! J: Qv.reserve( 100 );2 W  U7 W! g+ k, g( Y' T
for( int i = 0; i < 100; ++i )0 W- U7 `- h7 v) ^6 T/ P# ^$ F
    v.push_back( i );
7 w9 `) m$ v! g* v请注意,reserve() 和 resize() 是本质上完全不同的。reserve(n) 保留的是未使用而能够使用的原始空间,而 resize(n) 是真的创建了 n 个对象:
2 G3 l7 X/ S' W" U  ]3 L1 Y- P3 [9 @
IntVector v;
& N, \3 f* c6 k+ G  G( l% @9 Yv.resize( 100 ); // v 已经包含 100 个 0.
4 l( q4 g% @7 kfor( int i = 0; i < 100; ++i )
4 o4 ^( Y3 L/ K    v[i] = i; // 可以赋值' E' }; o. N# w5 V- B$ S
有时候,一个 vector 可能增长到较多个元素,然后又减少到较少的元素个数,这时,可能希望缩小 vector 分配的空间以节约内存。CArray<> 中提供了 FreeExtra(),但 vector<> 并没有提供相应的函数。这时必须进行复制:
) K- Z% j2 K" H; h4 d. ^( m
1 w' h; K  v. S' i: Z# x# gIntVector(v).swap( v );2 Z8 V" ?- J( y* E, m
有一种看法认为拷贝构造函数同时也复制了capacity(),而标准中并没有很明确地指出这一点,因此更安全的方法是* D/ Z/ j( x- T
- l% Z5 o" j- [0 p
IntVector(v.begin(),v.end()).swap(v);
; b' Q( ~; s1 y( Q9 {1 ^如果一个 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, 2026-8-14 02:05 , Processed in 0.024626 second(s), 15 queries .

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

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