C++学习:可变长数组类模板及实现流插入运算符重载

我们准备设计一个可变长度的数组类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;
}

你可能感兴趣的:(C++学习:可变长数组类模板及实现流插入运算符重载)