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

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

[复制链接]
发表于 2008-2-26 13:29:18 | 显示全部楼层 |阅读模式
作者:wangtianxing
% h9 z' G! x1 i7 p* P* H
! l+ V! Q. d& C$ N! {原文出处:http://www.cpphelp.net/issue/vector.html- R) U, c# I2 t  B# b) w1 [9 f
6 y1 _0 S+ t$ I) [5 b! R

  V: A  Z, b( ~, Z
# P- U3 G5 J5 A* w6 _7 v; O摘要: 本文介绍了C++标准库中的容器类vector,分析了它的优点,并且建议在应用程序中使用它作为动态数组的优先选择,而不是MFC的CArray<>等其他类模板。最后介绍了vector的接口和使用时的注意事项。
; i, y7 {5 U* i& X1 |9 b1 _: C5 V7 \$ E) p( m
在一些使用 MFC 的程序中,经常看到许多程序使用 CArray<>,由于 CArray<>的设计问题,造成使用它的代码的复杂化,增加了维护难度。因此建议使用 ::std::vector<> 代替 CArray<>。+ m1 S$ h+ x# c+ _3 y" Q9 N! u9 V
: v* y# m6 g+ J# A9 `8 f
另外,也看到一些程序在用 malloc/realloc/free/new[]/delete[] 等手工管理内存。在应用程序中,手工管理内存是容易导致错误的,应该用 ::std::vector<> 之类的对象来管理动态数组。
- b: v( `  ~. e. V, C& L
( {" h8 q6 d0 g: n. b& C2 ?. h4 A4 E由于 MSDN 中关于 ::std::vector 的内容较少,我们在这里做一些介绍,供参考。% ^5 }* `4 o# ?% @" j2 T6 u* u5 N
$ {( G$ A  u$ ~+ w! P
不熟悉 CArray<>/WIN32 也没关系,这里提到它们的地方并不太多。
2 u( _+ o$ u) w! P" a$ g. W7 t, Q7 b. P. R) ?
1. CArray<> VS ::std::vector<> ?
; Z; ^) C4 x3 F- nCArray<> 和 ::std::vector<> 一样,都是模板类,用于管理任意类型的对象的动态数组。都在解构时释放所管理的动态内存。因此都可以用于代替手工动态数组管理。
/ G* y8 w% F- `9 l' J4 A
" r) I# a/ u4 F但是,CArray<> 是在 C++ 标准化之前很多年(VC++2.0时代)设计的,当时对 C++程序设计,面向对象程序设计,模板程序设计等技术认识严重不足,尤其是当时对面向对象技术的错误信仰与宣传,造成 CArray<> 的设计有重大错误。
5 D$ N! a) P* R7 I5 d+ N( X$ ^% W' W9 C. B8 W
在 C++ 语言标准化以后(1998),以及 VC++ 6.0 出世以后,提供了标准的::std::vector<> 模板,基本上在任何方面都要优于 CArray<>。Microsoft 由于要支持老的程序,因此一直保留了 CArray<>,但显然并没有打算按照新的思想去发展它(至少应该提供operator=(CArray const&)吧)。
' L$ v; V* C5 M+ W1 \, q
2 p( n5 E# R+ s# G概括起来,CArray<> 与 ::std::vector<> 有以下不同:
9 V! I" Y1 u8 Q; L! r; ^' |  t1 E* A& f
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<> 没有继承任何东西,只是实现了管理一个动态数组该做的事。
, c# v/ i" t: {9 }& I. Y
5 i; C: V4 N- M* w- c2) CArray<> 不是一个恰当的值类型,例如下列操作都是不合法的:% e+ U$ C. |6 e4 S1 m

/ a$ ^+ ?: }# p( rCArray<int,int> a;" U  D8 B. {3 U0 [. H- P
CArray<int,int> b(a);  // error, must use Copy().
2 R; p. X8 M2 h* l. pb = a;        // error, must use Copy().: @  S" {7 D4 B$ g
b == a;       // error, you must write your own.! H( p$ y  B, f3 B: q7 v8 `6 Q
b < a;        // error, you must write your own.3 O4 \" m& U) Y* O9 {9 u
与 CArray<> 相反,::std::vector<> 是一个认真设计的值类型,天生是可以拷贝构造和可赋值的。如果 T 是可比较的,那么 ::std::vector<T> 将自动地是可以比较的。
. g3 t) j  h0 u+ S/ E2 @3 q7 W, ]; H0 ~  s
此外,由于涉及到四个特殊成员函数;
  p; E" h8 l$ P7 {
6 V0 i+ [5 _5 q3 ^T(); // 缺省构造函数(default constructor). F5 X' L7 X$ l4 g& H
~T(); // 解构函数(destructor)) ^# H- ?) y5 e1 r5 k$ Z
T( T const& ); // 拷贝构造函数
, V/ i. o* s3 R1 _3 J+ b- BT& operator=( T const& ); // 拷贝赋值函数# P( _' n9 H* v3 g: c' B9 \% M. j- V& u
的自动生成,如果使用 CArray() 作为 T 的成员变量,那么上述的四个特殊函数中的后两个将无法自动生成,需要手工写:$ W' K+ e. A4 M3 e2 b$ w  Q

0 p5 |7 c1 ]. V$ s& J0 D$ w6 k# ? struct T
5 o- r: }6 n3 k: ^{
( B/ l# z: ?" R: z  Y. I: i& |6 G   T() {}
6 {; ?- {7 B' C* z   T( T const& t )
  q4 d0 O3 f5 a! ^0 c, c; s   {
/ T$ F& W- P9 M, B5 a4 h       a_.Copy( t.a_ );* z7 o; z% D+ d- p9 P
       i_ = t.i_;2 J9 R6 L. Z0 H4 O1 _% L
       d_ = t.d_;; G8 u( M$ p- @  [% i, e6 \9 e
       s_ = t.s_;" n2 R2 n' r  l! K% ]
   }
9 A- R* n3 g5 K   T& operator = ( T const& t ); n5 \9 A0 o+ D% }
   {6 k; b' W1 ?/ e2 ^  ~% o5 G
       if( this != &t )
1 z) @& V9 X/ T       {' U+ e4 w* N3 z0 A% \! _
           a_.Copy( t.a_ );
/ ~* m7 T# p2 t6 z           i_ = t.i_;6 g0 i! P" Q9 A3 i
           d_ = t.d_;# f6 F. n4 Y2 W
           s_ = t.s_;- c' m6 \, L' T% M% ?0 c6 a
       }& i9 |. ~8 T& t: `" d
       return *this;
9 ~- b+ N/ x* Z& ^   }
) z+ w: k" [  A# K+ W* nprivate:
0 p7 d% v; l7 i' S2 R8 g# ?8 V   CArray<int,int> a_;6 G; w! ^1 B# g; H/ s9 f
   int i_;' j3 s: X" R0 U
   double d_;1 M1 f3 C; W7 J
   ::std::string s_;
/ `# Y7 A9 E& Z. T  ]' B6 Q};( _6 a/ e' d8 j5 t" n7 i# ]7 h
如果使用 ::std::vector<>:
8 B( d/ w  p* H/ ~, N" G( g4 z& L, d1 O: B
struct T
! v4 a8 r) j: g0 H{) n- s( p* X$ k* S% F
private:
% T2 W% }/ g/ M* _: e0 n   ::std::vector<int> a_;! x8 \# s1 |6 @( T* e9 ?
   int i_;. e% G* k' e: X9 J" ~
   double d_;" _! H. G6 R5 _+ `3 D9 T
   ::std::string s_;$ g0 r, p; h2 w6 Z! U1 g# D+ t  J
};
+ M( H* [) Z& _: z5 s0 ~4 G- e上面列出的三个特殊成员函数都不需要写。好处是明显的:当你增减 T 的成员变量时,你不必到/ Y1 b# p- P9 O8 F& W3 N9 v' d
T(T const&) 和 operator=() 中去相应地增减。
$ }# Q( y0 a. G0 X3 y2 y, X% M, c# @% k' O- b* k# _( K. c( A9 h
3) 没有现成的算法可以对 CArray<> 进行操作,而标准 C++ 里的标准算法大多都可以直接在2 u- Y8 m$ F& M! X
::std::vector<> 上运行。例如:
9 y7 Z: i) h. o1 k7 v# F7 v
8 \# X4 g) R/ ^/ Gstatic int const init_vals[] = { 3, 1, 4, 1, 6, 9 };$ _  {, W1 n3 Q; X: Q7 ^. \' Z
vector<int> a( init_vals, init_vals + 6 );8 q/ i; E; h- H/ }; N
*find( a.begin(), a.end(), 6 ) = 5;    // 把6改成5
# q- w3 X4 \. y5 I& j. _sort( a.begin(), a.end() );    // 排序。
0 R; M; j) F$ c可以说,CArray<> 的主要设计错误是把一个本来应该是一个简单的“值”类型的东西设计成一个难用的“对象”类型了。所有的“值”的好特性都丧失了,但那些从CArray<>继承的派生类呢?/ n# u2 Z" l) p

* l3 H. c6 m- S0 KCByteArray等的问题与 CArray<> 的问题一样,甚至更多(例如,CPtrArray,永远不要用)。
$ d4 K2 s: {9 A+ r
4 h( ?4 t/ d1 T7 R- Q7 b同样,其他的 MFC container 模板,象 CMap<>, CList<> 等,都有类似问题,都应该用
' U% N; n. T' ~, W+ q- }::std::map<>,::std::list<> 等设计更好的东西代替。
0 R, w' g- e- P' |. G% @
9 A$ d1 E# _! d& h- a* Z2. ::std::vector<> 在哪里?% P+ g  P! i+ k
::std::vector<> 在头文件 <vector> 中定义:
5 s( j( q/ o# e! P( `( z" r: p, p1 q+ W, j& E
(注意,标准的 C++ 头文件都没有 .h 后缀,有 .h 的文件是与 C 兼容的,或支持老的不标准的东西,象 <iostream.h>。)
5 R; ]0 M, C7 _) d! U
& q* n2 i9 A2 c2 B) u* P# Jnamespace std
9 |$ B& y3 {. s1 D3 ]) k4 V{/ B7 x8 j, _* R: Q6 K# T: W
    template<typename T, typename A = allocator<T> >8 O4 ]/ k! w$ j' Q  b) p$ U
    struct vector
. h8 z1 ~% P1 S8 ]    {/ N( z' E. S( m) R& K" Y
        // 具体内容稍后讨论
: W. h% N1 C* D+ X( g    };( ^% d7 |0 W9 i  d  C5 v+ r3 v  O% d3 \
* x& z5 z/ X( s0 `

/ B" a9 D; D2 a3 Z/ w$ h    template<typename T, typename A>
  H% `; Y0 j5 f' C) `. \: G( B        bool operator == ( vector<T,A> const& a, vector<T,A> const&    b );
6 p9 h% A  w/ h& b  l    template<typename T, typename A>
! J  ~, A! B, W3 p        bool operator != ( vector<T,A> const& a, vector<T,A> const&    b );
4 O; G$ g0 e: `+ n    template<typename T, typename A>5 e  T* a# R0 D# Q$ q1 w5 l; p
        bool operator < ( vector<T,A> const& a, vector<T,A> const&    b );7 t1 I: a9 y& L9 k0 m
    template<typename T, typename A>
. R& i3 K7 t7 x4 c        bool operator >= ( vector<T,A> const& a, vector<T,A> const&    b );& {2 _- C6 T! c% U
    template<typename T, typename A>
9 J+ X. i% }, A5 j( B& Y% h: w0 ^) c        bool operator > ( vector<T,A> const& a, vector<T,A> const&    b );
" I! q5 N' S$ |" ]( R4 b6 n3 z1 h    template<typename T, typename A>2 E3 ^% Y2 s1 s( H' q( ?
        bool operator >= ( vector<T,A> const& a, vector<T,A> const&    b );: A# K! I3 y  ]' _
}" a* o/ [2 ?/ l. Y) ?2 R0 y" n0 J
vector<> 定义在 namespace std 中,使用时为了减少击键次数,通常使用一个类型定义缩短类型名称:  I) w' |* }7 f; d. l
7 s" l0 }7 v2 ]5 W; S. c/ F
#include <vector>+ q  t( N* ]6 {/ f! h* `
typedef ::std::vector<int> IntVector;/ `; T! T' i1 N  z0 ]8 L8 S& h
IntVector a;" _  ^4 L( j7 J
IntVector b( a );. R2 v) G! J- B- ]- u
IntVector c;
5 ?. S, i/ Z$ i% Gc = b;
4 `* ?. @) [( a$ O" B/ o1 @assert( a == c );$ R5 C) `+ o8 ^) ]3 }8 x
请注意 <vector> 中定义了六个 vector<T,A> 的比较函数。这些函数只在真的用到时才会被实例化,才会要求 T 也提供 operator==() 和 operator<()。1 i" L8 U8 `- V3 S- m# z
+ y9 s  V9 @0 v; q2 V
另外,A = alloctor<T>:用于提供一个用户定义的存储管理类。由于这个参数很少用到,而且在 VC++6 的实现中有问题,不能用,因此以下的讨论忽略这一部分的内容。; O" n5 q6 |8 p8 K1 Y" p1 e  c. _

% e( Q5 d% ?) L- e: M3. ::std::vector<> 中的类型定义
# H& |$ z0 c6 Y: N( [vector<> 中定义了一些类型,下面只列出常用的:
; p1 A* G+ Z( R% j# @5 }/ ]
% A& d* F% D- e/ ~3 |1 H! Y9 vtypedef T value_type;+ t. d, f. ]% `8 U0 T8 m' Z
typedef T0 iterator;7 C! j/ n  a0 a1 P/ G* e
typedef T1 const_iterator;
' W+ ?! [7 Q& Z, n- A) f% t4 Jtypedef T2 reverse_iterator;
6 p3 {$ w5 M6 f0 l% z3 l$ E6 e1 Ytypedef T3 const_reverse_iterator;1 [9 I9 U" w$ U5 `# G
  E9 P/ K6 h0 O6 }- P' u3 o) q
value_type 就是 vector<T> 的元素类型,也就是 T。当写通用的算法处理任意类型的 vector<> 或其他容器类型时是很有用的。
. E3 l- o) B6 j! v7 m0 p
0 U  A" `( P: h( G* B5 r; X9 Piterator/const_iterator 是两个 vector<> 的实现定义的未知类型,用于访问vector<> 中的元素,类似于 T*/T const* 指针,他们的区别是一个指向的元素可被修改,另一个只可以读:! b! H# `# H% |$ M& z8 n* }( [- G
5 O5 L5 r; O1 Q
typedef ::std::vector<int> IntVector;
8 F, n+ T6 [* \! JIntVector::iterator iter;
9 ]0 V/ D$ ^3 l! v* t. f6 G9 z- V! X" RIntVector::const_iterator c_iter;& S) x& k) B% U4 ?
// ...8 ^8 }$ }3 E( B2 ]3 K7 l
++iter; iter++; // ok: increment, post-increment.
! g9 v. x# K1 Q( B" {--iter; iter--; // ok: decrement, post-decrement.
# R5 z* t4 y# b. a& O% J* Z++c_iter; c_iter++; // ok: increment, post-increment.
0 L- ]/ I4 P/ ]4 o7 Y" ^$ }--c_iter; c_iter--; // ok: decrement, post-decrement.
, K4 O, m" d. k  o5 l0 d: l*iter = 123; // ok.
5 `* I- x4 l9 Pint k = *iter; // ok.: H9 B1 ~& z- L( A6 E3 P
k = *--c_iter; // ok.
' p, p0 A% {% y, t& X*c_iter = k; // error.. s; \8 ^' f7 f6 c$ }& b: h
c_iter = iter; // ok: iterator is convertible to const_iterator.
9 J" B9 E% r1 B; p/ _: hiter = c_iter; // error: can't convert const_iterator to iterator.9 D, k" [1 |- D! Q
在使用上 iterator/const_iterator 和 T*/T const* 基本相同,事实上有些vector<> 的实现里就是用 T*/T const* 实现 iterator/const_iterator 的,但又不可以把 iterator/const_iterator 当作真正的 T*/T const*:& {( d, n; V9 i% X9 V- z2 M- ?
9 D+ {1 _$ t6 ~7 B7 F
T* p = iter; // may fail to compile.- T8 C) b" T; h, G
T const* q = c_iter; // may fail to compile.) L: k1 N/ x: r( s4 j
reverse_iterator/const_reverse_iterator 与 iterator/const_iterator 类似,但以相反的次序(从尾至头)访问 vector 中的元素。. N7 ?% x, y# R# @- T
8 r4 [5 c; ?& G, C2 C! L
各种各样的 iterator 在 STL 中有特别重要的意义,但这里我们不做具体介绍。只要理解通过 iterator 可以访问 vector 中的元素,大概相当于一个指示位置的指针就行了。
6 c1 a" n( i) |, i0 ^
$ G, b' }7 o  d" X0 V+ v; R4. ::std::vector<> 的构造
, |) u* U0 O+ h' |9 h: W) F! [vector<> 提供了以下构造函数:(忽略 allocator 参数)' T& G* N" _3 H

& q  p9 d! A2 j, Z" V) X. Kvector();
9 v1 u3 C+ a5 w) i# }/ Lvector( size_t n, T const t=T() );4 ~# u( |5 J" O1 T( Z% O
vector( vector const & );
5 N$ o# s8 [+ `/ J3 d/ x, Yvector( const_iterator first, const_iterator last );+ @! ?9 Y4 G# T5 ^! e; P
1) vector();
9 G1 H7 H1 M8 {# D! l0 l
$ \1 ~' R7 Q7 e0 i构造一个空的 vector,不包含任何元素。! y7 F8 E6 o' s
% n2 [9 H: y& H9 A
IntVector v1; // 空的整数向量。- m- K; D% L: ~/ m
2) vector( size_t n, T const t=T() );
: g& q: y% _% x, k
+ n- H0 s1 _$ |' y/ W) }1 a; ^构造一个 n 个相同元素 t 组成的 vector。如果不给出 t,那么将用 T() 做缺省值:
8 B8 Y( Z) r, E- M  V  H/ t$ O/ r
' t5 j% I  j, k8 L4 b% X" X) EIntVector v2( 100, 1234 ); // 100 个 1234.
0 T) L% g% d3 \& L$ o9 n# iIntVector v3( 100 ); // 100 个 0。
/ |/ h. x9 o5 r, X3 K- H3) vector( vector const& other );
' m% E8 h3 ~) o' l1 T5 e' C" }( B! W/ S7 S& ?5 Z
复制构造函数,复制 other 中的内容:! }3 S7 w2 y$ k- i4 ?! Y; L' d
' a5 A2 ^$ [8 @6 s( J
IntVector v4( v2 ); // 100 个 1234。
9 J' x2 z# h- P4) vector( const_iterator first, const_iterator last );
& ~' _  D$ H. m/ m
, r3 T8 e( t/ J( u; I事实上,这个构造函数应该为
1 E0 e  l: f8 b* o
) _% p% m1 F# E: p5 S, \# wtemplate<typename Iter>: M" T8 x: d; L2 B# J
    vector( Iter first, Iter last );) G# h- W: X4 T! z# a
