|
|
作者:wangtianxing+ O9 ]: V0 z3 l2 w- k6 F5 G
- y! D0 |& R8 D* [% B原文出处:http://www.cpphelp.net/issue/vector.html2 N, c3 A. \" \) N/ x: X
* ^/ U3 q+ r; n6 `9 b1 {5 q
! v/ O/ X7 U% ]' Y8 n' t, ?8 Z% z; f [, \$ g4 n; Z
摘要: 本文介绍了C++标准库中的容器类vector,分析了它的优点,并且建议在应用程序中使用它作为动态数组的优先选择,而不是MFC的CArray<>等其他类模板。最后介绍了vector的接口和使用时的注意事项。
5 S/ R3 L& n- s* }) D1 F
: {5 t0 K( `" m3 X- {1 u5 d在一些使用 MFC 的程序中,经常看到许多程序使用 CArray<>,由于 CArray<>的设计问题,造成使用它的代码的复杂化,增加了维护难度。因此建议使用 ::std::vector<> 代替 CArray<>。
7 c& w9 N# X D) M' E
: V: z1 d" K5 Y另外,也看到一些程序在用 malloc/realloc/free/new[]/delete[] 等手工管理内存。在应用程序中,手工管理内存是容易导致错误的,应该用 ::std::vector<> 之类的对象来管理动态数组。
5 }0 s X6 X# N( `
: q2 a4 r$ r. b由于 MSDN 中关于 ::std::vector 的内容较少,我们在这里做一些介绍,供参考。
6 ]' f" d. B3 ^: l* y9 }4 t- L" W/ B; G& \4 o) g+ V
不熟悉 CArray<>/WIN32 也没关系,这里提到它们的地方并不太多。
5 B) X, }! X X" i/ v& B8 u, I5 r3 G1 B( _. |6 h
1. CArray<> VS ::std::vector<> ?8 {: T7 K5 Z5 G4 b* C
CArray<> 和 ::std::vector<> 一样,都是模板类,用于管理任意类型的对象的动态数组。都在解构时释放所管理的动态内存。因此都可以用于代替手工动态数组管理。
# f2 F6 X% U- b# S; n3 ^2 T+ C9 A1 H/ H* p
但是,CArray<> 是在 C++ 标准化之前很多年(VC++2.0时代)设计的,当时对 C++程序设计,面向对象程序设计,模板程序设计等技术认识严重不足,尤其是当时对面向对象技术的错误信仰与宣传,造成 CArray<> 的设计有重大错误。
, a/ v& |9 O' B" Y/ P4 Z& f1 X# \" v% f) z* ]
在 C++ 语言标准化以后(1998),以及 VC++ 6.0 出世以后,提供了标准的::std::vector<> 模板,基本上在任何方面都要优于 CArray<>。Microsoft 由于要支持老的程序,因此一直保留了 CArray<>,但显然并没有打算按照新的思想去发展它(至少应该提供operator=(CArray const&)吧)。: r0 X p j, m2 L8 `/ ]% {, K
O% k9 [+ x9 Y7 ~0 {8 U" ?概括起来,CArray<> 与 ::std::vector<> 有以下不同:
* }- ?. [6 ?4 m, Y8 W
# i+ D4 t v' X, }. ^6 Z1) 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<> 没有继承任何东西,只是实现了管理一个动态数组该做的事。
7 X! p1 o( K1 L+ i+ C! O
1 P% B( a( A8 [ p& e" \3 z2) CArray<> 不是一个恰当的值类型,例如下列操作都是不合法的:( B% `/ s1 t$ B/ Z) @1 H
* o& X6 j& h1 {- c" D4 |CArray<int,int> a;
5 p8 Y1 \- I. J( hCArray<int,int> b(a); // error, must use Copy().( U( o0 j( s6 H2 a
b = a; // error, must use Copy().
* i3 o9 T$ D: P, c- o3 e* T6 I% rb == a; // error, you must write your own.
! }( C& d1 N' {! R* [b < a; // error, you must write your own.2 |" {* T4 [' e; I0 x4 k# b! U6 g
与 CArray<> 相反,::std::vector<> 是一个认真设计的值类型,天生是可以拷贝构造和可赋值的。如果 T 是可比较的,那么 ::std::vector<T> 将自动地是可以比较的。 w9 `& y- T7 @" w& \
% r4 l* f! w. g, c8 o此外,由于涉及到四个特殊成员函数;
& M. Y! ~* F3 O& W5 Y6 d8 P
/ U6 U; R: ], q p+ yT(); // 缺省构造函数(default constructor)
( g8 O+ v4 ]4 y. j: M~T(); // 解构函数(destructor)
" |4 T T" { }7 ]! ^T( T const& ); // 拷贝构造函数
' v1 A0 L2 Q+ S, F9 k7 N' ^. GT& operator=( T const& ); // 拷贝赋值函数# _" `2 C# A* e( q5 f
的自动生成,如果使用 CArray() 作为 T 的成员变量,那么上述的四个特殊函数中的后两个将无法自动生成,需要手工写:
8 c3 X7 t4 y5 d& ?1 ~$ X; o0 m5 J3 O
struct T, p& a+ A7 d; w8 C9 z
{) _1 x# G- \. _" I0 q
T() {}
% V/ o( l- c- I5 h, P0 j% b/ a# ^ T( T const& t )7 P) l0 a5 @7 p2 X2 j/ h) A5 ~# |
{! R# Y3 ~9 o9 D! m: o7 w
a_.Copy( t.a_ );* |! [! |; `- M6 ]% y
i_ = t.i_;8 P8 [; _6 C2 p" } P
d_ = t.d_;2 v6 ]! | j2 j# K+ P; _: i y
s_ = t.s_;) o9 o: a. a9 x) c
}, t1 Y1 a3 Y& O; W) h8 h
T& operator = ( T const& t )7 O: @; {: C4 X% z4 \+ k
{8 a: Q2 |7 c5 P% C, u
if( this != &t )7 m7 n' K* F. t E
{$ O1 a: h0 d- R( Z
a_.Copy( t.a_ );4 o7 G9 q- D0 Q( b6 L
i_ = t.i_;
5 j- q8 r9 K% i4 ~& _; } d_ = t.d_;/ X/ H$ S' K4 ~
s_ = t.s_;4 i, R1 B, o8 V1 r! Q
}
# o) P: d0 g7 g- y G( W& O1 b return *this;
( L7 i3 ~7 l) R }
0 E5 X6 m7 `1 E0 G! D; x% a: r5 jprivate:& h( y3 Y) u G# B$ Q# Q
CArray<int,int> a_;3 f% m1 Y& n4 N, k- s7 E
int i_;
7 A8 W( P9 S0 p1 @& t; U double d_;' f3 W) R# E" n
::std::string s_;/ a0 p7 F L3 o; O) N
};6 i1 m( X. q9 z
如果使用 ::std::vector<>:
: r* k1 j% j7 ]) w5 Q3 ~) H7 q
' b5 i V; g5 s0 ^# Gstruct T7 p* H. O) f; Y! [. v) n+ S
{/ a, o( i! X5 Y1 s0 p4 g* C* L
private:0 G: V9 Z6 U$ A! r
::std::vector<int> a_;1 N4 ?" L8 e/ j e# H4 k
int i_;
`0 n9 D( k ^7 B double d_;# `" V1 d* }" w7 I5 u* Y c
::std::string s_;
7 P4 y4 e$ u5 m9 C: g};
3 E% c2 n7 B$ y3 D0 L上面列出的三个特殊成员函数都不需要写。好处是明显的:当你增减 T 的成员变量时,你不必到* y4 ~& i# v4 |& M
T(T const&) 和 operator=() 中去相应地增减。
8 n! i3 t* ?' t4 [4 `7 B, S8 E
% w2 l8 }+ a* n' u3) 没有现成的算法可以对 CArray<> 进行操作,而标准 C++ 里的标准算法大多都可以直接在0 q& b* ~8 k0 S) P# }, J) ^
::std::vector<> 上运行。例如:& L* { b$ u, V1 I8 [ b7 e
8 r: p: D2 n" n2 Y+ d* t9 qstatic int const init_vals[] = { 3, 1, 4, 1, 6, 9 };; a1 x# j! c& D2 Z6 N: n
vector<int> a( init_vals, init_vals + 6 );
# N2 t3 f+ P: z. ^* A*find( a.begin(), a.end(), 6 ) = 5; // 把6改成5
, p( S) y# l* n8 nsort( a.begin(), a.end() ); // 排序。( x7 t4 T; i6 t% }1 t7 a
可以说,CArray<> 的主要设计错误是把一个本来应该是一个简单的“值”类型的东西设计成一个难用的“对象”类型了。所有的“值”的好特性都丧失了,但那些从CArray<>继承的派生类呢?" j8 d, ?6 o% W, W
& v* J, [9 c1 Y, I% G2 |) k6 }CByteArray等的问题与 CArray<> 的问题一样,甚至更多(例如,CPtrArray,永远不要用)。
5 x ]0 S O1 _, M! A) @6 n
4 n1 X& g7 B& h, ] D同样,其他的 MFC container 模板,象 CMap<>, CList<> 等,都有类似问题,都应该用
$ Y( U1 Q* `2 u9 m+ I::std::map<>,::std::list<> 等设计更好的东西代替。# y. \$ |' U+ M+ [& T* n! b, A5 V* E
! F9 }4 U; b: Y/ z# A1 [% x+ x2. ::std::vector<> 在哪里?$ d; H( n" b& t; ?, f
::std::vector<> 在头文件 <vector> 中定义:6 t& e0 W2 O) N. ~3 |' ~" ~% a
: K) @% `, U6 O* g(注意,标准的 C++ 头文件都没有 .h 后缀,有 .h 的文件是与 C 兼容的,或支持老的不标准的东西,象 <iostream.h>。)
2 p+ Q% l2 s. {+ i5 _8 E7 J
: S! t3 s/ U# H( N4 Y$ w9 mnamespace std ; I( j2 W4 s8 B( N3 x8 G4 V
{8 t4 `% o6 S- |7 B0 W; |/ f3 F7 E/ W2 G3 @
template<typename T, typename A = allocator<T> >
^% k0 j0 Y5 L2 x J struct vector
6 @+ \5 v# ^ V; ]7 O7 z {
3 u( v* w! g6 y/ x6 @" ?5 z/ B( v/ ? P // 具体内容稍后讨论
& A9 A0 M6 m- C2 U- x };
8 p9 T7 ? o3 w6 P( J* Q: p
! H O7 l" N$ k( @4 i4 ? k7 ^9 D# f1 o
template<typename T, typename A>! l7 P9 a$ D+ S2 [4 u7 R
bool operator == ( vector<T,A> const& a, vector<T,A> const& b );
# v) M+ X% ?) ^1 |, G- d template<typename T, typename A>
. Y5 \8 G* ^0 [: I) X4 T bool operator != ( vector<T,A> const& a, vector<T,A> const& b );8 d1 k" ]1 L- t( L
template<typename T, typename A>% I/ v% H5 \' ?9 Q
bool operator < ( vector<T,A> const& a, vector<T,A> const& b );9 {6 q8 G* D; b' J5 N' p6 m3 k( {
template<typename T, typename A> V3 A0 P4 _$ S3 c- |2 q2 O3 H
bool operator >= ( vector<T,A> const& a, vector<T,A> const& b );
; [ W) }1 U& S" M template<typename T, typename A>( K0 n; M% v8 ? P% e
bool operator > ( vector<T,A> const& a, vector<T,A> const& b );3 ]4 s% f" V! d$ v
template<typename T, typename A>+ p( v6 e. s7 {" A7 Z& z
bool operator >= ( vector<T,A> const& a, vector<T,A> const& b );
; e$ J5 X3 I' G j7 Y; I5 N}. y2 y; X: q7 W+ X% t [- _
vector<> 定义在 namespace std 中,使用时为了减少击键次数,通常使用一个类型定义缩短类型名称:/ K( E8 z; {5 E- }% C; E
/ w0 g- q3 N6 Q$ C+ B6 d5 {3 L#include <vector>0 f+ O" T. G- z# {" \4 d9 b% z
typedef ::std::vector<int> IntVector;* }/ S' ]3 S( j" @% V9 s F
IntVector a;
& Z+ I a" g% J4 v- z4 w' [0 @IntVector b( a );0 a& Z7 `- y+ [* g j6 J
IntVector c;
) f1 k3 f1 ?. d1 }( n% X1 J1 L' sc = b;6 p m7 Y- \% T# h2 t
assert( a == c );. v* `% l4 c; L7 O2 T
请注意 <vector> 中定义了六个 vector<T,A> 的比较函数。这些函数只在真的用到时才会被实例化,才会要求 T 也提供 operator==() 和 operator<()。
3 `5 A4 e- \/ b2 x+ c, Q% J( N8 \) _0 f; G; \
另外,A = alloctor<T>:用于提供一个用户定义的存储管理类。由于这个参数很少用到,而且在 VC++6 的实现中有问题,不能用,因此以下的讨论忽略这一部分的内容。) C; x( j# M+ q+ b" b
/ b' A* v0 [ n& v, k' z3. ::std::vector<> 中的类型定义
! }9 j% |& a: s7 X: j, g; Yvector<> 中定义了一些类型,下面只列出常用的:
. G6 t1 \9 r* W |7 |/ f' @& P% y! |3 ]" E" t: o
typedef T value_type;3 T8 w8 r. R& o
typedef T0 iterator;7 b( T; Y; b3 V
typedef T1 const_iterator;7 S6 q* h2 D( I
typedef T2 reverse_iterator;/ a) q" U! ]4 H% _4 g
typedef T3 const_reverse_iterator;% @+ b3 r! v" \0 b% n" h
" }& F/ A) S( N
value_type 就是 vector<T> 的元素类型,也就是 T。当写通用的算法处理任意类型的 vector<> 或其他容器类型时是很有用的。! ~# s& x3 v3 C; a/ P$ V( Y
% i, ^; C ]! W0 A9 ^# Iiterator/const_iterator 是两个 vector<> 的实现定义的未知类型,用于访问vector<> 中的元素,类似于 T*/T const* 指针,他们的区别是一个指向的元素可被修改,另一个只可以读:( r1 K$ l2 j( T6 _
% }# D8 N5 \: o% `8 H3 @typedef ::std::vector<int> IntVector;; h& Z; b) I! Z+ z' P
IntVector::iterator iter;
- f- W( Q6 U0 m' c0 ZIntVector::const_iterator c_iter;
. A s% H1 W" ?/ X/ w// ...
# `! u( _ Q9 f2 z# a++iter; iter++; // ok: increment, post-increment.7 P* i; s5 G0 p1 D; d2 I
--iter; iter--; // ok: decrement, post-decrement.
$ X# E6 {3 _/ s. G++c_iter; c_iter++; // ok: increment, post-increment.
+ [" s0 F$ {2 l1 S8 h+ ~--c_iter; c_iter--; // ok: decrement, post-decrement.
4 W" q; P, j6 j$ w*iter = 123; // ok.$ {3 ]3 A, \$ I8 a4 h
int k = *iter; // ok.# ?6 f, S2 b: s
k = *--c_iter; // ok.
6 f6 `7 {7 t. ]8 T; ]4 I*c_iter = k; // error." q5 m, }* w; F' r# Y% F2 x
c_iter = iter; // ok: iterator is convertible to const_iterator.* ?4 |4 A6 O5 i! H
iter = c_iter; // error: can't convert const_iterator to iterator.
& a v0 |, t1 Y R8 u/ |! w' r在使用上 iterator/const_iterator 和 T*/T const* 基本相同,事实上有些vector<> 的实现里就是用 T*/T const* 实现 iterator/const_iterator 的,但又不可以把 iterator/const_iterator 当作真正的 T*/T const*:
: V& P& k2 J1 R# H9 L# C' k$ z7 u w/ i2 x4 q* ]2 ~
T* p = iter; // may fail to compile.9 O/ I9 ^4 e* b" r
T const* q = c_iter; // may fail to compile.
! g$ o. |- |) y. E5 j! Oreverse_iterator/const_reverse_iterator 与 iterator/const_iterator 类似,但以相反的次序(从尾至头)访问 vector 中的元素。
( G) R% V1 |( T6 l4 Z7 Y( T
/ A ^' T! O2 N各种各样的 iterator 在 STL 中有特别重要的意义,但这里我们不做具体介绍。只要理解通过 iterator 可以访问 vector 中的元素,大概相当于一个指示位置的指针就行了。. b+ R T0 Z; B+ z( X1 a1 m
& E& C. T1 r/ J3 x, u. z
4. ::std::vector<> 的构造
. ?" r5 ?% o- X3 m: M6 \. Fvector<> 提供了以下构造函数:(忽略 allocator 参数)
( y$ p) K( z |! P, G. a. H! }
9 r6 v% c; q# tvector();
& a, [: {" {: ~8 [7 Ovector( size_t n, T const t=T() );
5 J5 r$ h S3 h6 X" ^ Yvector( vector const & );
0 S6 l6 c+ j9 R/ k4 R6 L7 Y9 X8 Ivector( const_iterator first, const_iterator last );
0 U6 X5 t! Y" \. ]1) vector();* O( k" U3 a( Z9 P
5 m [$ b7 f7 @" m, D8 l
构造一个空的 vector,不包含任何元素。5 A+ k$ ?2 _4 E& R/ ~, [3 H+ g
/ Q. o5 [2 o8 J2 AIntVector v1; // 空的整数向量。3 M; ^# `/ @7 U. w* z `
2) vector( size_t n, T const t=T() );
[( j& w$ h7 X
: v; D8 K1 ]0 ~: V: w+ J1 K9 t构造一个 n 个相同元素 t 组成的 vector。如果不给出 t,那么将用 T() 做缺省值:% @ x1 B/ T3 Y: k
4 Z. g( t/ i) q7 @IntVector v2( 100, 1234 ); // 100 个 1234.
. H7 m* x; p* B+ Q5 A' ]8 W3 MIntVector v3( 100 ); // 100 个 0。: t2 ~- f% k, p! w- M$ @2 `# u
3) vector( vector const& other );/ O. P9 z5 _3 J% r9 K5 z/ Z* O
9 R$ o6 a3 |* p4 e
复制构造函数,复制 other 中的内容:+ l- ^* N* s& f
$ X' l7 Z9 G% @# m oIntVector v4( v2 ); // 100 个 1234。
3 Z$ J' n; B. L7 }. D& S U4) vector( const_iterator first, const_iterator last ); |7 K1 `5 O+ d
$ I- l: [ i) f N2 B: s# ]3 W事实上,这个构造函数应该为' q6 G% @* x, L1 [( a/ c
6 }2 ~# ~6 d2 n/ u9 R$ F7 otemplate<typename Iter>
! A7 W2 d% |1 _/ K( v9 _/ M vector( Iter first, Iter last );$ T) q: d" C, o5 a% U
即拷贝任意的序列 [first,last) 到 vector 中。由于 VC++6sp0 编译程序的限制, Iter 被换为 const_iterator 了。不过,碰巧 const_iterator就是 T const*,所以可以如下使用:* W2 |7 R$ A( [
, l- }$ ]. r* J) |: E% D5 V3 G/ ?
int a[] = { 1, 2, 3, 4, 5 };1 \+ r7 q0 d& L( o
IntVector v5( a, a + 5 ); // {1,2,3,4,5}$ z9 p+ x! ^) E
IntVector v6( v5.begin() + 2, v5.end() ); // {3,4,5}
& S# }: j5 J; m+ k5. 访问 vector<> 中的元素
( {3 b' V- @* n: b& k; g以下成员函数/运算符用于访问 vector 中的一个元素:
( o" W; g5 G# a3 q. R+ s5 M9 U
. `; |7 a. Z% @2 v1 [+ tT& at( size_t n );
0 _# L% W9 @7 K0 p8 h$ P5 q' d, yT const& at( size_t n ) const;
( @% q6 D7 ]2 J8 y: x. m# g! o6 S+ k' @T& operator [] ( size_t n );9 ?+ ~3 r; w3 Z \7 k2 r
T const& operator [] ( size_t n ) const;
, J% f5 g$ |4 }" j% cT& front();
# I* R& Q9 F1 d: t; u% z( YT const& front() const;* J" S4 U, l% Z/ l
T& back();+ j( j& D2 n/ u& x: ~
T const& back() const;
9 _6 ~$ U! P7 _3 B/ c/ Y. H+ w请注意,由于 vector 是一个“值”语义的对象,所有的操作函数都必须严格保证 const 的正确性。所以,所有的元素访问方法都有 const 和非 const两个版本。+ n% L& ?# M* z* h( y
5 `( l3 W7 U, w, z3 Q$ [
at(n) 和 operator [] (n) 都返回下标为 n 的元素的引用,他们的区别是,at() 进行下标越界检查,若发现越界,抛出 range_error 异常,operator[]不进行下标检查。
* I o! ?$ F; P6 i# q
& x# p6 ^+ d0 H& \front() 返回下标为 0 的元素的引用,back() 返回最后一个元素的引用。- p( r( ]- K* @: b/ S
: E& W2 V8 s% g1 Fint a[] = { 4, 1, 4, 1, 5, 8 };7 i! M# k$ \7 \! n9 K! O. ?2 B: E
IntVector v( a, a + 6 );! `7 o3 B" u ?: X6 K
// 使用 front(), back():
/ a- t3 f5 U: z% D# Q/ V0 W U$ Wv.front() = 3;( q$ i) d+ @: B P. u3 |
v.back() = 9;3 r0 J& [0 Y/ I" W; N7 a1 j h
// 使用 operator [] ():
) m2 ]5 \7 s. x. {for( size_t i = 0; i < v.size(); ++i )8 o0 F# K& g# ~* b4 @4 P. D) c- u! a
::std::cout << v[i] << '\n';4 d7 d3 f( y& w, N( t7 j& U! ^
6. ::std::vector<> 的存储管理: P5 `! K! c7 }
以下成员函数用于存储管理:
# K) `; [* Y$ O# Y8 q$ X8 ~/ s( o y9 o& l1 L/ r
void reserve( size_t n );
9 J. l3 k. r" z3 Hsize_t capacity() const;( J4 U* O4 Z; ]( u1 m" y9 m
void resize( size_t n, T t=T() );
( B% i' \' x4 q* {+ X! W( g' Ivoid clear();7 I( }2 D5 L% o
size_t size() const;* Y* F7 @ C! v4 P8 D1 P) b
bool empty() const { return size() == 0; }. h: T, R- d s6 S- H- W" W' k
size_t max_size() const;
) D- K" l; V5 V/ Q" D( j
( O( i( V J+ B另外,push_back(), insert() 等也涉及到存储管理,后面另行介绍。
9 x% `% ^$ p F9 V0 l
6 f+ B) f9 D6 \) u# x1) max_size()( Q% V$ ]0 Z' t2 M/ y
! F# r& P2 \3 h8 x' o ^; M- Q& o0 G
返回 vector<T> 理论上可以装的最多 T 的个数。这只是一个理论上的数字, 大概是 4GB/sizeof(T),没有多大实用价值。在程序中不要用。( r2 O2 {" t1 V* J. L
8 H$ ^/ F8 j7 G; E8 X) j3 K7 r @5 d/ f2) size()
1 \6 e9 @8 {+ g: F ~& n' ]1 {- ^. w: ^
返回 vector<T> 中实际装的 T 的个数。相当于 CArray<>::GetSize()。% z1 G$ p9 k% d3 w" \
" z6 i. m; O K+ C- U x3) empty(): L9 m( {5 m2 Z. S
7 M& g3 Q, q' k0 |如果 vector<T> 中没有任何 T 对象,返回 true。也就是返回 size() == 0。
# H% [2 f- Y6 T2 A! t4 \- k7 _6 [7 |& x
4) clear();) ?# Y$ C( H0 F0 y3 Q6 A
3 j* Q* ~& V- v3 i清除 vector<T> 中的所有 T 对象。执行后 empty() 返回 true。大致相当于 resize(0),但不要求 T 可被缺省构造。相当于 CArray<>::RemoveAll()。" c& R& |2 H% _) r3 U5 j8 i
* R8 }7 d& L3 o& b2 q2 O* K) t. K- ]5) resize( size_t n, T t = T() );
/ f9 r; |" n0 L
$ M1 ~: R0 v' m* S2 m4 i将 vector 中的元素个数设置为 n,n 可以大于 size() 也可以小于 size。如果 n 小于 size(),那么 vector 中下标为 n..size()-1 的元素都将被解构。如果 n > size(),那么将在 vector 的后面新增加: K/ S1 G6 d3 i8 Y5 }2 x0 o% @' M
n - size() 个相同的元素 t。在增大 vector 时,可能发生存储再次分配。总之,调用resize( n, t ) 后,(size() == n) 成立。
$ I+ x, W/ j' V& ?+ ^* r; m9 l+ I$ h9 D5 w( S' n6 n: L
请注意,如果调用 resize( n ) 不带参数 t ,那么 T 必须可以缺省构造。
U$ m% u, O$ j- Y3 B4 V$ ]+ m0 r7 Y
+ _0 c) |* |9 U+ w6) reserve( size_t n );
' t; j) L# S ~! c+ `4 S3 h5 F m& b; c2 K
事先分配至少可以保存 n 个 T 对象的空间。调用后 (capacity() >= n)成立。
* L' @' V& S4 ]$ _; T. Z7 }- {- Y
9 a+ h4 ^6 ^- ^( _$ c2 @7) capacity(); Z- j4 f2 }: K. K
^8 N- c2 c: g/ r9 |2 q
返回已经分配的存储空间够容纳的 T 类型对象的个数。后续的增加元素操作(如 push_back(), insert())如果增加元素后 vector 中的总元素个数不超过 capacity(),那么 vector 的实现保证不重新分配存储空间。
1 T8 X' n1 X& c# @' Z% c0 \) T* K. x% j0 H6 j& A5 h% O
vector 管理的动态存储空间是连续的。执行操作8 [' Z# j2 A$ u+ Y& X
6 s3 J& K+ S% N5 `4 m8 w) HIntVector v(7, 1); // seven ones.: [ B& `, R- w$ A1 a
v.reserve( 12 );/ ?; X# _" \8 g- I2 \
后,v 的状态可以用下图表示:) h! ~9 f/ X* S8 w5 s6 L) `
. z9 w3 I) N) M# ?) |. C( ^ /--size()---\
# p3 B- Y* G3 K|1|1|1|1|1|1|1|-|-|-|-|-|
, y# [; z; h- _% |" r8 ]0 o2 B+ Z \--capacity()---------/
) A% K# ]. B4 S2 R/ h$ f; M" [其中,1 是已经构造的 int 类型的对象,- 是可以构造一个 int 类型的对象,但还没有构造的原始空间。再执行
6 |1 h7 [8 K. ]3 p- e7 N3 q9 m8 D' O4 l/ @. R) N
v.push_back( 2 );1 |& }$ O* M' K& x, I4 c
v.push_back( 3 );
7 O7 q, o; e# Y/ ^后,v 的状态可用下图表示:
$ e+ T3 A! C; N9 C# t9 Q: `/ V( Y5 d0 T+ Y
/----size()-----\
5 i3 M' s/ L W6 @8 ~' @4 S' C|1|1|1|1|1|1|1|2|3|-|-|-|' Y8 F: H$ I2 k' a8 Y1 P
\----capacity()-------/
2 a# `: C; {0 c. e, k9 O执行 resize( 11, 4 ); 后:
6 Y. {/ w% g2 a+ H" S( ^' {" r& F7 L! F+ j/ q7 i7 [! A2 P/ ?
/----size()---------\8 u- z6 H2 Z$ |# \
|1|1|1|1|1|1|1|2|3|4|4|-|
( X9 @7 H3 p* q5 _9 `' @5 P! z \----capacity()-------/ m& {% P. `2 u
capacity() >= size() 总是成立的。对于下标为 [size()..capacity()-1]的未构造对象的存储空间,是不可以访问的:- G4 u$ b2 t6 K
5 H: w$ p8 _; X
v[11] = 5; // undefined behavior - anything can happen.
9 p2 p$ s- T4 D1 M+ D7 |7. 添加元素到 vector 中8 S7 {' N& e8 L7 i$ V& I9 x
下列操作添加元素到 vector 中,并可能引起存储分配:' U7 w$ |! @4 X" |9 Y! x' z
2 z) C7 I) [: V2 Pvoid push_back( T const& t );
* e# e) ?2 p0 \4 c' Z4 `9 G( y( Tvoid insert( iterator pos, T const& t=T() );3 k: W4 _6 N( ]; G; D( ^$ M2 h
void insert( iterator pos, size_t n, T const& t );+ Y. n8 @9 j6 y" ?4 ^ g
template<typename Iter>5 o3 f1 P% F7 T4 c2 r
void insert( iterator pos, Iter first, Iter last );5 y0 Q1 v& P+ B, {2 Z1 x, X1 ~- x
push_back() 是把一个元素添加到 vector 的末尾。insert() 是把一个 t,或 n 个 t,或从 first 开始到 last 结束的一个序列插入到 pos 指示的位置之前。
) U/ @2 `6 Y: m1 B, j5 D0 |' g1 _. _! B8 Y8 [
当插入元素后 size() 将会大于 capacity() 时,将引起自动存储分配。vector 将会分配一个比需要的存储区大若干倍(通常是1.5到2)的新的存储区,把老的元素拷贝过去,同时完成添加或插入,然后释放老的存储区。! T# u+ w* S# J$ s
% r) J2 @" }( T% V+ i/ i9 z这就是说,vector 自动存储分配的空间大小是指数式增长的,这可以保证多次添加元素到 vector 中时,平均用时是接近于常数的。
) \4 D: H( j/ q; ^* e( d! u) l7 R- F& h
IntVector v;
7 U" U9 l; P$ \3 H3 |! q6 z0 `' y ~ 8 A2 r7 V# _8 k4 Z1 h* }3 `* K1 b$ p
// add 0, 1, ..., 99 to v:
: t) c; o# ^/ {6 Dfor( int i = 0; i < 100; ++i )
% ^- ^* U6 G0 b) B5 i1 `5 b% \v.push_back( i ); M( d7 E% t: ` L' ?1 K" O) ]
+ a0 X3 ~/ j) P// append 9, 8, 7,..., 0 to the end:
) m- D3 Z K# m0 T+ Pint a[] = { 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 };
. @- m, O! I! k0 O# o0 s ]v.insert( v.end(), a, a + 10 );2 Z( y7 x+ p' f( I# \% e
8. 删除元素( o" H7 ?9 S4 L: }6 K
下列成员函数完成元素删除:
/ B) W* h* }" I9 V5 M2 G5 i! k+ X5 g2 N. X2 c8 g c) w
void erase( iterator );
- C% B. n1 d4 o# T: ~void erase( iterator first, iterator last );/ r' o' r2 K5 _( {- E
void pop_back();; H) Y; n2 }+ y
void clear();
9 `8 J5 o0 [" s1 y这些函数分别删除一个,一串,最后一个,或全部元素。
" t1 H' \6 L% a9 E1 Q ~/ \6 k* \- `, Y$ K$ s
IntVector v;
# m4 {( _9 S Vfor( int i = 0; i < 100; ++i )
0 _) Z: [9 }1 U: K: t4 h; A v.push_back( i );$ t$ ?6 |# |9 l( H& V# M6 \( S
0 j) m }! w4 K4 x! y! c2 l( J. ~// 删除 50, 51, ..., 89:# t& k# ]- x B( \: s
v.erase( v.begin() + 50, v.end() - 10 );3 U i) x# G: B& v3 v7 j5 {- Z
; i) ]) V1 R* f# \) d// 删除 49, 48:% ?3 t6 z, O# _9 m2 ~0 d9 a+ X7 g
v.pop_back();
9 v, J! }' q7 \2 O& F7 Mv.pop_back();
' m/ v7 `/ z _; v5 b! a
! l0 E4 D4 @4 W3 F// 全部删除:/ R: Q2 H/ j: v. m8 ]3 q1 }7 H8 B6 ~
v.clear();/ A& S, s5 ^: j1 R' Y3 |2 Q
注意,删除操作不会引起存储分配,因此 capacity() 不变。: @" P- d8 w; V; m. L+ P9 c$ y
9 T4 Y4 J! }9 m0 ]9. 作为序列访问 vector 中的元素
. Y/ g4 q7 H' k7 g, ^7 {! }, ^4 ~序列(sequence)在 STL 中是一个非常重要的概念,所有的容器类型和算法都涉及到,而且所有的算法都是建立在“序列”这个概念之上的。
3 U( I: g! l: X$ c v' ]; m& S( O
+ \0 I; n' e2 ]) t; T5 F, r“序列”是一个线性结构,由一个指示其起始和一个指示结束的叠代子(iterator)来决定。如果 first 和 last 是某种类型的叠代子,那么经常用[first, last) 来表示一个序列。注意,first 指向的元素是这个序列的一个元素,而 last 指示的是这个序列最后一个元素之后的位置,可能根本没有元素可以访问。这种半闭半开的区间表示是整个 C++ 标准中的约定,而且确实可以简化程序。
6 y. V' s/ |0 A2 Q( u& b5 n) x' l; D Y Z' n* W# _/ d- L
叠代子是传统的 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 的要求。/ C5 U& G; p7 U% _- S
. g0 T; F( h, k8 N1 |& ^" Ivector<> 中定义了以下函数用于获取被控制(管理的)序列(动态数组)的各种叠代子:
, ?$ S/ R# K! {2 k" @* H O5 j' O' k! v
iterator begin();# e" p9 i2 |9 k, ^
iterator end();! X; G3 _% K& K2 Q6 Y
const_iterator begin() const;
% n c0 Q. o5 j# Kconst_iterator end() const;
& G' I4 W6 s3 h' W- ]0 M. Greverse_iterator rbegin();
, d/ O5 a$ k/ B/ treverse_iterator rend();
& d6 |2 V7 h& D# w" T4 dconst_reverse_iterator rbegin() const;
3 X+ t( b- f( W8 L: ]' u0 Fconst_reverse_iterator rend() const;
8 r# S& B" r2 T* \" q, t" T这里我们不讨论叠代子的一般概念,只举几个 random access iterator 的例子:0 z& V5 L0 L4 v$ |+ X/ d
$ B; W$ `5 g {9 J/ Gint a[] = { 1, 2, 3, 4, 5, 6 };( F* \' g2 s/ W( A ^! [
[a, a + 6) 是一个随机访问序列,指示了 a[] 中的所有元素。这里叠代子的类型为 int*。
8 s6 i: ~" Q5 ?+ ~4 D7 t3 t0 t
( F& k; V1 R% D[a + 2, a + 4) 也是一个序列,指示了 a[] 中的 3, 4 两个元素。叠代子的类型仍然是 int*。" \" E: N+ _& z, Y
6 {2 a7 M& _% W: z
IntVector v( 100, 1 ); // 100 个 1。
; Q5 u% z- E0 b. j; R[v.begin(), v.end()) 是一个随机访问序列,指示了 v 中的所有元素,叠代子的类型是 IntVector::iterator。2 E6 v6 i+ \; o& \% \$ X. s
. M- Y: x2 |+ g ^$ R0 s[v.begin() + 10, v.end() - 20 ) 也是一个随机访问序列,指的是 v 中除了头 10 个和尾 20 个元素外的其它元素。
1 r1 ~$ D! ]& t/ Q5 j% [
7 V4 K! s& {4 Z( r& d7 {[v.rbegin(), v.rend() ) 是一个随机访问序列,指的是 v 中的所有元素,但与 [v.begin(), v.end() ) 不同,这个序列是从尾到头遍历所有元素。+ S( e3 H! U. h( H2 W8 g
0 F M$ Q5 m. R" s" v. T
[v.rbegin() + 20, v.rend() - 10) 与 [v.begin() + 10, v.end() - 20 )指示的元素相同,但遍历顺序相反。8 e7 P' x& r9 h% H3 m& t
6 Z8 C A/ u+ m3 r. A
下图是有十个元素的 vector 的 begin()/end()/rbegin()/end() 的示意:8 t, m9 J0 p" p
4 H: P8 K3 E7 K4 v
begin() ----------> end()
- h4 |; O2 n. h | |
% m u+ @, G7 J% r5 F9 C5 ] v v" d" M f+ n# B! s. j6 D' B: v
|0|1|2|3|4|5|6|7|8|9|1 N5 `' O* k/ a c5 w1 W
^ ^$ r0 K$ @! P! }5 @
| | ]; X7 z2 ~: M) `2 K t7 L
rend() <---------- rbegin()
0 |7 A8 F; X- h$ P* c) y - N( G% W+ Q: C- R, g
IntVector v;) A# B# F/ U0 n; t" u$ J& S
for( int i = 0; i < 10; ++i )9 r* J- n1 m0 X/ m
v.push_back( i );% _" R/ r2 |- y# Q9 b
8 V6 ?( J# A! W5 t: a( O) C// print 0, 1, 2, ..., 9:2 j$ \% `( l; T2 l+ u: J
for( IntVector::iterator i = v.begin(); i != v.end(); ++i )
# B8 J) K8 z* R- f) U::std::cout << *i << '\n';( P) Q- F# y' S# q) }0 ^9 m
6 M$ K% {: Z' ?, R
// print 9, 8, ..., 0:
5 y9 E& T5 i8 d8 i0 ]6 `$ tfor( IntVector::reverse_iterator i = v.rbegin(); i != v.rend(); ++i )9 J9 d# I$ z) g
::std::cout << *i << '\n';+ f& u5 L2 O0 X
除了使用 begin()/end()/rbegin()/rend() 来遍历 vector 中的元素外,由于 vector 管理的空间是连续的,因此可以直接取地址进行处理:
5 V2 b8 F% v/ V- I1 [1 Z: D* O O! o) B7 g! h: X: C7 s- ~
::std::vector<HANDLE> handles;
x$ n2 d. ~1 C6 p! Shandles.push_back( handle1 );/ U- g/ o0 c9 B5 R& C; L8 g1 q
handles.push_back( handle2 );( ^* X3 C: g! {: }; s7 r! E$ h
+ D Q( B+ T4 f; Y3 D5 v8 C
- u" ^+ j1 r& m; W3 d: b' L9 M. M* ^::WaitForMultipleObjects(handles.size(), &handles[0],TRUE, INFINITE);- S' r, F' d) x9 g
这在与 C 库函数接口时尤其有用。& d# S9 d( D* i5 {$ V0 n5 z
- {8 f! b* H* t) E; Z. K10. 赋值和交换+ V. @: O6 S! C& }4 P0 }+ Q/ k6 J& }
vector<> 是可以赋值的,这也是一般的“值”类型必须提供的操作:" ~8 w- b$ M9 V9 F- p; s' P
- F( C# k5 w5 {" x9 [+ c# z, XIntVector v( 100, 123 );
5 a9 ]/ R- L$ `) n: DIntVector v1;
5 m7 k' a ^2 ?" l5 _5 Vv1 = v;
3 {. [/ H$ V+ p& Yvector 另外还提供了0 k, K/ o* i8 p4 W j1 E j
6 |& L4 v! `" [0 P" R9 u$ O- xtemplate<typename Iter>
+ y: M( S; H& ^, m1 r8 bvoid assign( Iter first, Iter last );6 O3 z& o/ G. H* t* G- m
void assign( size_t n, T const& t = T() );
) v) x1 G2 L) v0 q- q% B) S! k用于赋值:
9 `6 m" s: A1 [ L" Q! F- h6 P. a+ l, K6 u9 ~
int a[] = { 1, 3, 5, 7 };: h0 L. L# |/ i% i T7 t8 I
v.assign( a, a + 4 ); // v 将包含 1, 3, 5, 7.
! A, D6 G$ @& U4 T' T6 o/ {* F; Sv.assign( 100 ); // 100 个 0。$ H( L, y o6 [" W; ?8 p5 H8 o- \
还有一个很重要的操作:. E( T% y$ u4 _0 E% J, O9 z$ A
: M( E' {1 g* p9 tvoid swap( vector& v ) throw();
# o; b' Z- ~ A用于交换两个同类型的 vector 的值。它的特点是快速(只需要交换内部的三个指针),不产生异常。这在写一些保证异常安全的程序时非常有用。
2 X; R! x4 ~: N. e7 O0 @' _+ F# a( c) h$ S% k+ U- n- A
事实上,swap() 基本上已经被当作类似于 operator=() 的一个“值”类型应该提供的基本操作,::std::swap() 也应该为用户定义的类型进行特例化,调用相应的类的成员 swap() 函数:
3 C; g9 \" R2 l" b# O6 i! ^ Z" C$ d# U- k- a# V
struct MyVal5 t" {# m- w( x9 |8 I3 F- T' {
{
% n8 `% B/ r5 Q) D& x2 n$ {! y // blah blah.
$ V+ [4 s! f3 ] void swap( MyVal& ) throw();, P1 ]; q, ~# k7 G9 S; ^2 E3 q) Q; D
};! g( h7 N& d2 t
% S- Q4 k) t8 _4 ^1 ~4 V1 l) W8 g
namespace std {
7 _$ l2 h% v0 K% ^; H9 P4 \ template<>
0 \: M. h6 C4 N3 Y0 p6 s void swap( MyVal& a, MyVal& b )0 M1 W; f w' t3 v* w9 s
{ a.swap( b ); }/ o ?; J5 q% B F1 O* P
}, k1 { E7 C, m2 f. d
关于 swap(),值得专文讨论。这里我们只指出,vector<T>::swap() 是快速的,不抛出异常的,很有价值。
. G L" d0 |- u# y5 g$ n& D
0 E/ t" O1 x0 f" M7 ]11. 使用 vector 时的存储管理策略% H% D1 M8 V ^' m `
从前面的介绍中可以看到,vector 的自动存储分配是指数式的增加存储空间,而且永不缩小已经分配的空间。这在大多数情况下是合适的。 如果应用程序事先知道要用到的元素个数,可以先调用 reserve() 来保留(分配)空间,这样可以避免以后增加元素时不必要的重新分配和元素拷贝:
: D* O; S: B( Q3 H5 \+ ]3 a- y6 [- t
IntVector v;5 l+ |6 g" E, C6 t3 y$ A H
v.reserve( 100 );9 j3 H2 K; h2 C- D) p
for( int i = 0; i < 100; ++i )6 Q7 k1 E5 B' u" k5 p9 q
v.push_back( i );
( Q* b' R" A9 O5 ?$ W, ^请注意,reserve() 和 resize() 是本质上完全不同的。reserve(n) 保留的是未使用而能够使用的原始空间,而 resize(n) 是真的创建了 n 个对象:% \- _ z( F$ _9 ?+ t
0 V# z& S" H. I* \& n3 g1 }: d8 y
IntVector v;9 R1 `5 z# m" S
v.resize( 100 ); // v 已经包含 100 个 0.% J( U/ r0 y+ w. e5 G
for( int i = 0; i < 100; ++i )! J* Q2 V; T; j# a" a
v[i] = i; // 可以赋值
- Y0 T1 w3 U& n8 b有时候,一个 vector 可能增长到较多个元素,然后又减少到较少的元素个数,这时,可能希望缩小 vector 分配的空间以节约内存。CArray<> 中提供了 FreeExtra(),但 vector<> 并没有提供相应的函数。这时必须进行复制:& C) T5 N3 s5 i% o! l Z7 x
( J0 I0 [. C9 ^. U" }IntVector(v).swap( v );+ q+ Z9 k* o- Y- Y4 _: q+ T
有一种看法认为拷贝构造函数同时也复制了capacity(),而标准中并没有很明确地指出这一点,因此更安全的方法是* a% e; d% x% M$ ]" o$ C4 y5 L
& M2 D' x' I2 k( L5 m
IntVector(v.begin(),v.end()).swap(v);7 b+ \( l" T- z }3 r
如果一个 vector 中可能要存储的元素个数较多(例如,超过100个),而且事先无法确定其个数(因此无法调用 reserve()),那么通常 vector 不是一个恰当的数据结构,应该考虑用 ::std::deque<>。与 vector<> 相比,deque<>不保证背后的存储空间是连续的(因此象上面的WaitForMultipleObjects()中的应用不能用 deque<HANDLE> 代替),但有较好的伸缩性,还可以在数组的前端用 push_front()/pop_front() 增减元素(hence its name, doubly endedqueue)。 |
|