第五章 模板与群体数据(2)

本文介绍了如何使用C++实现一个自定义的动态数组类模板(Array),包括构造函数、深拷贝、重载赋值运算符、下标访问和指针转换运算符。同时,讨论了其与STL中的vector容器的相似性,指出在实际编程中优先考虑使用STL库的容器。此外,还展示了Array类模板在读取函数中的应用,说明了自定义容器类需要重载指针转换运算符的原因。
摘要由CSDN通过智能技术生成

群体的概念

  • 群体是由多个数据元素组成的集合体
  • 分为 线性群体非线性群体 两大类,如数组与链表
  • 线形群体中的元素按位置排列有序,可以区分为第一个元素,第二个元素等
  • 非线性群体不用位置顺序来标识元素

直接访问的线性群体--数组类

  • 静态数组是具有固定元素个数的群体,其中的元素可以通过下标直接访问
  • 动态数组由一系列位置连续的,任意数量相同类型的元素组成
  • vector 就是用类模板实现的动态数组

 vector 容器

  • 封装任意类型的动态数组,自动创建和删除
  • 数组下标越界检查
  • vector 对象的定义
    vector <元素类型> 数组对象名(数组长度)
    
    vector<int> arr(5);
  • vector 对象的使用
    对数组元素的引用
        vector 对象名 [下标表达式]
    
    获取数组长度
        size()

示例:

template <class T>
class Array {
private:
    T* list;    // 用于存放动态分配的数组内存首地址
    int size;
public:
    Array(int sz = 50);    //构造函数
    Array(const Array<T> &a);    //复制构造函数,深层复制
    ~Array();    //析构函数

    Array<T> & operator = (const Array<T> &rhs);    // 重载 = ,如果是默认重载 =,则是浅层复制
    T & operator [](int i);    // 重载 []
    cont T & operator [](int i) const;    // 重载 [] 常函数,处理常对象(初始化后数组每个元素不能被修改)
    operator T * ();    // 重载到 T* 类型的转换
    operator const T * () const;    // const 版本
    
    int getSize() const;    // 取数组大小
    void resize(int sz);    // 修改数组大小
};

template <class T> Array<T>::Array(int sz) {    // 构造函数
    assert(sz >= 0); // sz 为数组大小(元素个数),应当非负,异常处理应用异常来抛出,后续章节讲异常
    size = sz;    // 将元素个数赋值给变量 size
    list = new T[size];    // 动态分配 size 个 T 类型的元素空间
}

template <class T> Array<T>::~Array() { // 析构函数
    delete [] list;
}

template <class T>
Array<T>::Array(const Array<T> &a) {    // 复制构造函数,深层复制
    size = a.size;    // 从对象 x 取得数组大小,并赋值给当前对象的成员
    list = new T[size];    // 动态分配 n 个 T 类型的元素空间
    for(int i=0; i<size; i++)    // 从对象 a 复制数组元素到本对象
        list[i] = a.list[i];
}

// 重载 = 运算符,将对象 rhs 赋值给本对象,实现对象之间的整体赋值,同样是深层复制
template <class T>
Array<T> &Array<T>::operator = (const Array<T> &rhs) {
    if(&rhs != this) { 
        // 如果本对象中数组大小与 rhs 不同,则删除数组原有内存,然后重新分配
        if(size != rhs.size) {
            delete [] list;    // 删除数组原有内存
            size = rhs.size;    // 设置本对象的数组大小
            list = new T[size];    // 重新分配 size 个元素的内存
        }

        // 从对象 X 复制数组元素到本对象
        for(int i=0; i<size; i++)
            list[i] = rhs.list[i];
    }

    return *this;    // 返回当前对象的引用
}

 复制构造函数与重载 = 运算符都是深层复制

区别在于:

复制构造函数对象生成之际就要分配同样大小的空间

重载 = 则要比较原有对象与新对象的空间大小先,还要避免自身复制

// 重载下标运算符,实现与普通数组一样通过下标访问元素,具有越界检查功能
template <class T>
T & Array<T>::operator[] (int n) {
    assert(n >=0 && n <size);    // 检查下标是否越界
    return list[n];    // 返回下标为 n 的数组元素
}

template <class T>
const T & Array<T>::operator[] (int n) const{
    assert(n >=0 && n <size);    // 检查下标是否越界
    return list[n];    // 返回下标为 n 的数组元素
}
// 重载指针转换运算符,将 Array 类的对象名转换为 T 类型的指针
template <class T>
Array<T>::operator T * () {    // 参数与返回值都不能写
    return list;    // 返回当前对象中私有数组的首地址
}

// 取当前数组的大小
template <class T>
int Array<T>::getSize() const {
    return size;
}

 为什么需要重载指针转换运算符呢?

看一个示例:

void read(int *p, int n) {...}

int a[10];
read(a, 10);    // ok, 数组名可以直接传给指针

Array<int> a(10);    // 构造函数,希望也可以像数组一样用
read(a, 10);    // 类型不一样的情况下,编译器尝试做隐含的类型转换,不能做 Array<int> 与 int * 的转换,所以需要自己要实现指针运算符转换,将 Array 类的对象名转换为 T 类型的指针
// 将数组大小修改为 sz
template <class T>
void Array<T>::resize(int sz) {
    assert(sz >=0);    // 检查 sz 是否非负
    if(sz == size)
        return;

    T* newList = new T[sz];    // 申请新的数组内存
    int n = (sz < size) ? sz : size;    // 将 sz 与 size 中较小的一个赋值给 n

    // 将原有数组中前 n 个元素复制到新数组中
    for(int i=0; i<n; i++)
        newList[I] = list[I];

    delete [] list;    // 删除原数组
    list = newList;    // 使 list 指向新数组
    size = sz;    // 更新 size
}

 接着看看如何使用我们定义的 Array 类模板

Array<int> a(10);

...
后续直接把 a 当作数组来使用,譬如 : a[i]

STL 库里面的容器类型,实现的机理大致就是这样的,只是这里是比较简单的实现

通常情况下,我们都优先使用 STL 库里面的容器,除非不能满足需求才用自定义的容器

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值