即拷贝任意的序列 [first,last) 到 vector 中。由于 VC++6sp0 编译程序的限制, Iter 被换为 const_iterator 了。不过,碰巧 const_iterator就是 T const*,所以可以如下使用:) u) M) K$ L' ?0 A/ j' B0 y5 s/ ?

7 K) G( Q/ t7 \# A+ lint a[] = { 1, 2, 3, 4, 5 };* F8 X' k( H% Q2 |
IntVector v5( a, a + 5 ); // {1,2,3,4,5}
5 f* y3 ?/ f/ `) d) zIntVector v6( v5.begin() + 2, v5.end() ); // {3,4,5}
: ?7 p) P2 {1 T5. 访问 vector<> 中的元素, _' _5 p4 j3 E2 o3 d5 ?- @) Z2 T
以下成员函数/运算符用于访问 vector 中的一个元素:9 S$ y; ^8 r) m& t* j9 j) B% @9 L) ~9 D
. F7 G6 y4 G2 i# R' |
T& at( size_t n );) K4 h: d! t5 o4 Y1 G0 k
T const& at( size_t n ) const;
) I" T) Z/ N; W9 T* yT& operator [] ( size_t n );
1 p- w& b# D+ J- cT const& operator [] ( size_t n ) const;
$ H* d* o2 X) R4 J( u0 KT& front();- B/ F/ ^8 E/ P# w' [2 r2 Z/ C
T const& front() const;$ l3 ^& J" `1 r4 o( C9 V
T& back();2 t, o( f: Y  l4 f& x
T const& back() const;/ f: h  w0 F; R! g" N" C
请注意,由于 vector 是一个“值”语义的对象,所有的操作函数都必须严格保证 const 的正确性。所以,所有的元素访问方法都有 const 和非 const两个版本。9 e, l' [2 K$ p
# }+ \1 @/ H& @
at(n) 和 operator [] (n) 都返回下标为 n 的元素的引用,他们的区别是,at() 进行下标越界检查,若发现越界,抛出 range_error 异常,operator[]不进行下标检查。
6 ]3 \5 u, B( Q$ Q+ n5 Z! I0 \, W/ {$ ^
front() 返回下标为 0 的元素的引用,back() 返回最后一个元素的引用。
2 ?$ G& a' y/ m+ }2 k+ j; p# i  P% |; ?7 l; ^( A. d. n
int a[] = { 4, 1, 4, 1, 5, 8 };+ h% p* c7 [5 m+ f
IntVector v( a, a + 6 );
- |4 ]# f4 Z& K$ C/ |// 使用 front(), back():& O$ Z, M, v0 H8 k8 E* e
v.front() = 3;) g6 b6 \# O/ m8 D  I9 b' K
v.back() = 9;8 W" v$ F3 T7 V+ V' _+ h# q
// 使用 operator [] ():
1 M2 L6 F8 g  h6 n4 [- Cfor( size_t i = 0; i < v.size(); ++i )
0 u/ S! {) P3 c::std::cout << v[i] << '\n';
5 e2 \% e5 P! ^* M& }. E+ u6. ::std::vector<> 的存储管理1 x1 N' W# X7 q% L) s
以下成员函数用于存储管理:
& [8 d  x8 N. k$ m- T, s. O! e
. [. M# c# V6 Jvoid reserve( size_t n );% Z7 [& }8 x& P/ @' x, {
size_t capacity() const;! }% b* ^/ |1 r/ S
void resize( size_t n, T t=T() );! P# t  }/ h7 z* U
void clear();$ V$ t2 _; |+ X4 e! D- L
size_t size() const;
. c0 m' u' q3 S- H" hbool empty() const { return size() == 0; }4 H" s  ]: Q& u- W  m
size_t max_size() const;
* M0 ^8 I  D% F- E' y; r5 ]9 \3 V' f2 G4 `, d; w% Q3 s
另外,push_back(), insert() 等也涉及到存储管理,后面另行介绍。
, i0 a5 M, t' i4 ^, {
4 y( h( }( ?" g+ s5 c; d* S/ @1) max_size()3 Q$ P5 Z8 z* \2 g+ q6 I

3 C) ^" a& q( d( L! N3 U/ Q返回 vector<T> 理论上可以装的最多 T 的个数。这只是一个理论上的数字, 大概是 4GB/sizeof(T),没有多大实用价值。在程序中不要用。
% e4 K7 ?, [. o- D
4 o% r. a' o* N* M" b+ t  d7 m( L2) size()
% G2 F- U0 L9 E7 Z( U2 X
) I2 G4 T' X' `2 I* C返回 vector<T> 中实际装的 T 的个数。相当于 CArray<>::GetSize()。# Z- d, ^# P3 h/ @

) b. k6 ?. f8 M0 |7 V1 p3) empty()5 _% I; e% C4 e8 r* P

9 e9 w. ~& T7 E9 e- G如果 vector<T> 中没有任何 T 对象,返回 true。也就是返回 size() == 0。  h, }0 \, h1 I

