当前课程知识点:C++语言程序设计进阶 >  第九章 模板与群体数据 >  链表 >  链表类模板

返回《C++语言程序设计进阶》慕课在线视频课程列表

链表类模板在线视频

链表类模板

链表的基本操作

例9-6 链表类模板

//LinkedList.h
#ifndef LINKEDLIST_H
#define LINKEDLIST_H
#include "Node.h"

template <class T>
class LinkedList {
private:
    //数据成员:
    Node<T> *front, *rear;  //表头和表尾指针
    Node<T> *prevPtr, *currPtr;   //记录表当前遍历位置的指针,由插入和删除操作更新
    int size;   //表中的元素个数
    int position;   //当前元素在表中的位置序号。由函数reset使用

    //函数成员:
    //生成新结点,数据域为item,指针域为ptrNext
    Node<T> *newNode(const T &item,Node<T> *ptrNext=NULL);

    //释放结点
    void freeNode(Node<T> *p);

    //将链表L 拷贝到当前表(假设当前表为空)。
    //被拷贝构造函数、operator = 调用
    void copy(const LinkedList<T>& L);

public:
    LinkedList();   //构造函数
    LinkedList(const LinkedList<T> &L);  //拷贝构造函数
    ~LinkedList();  //析构函数
    LinkedList<T> & operator = (const LinkedList<T> &L); //重载赋值运算符

    int getSize() const;    //返回链表中元素个数
    bool isEmpty() const;   //链表是否为空

    void reset(int pos = 0);//初始化游标的位置
    void next();    //使游标移动到下一个结点
    bool endOfList() const; //游标是否到了链尾
    int currentPosition() const;    //返回游标当前的位置

    void insertFront(const T &item);    //在表头插入结点
    void insertRear(const T &item);     //在表尾添加结点
    void insertAt(const T &item);       //在当前结点之前插入结点
    void insertAfter(const T &item);    //在当前结点之后插入结点

    T deleteFront();    //删除头结点
    void deleteCurrent();   //删除当前结点

    T& data();              //返回对当前结点成员数据的引用
    const T& data() const;   //返回对当前结点成员数据的常引用

    //清空链表:释放所有结点的内存空间。被析构函数、operator= 调用
    void clear();
};

template <class T> //生成新结点
Node<T> *LinkedList<T>::newNode(const T& item, Node<T>* ptrNext)
{
    Node<T> *p;
    p = new Node<T>(item, ptrNext);
    if (p == NULL)
    {
        cout << "Memory allocation failure!\n";
        exit(1);
    }
    return p;
}

template <class T>
void LinkedList<T>::freeNode(Node<T> *p) //释放结点
{
    delete p;
}

template <class T>
void LinkedList<T>::copy(const LinkedList<T>& L) //链表复制函数
{
    Node<T> *p = L.front;   //P用来遍历L 
    int pos;
    while (p != NULL)   //将L中的每一个元素插入到当前链表最后
    {
        insertRear(p->data);
        p = p->nextNode();
    }
    if (position == -1) //如果链表空,返回
        return;
    //在新链表中重新设置prevPtr和currPtr
    prevPtr = NULL;
    currPtr = front;
    for (pos = 0; pos != position; pos++)
    {
        prevPtr = currPtr;
        currPtr = currPtr->nextNode();
    }
}

template <class T>  //构造一个新链表,将有关指针设置为空,size为0,position为-1
LinkedList<T>::LinkedList() : front(NULL), rear(NULL),
prevPtr(NULL), currPtr(NULL), size(0), position(-1)
{}

template <class T>
LinkedList<T>::LinkedList(const LinkedList<T>& L)  //拷贝构造函数
{
    front = rear = NULL;
    prevPtr = currPtr = NULL;
    size = 0;
    position = -1;
    copy(L);
}

template <class T>
LinkedList<T>::~LinkedList()    //析构函数
{
    clear();
}

template <class T>
LinkedList<T>& LinkedList<T>::operator=(const LinkedList<T>& L)//重载"="
{
    if (this == &L) // 不能将链表赋值给它自身
        return *this;
    clear();
    copy(L);
    return *this;
}

template <class T>
int LinkedList<T>::getSize() const  //返回链表大小的函数
{
    return size;
}

template <class T>
bool LinkedList<T>::isEmpty() const //判断链表为空否
{
    return size == 0;
}

