srilm 阅读文档1

Array.h Array.cc
文档作者:jianzhu
创立时间:08.08.19

--------------------------------------
1、基本类
--------------------------------------
    这两个文件主要以模板方式定义了一个动态数组(Array)和一个静态数组(StaticArray)。
其中静态数组继承自动态数组。
    继承结构图(Array.bmp)
Array类
    该类提供如下函数
    a) 构造函数
    b) 赋值操作符=函数
    c) 拷贝构造函数
    d) 下标操作符[]函数
    e) cast 操作符函数
    f) size函数
    g) memStats函数
StaticArray类
    该类继承自Array类,因此其继承了Array类的所有public和protect区域的成员
    在其继承上,该类提供了如下函数
    a) 构造函数
    b) 下标操作符[]函数

--------------------------------------
2、函数功能解释
--------------------------------------
Array类
a) 构造函数
<src>
0  Array(int base = 0, unsigned int size = 0)
1      : _base(base), _size(size), _data(0), alloc_size(0)
2  { 
3      if (size > 0) 
4      { 
5        alloc(size-1); 
6      }
7  }
</src>
   功能:用于对当前对象进行初始化,同时分配相应的内存空间。
   
   细解:
   第1行使用成员初始化列表初始化各成员变量
   unsigned int _base;     /* 记录当前数组的起始下标: 以0、1还是其他值开始 */
   unsigned int _size;     /* 记录当前数组中已经使用的单元数量 */
   unsigned int alloc_size; /* 记录当前数组中已经分配的单元空间数 */
   Data*        _data;      /* 指向保存数据成员的buffer */
   
   语义Bug:这里文档作者认为应该把_size和alloc_size均初始化为0,
   而alloc_size的值通过第5行调用alloc函数会被设为分配值。
   
   第5行调用私有函数alloc分配内存,为数据成员_data分配内存
   alloc函数
   <src>
   0   template <class DataT>
  1   void
  2   Array<DataT>::alloc(unsigned int size)
  3   {
   4     unsigned int newSize = size + 1 + alloc_size/2;
   5    DataT *newData = new DataT[newSize];
   6    assert(newData != 0);
   7
  8    #ifdef ZERO_INITIALIZE
   9     memset(newData, 0, newSize * sizeof(DataT));
  10   #endif
  11
   12   for (unsigned i = 0; i < alloc_size; i++) {
  13     newData[i] = _data[i];
   14   }
  15
   16   delete [] _data;
  17
   18     _data = newData;
   19     alloc_size = newSize;
  20  }
   </src>
   
   第4行使用 size + 1 + alloc_size/2,对待分配的数组空间扩展;
   第12-14行,将旧地址上的单元数据复制到新的地址单元处;
   第16-19行,释放旧的地址单元,同时让其指向新分配位置,并将
   alloc_size值设为新分配的大小。
b) 赋值操作符函数
<src>
0  template <class DataT>
1  Array<DataT> &
2  Array<DataT>::operator= (const Array<DataT> &other)
3  {
4    #ifdef DEBUG
5      cerr << "warning: Array::operator= called/n";
6   #endif
7
8     if (&other == this) {
9     return *this;
10    }
11
12    delete [] _data;
13
14    _base = other._base;
15    _size = other._size;
16    alloc_size = other.alloc_size;
17
18    _data = new DataT[alloc_size];
19    assert(_data != 0);
20
21  #ifdef ZERO_INITIALIZE
22     memset(_data, 0, alloc_size * sizeof(DataT));
23  #endif
24  
25    for (unsigned i = 0; i < alloc_size; i++) {
26    _data[i] = other._data[i];
27    }
28 
29    return *this;
30 }
</src>
  功能:将“=”左边的对象初始化右边的对象。
  
  细解:
   第8-10进行了判断防止对自己赋值,导致后面出错;
   第12-16行将当前数据单元释放,同时将相应数据成员设为
   other成员的值;
   第18行重新为数据单元分配内存,并在第25-27行将新分配
   的数据单元设为other单元的值;
   第29行返回当前对象的引用,这样做的目的是为了和标准
   赋值运算符一致,支持从右至左的连续赋值。
  注:重载赋值运算符时,需要判断是否发生自赋值问题,同时
  函数返回值类型应为当前对象的引用。
  
c) 拷贝构造函数
<src>
0    Array(Array<DataT> &source)
1    : _base(source._base), _size(0), _data(0), alloc_size(0)
2    { 
3    *this = source; 
4   }
</src>
  功能:将当前对象初始化为传入的对象。
  
  细解:
    第1行使用成员初始化列表将_base值设为source的相应_base值,同时
    将_data指针初始化为空,该操作十分必要,否则后续调用赋值运算符
    函数时,遇到delete [] _data;语句会出错。建议对指针类型的数据
    成员都预先将其初始化为空;
    第3行调用赋值运算符函数,将当前对象初始化为source相应值。
  注:拷贝构造函数一般通过重载的赋值运算符实现。
  
