当前课程知识点:C++语言程序设计进阶 > 第九章 模板与群体数据 > 链表 > 链表类模板
生成链表
插入结点
查找结点
删除结点
遍历链表
清空链表
//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
那接下来呢
我们就来尝试着
咱们自己来编写一个程序
去实现链表类的模板
在此之前呢
我们要清楚地知道
一个链表类
它常用的操作有哪些
首先呢
要能够生成一个链表
然后往链表里面插入结点
在链表里面查找
我们要找的这个数据
也就是查找结点
删除结点
遍历一个链表
清空一个链表
那么这些呢
就是链表的常用的操作
好 接下来我们来看
这个链表类模板的代码
这个例题中呢
我们给出了这一个
链表类模板的设计
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都被删除了
这样看来
用这个现成的链表类模板
大家看
是不是构造链表非常方便了
不用我们去操作指针
去动态分配结点 去释放
只要用链表类的接口函数
这个链表就能够构造
能够查找数据 删除数据
遍历 都可以了
-导学
--导学
-继承的基本概念和语法
-第七章 继承与派生--继承的基本概念和语法习题
-继承方式
-第七章 继承与派生--继承方式
-基类与派生类类型转换
-第七章 继承与派生--基类与派生类类型转换
-派生类的构造和析构
--派生类的构造函数
--派生类的析构函数
--第七章 继承与派生--派生类的构造和析构
-派生类成员的标识与访问
--虚基类
-第七章 继承与派生--派生类成员的标识与访问
-小结
--小结
-综合实例
--第七章综合实例
-实验七
--实验七
-导学
--导学
-第八章 多态性--导学
-运算符重载
--运算符重载的规则
-第八章 多态性--运算符重载
-虚函数
--虚函数
--虚析构函数
--虚表与动态绑定
-第八章 多态性--虚函数
-抽象类
--抽象类
--第八章 多态性--抽象类
-override与final
-第八章 多态性--override与final
-小结
--第八章小结
-综合实例
--第八章综合实例
-实验八
--实验八
- 第八章讲义
-导学
--导学
-模板
--函数模板
--类模板
-第九章 模板与群体数据--模板
-线性群体
--线性群体的概念
-第九章 模板与群体数据--线性群体
-数组
--数组类模板
-链表
--链表类模板
-第九章 模板与群体数据--链表
-栈
--栈类模板
--栈类模板课后习题
--例9-9 栈的应用课后习题
-队列
--队列类模板
-第九章 模板与群体数据--队列
-排序
--排序概述
--插入排序
--选择排序
--交换排序
-第九章 模板与群体数据--排序
-查找
--查找
--查找课后习题
-小结
--小结
-综合实例
--综合实例
-实验九
--实验九
- 第九章讲义
-导学
--导学
-泛型程序设计及STL的结构
--STL简介
-第十章 泛型程序设计与C++标准模板库--泛型程序设计及STL的结构
-迭代器
--迭代器
-第十章 泛型程序设计与C++标准模板库--迭代器
-容器的基本功能与分类
-第十章 泛型程序设计与C++标准模板库--容器的基本功能与分类
-顺序容器
--顺序容器的特征
--第十章 泛型程序设计与C++标准模板库--顺序容器
-关联容器
--集合
--映射
-第十章 泛型程序设计与C++标准模板库--关联容器
-函数对象
--函数对象
--函数适配器
-算法
--算法
-小结
--第十章小结
-综合实例
--综合实例
-实验十
--实验十
- 第十章讲义
-导学
--导学
-I/O流的概念及流类库结构
-第十一章 流类库与输入/输出--I/O流的概念及流类库结构
-输出流
--输出流概述
--向文本文件输出
--向二进制文件输出
--向字符串输出
-第十一章 流类库与输入/输出--输出流
-输入流
--输入流概述
--输入流应用举例
--从字符串输入
-第十一章 流类库与输入/输出--输入流
-输入/输出流
--输入/输出流
-第十一章 流类库与输入/输出--输入/输出流
-小结
--小结
-综合实例
--综合实例
-实验十一
--实验十一
- 第十一章讲义
-导学
--第12章导学
-异常处理的思想与程序实现
-第十二章 异常处理--异常处理的思想与程序实现
-异常处理中的构造与析构
-第十二章 异常处理--异常处理中的构造与析构
-标准程序库异常处理
-第十二章 异常处理--标准程序库异常处理
-小结
--第12章小结
-综合实例
--综合实例
-实验十二
--实验十二
- 第十二章讲义