C++中怎么创建一个容器类?

2024-11-23 23:26:31
推荐回答(2个)
回答1:

1. CArray<> VS ::std::vector<> ?
CArray<> 和 ::std::vector<> 一样,都是模板类,用于管理任意类型的对象的动态数组。都在解构时释放所管理的动态内存。因此都可以用于代替手工动态数组管理。
但是,CArray<> 是在 C++ 标准化之前很多年(VC++2.0时代)设计的,当时对 C++程序设计,面向对象程序设计,模板程序设计等技术认识严重不足,尤其是当时对面向对象技术的错误信仰与宣传,造成 CArray<> 的设计有重大错误。
在 C++ 语言标准化以后(1998),以及 VC++ 6.0 出世以后,提供了标准的::std::vector<> 模板,基本上在任何方面都要优于 CArray<>。Microsoft 由于要支持老的程序,因此一直保留了 CArray<>,但显然并没有打算按照新的思想去发展它(至少应该提供operator=(CArray const&)吧)。
概括起来,CArray<> 与 ::std::vector<> 有以下不同:
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<> 没有继承任何东西,只是实现了管理一个动态数组该做的事。
2) CArray<> 不是一个恰当的值类型,例如下列操作都是不合法的:
CArray a;
CArray b(a); // error, must use Copy().
b = a;    // error, must use Copy().
b == a;    // error, you must write your own.
b < a;    // error, you must write your own.
与 CArray<> 相反,::std::vector<> 是一个认真设计的值类型,天生是可以拷贝构造和可赋值的。如果 T 是可比较的,那么 ::std::vector 将自动地是可以比较的。
此外,由于涉及到四个特殊成员函数;
T(); // 缺省构造函数(default constructor)
~T(); // 解构函数(destructor)
T( T const& ); // 拷贝构造函数
T& operator=( T const& ); // 拷贝赋值函数
的自动生成,如果使用 CArray() 作为 T 的成员变量,那么上述的四个特殊函数中的后两个将无法自动生成,需要手工写:
struct T
{
  T() {}
  T( T const& t )
  {
    a_.Copy( t.a_ );
    i_ = t.i_;
    d_ = t.d_;
    s_ = t.s_;
  }
  T& operator = ( T const& t )
  {
    if( this != &t )
    {
      a_.Copy( t.a_ );
      i_ = t.i_;
      d_ = t.d_;
      s_ = t.s_;
    }
    return *this;
  }
private:
  CArray a_;
  int i_;
  double d_;
  ::std::string s_;
};
如果使用 ::std::vector<>:
struct T
{
private:
  ::std::vector a_;
  int i_;
  double d_;
  ::std::string s_;
};
上面列出的三个特殊成员函数都不需要写。好处是明显的:当你增减 T 的成员变量时,你不必到T(T const&) 和 operator=() 中去相应地增减。
3) 没有现成的算法可以对 CArray<> 进行操作,而标准 C++ 里的标准算法大多都可以直接在
::std::vector<> 上运行。例如:
static int const init_vals[] = { 3, 1, 4, 1, 6, 9 };
vector a( init_vals, init_vals + 6 );
*find( a.begin(), a.end(), 6 ) = 5;  // 把6改成5
sort( a.begin(), a.end() );  // 排序。
可以说,CArray<> 的主要设计错误是把一个本来应该是一个简单的“值”类型的东西设计成一个难用的“对象”类型了。所有的“值”的好特性都丧失了,但那些从CArray<>继承的派生类呢?
CByteArray等的问题与 CArray<> 的问题一样,甚至更多(例如,CPtrArray,永远不要用)。
同样,其他的 MFC container 模板,象 CMap<>, CList<> 等,都有类似问题,都应该用
::std::map<>,::std::list<> 等设计更好的东西代替。
2. ::std::vector<> 在哪里?
::std::vector<> 在头文件 中定义:
(注意,标准的 C++ 头文件都没有 .h 后缀,有 .h 的文件是与 C 兼容的,或支持老的不标准的东西,象 。)
namespace std
{
  template >
  struct vector
  {
    // 具体内容稍后讨论
  };
  template
    bool operator == ( vector const& a, vector const&  b );
  template
    bool operator != ( vector const& a, vector const&  b );
  template
    bool operator < ( vector const& a, vector const&  b );
  template
    bool operator >= ( vector const& a, vector const&  b );
  template
    bool operator > ( vector const& a, vector const&  b );
  template
    bool operator >= ( vector const& a, vector const&  b );
}
vector<> 定义在 namespace std 中,使用时为了减少击键次数,通常使用一个类型定义缩短类型名称:
#include
typedef ::std::vector IntVector;
IntVector a;
IntVector b( a );
IntVector c;
c = b;
assert( a == c );
请注意 中定义了六个 vector 的比较函数。这些函数只在真的用到时才会被实例化,才会要求 T 也提供 operator==() 和 operator<()。
另外,A = alloctor:用于提供一个用户定义的存储管理类。由于这个参数很少用到,而且在 VC++6 的实现中有问题,不能用,因此以下的讨论忽略这一部分的内容。
3. ::std::vector<> 中的类型定义
vector<> 中定义了一些类型,下面只列出常用的:
typedef T value_type;
typedef T0 iterator;
typedef T1 const_iterator;
typedef T2 reverse_iterator;
typedef T3 const_reverse_iterator;
value_type 就是 vector 的元素类型,也就是 T。当写通用的算法处理任意类型的 vector<> 或其他容器类型时是很有用的。
iterator/const_iterator 是两个 vector<> 的实现定义的未知类型,用于访问vector<> 中的元素,类似于 T*/T const* 指针,他们的区别是一个指向的元素可被修改,另一个只可以读:
typedef ::std::vector IntVector;
IntVector::iterator iter;
IntVector::const_iterator c_iter;
// ...
++iter; iter++; // ok: increment, post-increment.
--iter; iter--; // ok: decrement, post-decrement.
++c_iter; c_iter++; // ok: increment, post-increment.
--c_iter; c_iter--; // ok: decrement, post-decrement.
*iter = 123; // ok.
int k = *iter; // ok.
k = *--c_iter; // ok.
*c_iter = k; // error.
c_iter = iter; // ok: iterator is convertible to const_iterator.
iter = c_iter; // error: can't convert const_iterator to iterator.
在使用上 iterator/const_iterator 和 T*/T const* 基本相同,事实上有些vector<> 的实现里就是用 T*/T const* 实现 iterator/const_iterator 的,但又不可以把 iterator/const_iterator 当作真正的 T*/T const*:
T* p = iter; // may fail to compile.
T const* q = c_iter; // may fail to compile.
reverse_iterator/const_reverse_iterator 与 iterator/const_iterator 类似,但以相反的次序(从尾至头)访问 vector 中的元素。
各种各样的 iterator 在 STL 中有特别重要的意义,但这里我们不做具体介绍。只要理解通过 iterator 可以访问 vector 中的元素,大概相当于一个指示位置的指针就行了。
4. ::std::vector<> 的构造
vector<> 提供了以下构造函数:(忽略 allocator 参数)
vector();
vector( size_t n, T const t=T() );
vector( vector const & );
vector( const_iterator first, const_iterator last );
1) vector();
构造一个空的 vector,不包含任何元素。
IntVector v1; // 空的整数向量。
2) vector( size_t n, T const t=T() );
构造一个 n 个相同元素 t 组成的 vector。如果不给出 t,那么将用 T() 做缺省值:
IntVector v2( 100, 1234 ); // 100 个 1234.
IntVector v3( 100 ); // 100 个 0。
3) vector( vector const& other );
复制构造函数,复制 other 中的内容:
IntVector v4( v2 ); // 100 个 1234。
4) vector( const_iterator first, const_iterator last );
事实上,这个构造函数应该为
template
  vector( Iter first, Iter last );
