Array.h Array.cc
文档作者:jianzhu
创立时间:08.08.19
文档作者:jianzhu
创立时间:08.08.19
--------------------------------------
1、基本类
--------------------------------------
这两个文件主要以模板方式定义了一个动态数组(Array)和一个静态数组(StaticArray)。
其中静态数组继承自动态数组。
继承结构图(Array.bmp)
其中静态数组继承自动态数组。
继承结构图(Array.bmp)
Array类
该类提供如下函数
a) 构造函数
b) 赋值操作符=函数
c) 拷贝构造函数
d) 下标操作符[]函数
e) cast 操作符函数
f) size函数
g) memStats函数
该类提供如下函数
a) 构造函数
b) 赋值操作符=函数
c) 拷贝构造函数
d) 下标操作符[]函数
e) cast 操作符函数
f) size函数
g) memStats函数
StaticArray类
该类继承自Array类,因此其继承了Array类的所有public和protect区域的成员
在其继承上,该类提供了如下函数
a) 构造函数
b) 下标操作符[]函数
该类继承自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值设为新分配的大小。
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行返回当前对象的引用,这样做的目的是为了和标准
赋值运算符一致,支持从右至左的连续赋值。
<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相应值。
函数返回值类型应为当前对象的引用。
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值)
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行直接返回当前动态数组中存储的数据单元数。
<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]);
<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函数
其中拷贝构造函数一般可借由重载的赋值运算符实现
动态数组一般应提供以下几个公共函数,也即功能。
a) 构造函数
b) 拷贝构造函数
c) 重载的赋值运算符“=”
d) 下标运算符“[]”
e) size函数
其中拷贝构造函数一般可借由重载的赋值运算符实现
2、重载的赋值运算符
a) 重载的赋值运算符一般需要判断防止”自赋值“问题
b) 重载的赋值运算符一般返回当前对象的引用
a) 重载的赋值运算符一般需要判断防止”自赋值“问题
b) 重载的赋值运算符一般返回当前对象的引用
3、子类初始化问题
在对子类初始化之前需要对父类的数据成员进行初始化,一般可通过在子类构造
函数的成员初始化列表中调用父类的构造函数实现。
在对子类初始化之前需要对父类的数据成员进行初始化,一般可通过在子类构造
函数的成员初始化列表中调用父类的构造函数实现。