数组的新玩法,创建数组类。
C语言中的数组不好用吗?为什么需要数组类?仔细思考一下,原生数组有缺点吗?额,还真有!在使用数组的时候,使用者想要获取数组的长度是比较麻烦,如果不清楚函数参数传递的规则,使用函数来求数组长度,得到的必然是错误的结果。比如:
#include <iostream>
using namespace std;
int array_length(int a[])
{
return sizeof(a)/sizeof(*a);
}
int main(int argc, const char* argv[])
{
int a[5] = {0};
cout << array_length(a) << endl; // 结果是1
return 0;
}
输出奇怪,原因何在?为什么传了个数组进函数就不对了呢?这就要讲讲函数参数传递的原理了,一般的参数都是直接在内存里拷贝一份出来,然后在函数体里面随意使用,但是使用数组作为形参就特别一点了,编译器会化繁为简,直接将数组转换为指针进行传递。是不是没有办法求数组的长度了呢?NO,来看看强大的宏定义:
#include <iostream>
using namespace std;
#define array_length(a) sizeof(a)/sizeof(*a)
int main(int argc, const char* argv[])
{
int a[5] = {0};
cout << array_length(a) << endl;
return 0;
}
宏定义确实强大的一笔。但是宏定义是由预处理器处理的,编译器并不知道宏的存在,使用不当容易出错。
讲了一大堆原生数组的东西,现在直奔主题,数组类,用面向对象的思想实现的一个类。提供各种方法使得数组类拥有原生数组的各种操作。比如数组操作符[]的使用,这就很方便了,并且还能对下标参数进行越界检查,如果参数异常则默认使用 throw(0) 抛一个0,(补个小知识点:编译器对原生数组下标越界是摊手的,但程序可能会崩掉)。然后就是提供设置目标元素、获取目标元素以及获取数组长度的方法。
话不多说,上代码:
#ifndef ARRAY_H
#define ARRAY_H
template <typename T> // 使用模板,接收任意类型的参数
class Array
{
protected:
T* m_array; // 成员变量是指针,指向数组首元素
public:
bool set(int i, const T& value)
{
bool ret = (0 <= i) && (i < length());
if( ret )
{
m_array[i] = value;
}
else
{
throw(0);
}
return ret;
}
bool get(int i, const T& value) const
{
bool ret = (0 <= i) && (i < length());
if( ret )
{
value = m_array[i];
}
else
{
throw(0);
}
return ret;
}
T get(int i) const
{
T ret;
if( !get(i,ret) ) // 如果获取元素失败,抛异常
{
throw(0);
}
return ret;
}
T& operator [] (int i)
{
if( (0 <= i) && (i < length()) )
{
return m_array[i];
}
else
{
throw(0);
}
}
T operator [] (int i) const
{
if( (0 <= i) && (i < length()) )
{
return m_array[i];
}
else
{
throw(0);
}
}
T* array() // 返回数组首元素地址,可用于排序等操作
{
return m_array;
}
virtual int length() const = 0; // 声明为纯虚函数,作为基类只用于被继承而不能实列化
};
#endif
数组基类就创建完成了,对比着原生数组来做思考,我们需要怎么样的数组类呢?原生数组在定义的时候就已经确定长度,之后便再也无法修改,这是不是有点不方便呢,我们就对这做一点点的改进,提供两个数组类,一个是固定大小的,另一个则是可以动态决定大小的。这样选择可就丰富了!先来个简单的静态数组类压压惊。
#ifndef STATICARRAY_H
#define STATICARRAY_H
#include "Array.h"
template <typename T, int N> // 使用模板
class StaticArray : public Array<T>
{
protected:
T array[N]; // 实际所使用的空间
public:
StaticArray()
{
this->m_array = array; // 交出使用权
}
StaticArray(const StaticArray<T,N>& obj) // 拷贝构造函数,使用自身的空间,但是用别人的内容
{
this->m_array = array;
for(int i = 0; i < this->length(); ++ i)
{
this->m_array[i] = obj[i];
}
}
StaticArray<T,N>& operator = (const StaticArray<T,N>& obj)
{
if( this != &obj ) // 自赋值就没必要了
{
for(int i = 0; i < this->length(); ++ i)
{
this->m_array[i] = obj[i];
}
}
}
int length() const
{
return N;
}
};
#endif
静态数组类就完成了哦,马上来使用一下!
#include <iostream>
#include "StaticArray.h"
using namespace std;
int main(int argc, const char* argv[])
{
StaticArray<int,5> sa;
for(int i = 0; i < 5; ++ i)
{
sa[i] = i * i;
}
for(int i = 0; i < 5; ++ i)
{
cout << sa.get(i) << endl;
}
return 0;
}
完成了静态数组类啦,正所谓高下相盈,那就一鼓作气实现动态数组类吧。首先分析,何为动态,如何实现动态,其实作为一名学过C语言的人儿,心照不宣地想到堆空间的动态申请和动态释放。没错,动态数组类的实现要点就是从堆空间申请一片内存空间,当动态数组类对象觉得空间太小了,已经不够用了,那就重新申请一块大得堆空间内存,(!!!注意:必须释放之前的堆空间,内存泄漏极其危险),如果觉得太大。。。。也一样的操作。好了,上代码:
#ifndef DYNAMICARRAY_H
#define DYNAMICARRAY_H
#include "Array.h"
template <typename T>
class DynamicArray : public Array<T>
{
protected:
int m_length;
public:
DynamicArray((int len = 0)
{
T* array = new T(len); // 向堆空间动态申请一片内存
if( this->m_array != NULL ) // 申请成功
{
this->m_array = array;
this->m_length = len;
}
else
{
throw(0); // 申请失败,完事了
}
}
DynamicArray(const DynamicArray<T>& obj) // 拷贝构造函数
{
array = new T(obj.length()); // 重新申请一段内存
if( array != NULL )
{
this->m_array = array;
this->m_length = obj.length();
for(int i = 0; i < obj.length(); ++ i)
{
this->m_array[i] = obj[i];
}
}
}
DynamicArray<T>& operator = (const DynamicArray<T>& obj)
{
if( this != &obj ) // 杜绝自赋值
{
T* tem = obj.m_array; // 安全起见,保留一下旧地址
T* array = new T(obj.length());
if( array != NULL )
{
this->m_array = array;
this->m_length = obj.length();
for(int i = 0; i < obj.length(); ++ i)
{
this->m_array[i] = obj[i];
}
}
obj.m_array = NULL;
delete[] tem;
}
}
void resize(int newlen) // 实现动态数组的关键函数
{
T* tem = this->m_array;
int size = (this->m_length < newlen) ? this->m_length : newlen;
// 使用三目运算符计算需要拷贝的内容大小
T* array = new T(newlen);
if( array != NULL )
{
for(int i = 0; i < size; ++ i)
{
array[i] = this->m_array[i];
}
}
else
{
throw(0);
}
delete[] tem; // 很重要,把之前的内存空间释放
}
int length() const
{
return m_length;
}
};
#endif
动态数组的玩法如下:
#include <iostream>
#include "DynamicArray.h"
using namespace std;
int main(int argc, const char* argv[])
{
DynamicArray<int> da(5);
cout << "First length : " << da.length() << endl;
for(int i = 0; i < 5; ++ i)
{
da[i] = i * i;
}
cout << endl;
for(int i = 0; i < 5; ++ i)
{
cout << da.get(i) << " ";
}
da.resize(10); // 扩大一倍
cout << "Second length : " << da.length() << endl;
for(int i = 0; i < 10; ++ i)
{
da[i] = i;
}
cout << endl;
for(int i = 0; i < da.length(); ++ i)
{
cout << da[i] << " ";
}
return 0;
}
输出结果:
仅仅是一维数组好像不够酷!认真的?那就实现二维数组咯,温故知新,先回想一下C语言中的二维数组,形式确实是二维数组,但本质还是一维数组,只不过变成了数组的数组,二维数组在内存的排布是按照一个个大小相同数组顺序存储,所以利用数组类来实现二维数组就不难了,上代码:
#include <iostream>
#include "DynamicArray.h"
using namespace std;
int main(int argc, const char* argv[])
{
DynamicArray< DynamicArray<int> > dda(5); // 相当于五行
// 由此可知,动态数组的构造函数必须有默认值,默认值为0
for(int i = 0; i < dda.length(); ++ i)
{
dda[i].resize(5); // 相当于五列
}
for(int i = 0; i < dda.length(); ++ i)
{
for(int j = 0; j < dda.length(); ++ j)
{
dda[i][j] = i + j;
}
cout << endl;
}
cout << endl;
for(int i = 0; i < dda.length(); ++ i)
{
for(int j = 0; j < dda.length(); ++ j)
{
cout << " dda[" << i << "][" << j << "] = " << dda[i][j] << " ";
}
}
return 0;
}
二维数组的输出还是挺酷的嘛。