找回密码
 注册
查看: 6080|回复: 0

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

[复制链接]
发表于 2008-2-26 13:29:18 | 显示全部楼层 |阅读模式
作者:wangtianxing# F2 N* W8 M0 N* N" V
9 D3 c! `$ T3 O! |) H  G/ F2 E
原文出处:http://www.cpphelp.net/issue/vector.html
& N% n3 o( s& H! O% v5 u
# V- T8 F# x+ J8 ^& z2 \) @' I' G& S
3 Z$ a6 H8 B* [0 d  v: _4 B. p: k
摘要: 本文介绍了C++标准库中的容器类vector,分析了它的优点,并且建议在应用程序中使用它作为动态数组的优先选择,而不是MFC的CArray<>等其他类模板。最后介绍了vector的接口和使用时的注意事项。
. [3 q3 `' L* `  O. X9 i5 _8 N4 w) m. D6 P4 L- n3 ?
在一些使用 MFC 的程序中,经常看到许多程序使用 CArray<>,由于 CArray<>的设计问题,造成使用它的代码的复杂化,增加了维护难度。因此建议使用 ::std::vector<> 代替 CArray<>。
+ y) R7 B7 ?9 l* s8 V8 d- d/ L3 |
+ |  W% `6 H- W& v8 m另外,也看到一些程序在用 malloc/realloc/free/new[]/delete[] 等手工管理内存。在应用程序中,手工管理内存是容易导致错误的,应该用 ::std::vector<> 之类的对象来管理动态数组。
2 }, X* r9 H( n; m- m1 X( v; x/ ?& c% i/ ^( v
由于 MSDN 中关于 ::std::vector 的内容较少,我们在这里做一些介绍,供参考。
" _- t5 W- J; o5 L0 ?$ O8 {; x8 g/ r3 X( v4 Y' U) Y# M
不熟悉 CArray<>/WIN32 也没关系,这里提到它们的地方并不太多。
) ]# D' Z5 |# W3 Y8 Q1 p9 d# [6 c4 V; c3 `" V
1. CArray<> VS ::std::vector<> ?- r7 d* h0 S% _  ~7 `& C5 z
CArray<> 和 ::std::vector<> 一样,都是模板类,用于管理任意类型的对象的动态数组。都在解构时释放所管理的动态内存。因此都可以用于代替手工动态数组管理。
3 q# C8 O* N% x, ~; `
0 d# M: a: e6 j但是,CArray<> 是在 C++ 标准化之前很多年(VC++2.0时代)设计的,当时对 C++程序设计,面向对象程序设计,模板程序设计等技术认识严重不足,尤其是当时对面向对象技术的错误信仰与宣传,造成 CArray<> 的设计有重大错误。: P2 D- F% T9 t

- J+ @; t8 K& l% }: J! p: D在 C++ 语言标准化以后(1998),以及 VC++ 6.0 出世以后,提供了标准的::std::vector<> 模板,基本上在任何方面都要优于 CArray<>。Microsoft 由于要支持老的程序,因此一直保留了 CArray<>,但显然并没有打算按照新的思想去发展它(至少应该提供operator=(CArray const&)吧)。5 J3 y# \8 N! |( u  b
+ J! P* }4 T; P
概括起来,CArray<> 与 ::std::vector<> 有以下不同:
  d+ h8 F. G: I8 ^# w: p
& R- l/ z1 G$ }, I% p, Z3 z8 G1) 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<> 没有继承任何东西,只是实现了管理一个动态数组该做的事。8 Q  g' J# T' F, Q* r. n  t, h

