我们准备设计一个可变长度的数组类CArray
,用来存放个数未知的元素。
设计:
1、既然个数未知,我们可以采用数组存储加之动态分配内存的方法。可以定义三个变量,用来记录动态数组的空间大小,动态数组内存储的的元素个数,以及动态数组内数据存储的地址;通过capacity()
方法返回动态数组的容量,size()
方法返回动态数组内元素的个数,push_back()
方法向动态数组尾部增添元素。
2、push_back()
时,需要考虑是否超出动态数组的容量,如果超出,则需要加上部分容量作为新容量,并将原来的数据迁移至新地址,写入加入的元素,并释放旧内存。(注:部分容量可以是1个,也可以是若干个)
3、重载流插入运算符,使得“cout<<
[动态数组]”得以成立。
主要部分都好说,正常写就好。
定义:
template<class Type>
class CArray
{
private:
int num;
int arraySize;
Type *ptr;
public:
static int enlarge_num;
CArray();
CArray(int);
CArray(CArray<Type>& );
~CArray();
void push_back(const Type&);
int pop_back();
void resize(int);
CArray<Type>& operator=(const CArray<Type>& );
int capacity() const;
int size() const;
Type& operator[] (int i);
bool operator== (const CArray<Type>& );
bool operator!= (const CArray<Type>& );
void print() const;
};
实现:
template<class Type>
int CArray<Type>::enlarge_num = 5;
template<class Type>
CArray<Type>::CArray() : arraySize(0)
{
ptr = NULL;
num = arraySize = 0;
}
template<class Type>
CArray<Type>::CArray(int s) : arraySize(s)
{
if (arraySize > 0) {
num = 0;
ptr = new Type[arraySize];
//memset(ptr, 0, sizeof(Type)*arraySize);
}
else {ptr = NULL; num = arraySize = 0;}
}
template<class Type>
CArray<Type>::CArray(CArray& a)
{
if (a.num > 0) {
arraySize = num = a.num;
ptr = new Type[num];
memcpy(ptr,a.ptr,sizeof(Type)*num);
}
else {
ptr = NULL;
arraySize = num = 0;
}
}
template<class Type>
CArray<Type>::~CArray()
{
if (ptr) delete []ptr;
}
template<class Type>
CArray<Type>& CArray<Type>::operator=(const CArray<Type>& a)
{
if (ptr == a.ptr) return *this;
if (a.num > 0) {
num = a.num;
if (a.num > arraySize) {
delete []ptr;
arraySize = a.num;
ptr = new Type[arraySize];
}
memcpy(ptr, a.ptr, sizeof(Type)*a.num);
}
else {
if (ptr) delete []ptr;
ptr = NULL;
num = 0;
}
return *this;
}
template<class Type>
void CArray<Type>::print() const
{
for (int i = 0; i < num; i++) cout << ptr[i] << " ";
cout << endl;
}
template<class Type>
Type& CArray<Type>::operator[] (int i)
{
int new_num = i + 1;
if (new_num > num && new_num <= arraySize) num = new_num;
return ptr[i];
}
template<class Type>
int CArray<Type>::capacity() const
{
return arraySize;
}
template<class Type>
int CArray<Type>::size() const
{
return num;
}
template<class Type>
void CArray<Type>::resize(int new_size)
{
if (new_size <= 0) {
delete []ptr;
arraySize = 0;
num = 0;
ptr = NULL;
}
else if (new_size < arraySize) {
num = new_size;
arraySize = new_size;
}
else if (new_size == arraySize) return;
else
{
Type *t_ptr = new Type[new_size];
memcpy(t_ptr, ptr, sizeof(Type) * arraySize);
delete []ptr;
ptr = t_ptr;
arraySize = new_size;
}
}
template<class Type>
void CArray<Type>::push_back(const Type& a)
{
if (enlarge_num < 1) enlarge_num = 1;
if (num >= arraySize)
{
Type *t_ptr = new Type[arraySize + enlarge_num];
if (ptr) {
memcpy(t_ptr, ptr, sizeof(Type) * num);
delete []ptr;
}
ptr = t_ptr;
arraySize += enlarge_num;
}
ptr[num++] = a;
}
template<class Type>
int CArray<Type>::pop_back()
{
if (num == 0) return 0;
return ptr[num--];
}
template<class Type>
bool CArray<Type>::operator== (const CArray& a)
{
if (num != a.num) return false;
for (int i = 0; i < num; i++) {
if (ptr[i] != a.ptr[i]) return false;
}
return true;
}
template<class Type>
bool CArray<Type>::operator!= (const CArray& a)
{
if (num != a.num) return true;
for (int i = 0; i< num; i++) {
if (ptr[i] != a.ptr[i]) return true;
}
return false;
}
在写operator<<()
的重载时,我遇到了些麻烦;这里需要对模板类的友元函数有所了解。先说最简单的解决办法,就是在类内部定义友元函数:
template<class Type>
class CArray
{
...
friend ostream& operator<<(ostream& os, CArray<Type>& a)
{
for (int i = 0; i < a.size(); i++) os << a[i] << " ";
os << endl;
return os;
}
};
反正模板类应当把定义和实现写在一个文件里,又何必把定义写在类外呢?
如果读者满足于此,那么可以看到这里为止了。
但是假如就是想在类外定义,怎么办呢?可能有点麻烦。
先放参考链接:
C++ 类模板二(类模版与友元函数)- 博客园 https://www.cnblogs.com/zhanggaofeng/p/5661829.html
类模板的模板友元函数定义 - 博客园 https://www.cnblogs.com/zerolee/archive/2012/06/17/2552504.html
一个简单的类模板的友元函数和友元类示例 https://www.cnblogs.com/lsgxeva/p/7689547.html
假如,我们在类里面定义了这样的友元函数:
template<class Type>
class CArray
{
...
friend ostream& operator<<(ostream& os, CArray<Type>& a);
};
然后,我们实例化了一个CArray
,
那么我们需要有一个这样的函数的实现,否则则会出现“Undefined reference”:
ostream& operator<<(ostream& os, CArray<int>& a){...};
然后我们又实例化了一个CArray
,则又需要写相应的实现:
ostream& operator<<(ostream& os, CArray<int>& a){...};
ostream& operator<<(ostream& os, CArray<double>& a){...};
这样有些问题,一是编译器会报“没有使用模板函数”的warning警告;二是假如用户想使用CArray
存储自己的数据类型,还得劳烦用户自己写一个相应的重载。
于是我们尝试用和其他成员函数类似的代码进行类外定义:
template <class Type>
ostream& operator<<(ostream& os, CArray<Type>& a) {...};
然后发现,这样可以通过编译,但是在链接阶段,显示找不到定义……
原因大概是因为,编译器不知道我们类里边的声明是一个模板函数,而认为是一个普通的全局函数。两个不是一个东西,所以就找不到了吧。
所以,得先声明要使用的的模板友元函数,比如这样:
template <class T> ostream& operator<<(ostream&, T&);
然后在函数中再次将模板声明为友元
template<class Type>
class CArray
{
...
friend ostream& operator<< <>(ostream&, CArray<Type>&);
};
template <class T>
ostream& operator<<(ostream& os, T& a)
{
for (int i = 0; i < a.size(); i++) os << a[i] << " ";
os << endl;
return os;
}
注意<>
不能漏掉;如果愿意,也可以写成< CArray
,不写的话,编译器可以从函数参数推断出模板参数CArray
;如果模板函数没有参数,则不能省掉,得指明人家的具体化。
也可以这样:
template<class Type>
class CArray
{
...
template <class T>
friend ostream& operator<<(ostream&, T&);
};
template <class T>
ostream& operator<<(ostream& os, T& a)
{
for (int i = 0; i < a.size(); i++) os << a[i] << " ";
os << endl;
return os;
}
这样应该是大概能够实现类外的operator<<()
的重载的定义了;
但是这样还是有问题的。根据笔者的实验,貌似这样写会和<<
的其他重载发生混淆,不能过编译。
因此也许我们应该将模板参数细化,比如这样:
template<class Type> ostream& operator<<(ostream& os, CArray<Type>& a);
但因为用到了CArray
,所以得提前声明一下CArray
:
template<class Type> class CArray;
template<class Type> ostream& operator<<(ostream& os, CArray<Type>& a);
template<class Type>
class CArray
{
...
friend ostream& operator<< <Type>(ostream&, CArray<Type>&);
};
template <class Type>
ostream& operator<<(ostream& os, CArray<Type>& a)
{
for (int i = 0; i < a.size(); i++) os << a[i] << " ";
os << endl;
return os;
}
如果要指明这里的<>
里面的参数,那么它应该是Type
。
这样就可以了。
哦,貌似还可以这样写,不需要提前声明CArray
了:
template<class Type>
class CArray
{
...
template<class T>
friend ostream& operator<<(ostream& , CArray<T>& );
};
template <class T>
ostream& operator<<(ostream& os, CArray<T>& a)
{
for (int i = 0; i < a.size(); i++) os << a[i] << " ";
os << endl;
return os;
}
吐槽1:我们追求类外实现的优雅,但是现在看来,也许类内实现更优雅一些……
吐槽2:用print()方法代替重载是不是也挺好的?这样就不需要友元了……
这里我用了自己写的分数类来做实验,相关信息可以在这个链接里面看到:
自己写一个分数类
读者也可以使用自己的数据结构,但一定要记得对运算符做重载;或者修改动态数组对<<重载的实现,把cout<改成
a[i].print()
之类的方法就好了。
int main()
{
CArray<fraction>::enlarge_num = 10;
CArray<fraction> a;
a.push_back("1/2");
a.push_back("3.5");
a.push_back("4.2");
a.push_back("3.8");
a.push_back("2.99");
cout<<a;
return 0;
}
int main()
{
CArray<int>::enlarge_num = 10;
CArray<int> a;
a.push_back(1);
a.push_back(2);
a.push_back(3);
a.push_back(4);
a.push_back(5);
cout<<a;
return 0;
}