6 a0 s, M, B! m. m4) clear();8 C9 J* K2 v% g6 r. {

! U$ P% S& v4 T( ?清除 vector<T> 中的所有 T 对象。执行后 empty() 返回 true。大致相当于 resize(0),但不要求 T 可被缺省构造。相当于 CArray<>::RemoveAll()。6 S6 y2 h4 ~( Q9 }

6 A4 }7 X1 G4 V, K5) resize( size_t n, T t = T() );  a2 V  J5 W- Q, z

6 b* N% g% i7 Q; k! T将 vector 中的元素个数设置为 n,n 可以大于 size() 也可以小于 size。如果 n 小于 size(),那么 vector 中下标为 n..size()-1 的元素都将被解构。如果 n > size(),那么将在 vector 的后面新增加
' w$ h0 O* u' J: m0 c7 c" Bn - size() 个相同的元素 t。在增大 vector 时,可能发生存储再次分配。总之,调用resize( n, t ) 后,(size() == n) 成立。
+ F5 y3 H8 K; S' n: j3 U
1 ~: G0 i: c1 r请注意,如果调用 resize( n ) 不带参数 t ,那么 T 必须可以缺省构造。
/ L7 f' t: C1 u6 L# X# G$ _
* p: G7 T* F: m  M9 q6) reserve( size_t n );
8 q: e% v! h- A2 k  w- s* t/ G0 p2 L8 n3 @! p5 d6 d4 U
事先分配至少可以保存 n 个 T 对象的空间。调用后 (capacity() >= n)成立。
3 U8 q* g5 Q5 ]+ Q" G! D
5 C; X1 X% [. ^  `2 D* ?6 k( l7) capacity();( D& p9 e( t) F+ C9 d

# T# ^3 g. ?, c6 P- {返回已经分配的存储空间够容纳的 T 类型对象的个数。后续的增加元素操作(如 push_back(), insert())如果增加元素后 vector 中的总元素个数不超过 capacity(),那么 vector 的实现保证不重新分配存储空间。
! B8 e3 ?  r  n$ {( T
3 ~5 r% c' @7 R0 Wvector 管理的动态存储空间是连续的。执行操作& X4 P. c+ ]+ p/ t% d
' v6 y' e8 k% ~: Z$ ]/ _
IntVector v(7, 1); // seven ones.
, u6 E' y! `8 \, [, d0 K4 M! P% {v.reserve( 12 );+ V- K; V" E- F! u' Z7 [
后,v 的状态可以用下图表示:
% e- j4 r0 X6 `/ z$ Q7 b6 p! H1 z3 ]2 X+ }. `- S& `
/--size()---\, y+ M- m+ U: G' {6 s) Z
|1|1|1|1|1|1|1|-|-|-|-|-|
) j; R' x5 E* y8 I \--capacity()---------/
8 B& H" u* r6 f其中,1 是已经构造的 int 类型的对象,- 是可以构造一个 int 类型的对象,但还没有构造的原始空间。再执行( ?/ W7 B+ ?/ Z2 S# o( T; g
* j: h! a1 B; \7 r
v.push_back( 2 );
: p' D+ {. c0 P* E6 l3 n9 Y: Uv.push_back( 3 );9 }% p# n* U6 Z
后,v 的状态可用下图表示:3 K- u# K$ Y- I& }2 T" K2 @
9 `! r/ O5 z( W& |
/----size()-----\7 `% {0 {! ?. M' P8 q
|1|1|1|1|1|1|1|2|3|-|-|-|
/ V& M/ D' ]7 M' w \----capacity()-------/8 \, }. v  k  `4 E& A1 X& A) q: k
执行 resize( 11, 4 ); 后:2 ]) d2 T$ }# J2 q

) @2 a& T5 k& V8 D! } /----size()---------\
6 k$ N1 ~. ]* e- E  ]% W+ B1 C|1|1|1|1|1|1|1|2|3|4|4|-|; Y- j' t. j  [0 O, N2 Q6 w
\----capacity()-------/
( c; l1 {2 a5 \8 [/ s4 O" {3 [capacity() >= size() 总是成立的。对于下标为 [size()..capacity()-1]的未构造对象的存储空间,是不可以访问的:2 |9 D0 v% }3 J  U$ F& t# f
  m8 V$ r+ z# h7 Z
v[11] = 5; // undefined behavior - anything can happen.# H( a, W7 U2 {6 ]7 c
7. 添加元素到 vector 中
6 y  d5 A2 H& S下列操作添加元素到 vector 中,并可能引起存储分配:
4 j- M" A. d# F* O, t9 u; q! w/ m
void push_back( T const& t );" o' A8 N3 `, |% U5 |
void insert( iterator pos, T const& t=T() );5 I2 q+ O' @; z4 B* ^% Z
void insert( iterator pos, size_t n, T const& t );+ q3 R1 B* E+ p  Z1 c
template<typename Iter>1 o% ~/ {5 {; y$ V2 e+ ?
    void insert( iterator pos, Iter first, Iter last );/ N- Y( i, U# M
push_back() 是把一个元素添加到 vector 的末尾。insert() 是把一个 t,或 n 个 t,或从 first 开始到 last 结束的一个序列插入到 pos 指示的位置之前。
, @9 B* r- ]* K% I8 j1 x6 p4 t1 }# d3 @
当插入元素后 size() 将会大于 capacity() 时,将引起自动存储分配。vector 将会分配一个比需要的存储区大若干倍(通常是1.5到2)的新的存储区,把老的元素拷贝过去,同时完成添加或插入,然后释放老的存储区。
2 D9 u6 K7 {% B( c  p$ r) Q( V
/ L! A; M' C/ M6 R: t这就是说,vector 自动存储分配的空间大小是指数式增长的,这可以保证多次添加元素到 vector 中时,平均用时是接近于常数的。
4 |5 x$ ?3 K9 D  w
2 c, L/ H$ R) VIntVector v;5 m4 P; Z. R) U6 M5 d& |0 c3 w( V9 b
     I# g5 L; j/ n. J. G
// add 0, 1, ..., 99 to v:
) l/ l' D: }/ E; }0 W$ D' @for( int i = 0; i < 100; ++i )
0 K7 R# h& ^. E& Av.push_back( i );7 W" W9 [  y/ u. @
   8 M, W% D1 \- U% v; m6 @
// append 9, 8, 7,..., 0 to the end:2 B$ R8 D2 y9 k3 c2 b
int a[] = { 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 };  Y* g5 A: O3 B7 E3 d: \) f* W( g
v.insert( v.end(), a, a + 10 );
) u; e7 l4 @+ B  O% n6 m$ h8. 删除元素
+ Q6 ~5 w3 \) B2 L3 z* A下列成员函数完成元素删除:
- R+ s1 m4 |' k* y; H
- A9 e) @& C) c# C4 Xvoid erase( iterator );# Y; C. z$ M+ ^1 Z+ B6 Z
void erase( iterator first, iterator last );" x* T# u( Y( C6 Q  \, M
void pop_back();4 ^6 ~) r8 {2 ^' Z  P$ ?" n
void clear();
7 G; W6 l( o$ M3 D: S这些函数分别删除一个,一串,最后一个,或全部元素。
; a' @+ Z* S( G( p) H7 I! S+ l+ X3 R% `/ V& p6 N1 Z
IntVector v;$ ?: j& o: o  p  o# @& Q
for( int i = 0; i < 100; ++i )
+ R9 w1 {5 b& O+ I    v.push_back( i );- X% m" [1 A$ c" X
   % g+ }. [3 h+ V2 X
// 删除 50, 51, ..., 89:1 J4 u4 U- r- E
v.erase( v.begin() + 50, v.end() - 10 );
, Z* ~5 E7 I8 }; ~! g   
( z4 q: i4 j6 G// 删除 49, 48:' C$ V2 C4 C) Z3 c+ `5 v! k$ \
v.pop_back();9 t- Y% d1 Z+ h
v.pop_back();1 j  p+ w6 f$ I( J* b: Z
   / |0 E1 T% f% D. f1 P
// 全部删除:! w9 h) g9 ^: a; P: H5 s
v.clear();* P- a+ X' W1 S- @* ]
注意,删除操作不会引起存储分配,因此 capacity() 不变。
1 S+ _) n' H* Y8 R" C& X; y* l! v- v3 ?$ B: |
9. 作为序列访问 vector 中的元素
$ p2 F4 l- A) G$ L1 c. w. ~序列(sequence)在 STL 中是一个非常重要的概念,所有的容器类型和算法都涉及到,而且所有的算法都是建立在“序列”这个概念之上的。
3 s! @- T) G+ S, e& t2 D8 w0 t% f1 Y  }) l2 a2 B4 f" a
“序列”是一个线性结构,由一个指示其起始和一个指示结束的叠代子(iterator)来决定。如果 first 和 last 是某种类型的叠代子,那么经常用[first, last) 来表示一个序列。注意,first 指向的元素是这个序列的一个元素,而 last 指示的是这个序列最后一个元素之后的位置,可能根本没有元素可以访问。这种半闭半开的区间表示是整个 C++ 标准中的约定,而且确实可以简化程序。
- n( O7 O9 G  [% X
" ]# S% G' _9 W  N叠代子是传统的 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 的要求。7 p% Z7 j* |+ r9 m- b$ k/ L" c
% q( [3 [; ~7 b1 q* {: ^( X9 _5 A
vector<> 中定义了以下函数用于获取被控制(管理的)序列(动态数组)的各种叠代子:
, z; I- d" m, d# K9 _  s7 Y- S0 x$ l7 ~$ w" N8 y; O& _! F$ b0 S
iterator begin();
% T$ V8 }* @+ M4 Qiterator end();+ \" k/ X/ b- T
const_iterator begin() const;0 D9 q) x5 G5 }3 n
const_iterator end() const;3 L1 l/ \1 R5 d+ N% I! T
reverse_iterator rbegin();
0 t4 O1 z# r  K+ v3 D5 `7 ereverse_iterator rend();
' i9 |+ O( H# Z% pconst_reverse_iterator rbegin() const;* P  ~  s* n  Y
const_reverse_iterator rend() const;
& c! g0 Z- t% |这里我们不讨论叠代子的一般概念,只举几个 random access iterator 的例子:
3 G+ F5 ~/ a. U9 O% [) P# l8 n$ F0 Z1 a3 _4 {- Q) i' e
int a[] = { 1, 2, 3, 4, 5, 6 };
' ^$ o# |6 c2 d9 T! P& S( n$ H1 u[a, a + 6) 是一个随机访问序列,指示了 a[] 中的所有元素。这里叠代子的类型为 int*。
/ p+ R0 ~- l1 d* ?) s  |* E9 u+ f9 t5 q/ |7 p( f
[a + 2, a + 4) 也是一个序列,指示了 a[] 中的 3, 4 两个元素。叠代子的类型仍然是 int*。: r7 P' D) ~3 |. x. y
2 [1 y' W9 R2 K- A7 ?
IntVector v( 100, 1 ); // 100 个 1。
) c& Z/ K! Z' F/ x/ V[v.begin(), v.end()) 是一个随机访问序列,指示了 v 中的所有元素,叠代子的类型是 IntVector::iterator。
, r* V# h2 ^  `' \
% {3 j/ M* @0 Y$ T. O' ^. O[v.begin() + 10, v.end() - 20 ) 也是一个随机访问序列,指的是 v 中除了头 10 个和尾 20 个元素外的其它元素。
( L) T$ s9 c4 d& b5 K
' @( o" G9 E; F& Y[v.rbegin(), v.rend() ) 是一个随机访问序列,指的是 v 中的所有元素,但与 [v.begin(), v.end() ) 不同,这个序列是从尾到头遍历所有元素。
; Z5 l/ J% g% J" J! R9 A4 `0 p5 ?# S& x
[v.rbegin() + 20, v.rend() - 10) 与 [v.begin() + 10, v.end() - 20 )指示的元素相同,但遍历顺序相反。# M( e$ s0 A; Q

, e# t7 M" L2 k& i% O/ x' W下图是有十个元素的 vector 的 begin()/end()/rbegin()/end() 的示意:7 c; h. M4 a) c+ Y

, S1 @2 J% A/ |begin() ----------> end()
# o2 j- ?0 J7 d" L: n  |                   |
5 G, R! O9 s4 I: [4 C, Y, H& }  v                   v/ `( F% Z) x' G( j5 |( @9 x
|0|1|2|3|4|5|6|7|8|9|
3 l6 S' ~+ l1 H^                   ^! @" a& z" `, w2 Z% T% |" [/ z* C2 y
|                   |
: L: d" J- ?* ~1 N+ }- [8 ^rend() <---------- rbegin()
3 i' @  V/ \" N9 o6 N" Z   
& }$ r3 c* `+ H4 ~' Q9 x" S: tIntVector v;
: b+ Z: K, w" g8 E4 y& {for( int i = 0; i < 10; ++i ); s% L& n& c3 G& r2 p' r& z9 B( s  q
v.push_back( i );  p$ L  D8 T9 J7 C3 Y0 e2 X! j
   6 ?/ G. _0 F( S# x6 F7 n" `
// print 0, 1, 2, ..., 9:
3 {  F4 e% a" p0 Y1 I7 mfor( IntVector::iterator i = v.begin(); i != v.end(); ++i )( K: I' z6 y+ g/ F
::std::cout << *i << '\n';7 F0 n$ l) }4 C7 N* S  P. Y7 c" F
   
+ I) @/ R  _; A* p' Y2 P( f// print 9, 8, ..., 0:
) ]) Q! P, Q' W4 U+ s9 Qfor( IntVector::reverse_iterator i = v.rbegin(); i != v.rend(); ++i )8 N; {2 O" A+ o2 a; J: C8 S
::std::cout << *i << '\n';
" X3 O' `8 K8 C  u" u除了使用 begin()/end()/rbegin()/rend() 来遍历 vector 中的元素外,由于 vector 管理的空间是连续的,因此可以直接取地址进行处理:+ o' t0 {! y, J8 h, m
7 f" d  \4 }" ~0 f9 j
::std::vector<HANDLE> handles;
  W3 Q7 a$ `' Dhandles.push_back( handle1 );+ w: b4 x: v% M* \0 f* W
handles.push_back( handle2 );& T( [; ^- {; C
3 d9 f4 [9 Q0 h7 m8 a
9 S; D; |# ]/ o. e
::WaitForMultipleObjects(handles.size(), &handles[0],TRUE, INFINITE);2 D7 q' o2 c8 `; L
这在与 C 库函数接口时尤其有用。5 v) V# K# g0 g; w8 f" R