2 S$ _- g* s+ i2) CArray<> 不是一个恰当的值类型,例如下列操作都是不合法的:. a* j, `* X+ ?- f
" j+ d$ ]' w, l0 R" F
CArray<int,int> a;: r9 O' q. @& w5 w2 @; p& i
CArray<int,int> b(a);  // error, must use Copy().
" C7 ?% {0 q/ `- p' Q+ r  P9 cb = a;        // error, must use Copy().
) M5 c0 E7 E9 Q; O1 |b == a;       // error, you must write your own.7 [; J3 C3 ]0 N: M3 G9 m! l
b < a;        // error, you must write your own.
$ F& k  I  q) o" q" i2 f与 CArray<> 相反,::std::vector<> 是一个认真设计的值类型,天生是可以拷贝构造和可赋值的。如果 T 是可比较的,那么 ::std::vector<T> 将自动地是可以比较的。3 E4 O5 H' m- ~
: k5 Z5 a; f& G" r
此外,由于涉及到四个特殊成员函数;4 e5 o4 Y: y( R

7 e8 m: x* R) I5 a3 tT(); // 缺省构造函数(default constructor)
8 Z1 @9 m/ Z5 M6 w~T(); // 解构函数(destructor)
7 B- F0 {8 A7 L0 F5 ~/ Z' KT( T const& ); // 拷贝构造函数, o; [5 W% b- |& g' q- E3 `
T& operator=( T const& ); // 拷贝赋值函数
( x9 j0 Y, k1 F' l  Z4 Y的自动生成,如果使用 CArray() 作为 T 的成员变量,那么上述的四个特殊函数中的后两个将无法自动生成,需要手工写:( y: i4 V3 ~- a/ q; I

* i' s. d8 x9 i6 p9 @# m) j struct T+ B4 s5 W" ~8 \' v- \
{9 `4 h  ~0 S+ H. l; c
   T() {}* V2 @9 G. h( j/ l% Y# u" T6 R, s6 x
   T( T const& t )  ~5 L  o8 X7 R4 C% O
   {5 _3 w+ `6 i' G) n
       a_.Copy( t.a_ );
1 r2 }4 U+ |3 F9 O& v( D       i_ = t.i_;
) W9 ~4 b/ I9 r9 M: h       d_ = t.d_;- o! R* h  W# B- X2 L5 S
       s_ = t.s_;
0 ]7 z% s& c# n. D. N   }8 x' N/ ~5 V9 y% p% P
   T& operator = ( T const& t )
4 y9 u7 j: d( Y: {   {
1 P4 j7 E; K& H$ V4 }       if( this != &t )! X7 r- p  \' X5 a3 R" [! y
       {8 q5 k2 f; e5 Y3 \' d
           a_.Copy( t.a_ );# f# v2 h; @2 k; j, C6 o
           i_ = t.i_;4 L1 t+ [! W/ n& v) k9 ]3 j5 E
           d_ = t.d_;  G5 B  U/ x8 O5 |
           s_ = t.s_;3 h) O) o3 J$ ]. `8 z0 W& A
       }. J7 }$ n2 {$ C" O, v, |/ u' A# ?
       return *this;1 F# S; U& f7 r& p
   }
' D4 c  O5 |4 S9 O% M- p3 Z: Aprivate:. X$ T2 d- |( H( o8 D
   CArray<int,int> a_;3 D7 Z0 M5 L, d6 e
   int i_;3 k! O6 q: \5 S. ?5 K; R/ M
   double d_;
" l+ r2 [- E5 z' J- |2 \   ::std::string s_;2 u2 h2 ^8 g- B$ @
};
3 J( U' V  I  `, O; w如果使用 ::std::vector<>:3 t$ M# }$ }0 q) [- H- J( @

  N( `" H3 _; K9 vstruct T
' b2 g; R% E! F{
! f4 z3 m$ V' e2 [8 E0 d6 R# Mprivate:
- @: C0 m' C( J' z9 |   ::std::vector<int> a_;' U% U6 W4 w7 E, I: s9 {; U
   int i_;
3 ~7 J4 `9 q& Z. T' o   double d_;
. A$ H( W- C" O. H: d# I( [+ h6 I5 p   ::std::string s_;
) C+ Y3 {+ w0 r, X5 `1 h. v};
: g% m( T* o. f& Q+ |$ D: _上面列出的三个特殊成员函数都不需要写。好处是明显的:当你增减 T 的成员变量时,你不必到
1 e3 j& _" t+ YT(T const&) 和 operator=() 中去相应地增减。
" {) ~. t! ^1 O5 k8 A  e- Q) u
5 e1 `- c  u' z3) 没有现成的算法可以对 CArray<> 进行操作,而标准 C++ 里的标准算法大多都可以直接在
' l* G9 h8 k5 |$ s2 A::std::vector<> 上运行。例如:
( U1 {6 d( D1 j5 B: ~% p: n1 U* I8 R8 }+ I/ B( F- n6 @
static int const init_vals[] = { 3, 1, 4, 1, 6, 9 };2 G2 j4 ^- U( Q* }" `
vector<int> a( init_vals, init_vals + 6 );0 @, v6 r- `# \
*find( a.begin(), a.end(), 6 ) = 5;    // 把6改成5; r3 a6 s: I, D/ Y
sort( a.begin(), a.end() );    // 排序。( e8 C6 I) x, E0 e
可以说,CArray<> 的主要设计错误是把一个本来应该是一个简单的“值”类型的东西设计成一个难用的“对象”类型了。所有的“值”的好特性都丧失了,但那些从CArray<>继承的派生类呢?7 X. [* e, j6 N: y& [4 m% Z" P/ `9 ~8 @
8 ~# c' G/ S, v8 Z
CByteArray等的问题与 CArray<> 的问题一样,甚至更多(例如,CPtrArray,永远不要用)。
; K: X& g) o! C. Q4 M* r2 A
4 r$ {7 ~' A3 `同样,其他的 MFC container 模板,象 CMap<>, CList<> 等,都有类似问题,都应该用
6 M1 r9 d9 x6 a" R3 q; ?::std::map<>,::std::list<> 等设计更好的东西代替。
; H- O& l2 p8 _6 h  t" R
" X3 w$ b5 \' y, r6 a$ j9 j2. ::std::vector<> 在哪里?
1 ?( m# g1 s& ~::std::vector<> 在头文件 <vector> 中定义:
: G2 F& x; s+ v6 \4 z  ]9 Q: j' U4 V; @& W
(注意,标准的 C++ 头文件都没有 .h 后缀,有 .h 的文件是与 C 兼容的,或支持老的不标准的东西,象 <iostream.h>。)
2 D6 M% P" L% C, g5 [& g, a' ?8 n# O8 z4 E
namespace std 6 e, L7 I) p' G6 q5 S! Y4 X
{( d% Q3 i0 H* W
    template<typename T, typename A = allocator<T> >
/ Y% S# Y  r1 n2 {    struct vector
( N) ?# G6 x( _, i0 |    {) s3 Z: L4 E" J4 U2 C  J
        // 具体内容稍后讨论+ \3 V( y8 |' d4 X7 x  `; ?; D# F" g
    };
- |/ J% m$ f6 D2 F
4 ^2 Y/ ?! ~& ]- P" b
+ S' i5 {) k1 O6 {' m+ o0 C# [3 p  K    template<typename T, typename A>
8 h% {* t/ O0 e& E  w. h* H        bool operator == ( vector<T,A> const& a, vector<T,A> const&    b );2 `/ c. [; H4 X% {- B
    template<typename T, typename A>
1 `+ e: j; R" q. h$ i! q' n1 _% M        bool operator != ( vector<T,A> const& a, vector<T,A> const&    b );
  @1 i' Q0 c1 @# @    template<typename T, typename A>7 s" F: |$ Y5 k* j7 d; Y5 M0 @' ~
        bool operator < ( vector<T,A> const& a, vector<T,A> const&    b );
4 X. b: ]$ [2 A+ @. Z* i* g- J    template<typename T, typename A>- j- u( @. d1 d0 c* H0 F
        bool operator >= ( vector<T,A> const& a, vector<T,A> const&    b );
4 ]; ~0 w  g9 z6 d+ I3 D    template<typename T, typename A>
& |: Z" i$ m& G        bool operator > ( vector<T,A> const& a, vector<T,A> const&    b );  D! k5 S+ [; \
    template<typename T, typename A>2 Z, ^; i& {4 ~/ Q- y
        bool operator >= ( vector<T,A> const& a, vector<T,A> const&    b );+ }. D! `2 ^* i) i$ D
}5 `% F1 t0 w. W$ ?' X7 K
vector<> 定义在 namespace std 中,使用时为了减少击键次数,通常使用一个类型定义缩短类型名称:' d$ t4 ^$ _0 M2 H+ Z0 f1 f: S

0 N* x0 p! x4 Y" g9 q+ z9 O& e#include <vector>
1 c! s3 ]$ a: Ttypedef ::std::vector<int> IntVector;9 O9 b# b8 J. z- `9 V
IntVector a;
2 N) k  V5 E5 O3 k& HIntVector b( a );2 K; I3 z9 J2 `2 O8 c
IntVector c;
  g% s" N" |1 @/ I( h* M( G4 [c = b;
- o! a9 h. ?1 _. Xassert( a == c );0 X8 t. g' e$ J* v" P9 q5 B
请注意 <vector> 中定义了六个 vector<T,A> 的比较函数。这些函数只在真的用到时才会被实例化,才会要求 T 也提供 operator==() 和 operator<()。
" x- p% i! [6 l1 o/ P% A' A- }' }9 J
另外,A = alloctor<T>:用于提供一个用户定义的存储管理类。由于这个参数很少用到,而且在 VC++6 的实现中有问题,不能用,因此以下的讨论忽略这一部分的内容。
+ t: s2 Z3 r/ g4 j& l1 s! J3 T
" H1 I/ H: u" U" R3. ::std::vector<> 中的类型定义
" X) ?% q: J' d1 h% Gvector<> 中定义了一些类型,下面只列出常用的:9 ~$ @! l$ }5 u' z6 X/ v" l8 g, q

6 K7 `+ Y  Z' O% ^/ Itypedef T value_type;
  N$ x8 ]# ~" O& I8 I* e2 k  p' Itypedef T0 iterator;
9 E' Y0 m( g" ?typedef T1 const_iterator;1 e$ f9 j0 O! t3 t# A
typedef T2 reverse_iterator;; ^. M: J' \' [# v' [1 D$ y" N
typedef T3 const_reverse_iterator;
* U. @( u, R5 I% E+ Q  V% G  S  O7 v$ d% a6 A/ D5 Q% _6 c# r
value_type 就是 vector<T> 的元素类型,也就是 T。当写通用的算法处理任意类型的 vector<> 或其他容器类型时是很有用的。
  ~6 t) ^7 H. p& U, ?2 U; Z5 z: ?( P7 U  Y1 X
iterator/const_iterator 是两个 vector<> 的实现定义的未知类型,用于访问vector<> 中的元素,类似于 T*/T const* 指针,他们的区别是一个指向的元素可被修改,另一个只可以读:6 x) C0 c, k  G" \

; L1 a2 J$ b4 c. H) F' Atypedef ::std::vector<int> IntVector;( H" [7 S1 l- A
IntVector::iterator iter;9 `% W1 M7 ]( m* \# a. q9 H5 f
IntVector::const_iterator c_iter;, A. h8 v# U  H* b
// ...4 F5 j/ o8 ^- F& F0 F. f! l. c5 A
++iter; iter++; // ok: increment, post-increment.5 F2 E3 D; W$ d( G' Q. S5 E2 x) x
--iter; iter--; // ok: decrement, post-decrement.
! J( J$ K* F6 @, f% z) E" V++c_iter; c_iter++; // ok: increment, post-increment.! \  D3 A) f* |- S/ q! [
--c_iter; c_iter--; // ok: decrement, post-decrement., H+ _" m5 w4 l3 R) h
*iter = 123; // ok.
; I0 ]  o1 A5 r3 p6 C" z" Hint k = *iter; // ok.
, ]) L" v0 S' {) t; i" n( {k = *--c_iter; // ok.5 T& r  Z0 H) I% K( }  C
*c_iter = k; // error.
0 e0 Q% q! Y" x" jc_iter = iter; // ok: iterator is convertible to const_iterator.6 \% H$ j+ O* ^8 }' y4 ]8 G
iter = c_iter; // error: can't convert const_iterator to iterator.$ I9 D$ ^& Q( C9 G  V
在使用上 iterator/const_iterator 和 T*/T const* 基本相同,事实上有些vector<> 的实现里就是用 T*/T const* 实现 iterator/const_iterator 的,但又不可以把 iterator/const_iterator 当作真正的 T*/T const*:
6 Q/ Z5 @. N. E2 Y' ?0 `
9 d, V& k+ X* V1 h7 h) \/ N4 T% d( ?T* p = iter; // may fail to compile.
% D8 Y! N0 r) IT const* q = c_iter; // may fail to compile.! [& L1 X' B% C: q5 N% L
reverse_iterator/const_reverse_iterator 与 iterator/const_iterator 类似,但以相反的次序(从尾至头)访问 vector 中的元素。6 M1 `+ J) F- Z: A+ F8 Q

+ Y0 S$ }5 W. _7 O" u: Q/ S各种各样的 iterator 在 STL 中有特别重要的意义,但这里我们不做具体介绍。只要理解通过 iterator 可以访问 vector 中的元素,大概相当于一个指示位置的指针就行了。& ~8 }2 \8 i. e2 X

# S: N2 @' b! o. @7 m' C; |7 X4. ::std::vector<> 的构造
8 {$ Z4 x1 y0 d/ i2 y' svector<> 提供了以下构造函数:(忽略 allocator 参数)4 ?% V- {8 ~: U* Q" p" m9 Y- B8 e

% m# Q! ^# o$ Q5 X+ j8 fvector();# J0 F8 w9 r9 W8 d* H' b
vector( size_t n, T const t=T() );
6 B1 R: ?# `9 A0 F# [6 n8 ovector( vector const & );
, x. T0 U" ^1 X3 w; a, j0 A* ]% ]% Wvector( const_iterator first, const_iterator last );
# n* h6 [* A2 U+ n1) vector();
% c9 s) |" s& l- }
) O3 _! I4 Y/ [( H# @2 \构造一个空的 vector,不包含任何元素。; f" Q+ k: {/ X
' w- f: K( Q5 r4 ]! {
IntVector v1; // 空的整数向量。
7 Z- F: M; ?+ }- Q! i2) vector( size_t n, T const t=T() );
& t1 W3 ^; A6 A  ^! r" I4 d# V, ~. T1 R, K$ `# v
构造一个 n 个相同元素 t 组成的 vector。如果不给出 t,那么将用 T() 做缺省值:# m6 p9 _/ d' H! E7 u) d" w

