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

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

[复制链接]
发表于 2008-2-26 13:29:18 | 显示全部楼层 |阅读模式
作者:wangtianxing" T3 m9 b8 B8 u/ ^; ?
2 Y8 h# c; T+ h2 L& o
原文出处:http://www.cpphelp.net/issue/vector.html
4 ~. v- O2 b" J$ ?7 E0 e, C/ x$ c( X" W. Z& |& W. W
- X% h1 U6 W1 L1 ^
  S( |* X  j. o7 t2 d
摘要: 本文介绍了C++标准库中的容器类vector,分析了它的优点,并且建议在应用程序中使用它作为动态数组的优先选择,而不是MFC的CArray<>等其他类模板。最后介绍了vector的接口和使用时的注意事项。
2 O! ~: B$ K$ _( I2 L) s) p$ O. c% m
在一些使用 MFC 的程序中,经常看到许多程序使用 CArray<>,由于 CArray<>的设计问题,造成使用它的代码的复杂化,增加了维护难度。因此建议使用 ::std::vector<> 代替 CArray<>。
! o7 x0 h- U- @+ b* j
! ^  W7 r/ z. \另外,也看到一些程序在用 malloc/realloc/free/new[]/delete[] 等手工管理内存。在应用程序中,手工管理内存是容易导致错误的,应该用 ::std::vector<> 之类的对象来管理动态数组。
- E- s4 W  T0 A- ^) o' F) j; T) X/ t* C. Z
由于 MSDN 中关于 ::std::vector 的内容较少,我们在这里做一些介绍,供参考。
3 b: t& q$ a7 O5 r. @, |) `: }  B: O; m! |
不熟悉 CArray<>/WIN32 也没关系,这里提到它们的地方并不太多。2 I0 y1 p$ u+ t7 ^- T

* L( F; f1 Z+ [) g- z/ C' [1. CArray<> VS ::std::vector<> ?
- W0 |2 C1 T; M0 {CArray<> 和 ::std::vector<> 一样,都是模板类,用于管理任意类型的对象的动态数组。都在解构时释放所管理的动态内存。因此都可以用于代替手工动态数组管理。$ C9 p% b7 [* j) v  {$ _; H- w6 k
2 m3 a2 ?( j) {3 y( ?  o
但是,CArray<> 是在 C++ 标准化之前很多年(VC++2.0时代)设计的,当时对 C++程序设计,面向对象程序设计,模板程序设计等技术认识严重不足,尤其是当时对面向对象技术的错误信仰与宣传,造成 CArray<> 的设计有重大错误。
4 W; }  Z* W4 E  g- q0 H. l; E6 ]: J3 o0 [8 [
在 C++ 语言标准化以后(1998),以及 VC++ 6.0 出世以后,提供了标准的::std::vector<> 模板,基本上在任何方面都要优于 CArray<>。Microsoft 由于要支持老的程序,因此一直保留了 CArray<>,但显然并没有打算按照新的思想去发展它(至少应该提供operator=(CArray const&)吧)。7 C9 p$ `+ c6 N) J/ Z6 E