即拷贝任意的序列 [first,last) 到 vector 中。由于 VC++6sp0 编译程序的限制, Iter 被换为 const_iterator 了。不过,碰巧 const_iterator就是 T const*,所以可以如下使用:
int a[] = { 1, 2, 3, 4, 5 };
IntVector v5( a, a + 5 ); // {1,2,3,4,5}
IntVector v6( v5.begin() + 2, v5.end() ); // {3,4,5}
5. 访问 vector<> 中的元素
以下成员函数/运算符用于访问 vector 中的一个元素:
T& at( size_t n );
T const& at( size_t n ) const;
T& operator [] ( size_t n );
T const& operator [] ( size_t n ) const;
T& front();
T const& front() const;
T& back();
T const& back() const;
请注意,由于 vector 是一个“值”语义的对象,所有的操作函数都必须严格保证 const 的正确性。所以,所有的元素访问方法都有 const 和非 const两个版本。
at(n) 和 operator [] (n) 都返回下标为 n 的元素的引用,他们的区别是,at() 进行下标越界检查,若发现越界,抛出 range_error 异常,operator[]不进行下标检查。
front() 返回下标为 0 的元素的引用,back() 返回最后一个元素的引用。
int a[] = { 4, 1, 4, 1, 5, 8 };
IntVector v( a, a + 6 );
// 使用 front(), back():
v.front() = 3;
v.back() = 9;
// 使用 operator [] ():
for( size_t i = 0; i < v.size(); ++i )
::std::cout << v[i] << '\n';
6. ::std::vector<> 的存储管理
以下成员函数用于存储管理:
void reserve( size_t n );
size_t capacity() const;
void resize( size_t n, T t=T() );
void clear();
size_t size() const;
bool empty() const { return size() == 0; }
size_t max_size() const;
另外,push_back(), insert() 等也涉及到存储管理,后面另行介绍。
1) max_size()
返回 vector 理论上可以装的最多 T 的个数。这只是一个理论上的数字,大概是 4GB/sizeof(T),没有多大实用价值。在程序中不要用。
2) size()
返回 vector 中实际装的 T 的个数。相当于 CArray<>::GetSize()。
3) empty()
如果 vector 中没有任何 T 对象,返回 true。也就是返回 size() == 0。
4) clear();
清除 vector 中的所有 T 对象。执行后 empty() 返回 true。大致相当于 resize(0),但不要求 T 可被缺省构造。相当于 CArray<>::RemoveAll()。
5) resize( size_t n, T t = T() );
将 vector 中的元素个数设置为 n,n 可以大于 size() 也可以小于 size。如果 n 小于 size(),那么 vector 中下标为 n..size()-1 的元素都将被解构。如果 n > size(),那么将在 vector 的后面新增加
n - size() 个相同的元素 t。在增大 vector 时,可能发生存储再次分配。总之,调用resize( n, t ) 后,(size() == n) 成立。
请注意,如果调用 resize( n ) 不带参数 t ,那么 T 必须可以缺省构造。
6) reserve( size_t n );
事先分配至少可以保存 n 个 T 对象的空间。调用后 (capacity() >= n)成立。
7) capacity();
返回已经分配的存储空间够容纳的 T 类型对象的个数。后续的增加元素操作(如 push_back(), insert())如果增加元素后 vector 中的总元素个数不超过 capacity(),那么 vector 的实现保证不重新分配存储空间。
vector 管理的动态存储空间是连续的。执行操作
IntVector v(7, 1); // seven ones.
v.reserve( 12 );
后,v 的状态可以用下图表示:
/--size()---\
|1|1|1|1|1|1|1|-|-|-|-|-|
\--capacity()---------/
其中,1 是已经构造的 int 类型的对象,- 是可以构造一个 int 类型的对象,但还没有构造的原始空间。再执行
v.push_back( 2 );
v.push_back( 3 );
后,v 的状态可用下图表示:
/----size()-----\
|1|1|1|1|1|1|1|2|3|-|-|-|
\----capacity()-------/
执行 resize( 11, 4 ); 后:
/----size()---------\
|1|1|1|1|1|1|1|2|3|4|4|-|
\----capacity()-------/
capacity() >= size() 总是成立的。对于下标为 [size()..capacity()-1]的未构造对象的存储空间,是不可以访问的:
v[11] = 5; // undefined behavior - anything can happen.7. 添加元素到 vector 中
下列操作添加元素到 vector 中,并可能引起存储分配:
void push_back( T const& t );
void insert( iterator pos, T const& t=T() );
void insert( iterator pos, size_t n, T const& t );
template
void insert( iterator pos, Iter first, Iter last );
push_back() 是把一个元素添加到 vector 的末尾。insert() 是把一个 t,或 n 个 t,或从 first 开始到 last 结束的一个序列插入到 pos 指示的位置之前。
当插入元素后 size() 将会大于 capacity() 时,将引起自动存储分配。vector 将会分配一个比需要的存储区大若干倍(通常是1.5到2)的新的存储区,把老的元素拷贝过去,同时完成添加或插入,然后释放老的存储区。
这就是说,vector 自动存储分配的空间大小是指数式增长的,这可以保证多次添加元素到 vector 中时,平均用时是接近于常数的。
IntVector v;
// add 0, 1, ..., 99 to v:
for( int i = 0; i < 100; ++i )
v.push_back( i );
// append 9, 8, 7,..., 0 to the end:
int a[] = { 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 };
v.insert( v.end(), a, a + 10 );8. 删除元素
下列成员函数完成元素删除:
void erase( iterator );
void erase( iterator first, iterator last );
void pop_back();
void clear();
这些函数分别删除一个,一串,最后一个,或全部元素。
IntVector v;
for( int i = 0; i < 100; ++i )
v.push_back( i );
// 删除 50, 51, ..., 89:
v.erase( v.begin() + 50, v.end() - 10 );
// 删除 49, 48:
v.pop_back();
v.pop_back();
// 全部删除:
v.clear();
注意,删除操作不会引起存储分配,因此 capacity() 不变。

