「C++程序设计笔记」第九章 模板与群体数据(一)

第九章 模板与群体数据

模板

函数模板声明

template <模板参数表>
函数定义
示例
template <class T>  //定义函数模板
void outputArray(const T *array, int count) {
    for (int i = 0; i < count; i++)
        cout << array[i] << " "; //如果数组元素是类的对象,需要该对象所属类重载了流插入运算符“<<”
    cout << endl;
}

int main() {
    const int A_COUNT = 8, B_COUNT = 8, C_COUNT = 20;
    int a [A_COUNT] = { 1, 2, 3, 4, 5, 6, 7, 8 };
    double b[B_COUNT] = { 1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7, 8.8 };
    char c[C_COUNT] = "Welcome!";

    cout << " a array contains:" << endl;
    outputArray(a, A_COUNT);
    cout << " b array contains:" << endl;
    outputArray(b, B_COUNT);
    cout << " c array contains:" << endl;
    outputArray(c, C_COUNT);
    return 0;
}
运行结果如下:
a array contains:
1 2 3 4 5 6 7 8
b array contains:
1.1 2.2 3.3 4.4 5.5 6.6 7.7 8.8
c array contains:
W e l c o m e!
模板参数表的内容

类型参数: class(或typename)标识符

常量参数: 类型说明符 标识符

模板参数: template <参数表> class 标识符

注意

一个函数模板并非自动可以处理所有类型的数据

只有能够进行函数模板中运算的类型可以作为类型实参;

自定义的类, 需要重载模板中的运算符, 才能作为类型实参;

类模板

使用类模板使用户可以为类声明一种模式,
使得类中的某些数据成员, 某些成员函数的参数,
某些成员函数的返回值, 能取任意类型
(包括基本类型的和用户自定义类型)

可以生成针对特殊数据类型的类;

类模板声明

类模板


template<模板参数类>
class类名
{类成员声明}

如果需要在类模板以外定义其成员函数, 则要采用以下的形式:

template<模板参数表>
类型名 类名<模板参数标识符列表>::函数名(参数表)
示例
template <class *>
class Store{//类模板: 实现对任意类型数据进行存取
private:
    T item;   // item用于存放任意类型的数据
    bool haveValue; // haveValue 标记item是否已被存入内容
public:
    Store();
    T &getElem();  // 提取数据函数
    void putElem(const T&x);  // 存入数据函数
};
template <class T>  
Store<T>::Store(): haveValue(false) { }
template <class T>
T &Store<T>::getElem() {
    //如试图提取未初始化的数据,则终止程序
    if (!haveValue) {
        cout << "No item present!" << endl;
        exit(1);    //使程序完全退出,返回到操作系统。
    }
    return item;        // 返回item中存放的数据
}
template <class T>
void Store<T>::putElem(const T &x) {
   // 将haveValue 置为true,表示item中已存入数值
   haveValue = true;
   item = x;           // 将x值存入item
}

使用:

int main(){
    Store<int> s1, s2;
    s1.putElem(3);
    s2.putElem(-7);

    Student g = {1000, 23};
    Store<Student> s3;
    s3.putElem(g);
    cout << "The Student id is " << s3.getElem().id  << endl;

    Store<double> d;
    cout << d.getElem() << endl;
    //d未初始, 执行函数d.getElem()时终止
    return 0;
}

线性群体

线性群体的概念

  • 群体是指由多个数据元素组成的集合体;

    群体可以分为两大类: 线性群体非线性群体;

  • 线性群体中的元素按位置排列有序

    可以区分为第一个元素, 第二个元素等;

  • 非线性群体不用位置顺序来标识元素;

  • 线性群体中的元素次序与其逻辑位置关系是对应的;
    在线性群体中, 有可以按照访问元素的不同方法分为 直接访问,
    顺序访问索引访问;

接下来只介绍直接访问和顺序访问;

数组

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

基本数组存在着长度不可调, 下标越界编译器也没有语法检查

动态数组得到了改进, 现在再前进一步, 编写通用数组类

数组类模板

直接访问的线性群体—数组类
动态数组类模板示例
template <class T>           //数组类模板定义
class Array{
private:
    T* list;                  //用于存放动态分配的数组内存首地址
    int size;
public:
    Array(int sz = 50);
    Array(const Array<T> &a);  //复制构造函数
    ~Array();                  // 析构函数
}
#ifndef ARRAY_H
#define ARRAY_H
#include <cassert>

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); //重载"[]”
    const T & operator [] (int i) const;     //重载"[]”常函数
    operator T * ();        //重载到T*类型的转换
    operator const T * () 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++)     //从对象X复制数组元素到本对象
        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;
}

// 将数组大小修改为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
}
#endif  //ARRAY_H

assert宏的原型定义在<assert.h>中,其作用是如果它的条件返回错误,则终止程序执行。

数组类应用举例

求范围2~N中的质数,N在程序运行时由键盘输入。

#include <iostream>
#include <iomanip>
#include "Array.h"
using namespace std;
int main() {
    // 用来存放质数的数组,初始状态有10个元素
  Array<int> a(10);
    int n, count = 0;
    cout << "Enter a value >= 2 as upper limit for prime numbers: ";
    cin >> n;

    for (int i = 2; i <= n; i++) { //检查i是否能被比它小的质数整除
      bool isPrime = true;
      for (int j = 0; j < count; j++)
        //若i被a[j]整除,说明i不是质数
           if (i % a[j] == 0) {
          isPrime = false; break;
        }
      if (isPrime) {
        if (count == a.getSize())
               a.resize(count * 2);
        a[count++] = i;
      }
    }
    for (int i = 0; i < count; i++)
      cout << setw(8) << a[i];
    cout << endl;
    return 0;
}

需要扩容时直接翻倍减少调用resize()的次数;

链表

链表的概念与结点类模板

不管你是什么数组, 插入和删除的时候元素都在移动, 频繁插入删除频繁移动, 效率很低;

而链表就比较适合这种操作

顺序访问的线性群体—链表类

链表是一种动态数据结构, 可以用来表示顺序访问的线性群体;

链表是由系列结点组成的, 结点可以在运行时动态生成;

每一个结点包括数据域和指向链表中下一个结点的指针
(即下一个结点的地址); 如果链表每个结点中只有一个指向后继结点的指针,
则该链表称为单链表;

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lwMn8nIr-1600001828041)(pic/NodeList.png)]

物理地址不必相邻, 也往往不相邻;

单链表结点类模板
template <class T>
class Node{
private:
    Node<T> *next;
public:
    T data;
    Node(const T& item, Node<T>* next = 0);
    void insertAfter(Node<T> *p);
    Node<T> *deleteAfter();
    Node<T> *nextNode() const;
}

插入

template <class T>
void Node<T>::insertAfter(Node<T> *p){
// p结点指针域指向当前节点的后继节点
p->next = next;
next = p; //当前节点的指针域指向p
}

注意顺序, 找到节点的唯一办法是通过前驱节点找到;

删除

Node<T> *Node<T>::deleteAfter(void){
Node<T> *tempPtr = next;
if(next == 0)
    return 0;
next = tempPtr->next;
return tempPtr;
}

因为仅仅在这个函数里无法得知删除节点的用途, 所以不清理内存空间;

链表类模板

链表的基本操作

生成链表

插入结点

查找结点

删除结点

遍历链表

清空链表

链表类应用举例

模板略;

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值