5 y7 L  A5 R+ ^6 m  A! \! D概括起来,CArray<> 与 ::std::vector<> 有以下不同:& q  j. A& b* z
1 e8 ?3 q1 `/ J0 O, W4 l
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<> 没有继承任何东西,只是实现了管理一个动态数组该做的事。
& f1 x* O6 z# {' V  \7 j' Z1 y8 ^% N/ n  z& _
2) CArray<> 不是一个恰当的值类型,例如下列操作都是不合法的:
; d. J" |! z" U7 Q0 c
2 r( ~3 a1 P. HCArray<int,int> a;
' s5 L* z5 u3 {CArray<int,int> b(a);  // error, must use Copy().
- y0 O+ x* o, f+ h1 Y/ qb = a;        // error, must use Copy().
- j: t# r3 |. m* wb == a;       // error, you must write your own.
- g2 o/ M9 i8 b% ~. V% Ub < a;        // error, you must write your own.
) a0 o! c7 i; Q/ S6 C! m& a与 CArray<> 相反,::std::vector<> 是一个认真设计的值类型,天生是可以拷贝构造和可赋值的。如果 T 是可比较的,那么 ::std::vector<T> 将自动地是可以比较的。7 I/ m' O, M4 r5 B& Z* j  A6 d

# D" K) r5 x; e1 o" V此外,由于涉及到四个特殊成员函数;
. B$ |5 _5 d% m. o  \0 o' l) g0 C+ K% I7 Q# K3 z, Q- s% `, z4 w. _4 x2 \
T(); // 缺省构造函数(default constructor)" g3 [8 e1 g& R# F
~T(); // 解构函数(destructor)
$ w) R" E/ X. q6 b/ iT( T const& ); // 拷贝构造函数/ `. I) K$ O# M0 J2 ~, t3 _; B
T& operator=( T const& ); // 拷贝赋值函数
+ h* E8 P$ E/ o" i的自动生成,如果使用 CArray() 作为 T 的成员变量,那么上述的四个特殊函数中的后两个将无法自动生成,需要手工写:
/ }% G/ E* \0 D; T. Q0 ]% W( k* U
. N4 H; f0 |: r' }" ^% W struct T
* z5 c$ y* ^" l% k  c{9 }: d/ W4 E+ b  i+ b: @* n" @
   T() {}
+ u4 D7 r, m( L/ X3 d0 U   T( T const& t ); g$ G4 z' Y6 y& r& P
   {
" V; c8 T5 R  `# ?4 {       a_.Copy( t.a_ );. z3 @5 _& F" o
       i_ = t.i_;
4 x' G0 q' |7 |! I       d_ = t.d_;/ u. ?7 E5 P( j( c& z$ S6 ]( V1 M. I
       s_ = t.s_;, u0 y8 R4 n7 a; [; }9 S5 ~5 X4 F
   }: T( O1 P$ ~6 t! n1 S& o& X" N
   T& operator = ( T const& t )- u9 i9 i9 e/ K/ r
   {
4 g# F1 z" c! n+ v1 }       if( this != &t )% E( I+ d2 E  L* Q
       {
9 u9 l! _3 {: u4 I7 I8 F& P* B           a_.Copy( t.a_ );
7 R  A6 j! o1 h( {% @3 V           i_ = t.i_;
( G4 W+ o% J) d( h& f  B0 @* o           d_ = t.d_;; l# ^  {: }" [, r  h
           s_ = t.s_;
/ |8 P5 n+ G" v: t! |/ R- m       }
; z9 w4 s4 }  G2 I5 n       return *this;
8 n/ c, G' V4 t2 s: f6 g4 e4 v   }
' k# k. C8 A' m. E5 fprivate:: I- z) [( g. B9 \$ [
   CArray<int,int> a_;
8 b& B8 h6 O) f   int i_;7 y3 R0 B, f1 F4 c  i$ r/ w
   double d_;( l% s- e. r3 E+ \! j# W
   ::std::string s_;3 U2 U4 \( [4 i8 P
};+ _* H& v5 [$ l: E  T
如果使用 ::std::vector<>:8 E! m1 E. d) u7 y* G
* e3 d$ {% ^9 v5 m! ^
struct T  i' k- g( M8 C, e
{
  f7 F, X& }9 Cprivate:
9 L9 W9 J1 E# Y8 ^   ::std::vector<int> a_;+ `& ~& }5 L0 w
   int i_;
! o! o/ W5 _- w; I6 C1 ~* w3 `* H   double d_;7 b# h, ~; S" U' Q7 a% h
   ::std::string s_;
% q1 ]/ j' T4 `5 S};/ H1 F7 @, @8 d" N* W6 }( `. q( z
上面列出的三个特殊成员函数都不需要写。好处是明显的:当你增减 T 的成员变量时,你不必到6 ^6 R( E1 L7 X* \# [+ z1 F; D) G
T(T const&) 和 operator=() 中去相应地增减。$ R# ]  `6 h& h# n

/ i0 {& M4 B( `4 o( o& s( S3) 没有现成的算法可以对 CArray<> 进行操作,而标准 C++ 里的标准算法大多都可以直接在
' z7 @1 j$ x  S7 x( j::std::vector<> 上运行。例如:0 U9 H0 E/ D; J! ?0 Q' G
6 C+ r/ Z2 r6 P" a, v! j9 K) G6 Q
static int const init_vals[] = { 3, 1, 4, 1, 6, 9 };5 S* d1 [2 b: B) j+ f
vector<int> a( init_vals, init_vals + 6 );+ Q* G8 [! t# v9 M
*find( a.begin(), a.end(), 6 ) = 5;    // 把6改成5
! L8 i* ~: k7 @8 Y7 Y; n" r1 g1 B2 psort( a.begin(), a.end() );    // 排序。
/ {1 \+ F4 p* o可以说,CArray<> 的主要设计错误是把一个本来应该是一个简单的“值”类型的东西设计成一个难用的“对象”类型了。所有的“值”的好特性都丧失了,但那些从CArray<>继承的派生类呢?
: J* H; Y& f: U* I1 k1 f% k& ^" ?( E1 l0 {( f; A
CByteArray等的问题与 CArray<> 的问题一样,甚至更多(例如,CPtrArray,永远不要用)。
0 i- P: H" b+ ~- v0 r3 e/ p* |& w* p4 W% ?9 j* J- `1 E5 S- p
同样,其他的 MFC container 模板,象 CMap<>, CList<> 等,都有类似问题,都应该用
% h' x1 n3 I- a4 A::std::map<>,::std::list<> 等设计更好的东西代替。. Y8 d9 v" }9 c+ A% s

' m1 H3 U4 c1 t3 o6 S2. ::std::vector<> 在哪里?
" C  ?2 S8 y0 h* \6 ^5 I2 f::std::vector<> 在头文件 <vector> 中定义:: R" P; ], r  l* z* v- D3 E' }

5 ^- v! W9 Y, l, O! r(注意,标准的 C++ 头文件都没有 .h 后缀,有 .h 的文件是与 C 兼容的,或支持老的不标准的东西,象 <iostream.h>。). G0 S6 M  R; U2 {; P
! a' a  {$ [8 e$ D: B& h
namespace std % K" ]: b+ u) C! w
{& U( r- L/ b0 l7 n  x$ {! Z' M5 e
    template<typename T, typename A = allocator<T> >
: H$ G. f% t( f. U    struct vector
! H; ^4 u4 \, ~% G) u7 \+ r    {
3 n8 o$ M" ?& U! W* u        // 具体内容稍后讨论( ?2 F" p2 y8 l8 m
    };
. h" `8 @- O6 ]% c$ g. M6 c5 p& d) _; F! [

. r( S* c0 n& S0 n* m6 M$ Z    template<typename T, typename A>
; l$ c* O& s# D  d7 C7 N        bool operator == ( vector<T,A> const& a, vector<T,A> const&    b );  p/ ]% D4 T# m/ v3 l" W1 V) w
    template<typename T, typename A>6 v' L$ e* u1 u9 c6 R4 s" H% ~
        bool operator != ( vector<T,A> const& a, vector<T,A> const&    b );
+ E' @6 D  w% K    template<typename T, typename A>4 _6 Z# |9 V  H6 Y  Y
        bool operator < ( vector<T,A> const& a, vector<T,A> const&    b );% y+ H4 L3 ~( x0 E( d+ X9 A+ `" S- _
    template<typename T, typename A>
) s) [; w. j: J, U9 U( f$ ~. n2 i0 E        bool operator >= ( vector<T,A> const& a, vector<T,A> const&    b );1 o1 W2 ^% u. i0 U
    template<typename T, typename A>7 z, Y7 T6 z  y* ]" u
        bool operator > ( vector<T,A> const& a, vector<T,A> const&    b );
3 [/ n" k& c4 H( H- m6 [    template<typename T, typename A>& p3 ?# j: q" x* t& Y( ~
        bool operator >= ( vector<T,A> const& a, vector<T,A> const&    b );
2 S$ v; k+ N: ~( K. q}
% }4 ^  V) H- Bvector<> 定义在 namespace std 中,使用时为了减少击键次数,通常使用一个类型定义缩短类型名称:' T4 o' Q  x* u% Z- K
- _0 r5 h% F/ O" E% l  ]2 K- n
#include <vector>
7 u" \5 [, h4 N  f2 [9 v- Utypedef ::std::vector<int> IntVector;) q2 K6 k/ q  i1 o" R
IntVector a;
# H: D# f+ X- X1 ~# X% s% ?: PIntVector b( a );
' s. q( B2 v1 L2 FIntVector c;
% J7 f! q2 ]: [" `1 N% g: v1 dc = b;
" T% X0 R* y3 J) L4 |" A9 aassert( a == c );5 F3 \% K8 z7 m# K! P" y6 g
请注意 <vector> 中定义了六个 vector<T,A> 的比较函数。这些函数只在真的用到时才会被实例化,才会要求 T 也提供 operator==() 和 operator<()。
) v: @3 i- o$ X* @
5 k2 G- @$ [( X/ c- ^" E另外,A = alloctor<T>:用于提供一个用户定义的存储管理类。由于这个参数很少用到,而且在 VC++6 的实现中有问题,不能用,因此以下的讨论忽略这一部分的内容。" E0 s/ h0 _3 }, l
6 E9 h* F0 D& @& b' U
3. ::std::vector<> 中的类型定义! i$ U3 T% z0 T
vector<> 中定义了一些类型,下面只列出常用的:
7 Z& Y# k9 Z5 w' u
8 ~% q' a0 J! X1 U4 T  Btypedef T value_type;  ?$ ?& m# l8 O+ W+ \8 u
typedef T0 iterator;
1 n9 g. ^; W1 Xtypedef T1 const_iterator;
; q# L5 F" i9 M" q3 o: H# p0 `typedef T2 reverse_iterator;
6 |8 b$ g. D, }+ Y0 Ttypedef T3 const_reverse_iterator;7 x  u& O3 a% ~# |/ @5 t+ h

" L% }) @7 D; A: _% rvalue_type 就是 vector<T> 的元素类型,也就是 T。当写通用的算法处理任意类型的 vector<> 或其他容器类型时是很有用的。
1 h: P+ ^3 C+ L" a
1 f$ D- F/ W( h6 a, Riterator/const_iterator 是两个 vector<> 的实现定义的未知类型,用于访问vector<> 中的元素,类似于 T*/T const* 指针,他们的区别是一个指向的元素可被修改,另一个只可以读:8 B# ~! _% _, {! i9 w0 a

# q+ e  d4 }) n2 q# f  ~% P& _4 [) _typedef ::std::vector<int> IntVector;* B# P# r2 H, I( q1 p
IntVector::iterator iter;
5 j% b) Q1 @9 [/ aIntVector::const_iterator c_iter;
1 N1 ~& g( u$ Y- ], V5 s1 P. ?( o// ...4 V! S! p, q) j6 T
++iter; iter++; // ok: increment, post-increment.
& o0 q% Y" P1 X8 r* J- {% X  P& f--iter; iter--; // ok: decrement, post-decrement.3 z9 O( ^% X) V0 ?. s* V, C
++c_iter; c_iter++; // ok: increment, post-increment.
! ?$ k  U- b0 u+ ?$ v- }--c_iter; c_iter--; // ok: decrement, post-decrement.  T" B2 \" g0 I
*iter = 123; // ok.# D7 F: r4 h1 g% r
int k = *iter; // ok.
" Q( _; C- s) f( ?/ uk = *--c_iter; // ok.
8 t2 l" j% t+ e8 c0 h  p; A; o*c_iter = k; // error.7 X0 _2 G8 Z" d: H  v2 A# ^7 o
c_iter = iter; // ok: iterator is convertible to const_iterator.# D2 w4 \, b: A: B, p; E$ ]
iter = c_iter; // error: can't convert const_iterator to iterator./ L$ e' W+ @) c
在使用上 iterator/const_iterator 和 T*/T const* 基本相同,事实上有些vector<> 的实现里就是用 T*/T const* 实现 iterator/const_iterator 的,但又不可以把 iterator/const_iterator 当作真正的 T*/T const*:5 n8 D9 Q, q5 ]" F- @/ h

: h( a) ?( P% g! G+ X6 kT* p = iter; // may fail to compile.' ?; j" ^  Q1 {+ R5 [
T const* q = c_iter; // may fail to compile.) g: W: L, }* a; W) f- m
reverse_iterator/const_reverse_iterator 与 iterator/const_iterator 类似,但以相反的次序(从尾至头)访问 vector 中的元素。
% y$ j; D( Q& k( O8 W* D  p7 P* |  y1 P2 l. p1 [4 w) f4 R. `/ ?
各种各样的 iterator 在 STL 中有特别重要的意义,但这里我们不做具体介绍。只要理解通过 iterator 可以访问 vector 中的元素,大概相当于一个指示位置的指针就行了。
. s! n. e7 v2 N' `, q5 Y8 |$ s0 i
4. ::std::vector<> 的构造
9 p# ~4 p' l8 u1 q' e+ C: V$ O" evector<> 提供了以下构造函数:(忽略 allocator 参数)# j' \6 [8 c7 A% x
' y" W- V! c$ y' Q8 f# J7 a
vector();
% p$ b# M) z; N9 `) ~# lvector( size_t n, T const t=T() );
# _0 q2 A0 c& [vector( vector const & );' t" W: E9 r5 W- c) [
vector( const_iterator first, const_iterator last );
3 e3 m6 D: o; T3 u9 f. U1) vector();1 A! n$ @3 \9 P) O# ^

0 V* I$ t+ i( _  D8 b0 u& d构造一个空的 vector,不包含任何元素。; D9 H6 R% k5 ^
( ^2 f9 j! w4 u# Y2 c/ X
IntVector v1; // 空的整数向量。
2 ~4 e' C% f4 X8 H. M2) vector( size_t n, T const t=T() );
# J. c6 g2 V& a/ e% v6 I0 y
& u) t6 s* L, h构造一个 n 个相同元素 t 组成的 vector。如果不给出 t,那么将用 T() 做缺省值:5 Q/ {2 @2 `4 O

, O- e1 w' W! L2 z- a0 M3 [7 ?IntVector v2( 100, 1234 ); // 100 个 1234.: }: K: V* w. b" B) g  ~! G
IntVector v3( 100 ); // 100 个 0。
  N' J8 T! S, u! `, l( I) k" e3) vector( vector const& other );
! U6 d4 {. I/ P% X: B8 T. k1 F
& D) J+ u) }5 q$ \1 C复制构造函数,复制 other 中的内容:! W5 o. A' J% u

; z; U+ Y% p( D2 n8 \  bIntVector v4( v2 ); // 100 个 1234。
5 l1 Q# k  L- Q% I4) vector( const_iterator first, const_iterator last );
( W1 I+ I7 m5 @# \
6 O0 p$ C) U$ L事实上,这个构造函数应该为7 f, b. Z" R+ q  p+ _3 B
6 e& q; e" o* h0 a4 _: w
template<typename Iter>
8 c$ ]; N- Q2 ^  z    vector( Iter first, Iter last );5 A$ V% v+ P: F( D5 p- k  M. I, o
即拷贝任意的序列 [first,last) 到 vector 中。由于 VC++6sp0 编译程序的限制, Iter 被换为 const_iterator 了。不过,碰巧 const_iterator就是 T const*,所以可以如下使用:9 i, F  Y0 E! u& V1 ^9 X

7 A( L) S* p$ Xint a[] = { 1, 2, 3, 4, 5 };0 s3 N3 `6 K' H% i" C. }
IntVector v5( a, a + 5 ); // {1,2,3,4,5}
! z' d, r$ m  ^- ?) F9 jIntVector v6( v5.begin() + 2, v5.end() ); // {3,4,5}* D% w; J, N9 q$ c, A& B/ u% N" i
5. 访问 vector<> 中的元素
- ]& D/ g, d: `" t以下成员函数/运算符用于访问 vector 中的一个元素:7 i2 I" V9 f- N4 E5 H
( Q; a" ]0 Z0 H* q+ r% p
T& at( size_t n );
0 y/ S  j6 w) _T const& at( size_t n ) const;
  e) M" s4 v7 Q8 O* q" [T& operator [] ( size_t n );
) \( i4 \) o! h3 X4 m( P) k, ^5 bT const& operator [] ( size_t n ) const;  G5 S' P3 Y' P" i, ?2 E. y$ `
T& front();* l# r% E7 p- l% m4 @# a" q% q
T const& front() const;
7 u# D/ J$ @$ f9 i/ ^& B! sT& back();7 i. [, R& @5 m! D/ H) T! {
T const& back() const;
7 F4 ^  |+ h  [/ h% F请注意,由于 vector 是一个“值”语义的对象,所有的操作函数都必须严格保证 const 的正确性。所以,所有的元素访问方法都有 const 和非 const两个版本。) w1 {% m6 o5 G! T$ c3 [. K. \# o
" y' u- d3 X: a7 B% o5 L% O5 d( l
at(n) 和 operator [] (n) 都返回下标为 n 的元素的引用,他们的区别是,at() 进行下标越界检查,若发现越界,抛出 range_error 异常,operator[]不进行下标检查。  g% Y) h! m0 [: g2 X9 f

6 x  |2 |3 R3 i8 i9 g' cfront() 返回下标为 0 的元素的引用,back() 返回最后一个元素的引用。/ q: H' [9 K- y1 v# V( ~5 |
- S2 z2 m: H( I5 g; |& K
int a[] = { 4, 1, 4, 1, 5, 8 };
) ?/ U; I2 B( |$ a. q5 iIntVector v( a, a + 6 );% ?/ b  `, j7 V
// 使用 front(), back():0 [7 w8 K- j: m. A+ {; M1 f) ~( V
v.front() = 3;/ j) `( R% c5 d* Y% x
v.back() = 9;
+ F& F) I7 o  D- B// 使用 operator [] ():
4 o$ R; ?) Z% E+ q1 @' z7 mfor( size_t i = 0; i < v.size(); ++i )
3 B# N- n& f  e( K6 i, k/ G' B::std::cout << v[i] << '\n';
8 H$ r  P- O4 L; l6. ::std::vector<> 的存储管理
$ g. O+ g/ h! B" \5 m3 D, L以下成员函数用于存储管理:
6 i. Y8 Y- }. j( ~$ \% N' {9 V7 R7 b; `- S+ I0 E  @& l6 C( s
void reserve( size_t n );  E5 u/ E2 j4 ]! d/ |' ~% s
size_t capacity() const;
! e4 a/ k, E1 b4 X) Rvoid resize( size_t n, T t=T() );
! b( G( U) d, O# m2 b  L' u/ ?+ Ovoid clear();
; j3 @" k; F6 S; E- l6 Zsize_t size() const;" t; @* |( V0 H; x) W0 d. v
bool empty() const { return size() == 0; }5 `$ i/ q. k* X3 |: g4 [/ g' N
size_t max_size() const;( s7 R7 L) I% Q% B
3 @6 C! L' e; M5 q0 N
另外,push_back(), insert() 等也涉及到存储管理,后面另行介绍。
' ^# X  f; r4 E3 z% Y# X; ]
  ]3 k! W* ~, A2 j0 e, |+ D- M$ n1) max_size()  ~2 s/ x/ Y/ _4 S6 @5 I
4 \! [- P4 \5 M& Y1 K3 y
返回 vector<T> 理论上可以装的最多 T 的个数。这只是一个理论上的数字, 大概是 4GB/sizeof(T),没有多大实用价值。在程序中不要用。
! @) x, C2 K% |; S! u7 T0 E4 A; D+ S  B8 J6 b
2) size()" {7 A7 E6 w! r' X2 s0 h

' P2 O3 W4 K6 G返回 vector<T> 中实际装的 T 的个数。相当于 CArray<>::GetSize()。
  T- k+ x9 M, G& \$ U& ~  `; }& J- l0 A# [
3) empty()
$ F# z. q* c4 Q# B1 z
8 J4 R6 b3 @; b' ^" W如果 vector<T> 中没有任何 T 对象,返回 true。也就是返回 size() == 0。, e$ }- d6 ~' a: C  {! W" Q; Z
- i3 C" R* k" W% {
4) clear();
3 H0 j) j% {  z7 q; X' \5 |4 O. @/ U( x. _/ s  s
清除 vector<T> 中的所有 T 对象。执行后 empty() 返回 true。大致相当于 resize(0),但不要求 T 可被缺省构造。相当于 CArray<>::RemoveAll()。& Y4 Q$ j! p) r6 K5 C# m3 y
) \! f7 n2 v) V
5) resize( size_t n, T t = T() );8 z0 J* |# M! w" w- [9 @' K# ~
) X! ^8 h# W' b5 Q6 b
将 vector 中的元素个数设置为 n,n 可以大于 size() 也可以小于 size。如果 n 小于 size(),那么 vector 中下标为 n..size()-1 的元素都将被解构。如果 n > size(),那么将在 vector 的后面新增加2 k2 E" N, f* d" l0 ?& U+ a
n - size() 个相同的元素 t。在增大 vector 时,可能发生存储再次分配。总之,调用resize( n, t ) 后,(size() == n) 成立。
9 h0 ~- o- g; R$ z$ U' [5 C
6 n( D9 l# y. T4 v' o, p请注意,如果调用 resize( n ) 不带参数 t ,那么 T 必须可以缺省构造。
2 J% P( ]  j8 F& x3 t# |7 T# a& S2 k2 S% o
6) reserve( size_t n );% D5 m+ F; {+ Z

% k) [; C0 K! `1 F# p8 M; u. o事先分配至少可以保存 n 个 T 对象的空间。调用后 (capacity() >= n)成立。
3 o3 C* d; }* E0 O. A6 a( l8 t/ ?6 c% x* v( i
7) capacity();; w+ c- z- D+ Y7 U1 @
& w: M; b/ @3 d/ W- x- s
返回已经分配的存储空间够容纳的 T 类型对象的个数。后续的增加元素操作(如 push_back(), insert())如果增加元素后 vector 中的总元素个数不超过 capacity(),那么 vector 的实现保证不重新分配存储空间。8 n1 U# m; l! Z& _# U8 q/ w3 k

1 x5 e, Y1 K& z$ o5 b1 ^/ ovector 管理的动态存储空间是连续的。执行操作
- s% j* g6 j9 c/ F8 t3 Q. I% o
" l$ W$ `0 @" s0 C' z# s: AIntVector v(7, 1); // seven ones.3 K7 A4 J2 l1 d0 k8 {8 t
v.reserve( 12 );+ q) l% s/ n  @" a
后,v 的状态可以用下图表示:( T6 n# F) U6 ~6 Q  P. }

7 m, o3 l6 j' P- V /--size()---\
2 {9 I7 {4 ^% A, i, ]|1|1|1|1|1|1|1|-|-|-|-|-|& f/ u3 [% r+ U- x5 ~
\--capacity()---------/
( ]' P# v  e% [0 I; w其中,1 是已经构造的 int 类型的对象,- 是可以构造一个 int 类型的对象,但还没有构造的原始空间。再执行) P2 a% W% D. R) e& Y* p% x
$ m1 I) L3 n1 w% W2 @, ?
v.push_back( 2 );) C* r' Q+ i, y" t+ W
v.push_back( 3 );
. F+ _0 |0 q4 K后,v 的状态可用下图表示:
* L1 Q. D' I( Q. U" S  L% b) ~4 G! a* o2 }: e! \( N6 M
/----size()-----\8 m7 e# `; R4 _0 v' ]
|1|1|1|1|1|1|1|2|3|-|-|-|
4 s4 r1 U3 m. F$ h \----capacity()-------/
* w+ P5 c0 M7 a1 ?6 S" p" O6 f) d8 _执行 resize( 11, 4 ); 后:
9 d( y' s% n# E' B5 k# |4 n( T
- J# M5 ^4 E9 m4 S6 q; a /----size()---------\
3 C+ ?( Z  e# T/ Z! d|1|1|1|1|1|1|1|2|3|4|4|-|5 w; A; f' X2 W8 t2 J
\----capacity()-------/
& q) ^  R. a; j( kcapacity() >= size() 总是成立的。对于下标为 [size()..capacity()-1]的未构造对象的存储空间,是不可以访问的:
7 G5 \- ~4 C1 y$ D9 |* }/ u1 l) Y
v[11] = 5; // undefined behavior - anything can happen.- F( d0 k7 W  E, b8 l- A
7. 添加元素到 vector 中" A- |" R2 m) n
下列操作添加元素到 vector 中,并可能引起存储分配:
% g9 h7 \  k( ]0 [# ~" K' Y. }% S
void push_back( T const& t );
5 l+ e! B. x, c0 i1 W! c. vvoid insert( iterator pos, T const& t=T() );
3 Z6 e3 a1 W$ N2 ]! rvoid insert( iterator pos, size_t n, T const& t );, N5 d/ f/ V3 O. m, B- F8 Y
template<typename Iter>: y# v( d6 f$ D! @
    void insert( iterator pos, Iter first, Iter last );' S, L" w4 k! n2 d1 c4 x
push_back() 是把一个元素添加到 vector 的末尾。insert() 是把一个 t,或 n 个 t,或从 first 开始到 last 结束的一个序列插入到 pos 指示的位置之前。
/ x% h* h0 }' w+ x; @% J7 B
  q. w& ~7 {4 n当插入元素后 size() 将会大于 capacity() 时,将引起自动存储分配。vector 将会分配一个比需要的存储区大若干倍(通常是1.5到2)的新的存储区,把老的元素拷贝过去,同时完成添加或插入,然后释放老的存储区。
. v) y* |! w; Q0 [# O! U/ U
" f2 F' a' |" e/ w8 V5 M这就是说,vector 自动存储分配的空间大小是指数式增长的,这可以保证多次添加元素到 vector 中时,平均用时是接近于常数的。9 k+ B' D- f' j) d0 v9 d
* e7 o- w, l& p% V  J3 P$ x
IntVector v;
: \& U+ O, c, |6 D   2 p- g  U# z% \" b8 ?
// add 0, 1, ..., 99 to v:, p% j% Y- w- |+ z5 L$ p
for( int i = 0; i < 100; ++i )0 J9 i& V3 V: X& v  N# h2 J( U
v.push_back( i );& J; t2 ?7 `& u6 I4 ]% n
   3 O) m7 R  _  {# B! n, D# j
// append 9, 8, 7,..., 0 to the end:  K/ q) \- a" ^7 e) P! v6 G
int a[] = { 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 };
( x* F& K. m9 N- K& A+ y$ Sv.insert( v.end(), a, a + 10 );
( I$ a! z) k. [8. 删除元素
1 q' e. u5 j/ v) [7 O5 Z* ^- f下列成员函数完成元素删除:5 K- h, F5 t& U3 b( m9 l5 X2 O
( v( j6 A) F# `( M7 W
void erase( iterator );
; l( V( U7 C7 r# z* _void erase( iterator first, iterator last );* N2 N  O) ?: O( ^
void pop_back();
5 O6 C% G$ b0 E" E: T; Jvoid clear();
% W( ~; K5 q- B6 q, f2 K- L这些函数分别删除一个,一串,最后一个,或全部元素。
) J+ K: ?. a- b) D% n/ Y% e. D
7 U# R) G4 I. x2 k- ?8 YIntVector v;. g; e8 w1 x% a1 C9 i- e) d
for( int i = 0; i < 100; ++i )
) G, f6 y3 Z0 Y# q( R    v.push_back( i );
  S8 F+ |% x  ~+ _$ ^   ) i1 b0 V7 w9 L$ d1 r, i3 u$ i
// 删除 50, 51, ..., 89:1 W% g5 g2 N( \
v.erase( v.begin() + 50, v.end() - 10 );/ W8 O( i9 @' J4 o* K( K+ R8 @
   
% x# v9 A1 c0 N) B, k& I$ V5 ^& z// 删除 49, 48:
' n2 v. q# T. n1 S4 o# kv.pop_back();
2 t8 H' H, c, o7 p; u, Yv.pop_back();
- w6 p. Y, `  \: Y' V   ; U1 b! I; s) q6 Z! a2 L
// 全部删除:
& I  w: A' S  ~9 B/ s* X# u- I5 f6 Z8 mv.clear();6 _+ F5 M4 C  ~0 A
注意,删除操作不会引起存储分配,因此 capacity() 不变。1 d$ G" ~) Z$ Z, [7 @; q+ p
& |. N2 x" Y# Q0 }9 z+ O
9. 作为序列访问 vector 中的元素
# W" P1 l& g7 D8 Z: @序列(sequence)在 STL 中是一个非常重要的概念,所有的容器类型和算法都涉及到,而且所有的算法都是建立在“序列”这个概念之上的。
, E" I% \  K0 w+ B) Y1 e* t* [4 V( I4 n+ }+ T; @* E' h% n
“序列”是一个线性结构,由一个指示其起始和一个指示结束的叠代子(iterator)来决定。如果 first 和 last 是某种类型的叠代子,那么经常用[first, last) 来表示一个序列。注意,first 指向的元素是这个序列的一个元素,而 last 指示的是这个序列最后一个元素之后的位置,可能根本没有元素可以访问。这种半闭半开的区间表示是整个 C++ 标准中的约定,而且确实可以简化程序。$ o5 F) b" [8 \. _/ I: V

$ V' v3 P; v7 `- c7 M叠代子是传统的 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 的要求。; r  n1 P+ D5 [
6 U1 d# ~* s# W+ B' l& J
vector<> 中定义了以下函数用于获取被控制(管理的)序列(动态数组)的各种叠代子:
. P: @/ g5 _* h; J  v& n6 l
5 b; I# M" a. @iterator begin();# W* N' {  T3 f" q1 x
iterator end();- x) ^- d$ `  G+ L1 t4 |8 ^/ \
const_iterator begin() const;; \: t5 K' |0 Y$ q
const_iterator end() const;/ d2 T. y7 N" S/ M& I, t
reverse_iterator rbegin();/ i2 ~# a4 V9 i* \" w2 t
reverse_iterator rend();
; p3 f# `# l, l8 h/ c. d6 N# Gconst_reverse_iterator rbegin() const;
8 s' j/ l; c! S4 t2 Lconst_reverse_iterator rend() const;
( Z; ^7 ]  e; H4 Q这里我们不讨论叠代子的一般概念,只举几个 random access iterator 的例子:! p4 P1 Q2 s* l
" [/ R1 z# v: g" W. g
int a[] = { 1, 2, 3, 4, 5, 6 };8 d9 x6 W( n3 \1 ]8 n* X8 ^
[a, a + 6) 是一个随机访问序列,指示了 a[] 中的所有元素。这里叠代子的类型为 int*。8 {. B3 Z) p8 i, V+ w- h
1 w  A+ L/ J; i# K: f7 I
[a + 2, a + 4) 也是一个序列,指示了 a[] 中的 3, 4 两个元素。叠代子的类型仍然是 int*。
3 U4 z1 n$ ~, z  m- a; W  K5 k# o4 G8 |* r( o3 s: k. ]% m0 f
IntVector v( 100, 1 ); // 100 个 1。
& c! t! t( |! ^, C4 M, A[v.begin(), v.end()) 是一个随机访问序列,指示了 v 中的所有元素,叠代子的类型是 IntVector::iterator。
$ v( V, \+ w3 W& g5 ^* M
# V1 b) R, |" _& k, X( z[v.begin() + 10, v.end() - 20 ) 也是一个随机访问序列,指的是 v 中除了头 10 个和尾 20 个元素外的其它元素。
% G' G( A* J# {  \. z) |* |$ j7 ~4 v; P9 O; D% Q$ i
[v.rbegin(), v.rend() ) 是一个随机访问序列,指的是 v 中的所有元素,但与 [v.begin(), v.end() ) 不同,这个序列是从尾到头遍历所有元素。
* E( f3 F" P# D$ D7 K+ M/ s
5 k" w; G6 E+ B, q2 ~. O[v.rbegin() + 20, v.rend() - 10) 与 [v.begin() + 10, v.end() - 20 )指示的元素相同,但遍历顺序相反。( c2 N: Z" v% B# m/ V

5 q& g% i3 L6 m' M下图是有十个元素的 vector 的 begin()/end()/rbegin()/end() 的示意:- b& {; m, l0 S! H! {( I; x

# l% Q5 W# ?8 [8 ]8 r4 f, G$ rbegin() ----------> end()
4 t& H/ k' t7 B8 L% {. `% G+ w1 Y  |                   |
* L4 D% f) G& R& w  v                   v" s' g4 W# N0 Z/ I
|0|1|2|3|4|5|6|7|8|9|
2 h+ H5 n. L& u2 c' t9 U+ T0 Z^                   ^' n7 r$ e% Y2 ~' h; @6 {
|                   |
& |0 _# y: P9 [5 {, I$ X& U7 ~rend() <---------- rbegin()& g  x1 E- d  O7 N" n& i" ?
   9 J9 c. {0 o; ]: g3 `$ z5 k
IntVector v;$ P3 L% q- k$ C: n* K9 U. C
for( int i = 0; i < 10; ++i )
4 t, t# j1 F$ E- z- iv.push_back( i );$ |1 F/ G% v7 K- \) X3 |* B
   2 S2 G5 R- A" @' E" u# ~  r5 _3 y
// print 0, 1, 2, ..., 9:) d- ?$ B$ X  I& ?' ?# a+ t+ W" y/ |
for( IntVector::iterator i = v.begin(); i != v.end(); ++i )
0 Q5 v$ H/ q  q::std::cout << *i << '\n';
. e' J1 v( H  Q  x! b! K   
6 h9 s% {* S1 z! i: y0 N// print 9, 8, ..., 0:# G, ]8 q3 t' d* S7 O5 r8 t
for( IntVector::reverse_iterator i = v.rbegin(); i != v.rend(); ++i )
2 |" v+ d, I2 B::std::cout << *i << '\n';& r% O( V) v9 C' [$ m* t
除了使用 begin()/end()/rbegin()/rend() 来遍历 vector 中的元素外,由于 vector 管理的空间是连续的,因此可以直接取地址进行处理:
" c3 l- r- Y5 f# i
. B3 O4 b; |- W- h" o. T::std::vector<HANDLE> handles;
& w) z; i7 t+ O& O3 q+ Ihandles.push_back( handle1 );
7 z& U& k& L' ~5 T/ ], Rhandles.push_back( handle2 );
3 D7 c( V  m* S( a6 \, e" A- U2 {* I' G. ?" _+ ]
1 ?; f3 V3 R$ B! ?( f' j. k' w+ [7 p
::WaitForMultipleObjects(handles.size(), &handles[0],TRUE, INFINITE);5 Q( l& Q3 S. t5 G5 W2 ?/ T
这在与 C 库函数接口时尤其有用。/ p  x$ e" ]% f% h

! T2 b! r' n4 w7 O10. 赋值和交换/ ^  Y; K% }8 m7 \
vector<> 是可以赋值的,这也是一般的“值”类型必须提供的操作:
0 F7 C& b5 `- c2 l2 d" y. Y- g4 T  q3 Y, ]6 j; }) `
IntVector v( 100, 123 );$ B2 {0 ?. T4 n1 ]9 P
IntVector v1;9 r3 Y2 c% z+ {! H  i; Z
v1 = v;" s5 Z( s( n/ m; {  u$ ^
vector 另外还提供了; \9 j8 p  E2 G1 C2 E
) \6 p+ O, O  {( S* A$ `
template<typename Iter>: J7 o) [* @4 i) P, o" l5 y
void assign( Iter first, Iter last );
; r' a5 Q! @0 X; J: h7 l+ {+ qvoid assign( size_t n, T const& t = T() );7 m" s4 u, Y  c7 k! H/ ^
用于赋值:
7 |* {. a. k3 U3 ?
0 Z+ i8 {+ G) M+ Y8 P9 R  m8 }int a[] = { 1, 3, 5, 7 };, d/ u1 k0 R6 T, b  S( a# Q
v.assign( a, a + 4 ); // v 将包含 1, 3, 5, 7.
2 J/ G7 E# V# ^, g! \, u  Cv.assign( 100 ); // 100 个 0。
, G* v  }& F  v还有一个很重要的操作:
5 ?1 e" z& w' d; s* D+ P
; x4 }* @! M& U7 T( V7 Bvoid swap( vector& v ) throw();& E) p+ Y: q" @1 w# G7 N& J; j' U
用于交换两个同类型的 vector 的值。它的特点是快速(只需要交换内部的三个指针),不产生异常。这在写一些保证异常安全的程序时非常有用。
+ Y6 U6 V9 n- `0 A" [' d
( S5 L4 g+ i9 u1 h0 m6 l事实上,swap() 基本上已经被当作类似于 operator=() 的一个“值”类型应该提供的基本操作,::std::swap() 也应该为用户定义的类型进行特例化,调用相应的类的成员 swap() 函数:) |4 Y% v4 D& O8 X8 C( T
' T9 D6 r5 r6 p) C) G9 `9 m
struct MyVal
  d5 e" D0 J/ @# ^. B4 X0 X! z{0 J7 T+ h6 c5 S/ f" Y- L
  // blah blah.6 P+ x9 M, f% u3 D
  void swap( MyVal& ) throw();  q2 G% a+ J4 C9 x2 D6 D3 J
};
1 M, t& Y$ i! o9 T" A( E   
! E- L) @' C( ~- Z8 ?namespace std {( j+ j4 P4 `- j1 a
  template<>
/ a) |5 H. @; W0 h    void swap( MyVal& a, MyVal& b )1 G  [6 [$ O( Q5 P; w
    { a.swap( b ); }
" Y. J7 c9 Y) e' ^% L  m, h}
' `6 l) O+ t6 Q7 v; J关于 swap(),值得专文讨论。这里我们只指出,vector<T>::swap() 是快速的,不抛出异常的,很有价值。4 I- `2 I6 }5 k  q- p

! N# l( O; h6 W# |5 F4 A0 F+ j* L) X11. 使用 vector 时的存储管理策略
6 k2 c7 v" O& \8 h! ~5 J; N2 C从前面的介绍中可以看到,vector 的自动存储分配是指数式的增加存储空间,而且永不缩小已经分配的空间。这在大多数情况下是合适的。 如果应用程序事先知道要用到的元素个数,可以先调用 reserve() 来保留(分配)空间,这样可以避免以后增加元素时不必要的重新分配和元素拷贝:  L- I9 o0 e& V& J- F

; @% }! }% t; U' D7 ]7 LIntVector v;' U) F! t3 h" e: H% d( g2 c9 N
v.reserve( 100 );
8 f7 c- {# N4 e$ ?; r; Kfor( int i = 0; i < 100; ++i ); M( g; m; g6 y' Z. B; g
    v.push_back( i );
$ S9 P) Q* {# }请注意,reserve() 和 resize() 是本质上完全不同的。reserve(n) 保留的是未使用而能够使用的原始空间,而 resize(n) 是真的创建了 n 个对象:4 E# O+ ?" `! y5 b
2 O* |' O& E; c4 p
IntVector v;
$ I( F; [- O# B$ N/ t( W- Y1 k8 Bv.resize( 100 ); // v 已经包含 100 个 0.
" N9 y! C1 {8 u, p7 [  Sfor( int i = 0; i < 100; ++i )6 \. F7 P# B6 s2 L2 z5 ^7 v
    v[i] = i; // 可以赋值
' V, I1 c  i! G, E4 F* [9 o) |. L有时候,一个 vector 可能增长到较多个元素,然后又减少到较少的元素个数,这时,可能希望缩小 vector 分配的空间以节约内存。CArray<> 中提供了 FreeExtra(),但 vector<> 并没有提供相应的函数。这时必须进行复制:7 ^) g# @0 b1 [4 {
4 g( T2 ]6 Z" r, ~
IntVector(v).swap( v );: I! t# ]! c% K( p. Y
有一种看法认为拷贝构造函数同时也复制了capacity(),而标准中并没有很明确地指出这一点,因此更安全的方法是6 X: c& k. t% m, t6 M" A: `5 m

0 }1 n1 l1 Q' C2 c$ [IntVector(v.begin(),v.end()).swap(v);3 a5 o+ _- l, V  }9 b
如果一个 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:09 , Processed in 0.022301 second(s), 15 queries .

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

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