9 x/ k0 e8 W7 t. }IntVector v2( 100, 1234 ); // 100 个 1234.6 _: O& b! G2 }% b- f
IntVector v3( 100 ); // 100 个 0。
" C- R, U6 z6 g, Z! z3) vector( vector const& other );9 o# ]2 L- s4 T2 K) o6 r* I8 @, T
0 q$ U8 C1 k/ o
复制构造函数,复制 other 中的内容:
* Y1 M1 F. T' W6 C) ~: n1 }- [6 l" z  c
( h% R' D2 B. \0 H- ?& nIntVector v4( v2 ); // 100 个 1234。
2 @( Y$ [, P0 F3 U; Z; F4) vector( const_iterator first, const_iterator last );! X7 W3 Y$ Z( {8 \# W3 ?

; X& t+ q# ?) m1 Q  H' K事实上,这个构造函数应该为1 {: M' Z) I6 x+ r+ r! v/ t
/ ^0 P. U$ Q) S$ `, p0 |3 T! L
template<typename Iter>9 z  \$ r- {: ^( ]+ f5 |, x
    vector( Iter first, Iter last );4 e2 Z# w3 E, e  S
即拷贝任意的序列 [first,last) 到 vector 中。由于 VC++6sp0 编译程序的限制, Iter 被换为 const_iterator 了。不过,碰巧 const_iterator就是 T const*,所以可以如下使用:8 z$ P9 J9 o0 q, h/ a/ s6 e7 b, _
. e; d4 E3 @# c' C5 A
int a[] = { 1, 2, 3, 4, 5 };
4 ^5 n* X. N% Y0 r7 G! AIntVector v5( a, a + 5 ); // {1,2,3,4,5}
9 v: b/ q; \# X+ v" N" qIntVector v6( v5.begin() + 2, v5.end() ); // {3,4,5}
4 z& s4 V  F9 a' q2 A& E) U) s  S5. 访问 vector<> 中的元素6 M' f& B9 r5 p$ ?
以下成员函数/运算符用于访问 vector 中的一个元素:
! B6 s9 L# c" z8 v9 ]" p& Z; Q, i0 b9 P% X
T& at( size_t n );
% {2 m( k3 z5 @# V6 R& o4 H# yT const& at( size_t n ) const;
, M' |" A* \5 W; bT& operator [] ( size_t n );9 a" i3 w  T+ h2 S. T
T const& operator [] ( size_t n ) const;/ P6 i, x$ T0 [
T& front();
: g2 M6 x. f, j. R( l. [T const& front() const;
% s+ S: G1 u; q% x: p$ ?T& back();# O( p1 O2 n7 Z* ]
T const& back() const;: G" N# C1 a+ M6 `: g: L
请注意,由于 vector 是一个“值”语义的对象,所有的操作函数都必须严格保证 const 的正确性。所以,所有的元素访问方法都有 const 和非 const两个版本。6 q) f" ?# o, x! I+ L2 g; V& b  {& x5 d

" n( H0 G& I$ dat(n) 和 operator [] (n) 都返回下标为 n 的元素的引用,他们的区别是,at() 进行下标越界检查,若发现越界,抛出 range_error 异常,operator[]不进行下标检查。* _9 r2 m+ l) m; N- S+ A
; ?; e7 f( g5 r# w* N
front() 返回下标为 0 的元素的引用,back() 返回最后一个元素的引用。* G1 a5 X, r" ^# i* o/ v
% X* H' Z, [1 s' \1 l$ U" D0 g
int a[] = { 4, 1, 4, 1, 5, 8 };
# B# r" y5 _. |IntVector v( a, a + 6 );$ Z' {  \# H/ `, Z
// 使用 front(), back():6 R2 Y+ G$ }9 R% Q) Z4 C* I
v.front() = 3;: Z! `8 t7 P) \
v.back() = 9;
; h9 N1 a$ ]2 p7 P9 a' V// 使用 operator [] ():% i. |  {7 Z6 G& y4 e
for( size_t i = 0; i < v.size(); ++i )/ Q" q9 I8 x% ], V
::std::cout << v[i] << '\n';
1 b( `5 q2 c. \: V! ?6. ::std::vector<> 的存储管理
; m$ i; \3 V( E% V) M* P  t# d  T6 w以下成员函数用于存储管理:
( D) x% ?5 n5 z* ]( _- C% a
8 A$ X2 j$ p. e+ H9 N6 @void reserve( size_t n );
- N; C4 h, Q7 P9 Zsize_t capacity() const;
9 A6 ^/ r" G8 J" G, s. D5 a. Q$ Gvoid resize( size_t n, T t=T() );4 I% |1 w* |7 w. q0 L2 }# i
void clear();! }% d: E2 z+ A
size_t size() const;
. M: x/ i! E# V/ A: _bool empty() const { return size() == 0; }5 q8 b+ k4 q3 b8 D) b2 [
size_t max_size() const;
/ c, t3 `  O2 ]8 B3 O7 Q9 b6 P* L
) D' H6 d6 w8 [! H) x) U0 l7 d另外,push_back(), insert() 等也涉及到存储管理,后面另行介绍。, ?' j3 a% G: x7 E
3 e' _. ?% n. C$ j2 V6 N1 |
1) max_size()
' R2 g' Q$ k' S( P7 i5 q, k& g* B" @. K1 s
返回 vector<T> 理论上可以装的最多 T 的个数。这只是一个理论上的数字, 大概是 4GB/sizeof(T),没有多大实用价值。在程序中不要用。/ q6 Y3 j  e$ G" T, m
4 N- r' ]4 w% k: f
2) size()5 M, e- x! B1 |; Y8 g

$ c- K' |1 I% A0 o返回 vector<T> 中实际装的 T 的个数。相当于 CArray<>::GetSize()。
0 |( ^7 L1 b/ C* v
  `% g- }* I) ?3 }9 |3) empty()& M/ ^" _; ~, y( k+ |) s

