目录
STL是C++标准库的重要组成部分,list容器在实际中非常的重要,它的常见的接口使用我们都要熟悉,这次肝一波list容器常见的接口使用,帮助大家深入理解
list容器的原理是双向带头循环链表,理解后再看list容器会更容易
双向带头链表实现
我们分文件编写,在头文件 List.h 进行声明,在 List.c 当中进行具体的函数定义,在 test.c 当中的做具体的测试实现
先来了解一下双向链表头文件 List.h 当中接口
#pragma once //防止重复包含
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
//双向链表实现
typedef int LDatetype;
typedef struct ListNode
{
LDatetype date;//数据
struct ListNode* prev;//
struct ListNode* next;//
}LTNode;
LTNode* ListInit();//初始化
LTNode* BuyListNode(LDatetype x);//开辟新节点函数
void ListDestroy(LTNode* phead);//置空函数
void ListPrint(LTNode* phead);//打印函数
void ListPushBack(LTNode* phead, LDatetype x);//尾插
void ListPopBack(LTNode* phead);//尾删
void ListPushFront(LTNode* phead, LDatetype x);//头插
void ListPopFront(LTNode* phead);//头删
LTNode* ListFind(LTNode* phead, LDatetype x);//查找
void ListInsert(LTNode* pos, LDatetype x);//指定位置插入
void ListErase(LTNode* pos);//指定位置删除
函数的定义方法非常重要,需要深入理解,下面详细学习在 List.c 当中具体的函数实现
初始化函数定义
LTNode* ListInit()//初始化
{
//先开空间给哨兵位
LTNode*phead = (LTNode*)malloc(sizeof(LTNode));
phead->next = phead;//都指向自己
phead->prev = phead;
return phead;//采用返回值或二级指针
}
增加新节点函数定义
//开辟新节点函数
LTNode* BuyListNode(LDatetype x)
{
//考虑当链表为空时,所以先开辟一个新的节点
LTNode* newnode =
(LTNode*)malloc(sizeof(LTNode));
if (newnode == NULL)
{
printf("malloc fail\n");
exit(-1);
}
newnode->date = x;//插入数据
newnode->next = NULL;
newnode->prev = NULL;
return newnode;//返回新节点
}
打印函数定义
每一个节点当中都会有一个next存储下一个节点的地址
void ListPrint(LTNode* phead)//打印函数
{
assert(phead);
//到phead结束
LTNode* cur = phead->next;
//此时cur是等于传过来指向头节点的指针
//里面有一个数据存着第一个节点的地址
while (cur != phead)
{
printf("%d ", cur->date);//使用指针获取头节点数据
cur = cur->next;
//每一个节点当中都会有一个next存储下一个节点的地址
//通过地址就可以找到下一个节点
}
printf("\n");
}
尾部插入函数定义
尾是哨兵位的前一个
void ListPushBack(LTNode* phead, LDatetype x)//尾插
{
assert(phead);
LTNode* tail = phead->prev;//尾是哨兵位的前一个
LTNode* newnode = BuyListNode(x);//调用上面开辟新节点函数
//phead tail newnode
tail->next = newnode;//分别依次交换
newnode->prev = tail;
newnode->next = phead;
phead->prev = newnode;
}
尾部删除函数定义
void ListPopBack(LTNode* phead)//尾删
{
assert(phead);
assert(phead->next != phead);//不能删掉哨兵位
//先找尾巴
LTNode* tail = phead->prev;
//要先给数据 在删除
LTNode* tailprev = tail->prev;//找一个指针记录tail
free(tail);
//
tailprev->next = phead;
phead->prev = tailprev;
//ListErase(phead->prev)//可以复用指定删除
}
头部插入函数定义
void ListPushFront(LTNode* phead, LDatetype x)//头插
{
//phead newnode next
assert(phead);
LTNode* newnode = BuyListNode(x);//
LTNode* next = phead->next;
//phead newnode next
phead->next=newnode;
newnode->prev = phead;
newnode->next = next;
next->prev = newnode;
}
头部删除函数定义
void ListPopFront(LTNode* phead)//头删
{
assert(phead);
assert(phead->next != phead);//不能删掉哨兵位
//phead next nextNext
LTNode* next = phead->next;
LTNode* nextNext = next->next;
phead->next = nextNext;
nextNext->prev = phead;
free(next);//...
//ListErase(phead->next);//可以复用指定删除
}
置空函数定义
置空函数一般会放在我们进行插入或删除的函数最后,释放我们在堆上申请的空间,将其还给操作系统,另外也会相应的进行检查越界等问题
void ListDestroy(LTNode* phead)//销毁链表
{
assert(phead);
LTNode* cur = phead->next;
while (cur != phead)
{
LTNode* next = cur->next;//先保存下一个
free(cur);//删除
cur = next;//接着往后
}
free(phead);//在释放头节点
phead = NULL;//传一级无法置空
}
查找某个节点位置
LTNode* ListFind(LTNode* phead, LDatetype x) //查找
{
assert(phead);
LTNode* cur = phead->next;
while (cur != phead)
{
if (cur->date==x)
{
return cur;
}
cur = cur->next;
}
return NULL;
}
指定位置之前插入
void ListInsert(LTNode* pos, LDatetype x)//指定插入
{
//posprev newnode pos
assert(pos);
LTNode* posprev = pos->prev;//
LTNode* newnode = BuyListNode(x);
posprev->next = newnode;//注意顺序
newnode->prev = posprev;
newnode->next = pos;
pos->prev = newnode;
}
删除指定位置
void ListErase(LTNode* pos)//指定删除
{
assert(pos);
LTNode* posprev = pos->prev;
LTNode* posnext = pos->next;
//posprev pos posnext
posprev->next = posnext;
posnext->prev = posprev;
free(pos);
pos = NULL;
}
我们来用上面接口实现在 test.c 当中测试案例
//头尾插入删除
void TestList1()
{
LTNode* plist = ListInit();
ListPushBack(plist, 1);//尾插1234
ListPushBack(plist, 2);
ListPushBack(plist, 3);
ListPushBack(plist, 4);
ListPrint(plist);//打印1 2 3 4
ListPopBack(plist);//尾删两次
ListPopBack(plist);
ListPrint(plist);//打印1 2
ListPushFront(plist, 1);//在头插1 2 3 4
ListPushFront(plist, 2);
ListPushFront(plist, 3);
ListPushFront(plist, 4);
ListPrint(plist);//打印4 3 2 1 1 2
ListPopFront(plist);//头删一次
ListPrint(plist);//打印3 2 1 1 2
ListDestroy(plist);//置空函数 在最后
plist = NULL;
}
//指定删除某个位置
void TestList2()
{
LTNode* plist = ListInit();
ListPushFront(plist, 1);//头插1 2 3 4
ListPushFront(plist, 2);
ListPushFront(plist, 3);
ListPushFront(plist, 4);
LTNode* pos = ListFind(plist, 2);//删除2
if (pos)
{
ListErase(pos);
}
ListPrint(plist);//打印4 3 1
ListDestroy(plist);//置空函数 在最后
plist = NULL;
}
int main()
{
TestList1();
//TestList2();
}
list容器的模拟实现
模拟实现list容器可以帮助我们更好的理解底层迭代器是如何实现的
模拟实现函数的接口
#include<assert.h>
//模拟实现
template<class T>
struct list_node
{
T _data;
list_node<T>* _next;
list_node<T>* _prev;
//构造...
list_node(const T& val = T())//
:_next(nullptr)
, _prev(nullptr)
, _data(val)
{}
};
//迭代器底层模拟
template<class T ,class Ref,class Ptr >//模板
struct _list_iterator//公有
{
typedef list_node<T>Node;
typedef _list_iterator<T,Ref,Ptr> self;//
Node* _node;
//迭代器构造
_list_iterator(Node* node)//
:_node(node)
{}
//取节点数据
Ref operator*()
{
return _node->_data;
}
//重载箭头
Ptr operator->()
{
//复用*
return &_node->data;
}
//前置++
self& operator++()//迭代器++返回迭代器
{
_node = _node->_next;
return *this;
}
//后置++
self& operator++(int)//迭代器++返回迭代器
{
self tmp = (*this);
_node = _node->_next;
return tmp;
}
//前置--
self& operator--()//迭代器++返回迭代器
{
_node = _node->_prev;
return *this;
}
//后置--
self& operator--(int)//迭代器++返回迭代器
{
self tmp = (*this);
_node = _node->_prev;
return tmp;
}
//不相等
bool operator!=(const self& it)
{
return _node != it._node;//注意用.
}
//相等
bool operator==(const self& it)
{
return _node = it._node;//注意用.
}
//析构 节点不属于迭代器 不需要释放 链表浅拷贝
//拷贝构造 默认生成的浅拷贝就可以
};
//链表类
template<class T>
class list
{
typedef list_node<T> Node;//私有数据
public:
typedef _list_iterator<T,T&,T*> iterator;//gongyou 普通迭代器
//公有 const迭代器
typedef _list_iterator<T,const T&,const T*> const_iterator;
//普通迭代器
iterator begin()//返回第一个节点迭代器
{
return iterator(_head->_next);
}
//
iterator end()//返回哨兵位
{
return iterator(_head);
}
//const迭代器
const_iterator begin()const//返回第一个节点迭代器
{
return const_iterator(_head->_next);
}
const_iterator end()const//返回哨兵位
{
return const_iterator(_head);
}
//无参构造
list()
{
cout << "调用无参构造" << endl;
_head = new Node();//
_head->_next = _head;
_head->_prev = _head;
}
//空初始化
void empty_init()
{
_head = new Node();//
_head->_next = _head;
_head->_prev = _head;
}
//有参构造 现代写法 迭代器区间
template <class InputIterator>
list(InputIterator first, InputIterator last)
{
cout << "调用有参构造" << endl;
_head = new Node();//
_head->_next = _head;
_head->_prev = _head;
empty_init();
while (first != last)
{
push_back(*first);
++first;
}
}
//交换函数
void swap(list<T>& s)
{
std::swap(_head, s._head);//交换头指针
}
//拷贝构造
//s2(s)
list(const list<T>& s)
{
cout << "调用拷贝构造" << endl;
empty_init();//先初始化 在和tmp交换
list<T>tmp(s.begin(), s.end());
swap(tmp);
}
//赋值
//s2=s
list<T>& operator=(list<T> s)
{
cout << "调用赋值" << endl;
swap(s);
return *this;
}
//析构
~list()
{
cout << "调用析构" << endl;
clear();//先挨着删除
delete _head;//在释放头节点
_head = nullptr;//哨兵位头节点置空
}
//清除
void clear()
{
iterator it = begin();
while (it != end())
{
it = erase(it);//
}
}
//尾部插入
void push_back(const T& x)
{
//找尾
Node* tail = _head->_prev;//尾是哨兵位的前一个
Node* newnode = new Node(x);//先申请新节点
//_head tail newnode
tail->_next = newnode;
newnode->_prev = tail;
newnode->_next = _head;
_head->_prev = newnode;
}
//头部插入
void push_front(const T& x)
{
insert(begin(),x);
}
//尾删
void pop_back()
{
erase(--end());
}
void pop_front()
{
erase(begin());
}
//指定位置插入 迭代器失效问题 pos之前
iterator insert(iterator pos, const T& x)
{
Node* newnode = new Node(x);//申请新节点
Node* cur = pos._node;//cur就是pos位置
Node* posprev = cur->_prev;
//prev newnode cur
posprev->_next = newnode;
newnode->_prev = posprev;
newnode->_next = cur;
cur->_prev = newnode;
return iterator(newnode);//
}
//指定位置删除
iterator erase(iterator pos)
{
assert(pos != end());
//找到pos后面和前面
//连接后 在删除pos
Node* cur = pos._node;//cur就是pos位置
Node* posprev = cur->_prev;
Node* posnext = cur->_next;
//posprev cur posnext
posprev->_next = posnext;
posnext->_prev = posprev;
delete cur;//
return iterator(posnext);//返回当前位置的下一个位置
}
private:
Node* _head;//哨兵位头节点
};
模拟实现的测试
测试要和上面模拟接口放到一个命名空间里面
//定义和实现
//const 迭代器 用模板控制
void print_list(const list<int>& s)
{
list<int>::const_iterator it = s.begin();
while (it != s.end())
{
//*it = 10;//const 迭代器不允许修改
cout << *it << " ";
++it;
}
cout << endl;
}
//迭代器模拟测试
void test_list1()
{
list<int>s;
s.push_back(1);
s.push_back(2);
s.push_back(3);
s.push_back(4);
list<int>::iterator it = s.begin();
while (it != s.end())
{
//*it = 20;//可修改
cout << *it << " ";
++it;
}
cout << endl;
print_list(s);
}
//头部 尾部删除测试
void test_list2()
{
list<int>s;
s.push_back(1);
s.push_back(2);
s.push_back(3);
s.push_back(4);
s.push_front(10);
s.push_front(10);
s.pop_back(); //尾部删除一个
s.pop_front();//头部删除一个
list<int>::iterator it = s.begin();
while (it != s.end())
{
cout << *it << " ";//打印 10 1 2 3
++it;
}
cout << endl;
}
//指定位置插入
void test_list3()
{
list<int>s;
s.push_back(1);
s.push_back(2);
s.push_back(3);
s.push_back(4);
s.push_back(5);
//偶数前插入这个 偶数*10
auto s1 = s.begin();
while (s1 != s.end())
{
if (*s1 % 2 == 0)
{
s.insert(s1, *s1 * 10);
}
++s1;
}
list<int>::iterator it = s.begin();
while (it != s.end())
{
cout << *it << " ";
++it;
}
cout << endl;
}
//删除指定位置
void test_list4()
{
list<int>s;
s.push_back(1);
s.push_back(2);
s.push_back(3);
s.push_back(4);
s.push_back(5);
s.push_back(6);
s.push_back(7);
//删除所有偶数
auto s1 = s.begin();
while (s1 != s.end())
{
if (*s1 % 2 == 0)
{
//s.erase(s1);//注意删除 存在迭代器失效
s1 = s.erase(s1);
}
else
{
++s1;
}
}
for (auto e : s)
{
cout << e << " ";//打印 1357
}
cout << endl;
s.clear();//清除
s.push_back(1);
s.push_back(2);
for (auto e : s)
{
cout << e << " ";//打印 12
}
cout << endl;
}
//测试拷贝构造和赋值
void test_list5()
{
list<int>s;
s.push_back(1);
s.push_back(2);
s.push_back(3);
s.push_back(4);
list<int>s2;
s2 = s;//调用赋值
for (auto e : s2)
{
cout << e << " ";
}
cout << endl;
}
学习list一定要学会查看文档,这里放一个官方文档链接list官方文档,list容器在实际中非常的重要,我们只要熟悉常见的接口就可以
希望这篇文章大家有所收获,我们下篇见