template <class T>
void LinkedList<T>::reset(int pos)  //将链表当前位置设置为pos 
{
    int startPos;
    if (front == NULL)  // 如果链表为空,返回
        return;
    if (pos < 0 || pos > size - 1)  // 如果指定位置不合法,中止程序
    {
        std::cerr << "Reset: Invalid list position: " << pos << endl;
        return;
    }
    // 设置与遍历链表有关的成员
    if (pos == 0)   // 如果pos为0,将指针重新设置到表头
    {
        prevPtr = NULL;
        currPtr = front;
        position = 0;
    }
    else    // 重新设置 currPtr, prevPtr, 和 position 
    {
        currPtr = front->nextNode();
        prevPtr = front;
        startPos = 1;
        for (position = startPos; position != pos; position++)
        {
            prevPtr = currPtr;
            currPtr = currPtr->nextNode();
        }
    }
}

template <class T>
void LinkedList<T>::next()  //将prevPtr和currPtr向前移动一个结点
{
    if (currPtr != NULL)
    {
        prevPtr = currPtr;
        currPtr = currPtr->nextNode();
        position++;
    }
}

template <class T>
bool LinkedList<T>::endOfList() const   // 判断是否已达表尾
{
    return currPtr == NULL;
}

template <class T>
int LinkedList<T>::currentPosition() const  // 返回当前结点的位置
{
    return position;
}

template <class T>
void LinkedList<T>::insertFront(const T& item)   // 将item插入在表头
{
    if (front != NULL)  // 如果链表不空则调用Reset 
        reset();
    insertAt(item); // 在表头插入
}

template <class T>
void LinkedList<T>::insertRear(const T& item)   // 在表尾插入结点
{
    Node<T> *nNode;
    prevPtr = rear;
    nNode = newNode(item);  // 创建新结点
    if (rear == NULL)   // 如果表空则插入在表头
        front = rear = nNode;
    else
    {
        rear->insertAfter(nNode);
        rear = nNode;
    }
    currPtr = rear;
    position = size;
    size++;
}

template <class T>
void LinkedList<T>::insertAt(const T& item) // 将item插入在链表当前位置
{
    Node<T> *nNode;
    if (prevPtr == NULL)    // 插入在链表头,包括将结点插入到空表中
    {
        nNode = newNode(item, front);
        front = nNode;
    }
    else    // 插入到链表之中. 将结点置于prevPtr之后
    {
        nNode = newNode(item);
        prevPtr->insertAfter(nNode);
    }
    if (prevPtr == rear)    //正在向空表中插入,或者是插入到非空表的表尾
    {
        rear = nNode;   //更新rear 
        position = size;    //更新position 
    }
    currPtr = nNode;    //更新currPtr
    size++; //使size增值
}

template <class T>
void LinkedList<T>::insertAfter(const T& item)  // 将item 插入到链表当前位置之后
{
    Node<T> *p;
    p = newNode(item);
    if (front == NULL)   // 向空表中插入
    {
        front = currPtr = rear = p;
        position = 0;
    }
    else    // 插入到最后一个结点之后
    {
        if (currPtr == NULL)
            currPtr = prevPtr;
        currPtr->insertAfter(p);
        if (currPtr == rear)
        {
            rear = p;
            position = size;
        }
        else
            position++;
        prevPtr = currPtr;
        currPtr = p;
    }
    size++;              // 使链表长度增值
}

template <class T>
T LinkedList<T>::deleteFront()  // 删除表头结点
{
    T item;
    reset();
    if (front == NULL)
    {
        cerr << "Invalid deletion!" << endl;
        exit(1);
    }
    item = currPtr->data;
    deleteCurrent();
    return item;
}

template <class T>
void LinkedList<T>::deleteCurrent() // 删除链表当前位置的结点
{
    Node<T> *p;
    if (currPtr == NULL)    // 如果表空或达到表尾则出错
    {
        cerr << "Invalid deletion!" << endl;
        exit(1);
    }
    if (prevPtr == NULL)    // 删除将发生在表头或链表之中
    {
        p = front;  // 保存头结点地址
        front = front->nextNode();  //将其从链表中分离
    }
    else    //分离prevPtr之后的一个内部结点,保存其地址
        p = prevPtr->deleteAfter();

    if (p == rear)  // 如果表尾结点被删除
    {
        rear = prevPtr; //新的表尾是prevPtr 
        position--; //position自减
    }
    currPtr = p->nextNode();    // 使currPtr越过被删除的结点
    freeNode(p);    // 释放结点,并
    size--; //使链表长度自减
}

template <class T>
T& LinkedList<T>::data()    //返回一个当前结点数值的引用
{
    if (size == 0 || currPtr == NULL)   // 如果链表为空或已经完成遍历则出错
    {
        cerr << "Data: invalid reference!" << endl;
        exit(1);
    }
    return currPtr->data;
}