5 w$ I. |0 R! i10. 赋值和交换; p, r# B. C; `( b9 {
vector<> 是可以赋值的,这也是一般的“值”类型必须提供的操作:
- ~% U$ O7 t) k8 N4 p* a# z
' ~3 P0 u! j7 u- o! \' `IntVector v( 100, 123 );
( K5 z$ N. Y$ }! O! J( j/ ZIntVector v1;
, x2 L. D( x' M# tv1 = v;
& X% V! C1 T- c! o' ~' O+ avector 另外还提供了
* p1 b+ P( b% I  s( X7 }: j! s  \; E# |/ U. k& h# G
template<typename Iter>& m7 r% W6 ~8 t: {; ]: G
void assign( Iter first, Iter last );
4 Z0 d: |/ \) w4 F8 fvoid assign( size_t n, T const& t = T() );
+ |; i; n6 f( T! m$ l# }用于赋值:8 T: G4 U9 V( y3 e
0 ]; Z  A! o7 X
int a[] = { 1, 3, 5, 7 };
, o) ]9 m2 d! Q3 V( `$ ~' Fv.assign( a, a + 4 ); // v 将包含 1, 3, 5, 7.
- G; p* v! t+ Fv.assign( 100 ); // 100 个 0。
: c  p0 ~) w2 p- D7 D  S还有一个很重要的操作:8 L. @3 M. V( {0 M1 j9 R* e) l" l

' U1 a3 @' t5 W+ H) jvoid swap( vector& v ) throw();7 }  U. s4 d" ]
用于交换两个同类型的 vector 的值。它的特点是快速(只需要交换内部的三个指针),不产生异常。这在写一些保证异常安全的程序时非常有用。
* A. O: h% x- e2 g5 x% A" X6 K' `" ~5 z
事实上,swap() 基本上已经被当作类似于 operator=() 的一个“值”类型应该提供的基本操作,::std::swap() 也应该为用户定义的类型进行特例化,调用相应的类的成员 swap() 函数:: q6 o# s4 s6 O' j

) b; S6 V; f, P& k$ ^- q+ W# A/ ]2 A" Lstruct MyVal
" R2 K- Q5 R8 e' ]4 p( ]{/ q4 s8 H3 O7 q5 I
  // blah blah.