# f5 {, z% R8 m, z& L6 R1 j如果 vector<T> 中没有任何 T 对象,返回 true。也就是返回 size() == 0。" N/ s$ q3 d- \

% \' \7 |9 {5 I3 Q9 B9 v5 n! Q9 H7 D4) clear();* K& y! f4 c5 u7 B4 ?# C" E+ z

* C* a+ M3 S. u; o1 l清除 vector<T> 中的所有 T 对象。执行后 empty() 返回 true。大致相当于 resize(0),但不要求 T 可被缺省构造。相当于 CArray<>::RemoveAll()。
: m, x0 F0 [9 S9 j
6 s2 Q" J! d/ d5) resize( size_t n, T t = T() );
7 ^9 u: `1 ]  l5 t1 t- I
+ ^* N3 U3 o9 Z  F  ~0 n将 vector 中的元素个数设置为 n,n 可以大于 size() 也可以小于 size。如果 n 小于 size(),那么 vector 中下标为 n..size()-1 的元素都将被解构。如果 n > size(),那么将在 vector 的后面新增加
) u+ q1 ?. U/ r/ W- ?+ Pn - size() 个相同的元素 t。在增大 vector 时,可能发生存储再次分配。总之,调用resize( n, t ) 后,(size() == n) 成立。
& _% M( r/ l4 e4 z8 i) L. f. C3 l8 k
, N# c4 \. E  ?! Y8 w" V3 Z请注意,如果调用 resize( n ) 不带参数 t ,那么 T 必须可以缺省构造。( e4 I' ?+ U( P/ p, t  k

. `7 U; R; [) J6 q2 N6) reserve( size_t n );* _- X! Q% `9 o" H) J7 F

" }/ X( y2 r2 L9 P5 [0 l# ~& C事先分配至少可以保存 n 个 T 对象的空间。调用后 (capacity() >= n)成立。
/ ^3 E; y( ~) A6 X  I
4 k5 w9 D- Q6 E; N, M7) capacity();5 m8 |) W5 i7 q7 J$ f# r1 O1 M. A

* @1 H6 b! F' P! ]' N/ h" \返回已经分配的存储空间够容纳的 T 类型对象的个数。后续的增加元素操作(如 push_back(), insert())如果增加元素后 vector 中的总元素个数不超过 capacity(),那么 vector 的实现保证不重新分配存储空间。
" k6 A4 Q+ ^8 p* V* p- K$ u; f0 n/ ^6 ?) g. t
vector 管理的动态存储空间是连续的。执行操作' S- e* G& {5 u" {) x6 s$ l5 s
1 |4 b4 f3 C1 G: |
IntVector v(7, 1); // seven ones.
0 t/ w0 l3 a5 s# S2 K. O  b( u9 xv.reserve( 12 );
  b$ M$ ~4 a5 X6 @% r6 _后,v 的状态可以用下图表示:
  c( ?8 x7 m- }& H/ `* v2 l2 v3 x' a2 I% A2 S