template <class T>
void LinkedList<T>::clear() //清空链表
{
    Node<T> *currPosition, *nextPosition;
    currPosition = front;
    while (currPosition != NULL)
    {
        nextPosition = currPosition->nextNode(); //取得下一结点的地址
        freeNode(currPosition); //删除当前结点
        currPosition = nextPosition;    //当前指针移动到下一结点
    }
    front = rear = NULL;
    prevPtr = currPtr = NULL;
    size = 0;
    position = -1;
}
#endif  //LINKEDLIST_H



下一节:栈类模板

返回《C++语言程序设计进阶》慕课在线视频列表

链表类模板课程教案、知识点、字幕

那接下来呢

我们就来尝试着

咱们自己来编写一个程序

去实现链表类的模板

在此之前呢

我们要清楚地知道

一个链表类

它常用的操作有哪些

首先呢

要能够生成一个链表

然后往链表里面插入结点

在链表里面查找

我们要找的这个数据

也就是查找结点

删除结点

遍历一个链表

清空一个链表

那么这些呢

就是链表的常用的操作

好 接下来我们来看

这个链表类模板的代码

这个例题中呢

我们给出了这一个

链表类模板的设计

LinkedList

这些成员函数的实现呢

我们在课上就不讲了

大家如果需要了解的话

可以去网上下载原代码

作为参考

我们看这里面有几部分内容啊

首先是私有成员

然后这边有公有成员

这些公有的函数

才是使用这个链表类模板的代码

能够访问和使用的函数

那么在私有成员里面定义的

除了数据以外

还有几个函数

这些都是为本类的其他函数

起辅助作用的

在类外是不可以看到它

也调用不了的

好 现在我们看内部的数据

这个数据也都是私有成员

在外部也不知道

它有什么数据

这些私有成员

都是为了本类函数

去实现这些链表的操作

要用到的

这个是表头指针

指向链表的头结点

这是表尾指针

指向链表的最后一个尾结点

然后这两个实际上

我们可以简单称它为工作指针

这个currPtr

是指向

永远指向当前

你正在操作的这个结点的

因为我们如果要遍历整个链表

要依次访问

链表中的各个元素的话

我们不能去移动表头指针

标头指针要始终指向表头

如果它被移动了

那么有的结点的空间

可能就要泄露了

被丢了

所以表头指针保持着

我们要去指向列表

中间的其他结点的时候呢

用另外的工作指针向后移

但是呢

如果我们已经指向当前结点了

很多对当前结点的操作

是要依赖于它的前驱结点的

比如说

我们要删除当前这个结点

实际上是操作

它前驱结点的后继指针

所以呢

我们还是需要另外一个

另外一个指针

始终跟当前结点的这个指针

并行在走

那就是它

当前结点的前驱结点指针

这个如果大家去实现

这些函数体的话呢

会用到这些成员的

然后再看

这个size链表中元素的个数

这个position呢

是当前元素在链表中的位置序号

它是由reset函数来使用的

这个newNode

是生成一个新结点的函数

它的参数

第一个是它的数据项的引用

第二个是它后继结点指针

初始化为空

freeNode是释放一个结点

还有copy是进行复制用的

在这个类中

有复制构造函数

赋值运算符函数

都要复制链表内容

那么为了避免代码的冗余

也要保证这个复制算法的一致性

所以写了一个私有的辅助函数

copy

去实现这个复制

然后复制构造函数

和赋值运算符函数

都会调用它来完成这个复制

这样的话复制的算法

就是统一的

那么这个复制呢

是假设当前链表为空的状态

好 现在再看

我们使用的这些对外接口函数

包括构造函数

复制构造函数 析构函数

还有重载赋值运算符函数

这个是返回链表中的元素个数

判断链表是否为空

初始化呢

当前这个游标的这个当前位置

初始化默认把它置到0

然后next是这个游标

移动到下一个结点

所谓游标

我们就是假设有这么一个标记

始终指向你当前要

处理的这个结点

你要想处理那个结点

要把标记移到那儿去

方便处理

所以有这么一个游标在里面

实际上就是我们前面说的

这个currPtr

这个指针实现的这个游标

但是内部数据到底怎么存的

用了什么指针

对外是不公布的

对外公布的只是

这样一些接口函数

这个endOfList是判断

是否游标已经指到了链表尾

currentPosition

是返回游标的当前位置

insertFront

是在表头插入一个新结点

insertRear

是在表尾添加一个结点