d) 下标操作符[]函数
<src>
0    DataT &operator[](long index)
1    {
2     long offset = index - _base; 
3     assert(offset >= 0);
4      if (offset >= _size) {
5        _size = offset + 1;
6       
7           if (offset >= alloc_size) {
8        alloc(offset); 
9       }
10    }
11   return _data[offset];
12 }
</src>
  功能:返回相应下标位置处的存储单元的引用。如果当前下标超过存
  储单元数则对记录存储单元数的变量进行相应更新;当存储数据的空
  间不足时,则扩大存储空间。
  
  细解:
    第2行计算当前index对应的数组下面索引位置
    第4行计算当前偏移位置是否超过已经使用的单元数,如果没有则跳转到
    第12行,返回相应相应数据单元的引用。否则运行第5-9行,将当前数据
    单元使用数增一,同时判断当前偏移位置是否大于已经分配的空间大小,
    如果超过,则调用alloc函数分配更大的空间。
    
e) cast操作符函数
<src>
0    operator DataT* () 
1   { 
2    return data(); 
3   }
4
5   DataT *data() const 
6   { 
7    return _data - _base;
8   }
</src>
  功能:返回储存单元经过基址运算后的首地址。
  
  细解:
  第2行直接调用data()函数返回相应的储存数据的数组单元首地址
  第7行data函数内部将_data减去_base,然后返回想减后的地址,
  这样做的目的是为了在后续使用过程中直接考虑_base的作用。
  譬如说用户自定数组单元的_base值为1。则后续使用
         _tmp = _data-_base
  _tmp[1]正好相当于调用
         DataT &operator[](long index);
  下标运算符函数时传入1。(注:1为当前数组开始下标,即_base值)
f) size函数
<src>
0   unsigned int size() const 
1   { 
2     return _size; 
3   }
</src>
    功能:返回当前动态数组中存储的数据单元数。
    
    细解:
    第2行直接返回当前动态数组中存储的数据单元数。
g) memStats函数
<src>
0   template <class DataT>
1   void
2   Array<DataT>::memStats(MemStats &stats) const
3   {
4       stats.total  += _size * sizeof(_data[0]);
5       stats.wasted += (alloc_size - _size) * sizeof(_data[0]);
6   }
</src>
    功能:该函数用于设定当前动态数组的内存使用情况
    
    细解:
    第4行应该是一个语义bug,因为动态数组总的内存使用量应为
    stats.total += alloc_size*sizeof(_data[0]);

StaticArray类
a) 构造函数
<src>
0    StaticArray(unsigned size)
1     : Array<DataT>(0, size)
2    {
3    }
4
5     StaticArray(int base, unsigned size)
6    : Array<DataT>(base, size) 
7   {
8   }
</src>
  功能:对StaicArray类进行初始化
  
  细解:
  第0-3行的构造函数只含有一个参数,用于指定当前静态数组欲保存的数据单元数
  即_size的最大值,_base为默认值0;
  第5-8行的构造函数含有两个参数,第一个参数用于制定基址(即基索引),第二
  个用于指定当前静态数组欲保存的数据单元数。
  
  注:子类初始化时,需要预先对父类初始化。如上面的第1行和第6行,通过成员初
  始化列表的方式调用父类的构造函数对父类成员进行初始化。
  
b) 下标操作符[]函数
<src>
0   DataT &operator[](int index)
1     {
2     return Array<DataT>::_data[index - Array<DataT>::_base]; 
3   }
</src>
    功能:直接返回相应下标的存储单元的引用。
    
    细解:
    第2行进行基址运算后,直接返回相应存储单元的引用。
    
    注:在子类中引用父类的protect或public区域的成员对象可以通过父类名加作用域
    运算符实现。

--------------------------------------
知识点:
--------------------------------------
1、动态数组
       动态数组一般应提供以下几个公共函数,也即功能。
       a) 构造函数
       b) 拷贝构造函数
       c) 重载的赋值运算符“=”
       d) 下标运算符“[]”
       e) size函数
   其中拷贝构造函数一般可借由重载的赋值运算符实现
2、重载的赋值运算符
       a) 重载的赋值运算符一般需要判断防止”自赋值“问题
       b) 重载的赋值运算符一般返回当前对象的引用
3、子类初始化问题
       在对子类初始化之前需要对父类的数据成员进行初始化,一般可通过在子类构造
   函数的成员初始化列表中调用父类的构造函数实现。

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值