, N# y$ _; |* M; C9 C; c/ u0 _  void swap( MyVal& ) throw();- e4 T* n6 M9 i" U
};2 k7 K) k/ S1 b
   6 y8 P9 {7 B+ j9 x: V
namespace std {% Y# J" F& q; F6 f( \" F
  template<>* ^' k; Y! X  F( k- E
    void swap( MyVal& a, MyVal& b ), G1 V; ^2 u: W( Z
    { a.swap( b ); }+ Q( Q0 ?( V, ^7 C. [" P
}! r; ~3 e1 e, @2 y
关于 swap(),值得专文讨论。这里我们只指出,vector<T>::swap() 是快速的,不抛出异常的,很有价值。
; j; g( W- l; P* B; E# F& B4 h& o" {: z8 A
11. 使用 vector 时的存储管理策略
. X( r& H: b0 @( R; ^+ `从前面的介绍中可以看到,vector 的自动存储分配是指数式的增加存储空间,而且永不缩小已经分配的空间。这在大多数情况下是合适的。 如果应用程序事先知道要用到的元素个数,可以先调用 reserve() 来保留(分配)空间,这样可以避免以后增加元素时不必要的重新分配和元素拷贝:
! F9 P* r5 M% l9 {
9 U0 P* \$ R7 O7 I+ mIntVector v;* s( G2 I9 S/ p+ B! n( A" N" m/ ~
v.reserve( 100 );
% c! n; I& ^# Q; @& x; ufor( int i = 0; i < 100; ++i )5 C) ?) V: a( K: ?6 [/ a
    v.push_back( i );3 A) L/ a$ ~6 C1 x& r% d
请注意,reserve() 和 resize() 是本质上完全不同的。reserve(n) 保留的是未使用而能够使用的原始空间,而 resize(n) 是真的创建了 n 个对象:
3 V  F7 l& N; `* [6 i: ~
( d# v4 `3 K2 o$ u. ]) ?IntVector v;/ D1 b; ]" [$ e7 b, A! q7 ~! V
v.resize( 100 ); // v 已经包含 100 个 0.0 v5 X- @& A' I9 i- Y6 T1 p
for( int i = 0; i < 100; ++i )
- S1 O, L" s# B% r4 C; @9 W    v[i] = i; // 可以赋值/ C' `0 N! R7 S/ A8 T
有时候,一个 vector 可能增长到较多个元素,然后又减少到较少的元素个数,这时,可能希望缩小 vector 分配的空间以节约内存。CArray<> 中提供了 FreeExtra(),但 vector<> 并没有提供相应的函数。这时必须进行复制:1 v9 b6 d5 c8 C7 p( o
2 O: X3 C6 o! r
IntVector(v).swap( v );
; l& H6 e6 r5 z+ I有一种看法认为拷贝构造函数同时也复制了capacity(),而标准中并没有很明确地指出这一点,因此更安全的方法是; W, z! I, L/ i; ]' }* m

" L' X) u# a7 J, G& T+ H( O' |! gIntVector(v.begin(),v.end()).swap(v);
5 n& r7 \5 v. j) P" 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-6-19 15:38 , Processed in 0.038932 second(s), 15 queries .

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

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