insertAt是当前结点

当前这个游标指向的这个结点

之前插入一个结点

insertAfter是在当前结点之后

插入一个结点

这个deleteFront删除表头结点

deleteCurrent删除当前结点

这个data呢

就是返回当前结点

它的数据成员的引用

所以它返回一个T类型的引用

那这个呢

有两种方式 两种形式

一个是普通的

一个是const版本的

也就是说如果

是常对象的话

那返回的就应该是一个常引用

通过这个引用可以读取数据值

但是不能修改它

最后这个函数是清空链表

那么它需要释放

所有的结点的空间

这个呢

会被析构函数

和赋值运算符函数调用的

那么这是链表类的这么一个设计

它的实现呢

我们在课上就不讲了

我们设计了一个很好用的链表类

那接下来

我们就用用试试看

所以下面一个例题呢

我们就用来一下这个链表类模板

在这个例题中呢

要求用户从键盘输入10个整数

我们用这些整数呢

作为结点的数据

来生成一个链表

然后按顺序

输出链表中的所有结点的数值

也就是第一次遍历一下链表

所谓遍历

就是整个把链表里面的

所有结点都经历一遍

过一遍

遍历完一遍以后呢

让用户再从键盘输入一个

需要查找的整数

然后我们在这些链表的结点中

去找

看看有没有这个整数

这就是查找结点

如果找到了这个数据所在的结点

就删除这个结点

如果有多次出现呢

那把出现的结点就都删除

最后再输出一遍

删除结点以后的链表

在程序结束之前呢

还要清空链表

好 在这里呢

我们构造了一个链表类的对象

那这个链表呢

它的数据元素

是打算用来存放整数类型的

我们要用整数来构造链表

所以它的类型参数

给出来是int

好 接下来呢

我们就用一个for循环

不是输入10个整数吗

从键盘依次输入10个整数

然后将这10个整数

插入到链表的表头

要用insertFront

那这个insertFront函数呢

就会用这个item这个整数

去构造一个新结点

并且把这个新结点呢

插入到表头

大家看每输入一个整数

都插到表头去

实际上这个链表呢

它是反向生成的

最后插入的结点在最头上

大家也可以回去改改这个程序

你让它正向生成

你调用另外一个函数

每次把新的结点

插入到表尾也是可以的

实际上

一个正向生成的链表

是可以当做队列来使用的

一个反向生成

像这样反向生成的链表

可以当做栈来使用的

队列和栈呢

稍后我会给大家介绍

好 那全部元素都插入进去以后

调用一下reset

把当前那个游标

指向链表的最头上的结点

然后接下来干什么呢

遍历整个链表 遍历链表

输出每个元素

那这个while循环

用什么来控制循环结束呢

list点 endOfList

不为

也就是没有达到表尾的情况下

所以非list.endOfList

那么endOfList不为真

那么整个表达式就不为假

就是没有达到表尾

这个时候读取

用data这个函数

取得每一个数据值

然后输出它

完了以后还要调用next

使游标指向下一个结点

接下来请用户输入一个

需要查找的值

那比如我们看前面运行结果

如果用户先输入了这么一个序列

先输入这么一个序列
然后经过反向生成的过程以后

我们看到

这个list里面的值列出来

跟我们输出的值正好是反序的

接下来

在这个时候请用户

输入一个待查找的数据

那么用户输入了一个5

我们看到5

在这里面是出现了多次的

出现了一共3次

那也就是我们希望

把这些5从链表中都删除

那怎么删除呢

接下来再调用一次reset

使这个游标再指向链表的表头

然后当没有到表尾的时候呢

逐项跟这个要删除的这个T

去比较

如果相等

就调用deleteCurrent

把当前结点删除

然后游标再指向下一个结点

经过这样一个while循环

按说就所有的

应该所有的跟T相等的值

就都删除了

那是不是这样呢

我们再验证一下

验证一下

再调用里希reset

把这个游标

从新置到表头去

然后同样用一个while循环

再次遍历这个列表

输出各个元素

验证一下果然

我们要删除的5都被删除了

这样看来

用这个现成的链表类模板

大家看

是不是构造链表非常方便了

不用我们去操作指针

去动态分配结点 去释放

只要用链表类的接口函数

这个链表就能够构造

能够查找数据 删除数据

遍历 都可以了

C++语言程序设计进阶课程列表:

第七章 继承与派生

-导学

--导学

-继承的基本概念和语法

--继承的基本概念和语法

-第七章 继承与派生--继承的基本概念和语法习题

-继承方式

