一、用面向对象的思想,用c++语言实现单链表类
(1)链表结点的定义
typedef int DataType;
struct ListNode
{
ListNode(const DataType& x)
:data(x)
,next(NULL)
{}
DataType data;
ListNode* next;
};
(2)链表的成员变量
Node* _head;//指向链表的头结点
Node* _tail;//指向链表的尾结点
(3)链表的成员函数
①默认成员函数函数
在这里我只说两点:
1)链表的拷贝构造和赋值运算符重载函数的区别:
赋值运算符重载其实和拷贝构造差不多,【拷贝构造】是直接给一个没有初始化的新对象初始化;而【赋值运算符的重载】是给一个已经初始化的对象重新赋值;—>所以赋值运算符之前要初始化两个对象,必须要调用两次构造函数或者一次构造,一次拷贝构造;而拷贝构造函数之前则是只调用一次构造;(以上所说情况,只对两个对象)
2)赋值运算符重载函数写法中的效率问题:
在写链表的赋值运算符时,由于是一个链表a 给另一个链表b 赋值;所以可以首先从两个链表的第一个结点开始,将链表a中的结点值赋给链表b;当链表b的结点已经到了最后一个,链表a的值还没有赋值完;然后再用链表a剩余的结点值采用new操作符创建结点;链接到链表b的后面;这样做的好处是;利用已经开辟的空间,进行赋值操作;提高效率;
如果一上来,就是将链表b的结点全部释放,然后重新开辟结点连到链表上;这样的话,空间的释放与开辟次数过多;影响程序的运行效率;
②功能性成员函数
void PushBack(const DataType& x)//尾插
void Popback()//尾删
void PushFront(const DataType& x)//头插
void PopFront()//头删
void Insert(Node* pos,const DataType x)//某一个位置插入某个元素
Node* Find(const DataType& x)//查找某一个元素的地址,没有返回空---二分查找
void Remove(const DataType& x)//删除链表中第一次出现的该元素
void RemoveAll(const DataType& x)//删除链表中全部的改元素
void Sort()//链表元素排序--冒泡(将结点值交换)----升序
void Destroy()//释放链表所有结点内存,并将头,尾指针赋为NULL
二、源代码(待测试部分)
//c++模拟实现单链表
#include<iostream>
using namespace std;
typedef int DataType;
struct ListNode
{
ListNode(const DataType& x)
:data(x)
,next(NULL)
{}
DataType data;
ListNode* next;
};
class LinkList
{
typedef ListNode Node;
friend ostream& operator<<(ostream& os,LinkList& linklist);
public:
LinkList()//构造函数
:_head(NULL)
,_tail(NULL)
{}
LinkList(const LinkList& linklist)//拷贝构造函数
{
/*写法一:
//1.一进来,先判断链表是否为空,若为空,没有数据进来构造新对象,直接返回。
if (linklist._head==NULL)
{
return;
}
Node* cur=linklist._head;//取用来构造的链表的头
//2.由于新对象一进来,还未初始化,成员指针为野指针;
//先构造第一个结点;然后用结点的地址初始化成员变量;
_head=new Node(cur->data);
_tail=_head;
//3.接下来,采用循环的方式赋值链表;
if (cur->next)
{
cur=cur->next;
while (cur)
{
Node* tmp=new Node(cur->data);
_tail->next=tmp;
_tail=tmp;
cur=cur->next;
}
}*/
//***********************************************************************************
//写法二:直接给链表的成员变量初始化为NULL,等到在while循环赋值链表时;特殊处理
if (linklist._head==NULL)
{
return;
}
_head=NULL;
_tail=NULL;
Node* cur=linklist._head;
while (cur)
{
Node* tmp=new Node(cur->data);
//为什么在这里不用类名+对象名的方式构造一个结点对象结点,然后取对象的地址,将结点连到链表上呢???
//答:因为如果用你说的方式,创建的对象结点是一个临时变量;在栈上创建;出了该函数作用域,对象自动销毁;
//连到链表 上的结点不见了;这样会导致链表出了函数作用域,没有数据可以供其他函数操作;
//而用new操作符开辟一段空间,在此空间创建一个结点对象并初始化;该对象空间实在堆上创建,出了作用域,
//结点依然在链表上存在;可供其他函数对结点数据进行管理;当程序结束时,采用delete操作符释放所开辟的结点空间;
if (_head==NULL)
{
_head=tmp;
_tail=tmp;
}
_tail->next=tmp;
_tail=tmp;
cur=cur->next;
}
}
//在写链表的赋值运算符时,由于是一个链表a 给另一个链表b 赋值;所以可以首先从两个链表的第一个结点开始,
//将链表a中的结点值赋给链表b;当链表b的结点已经到了最后一个,链表a的值还没有赋值完;然后再用链表a剩余的
//结点值采用new操作符创建结点;链表到链表b的后面;
//这样做的好处是;利用已经开辟的空间,进行赋值操作;提高效率;
//如果一上来,就是将链表b的结点全部释放,然后重新开辟结点连到链表上;这样的话,空间的释放与开辟次数过多;
//影响程序的运行效率;
LinkList& operator=(const LinkList& linklist)//赋值运算符重载
{
//赋值运算符重载其实和拷贝构造差不多,拷贝构造是直接给一个没有初始化的新对象
//初始化;而赋值运算符的重载是给一个已经初始化的对象重新赋值;所以赋值运算符之前要初始化两个
//对象必须要调用两次构造函数或者一次构造,一次拷贝构造;而拷贝构造函数之前则是只调用一次构造;
//(以上所说情况,只对两个对象对象)
if (linklist._head==NULL)
{
return *this;
}
if (this!=&linklist)
{
Node* cur=linklist._head;
Node* cur1=_head;
while (cur && cur1)
{
cur1->data=cur->data;
cur1=cur1->next;
cur=cur->next;
}
if (cur1==NULL)
{
while (cur)
{
Node* tmp=new Node(cur->data);
_tail->next=tmp;
_tail=tmp;
cur=cur->next;
}
}
}
return *this;
}
~LinkList()//析构函数
{
Destroy();
}
public:
void PushBack(const DataType& x)//尾插
{
Node* tmp=new Node(x);
if (_head==NULL)
{
_head=tmp;
_tail=tmp;
}
else
{
_tail->next=tmp;
_tail=tmp;
}
tmp->next=NULL;
}
void Popback()//尾删
{
//1.如果链表为空
if (_head==NULL)
{
return ;
}
//2.如果链表只有一个结点
if (_head->next==NULL)
{
delete _head;
_head=NULL;
_tail=NULL;
}
//3.链表有大于一个结点
Node* cur=_head;
while (cur->next->next)//寻找倒数第二个结点
{
cur=cur->next;
}
delete _tail;
cur->next=NULL;
_tail=cur;
}
void PushFront(const DataType& x)//头插
{
Node* tmp=new Node(x);
if(_head==NULL)
{
_head=tmp;
}
Node* cur=_head;
_head=tmp;
tmp->next=cur;
}
void PopFront()//头删
{
if (_head==NULL)
{
return ;
}
Node* tmp=_head->next;
delete _head;
_head=tmp;
}
void Insert(Node* pos,const DataType x)//某一个位置插入某个元素
{
Node* cur=_head;
if (_head==NULL||pos==NULL)//1.链表为空 或者 插入的位置正好为空位置
{
return ;
}
while (cur)//2.找结点位置
{
if (cur==pos)
{
break;
}
cur=cur->next;
}
if (cur==NULL)//2.1pos位置没有在链表中
{
return ;
}
//2.2pos在链表在链表中,创建结点并插入
Node* tmp=new Node(x);
Node* next=cur->next;
cur->next=tmp;
tmp->next=next;
//如果是想让插入的结点在在pos位置之前,那么加上交换
std::swap(cur->data,tmp->data);
}
Node* Find(const DataType& x)//查找某一个元素的地址,没有返回空---二分查找
{
if (_head==NULL)
{
return NULL;
}
Node* cur=_head;
while(cur)
{
if (cur->data==x)
{
return cur;
break;
}
cur=cur->next;
}
return NULL;
}
void Remove(const DataType& x)//删除链表中第一次出现的该元素
{
if (_head==NULL)//1.如果链表为空,直接返回
{
return ;
}
Node* pos=Find(x);//2.先找到链表中改结点第一个的位置
if (pos==NULL)//2.1如果链表中没有改元素。直接返回
{
return ;
}
if (pos==_head&&_head->next==NULL)//2.2如果元素是链表的第一个元素,且链表只有一个元素;
{
delete _head;
_head=NULL;
return ;
}
if (pos==_tail)//2.3如果是最后一个元素
{
Popback();
return ;
}
//3.除了上述三种特殊情况以外
Node* next=pos->next;
std::swap(pos->data,next->data);
if (next->next)
{
Node* next_next=next->next;
delete next;
pos->next=next_next;
}
else//pos在倒数第二个结点
{
delete next;
pos->next=NULL;
_tail=pos;
}
}
void RemoveAll(const DataType& x)//删除链表中全部的改元素
{
while(Find(x))
{
Remove(x);
}
}
void Sort()//链表元素排序--冒泡(将结点值交换)----升序
{
//1.如果链表只有一个结点或者没有结点,直接返回
if (_head==NULL||_head->next==NULL)
{
return ;
}
//2.链表有多个结点
Node* tail=NULL;
while(tail!=_head)
{
Node* cur=_head;
while (cur->next!=tail)
{
Node* next=cur->next;
if (cur->data > next->data)
{
std::swap(cur->data,next->data);
}
cur=cur->next;
}
tail=cur;
}
}
void Destroy()//释放链表所有结点内存,并将头,尾指针赋为NULL
{
Node* cur=_head;
while(cur)
{
Node* tmp=cur;
cur=cur->next;
delete tmp;
}
_head=NULL;
_tail=NULL;
}
private:
Node* _head;//指向链表的头结点
Node* _tail;//指向链表的尾结点
};
ostream& operator<<(ostream& os,LinkList& linklist)
{
ListNode* cur=linklist._head;
while(cur)
{
os<<cur->data<<" ";
cur=cur->next;
}
return os;
}
void test()
{
LinkList l;
l.PushBack(2);
l.PushBack(7);
l.PushBack(8);
l.PushBack(9);
l.PushBack(1);
l.PushBack(4);
l.PushBack(5);
l.PushBack(3);
l.PushBack(3);
l.PushBack(3);
l.PushBack(3);
l.PushBack(3);
l.PushBack(3);
cout<<l<<endl;
l.RemoveAll(3);
cout<<l<<endl;
//ListNode* pos=l.Find(1);
//l.Insert(pos,99);
//cout<<l<<endl;
//ListNode* pos=l.Find(3);
// l.Insert(pos,99);
//cout<<l<<endl;
//l.PopFront();
//l.PopFront();
//l.PopFront();
//l.Popback();
//l.Popback();
//l.Popback();
//l.Sort();
//cout<<l<<endl;
//cout<<"----"<<endl;
//LinkList l2;
//l2.PushBack(6);
//l2.PushBack(7);
//l2.PushBack(8);
//l2.PushBack(9);
//l2.PushBack(10);
//l2.PushBack(11);
//l2.PushBack(12);
//l2.PushBack(13);
//cout<<l2<<endl;
// l=l2;
//cout<<l<<endl;
}
int main()
{
test();
return 0;
}
接下来,开始写c++的三大特性之 继承和多态方面的知识