/--size()---\
0 R6 k% c7 N" F. J3 C|1|1|1|1|1|1|1|-|-|-|-|-|8 W7 z7 F4 i! ~0 J
\--capacity()---------/
) ~: `1 Q7 [% p3 E4 s* d; g! o其中,1 是已经构造的 int 类型的对象,- 是可以构造一个 int 类型的对象,但还没有构造的原始空间。再执行
4 s5 Q1 @/ a- x% k+ @! l
! P( s- {- |) ]: s: y9 ^: B( q: O" av.push_back( 2 );
6 ~0 x# v* O! ]! }' \# N8 n7 Qv.push_back( 3 );
, z8 @2 n  s- y& D7 g/ @0 Q后,v 的状态可用下图表示:
& G5 X! o9 L2 A
( D2 u# E) l9 I% p, Z+ W /----size()-----\! T$ }# Z) r% C, e* r* h
|1|1|1|1|1|1|1|2|3|-|-|-|
$ V& G9 y- w/ Z* o6 C2 S3 V# T- p \----capacity()-------/
- ~0 D$ @9 Z( q& E( H执行 resize( 11, 4 ); 后:+ _. Z) x8 Y4 B! C

0 x! s! M7 U7 ? /----size()---------\
: {! x. q, s* e$ P) J9 H|1|1|1|1|1|1|1|2|3|4|4|-|: V$ T+ y* }. N# x% ^
\----capacity()-------/
9 x( \# C9 Z# Y4 ecapacity() >= size() 总是成立的。对于下标为 [size()..capacity()-1]的未构造对象的存储空间,是不可以访问的:
0 z7 t0 q0 @' S" B+ m, y- m* j! i6 t$ H  q; N/ z/ j% n
v[11] = 5; // undefined behavior - anything can happen.3 q. }4 T8 j) C8 q9 r! `
7. 添加元素到 vector 中! D. N% v' P, W/ ?! o1 S
下列操作添加元素到 vector 中,并可能引起存储分配:0 T1 _; Y" p/ }: y! L4 t$ M
3 g, K$ F  w+ C2 m. v* T9 T2 U
void push_back( T const& t );7 v0 q/ O  m5 b% X1 p# Y
void insert( iterator pos, T const& t=T() );' b& E) q6 ^7 [1 s$ P' z
void insert( iterator pos, size_t n, T const& t );' Z* s- y( W# q9 d( q
template<typename Iter>) k& B# L5 r; @) `) i
    void insert( iterator pos, Iter first, Iter last );