--继承方式简介及公有继承

--私有继承和保护继承

-第七章 继承与派生--继承方式

-基类与派生类类型转换

--基类与派生类类型转换

-第七章 继承与派生--基类与派生类类型转换

-派生类的构造和析构

--派生类的构造函数

--派生类的构造函数举例

--派生类的复制构造函数

--派生类的析构函数

--第七章 继承与派生--派生类的构造和析构

-派生类成员的标识与访问

--访问从基类继承的成员

--虚基类

-第七章 继承与派生--派生类成员的标识与访问

-小结

--小结

-综合实例

--第七章综合实例

-实验七

--实验七

-第七章讲义

第八章 多态性

-导学

--导学

-第八章 多态性--导学

-运算符重载

--运算符重载的规则

--双目运算符重载为成员函数

--单目运算符重载为成员函数

--运算符重载为非成员函数

-第八章 多态性--运算符重载

-虚函数

--虚函数

--虚析构函数

--虚表与动态绑定

-第八章 多态性--虚函数

-抽象类

--抽象类

--第八章 多态性--抽象类

-override与final

--override与final

-第八章 多态性--override与final

-小结

--第八章小结

-综合实例

--第八章综合实例

-实验八

--实验八

- 第八章讲义

第九章 模板与群体数据

-导学

--导学

-模板

--函数模板

--类模板

-第九章 模板与群体数据--模板

-线性群体

--线性群体的概念

-第九章 模板与群体数据--线性群体

-数组

--数组类模板

--例9-4数组类应用举例

-链表

--链表的概念与结点类模板

--链表类模板

-第九章 模板与群体数据--链表

-栈

--栈类模板

--栈类模板课后习题

--例9-9 栈的应用

--例9-9 栈的应用课后习题

-队列

--队列类模板

-第九章 模板与群体数据--队列

-排序

--排序概述

--插入排序

--选择排序

--交换排序

-第九章 模板与群体数据--排序

-查找

--查找

--查找课后习题

-小结

--小结

-综合实例

--综合实例

-实验九

--实验九

- 第九章讲义

第十章 泛型程序设计与C++标准模板库

-导学

--导学

-泛型程序设计及STL的结构

--泛型程序设计的基本概念

--STL简介

-第十章 泛型程序设计与C++标准模板库--泛型程序设计及STL的结构

-迭代器

--迭代器

-第十章 泛型程序设计与C++标准模板库--迭代器

-容器的基本功能与分类

--容器的基本功能与分类

-第十章 泛型程序设计与C++标准模板库--容器的基本功能与分类

-顺序容器

--顺序容器的基本功能

--顺序容器的特征

--顺序容器的插入迭代器与适配器

--第十章 泛型程序设计与C++标准模板库--顺序容器

-关联容器

--关联容器分类和基本功能

--集合

--映射

--多重集合和多重映射

-第十章 泛型程序设计与C++标准模板库--关联容器

-函数对象

--函数对象

--函数适配器

-算法

--算法

-小结

--第十章小结

-综合实例

--综合实例

-实验十

--实验十

- 第十章讲义

第十一章 流类库与输入/输出

-导学

--导学

-I/O流的概念及流类库结构

--I/O流的概念及流类库结构

-第十一章 流类库与输入/输出--I/O流的概念及流类库结构

-输出流

--输出流概述

--向文本文件输出

--向二进制文件输出

--向字符串输出

-第十一章 流类库与输入/输出--输出流

-输入流

--输入流概述

--输入流应用举例

--从字符串输入

-第十一章 流类库与输入/输出--输入流

-输入/输出流

--输入/输出流

-第十一章 流类库与输入/输出--输入/输出流

-小结

--小结

-综合实例

--综合实例

-实验十一

--实验十一

- 第十一章讲义

第十二章 异常处理

-导学

--第12章导学

-异常处理的思想与程序实现

--异常处理的思想与程序实现

-第十二章 异常处理--异常处理的思想与程序实现

-异常处理中的构造与析构

--异常处理中的构造与析构

-第十二章 异常处理--异常处理中的构造与析构

-标准程序库异常处理

--标准程序库异常处理

-第十二章 异常处理--标准程序库异常处理

-小结

--第12章小结

-综合实例

--综合实例

-实验十二

--实验十二

- 第十二章讲义

链表类模板笔记与讨论

也许你还感兴趣的课程:

© 柠檬大学-慕课导航 课程版权归原始院校所有,
本网站仅通过互联网进行慕课课程索引,不提供在线课程学习和视频,请同学们点击报名到课程提供网站进行学习。