回答2:

#include 

struct Person
{
//...Person成员变量
void print()
{
//todo 输出Person信息
}
};

class PersonSet
{
public:
enum{DEFAULT_CAPACITY = 4};
typedef Person ElemType;
typedef int SizeType;

PersonSet();
~PersonSet();

void add(ElemType& person); //往容器中加入一个对象

//这里返回Person&是不合理的(如果最后没有元素了,这个空的引用要怎么返回???)。
//可以改成Person*或者void
void removeElement(); //删除容器中的最后一个对象
void removeElement(int const&index);//删除容器中指定位置的对象

int getSize() const;//获取当前容器中有多少个对象
void print() const;//打印容器中各个对象的信息

protected:
void checkGrow();
void checkShrink();
void grow();
void shrink();

protected:
ElemType** _elements; //为什么要用二级指针,只是为了考察它的用法???
SizeType _capacity;
SizeType _size;
int _index; //index有何用???
};

PersonSet::PersonSet()
:_capacity(DEFAULT_CAPACITY)
,_size(0)
,_index(0)
{
_elements = new ElemType*;
*_elements = new ElemType[_capacity];
}

PersonSet::~PersonSet()
{
delete [] *_elements;
delete _elements;
}

void PersonSet::add(ElemType& person)
{
checkGrow();

(*_elements)[_size++] = person;
}

void PersonSet::removeElement()
{
_size--;

checkShrink();
}

void PersonSet::removeElement(int const&index)
{
if(index < 0 || index > _size)
return;

//元素依次前移
std::copy(*_elements+index+1, *_elements+_size-1, *_elements+index);

_size--;

checkShrink();
}

int PersonSet::getSize() const
{
return _size;
}

void PersonSet::print() const
{
for(SizeType i = 0; i < _size; i++)
{
(*_elements)[i].print();
}
}

void PersonSet::checkGrow()
{
if(_size == _capacity)
grow();
}

void PersonSet::checkShrink()
{
if(_size < _capacity / 2)
shrink();
}

void PersonSet::grow()
{
_capacity >>= 2;

ElemType* p = new ElemType[_capacity];
std::copy(*_elements, *_elements + _size - 1, p);

delete [] *_elements;

*_elements = p;
}

void PersonSet::shrink()
{
_capacity <<= 2;
}