Srilm 阅读文档1

取自 自然语言处理百科

跳转到: 导航, 搜索

Array.h Array.cc

文档作者:jianzhu

创立时间:08.08.19


1、基本类


这两个文件主要以模板方式定义了一个动态数组(Array)和一个静态数组(StaticArray)。其中静态数组继承自动态数组。

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、子类初始化问题

      在对子类初始化之前需要对父类的数据成员进行初始化,一般可通过在子类构造
  函数的成员初始化列表中调用父类的构造函数实现。


转自:http://blog.chinaunix.net/u1/58264/showart_1333574.html

个人工具
工具箱