6 z( u; H  Q0 q' S% r3 b) e" X2 k, @push_back() 是把一个元素添加到 vector 的末尾。insert() 是把一个 t,或 n 个 t,或从 first 开始到 last 结束的一个序列插入到 pos 指示的位置之前。5 y$ F0 S& V6 }4 F4 C1 f2 e7 g! {

1 q3 v/ i( o# Y) u& M+ Z当插入元素后 size() 将会大于 capacity() 时,将引起自动存储分配。vector 将会分配一个比需要的存储区大若干倍(通常是1.5到2)的新的存储区,把老的元素拷贝过去,同时完成添加或插入,然后释放老的存储区。8 M7 S# ], u5 S  q! Y
$ [$ K6 P9 [: S" \5 ~3 ^
这就是说,vector 自动存储分配的空间大小是指数式增长的,这可以保证多次添加元素到 vector 中时,平均用时是接近于常数的。
! ?( r4 F* E5 G( r5 L! s7 k  \5 p/ y
2 y, D6 G+ ~8 [, {$ OIntVector v;
8 K- L6 @* w+ k9 z" V, `, H   , k" n2 v: J0 ~
// add 0, 1, ..., 99 to v:% R3 [. M; p8 C& F
for( int i = 0; i < 100; ++i )
2 V& q0 ?' P5 ?' |8 o+ O6 G- sv.push_back( i );
* E; ^6 R! g- ?  K   
7 C8 E4 R( K2 w% {' ?/ ]5 Y1 x// append 9, 8, 7,..., 0 to the end:
& J% A/ D0 T" b1 Eint a[] = { 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 };/ v0 c7 y6 t( b0 X2 T. H
v.insert( v.end(), a, a + 10 );
% |4 F* v8 G5 T. J* x( z( B8. 删除元素
2 U1 A* M7 ?/ ]1 Y8 {下列成员函数完成元素删除:7 Y& W$ z7 C5 S: ]8 |6 N; @+ O, Y. ^, V

5 z6 m' M7 f) o/ @$ n0 C% Dvoid erase( iterator );+ b5 A0 l  c) M% |6 ?8 d3 ~
void erase( iterator first, iterator last );. A. c$ T% i7 j" K) ~: i2 J* d
void pop_back();2 k, x4 f- G" W. U
void clear();0 t* U0 w. ?- D
这些函数分别删除一个,一串,最后一个,或全部元素。- Y# M. H5 E2 Z; @  f
- `+ E. b1 P; R7 W4 k! a; I
IntVector v;. [! M. T0 ~3 |' R% Q+ y
for( int i = 0; i < 100; ++i )
0 n) d- n  U. e5 P" ^( I9 V    v.push_back( i );
" n8 u8 W2 z+ Y6 B- f7 O( L6 x   - C$ k7 W# @# g! ]# W+ |, `
// 删除 50, 51, ..., 89:( g$ d2 j8 `" g- a$ S
v.erase( v.begin() + 50, v.end() - 10 );
2 T" `4 Z, K8 _3 k! e! y% y   1 T+ t# O7 ]) Y* r7 t
// 删除 49, 48:
* J! N. G' j9 q3 F! I9 `. T5 K! wv.pop_back();
+ |% f' _3 Q" E) ^; Gv.pop_back();
8 E& Z" [: `5 W$ T7 C, ^   3 }5 E. y% A5 x0 g7 E
// 全部删除:% Z/ S5 ]6 K( ~
v.clear();
1 \# p% ~1 [9 p7 ^注意,删除操作不会引起存储分配,因此 capacity() 不变。! [$ M. X! @4 L! \, X
4 j% Y5 M- j9 Z( X
9. 作为序列访问 vector 中的元素8 d" B. M  u! O7 R6 o7 s2 I; m2 f. [
序列(sequence)在 STL 中是一个非常重要的概念,所有的容器类型和算法都涉及到,而且所有的算法都是建立在“序列”这个概念之上的。7 P) t- P! ]( d# S
# U; Y8 ]# ?# V1 Q4 ]
“序列”是一个线性结构,由一个指示其起始和一个指示结束的叠代子(iterator)来决定。如果 first 和 last 是某种类型的叠代子,那么经常用[first, last) 来表示一个序列。注意,first 指向的元素是这个序列的一个元素,而 last 指示的是这个序列最后一个元素之后的位置,可能根本没有元素可以访问。这种半闭半开的区间表示是整个 C++ 标准中的约定,而且确实可以简化程序。
2 A; J! i. r2 m/ `* d
3 Y) C. k& }5 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 的要求。
# D. m/ j) r6 g8 T: K" M
/ v% H; f1 H* o! i/ }; lvector<> 中定义了以下函数用于获取被控制(管理的)序列(动态数组)的各种叠代子:
" ]: H) H4 o$ }" c# V9 Y! {4 E- t8 P, Z* l* ?4 B  Q3 U
iterator begin();5 n1 F" I4 D7 R. @
iterator end();! }& w) _! a* X% b, l/ Z, @
const_iterator begin() const;
4 [2 @' N/ A9 f! Z! V. @const_iterator end() const;
2 h2 e7 L7 z* Z# @# G7 U( t  \+ areverse_iterator rbegin();
7 ^3 g; b! {6 ~% Ureverse_iterator rend();
, l0 a4 p7 p( D. Aconst_reverse_iterator rbegin() const;" m8 c+ r& {8 [& @6 g9 \/ }
const_reverse_iterator rend() const;
# A- I( h. U1 k! X( i4 S这里我们不讨论叠代子的一般概念,只举几个 random access iterator 的例子:% K: m; e3 x, H  g! Y

+ m2 v1 u8 z7 W, Z6 m' Pint a[] = { 1, 2, 3, 4, 5, 6 };, Y3 p& _4 K" l6 H9 T; }
[a, a + 6) 是一个随机访问序列,指示了 a[] 中的所有元素。这里叠代子的类型为 int*。; Z9 g; H4 h0 X- p0 f) ~

& F1 s. W/ ]* k/ |[a + 2, a + 4) 也是一个序列,指示了 a[] 中的 3, 4 两个元素。叠代子的类型仍然是 int*。3 f& g6 ?9 ]  L" F& x& e

& i' M0 q7 D8 n$ l1 _- ?& Z, f, J4 qIntVector v( 100, 1 ); // 100 个 1。/ B6 Y9 w, w. q# k' S9 W9 I
[v.begin(), v.end()) 是一个随机访问序列,指示了 v 中的所有元素,叠代子的类型是 IntVector::iterator。1 D' c4 m1 c, m! A+ A( W+ j

* \* P9 ]2 h( i8 T* a8 J+ b: F2 l[v.begin() + 10, v.end() - 20 ) 也是一个随机访问序列,指的是 v 中除了头 10 个和尾 20 个元素外的其它元素。: \/ a4 y' [4 v* y! }3 y- I

6 ^  j6 C& e8 q: n; r( Y[v.rbegin(), v.rend() ) 是一个随机访问序列,指的是 v 中的所有元素,但与 [v.begin(), v.end() ) 不同,这个序列是从尾到头遍历所有元素。& Q' e4 C# K3 X7 ^" R7 W) k7 I

: q- D& Y2 g+ H% n& N[v.rbegin() + 20, v.rend() - 10) 与 [v.begin() + 10, v.end() - 20 )指示的元素相同,但遍历顺序相反。9 [1 D0 ]2 U; N( B

& P+ W# [( L) P0 ^# G4 b2 E, @$ S下图是有十个元素的 vector 的 begin()/end()/rbegin()/end() 的示意:
4 c* H- c" R5 i8 [1 b& }! ?$ p/ ^5 G: n4 X! n
begin() ----------> end()
$ B5 j  R9 ^2 U& k* F  |                   |
& O! |6 U- o% q- n  r  v                   v8 m, F+ Y6 d. T. ?, v
|0|1|2|3|4|5|6|7|8|9|5 v6 j6 m8 @" n
^                   ^
$ T. O! E1 l7 [5 s! m  h|                   |; T$ f+ d$ `. z* p2 [
rend() <---------- rbegin(): C8 @5 i) F& ]" ^! H0 |' \# g( W
   
; Q+ [" F0 s. W0 _IntVector v;
$ P& F6 Z2 ^2 R0 Q8 c% P$ Rfor( int i = 0; i < 10; ++i ), Y( R& w  Z: q0 P1 m  i/ k& X2 h
v.push_back( i );
$ W' r: g; l- `   ; F2 e; N& D, h9 X$ y
// print 0, 1, 2, ..., 9:
- A! F$ C, f( p. V& d9 ^% mfor( IntVector::iterator i = v.begin(); i != v.end(); ++i )8 o9 K4 ]/ S$ ~* D
::std::cout << *i << '\n';
2 Y1 s+ K( F" g9 U! ?' f# S2 f   % B3 \2 ^$ y2 M; ^5 m* e4 [, c
// print 9, 8, ..., 0:, k9 h  u  l) v* p7 b* [
for( IntVector::reverse_iterator i = v.rbegin(); i != v.rend(); ++i )4 Z. }) o% R' W$ h4 ^, H) J
::std::cout << *i << '\n';7 f' {4 {3 `2 t% p5 v
除了使用 begin()/end()/rbegin()/rend() 来遍历 vector 中的元素外,由于 vector 管理的空间是连续的,因此可以直接取地址进行处理:
; G! b/ `6 I. r2 A# B9 g/ x' f1 Q6 U/ u& ]2 V
::std::vector<HANDLE> handles;, O9 W, U) Z8 ?5 R2 ^
handles.push_back( handle1 );9 Z2 J$ D2 x, m
handles.push_back( handle2 );
' u* e2 |5 Y' \4 o" |+ U2 e" c
8 ?: a- w9 b; G
! d, `& H0 k; N1 [) P$ c, r5 R) I::WaitForMultipleObjects(handles.size(), &handles[0],TRUE, INFINITE);6 C3 V( V4 i# ]( G; M- c9 f* W
这在与 C 库函数接口时尤其有用。
% X( Y# J' F/ g, K/ s5 X( b7 |& d) U* E0 x# u/ ^* J; m  g9 g& ?8 O
10. 赋值和交换
, n9 c! T6 o6 G$ e0 ^: dvector<> 是可以赋值的,这也是一般的“值”类型必须提供的操作:
) H. O9 J7 O$ Q, ]- t0 [1 f# k- W
IntVector v( 100, 123 );! T" v: X# L; J
IntVector v1;
7 U6 }, f3 Z5 \- p5 n2 F( Ov1 = v;
; ~- w' ^& e  R  ^) k9 X: I) [vector 另外还提供了9 a/ z; E# o% p; L6 G
* e- G% y: S+ _
template<typename Iter>
  W- T- N# V0 Nvoid assign( Iter first, Iter last );
0 y- [" q: w0 w' q( u. b! Kvoid assign( size_t n, T const& t = T() );
, |1 |$ r+ r, V; f: _# `用于赋值:
. P* C# u( S! {: }  U; c& O
3 }8 o1 L# O0 kint a[] = { 1, 3, 5, 7 };( n1 t, @  l- G# q0 e' |9 ^
v.assign( a, a + 4 ); // v 将包含 1, 3, 5, 7., d3 F) E7 m& B$ f7 ?4 u
v.assign( 100 ); // 100 个 0。
) X! y' ]8 @' }! k& g还有一个很重要的操作:1 h" e' T: y  z3 E1 C
, `# w' y% m/ ~' x$ E
void swap( vector& v ) throw();
4 T6 m2 E, W" W9 m" A用于交换两个同类型的 vector 的值。它的特点是快速(只需要交换内部的三个指针),不产生异常。这在写一些保证异常安全的程序时非常有用。) g1 Q- l! O' Y* y* ]
7 U  h$ Z2 ?/ y: c
事实上,swap() 基本上已经被当作类似于 operator=() 的一个“值”类型应该提供的基本操作,::std::swap() 也应该为用户定义的类型进行特例化,调用相应的类的成员 swap() 函数:
( L4 [( V; V. U5 ?/ d; h
" x; q/ W: R: d" W  v2 Cstruct MyVal( {, w" ~1 ]8 O1 S
{) `' P: x9 R9 G
  // blah blah.7 ]9 n4 G, W3 ?9 Y- C+ U6 C: f
  void swap( MyVal& ) throw();
& w4 D( k1 c+ h$ W/ W};7 L0 E" y1 M- T1 t+ Q7 w
   ( t3 G3 J& K. D3 `) ]. a1 X1 q
namespace std {
. |# Q3 k( ~( V  template<>6 X1 b9 F) ?6 i/ Q( l' L4 i, t
    void swap( MyVal& a, MyVal& b )8 Q( k" J0 Q( {3 _0 p( l% Z; s
    { a.swap( b ); }! V- ~) }' Y6 _0 G" G+ r* z) j" j! }  X
}$ c9 _! _+ v4 r6 ^$ d1 o& L
关于 swap(),值得专文讨论。这里我们只指出,vector<T>::swap() 是快速的,不抛出异常的,很有价值。' Z& H* y( Y) R/ K& E
  L. l' n; d2 G) P" f3 z
11. 使用 vector 时的存储管理策略
  \$ P1 P. |7 v- K9 U4 B) n从前面的介绍中可以看到,vector 的自动存储分配是指数式的增加存储空间,而且永不缩小已经分配的空间。这在大多数情况下是合适的。 如果应用程序事先知道要用到的元素个数,可以先调用 reserve() 来保留(分配)空间,这样可以避免以后增加元素时不必要的重新分配和元素拷贝:& G: A- N1 b5 r  Q. e+ c5 g

2 z# m2 ^% t/ z/ r5 U- M0 UIntVector v;7 B, u& ]6 w0 r6 P- i7 b# L% ^- I3 q
v.reserve( 100 );
- p8 h4 h* L# Gfor( int i = 0; i < 100; ++i )! I! W' I5 A$ Q9 h
    v.push_back( i );
  D  Y7 M0 _& w. c, p4 c  y( P/ @# s请注意,reserve() 和 resize() 是本质上完全不同的。reserve(n) 保留的是未使用而能够使用的原始空间,而 resize(n) 是真的创建了 n 个对象:# l8 H0 ~  Z7 T' X" s5 v

) v$ P) d0 I, R7 u; nIntVector v;! r$ o/ N) m3 f3 B0 G6 _" N; O
v.resize( 100 ); // v 已经包含 100 个 0.5 b2 J8 I6 L2 a0 e
for( int i = 0; i < 100; ++i ); w1 C, j( h$ Z; @% R- z
    v[i] = i; // 可以赋值- U4 O; L3 m! k# n% h8 F9 Z
有时候,一个 vector 可能增长到较多个元素,然后又减少到较少的元素个数,这时,可能希望缩小 vector 分配的空间以节约内存。CArray<> 中提供了 FreeExtra(),但 vector<> 并没有提供相应的函数。这时必须进行复制:
$ Y- G% Z6 k& p: V7 \
- F" n3 |2 }6 S" b) S: \0 K" JIntVector(v).swap( v );" o9 J) W6 S3 D9 k7 e4 ]
有一种看法认为拷贝构造函数同时也复制了capacity(),而标准中并没有很明确地指出这一点,因此更安全的方法是- r: e4 R/ N4 {) [
' _" \  q6 F& U! e7 i0 v9 ~
IntVector(v.begin(),v.end()).swap(v);& C# i2 M1 W) J1 i* \0 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, 2026-10-1 22:01 , Processed in 0.018309 second(s), 15 queries .

Powered by Discuz! X3.5

© 2001-2026 Discuz! Team.

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