stl的list是带头双向循环链表
list的特殊成员函数
--splice
(1)void splice (iterator position, list& x);
将链表x上的所有结点,转移给当前链表的position前一个位置
//lt1 :1 2 3 4
list<int> lt1;
lt1.push_back(1);
lt1.push_back(2);
lt1.push_back(3);
lt1.push_back(4);
//lt2 :5 6 7 8
list<int> lt2;
lt2.push_back(5);
lt2.push_back(6);
lt2.push_back(7);
lt2.push_back(8);
list<int>::iterator it1 = lt1.begin();
//1 void splice(iterator pos, list& x)
//将x链表的数据全部转移给某个链表的pos之前
lt1.splice(it1, lt2);
//此时 lt1:5 6 7 8 1 2 3 4
cout << "lt1的数据:";
for (auto x : lt1)
cout << x << " ";
cout << endl;
cout << "lt2的数据:";
for (auto x : lt2)
cout << x << " ";
cout << endl;
(2) void splice (iterator position, list& x, iterator i);
将x链表位于迭代器位置i的结点,转移到当前链表的position前
// lt1:5 6 7 8 1 2 3 4
// lt2:10 11
lt2.push_back(10);
lt2.push_back(11);
//将x链表的i位置数据,转移给pos之前
//2 void splice(iterator pos, list& x, iterator i)
//将lt1第一个迭代器的数据,转移给lt2首元素之前
lt2.splice(lt2.begin(), lt1, lt1.begin());
cout << "lt1的数据:";
for (auto x : lt1)
cout << x << " ";
cout << endl;
cout << "lt2的数据:";
for (auto x : lt2)
cout << x << " ";
cout << endl;
(3) void splice (iterator position, list& x, iterator first, iterator last);
将x链表的某个迭代器区间[ 左闭右开 ), 转移到当前链表的pos之前
//lt1:6 7 8 1 2 3 4
//lt2:5 10 11
//将x链表的某个迭代器区间[first, last),转移到pos之前
//3 void splice(iterator pos, list& x, iterator first, iterator last)
//将lt1的7~3所有结点(不包含3),转移给lt2头部之前
list<int>::iterator first = find(lt1.begin(), lt1.end(), 7);
list<int>::iterator last = find(lt1.begin(), lt1.end(), 3);
if (first != lt1.end() && last != lt1.end())
lt2.splice(lt2.begin(), lt1, first, last);
cout << "lt1的数据:";
for (auto x : lt1)
cout << x << " ";
cout << endl;
cout << "lt2的数据:";
for (auto x : lt2)
cout << x << " ";
cout << endl;
--remove
void remove (const T& val);
删除链表中所有值为val的结点
list<int> lt;
lt.push_back(1);
lt.push_back(2);
lt.push_back(1);
lt.push_back(4);
lt.push_back(1);
//void remove(const T& val)
lt.remove(1);
for (auto x : lt)
cout << x << " ";
cout << endl;
--sort
(1) 算法库里的sort用的是快速排序,必须满足空间连续才能使用,例如vector和string
(2) 链表的sort用的是归并排序,归并对数组需要开空间,对链表不需要开额外的空间.
(3) 涉及高速缓存命中率,vector连续的物理空间会让访问数据的速度更快
一组数据,【list + sort 排序】 不如用 【vector + 算法库sort排序】
list<int> lt1;
vector<int> v1;
srand((unsigned)time(nullptr));
int num = 1000000;
//产生num个随机数,把它们分别插入到list和vector中,比较排序的时间
for (int i = 0; i < num; ++i)
{
int insert = rand() % 100000;
lt1.push_back(insert);
v1.push_back(insert);
}
//链表的排序
clock_t beginList = clock();
lt1.sort();
clock_t endList = clock();
//算法库的排序
clock_t beginVector = clock();
sort(v1.begin(), v1.end());
clock_t endVector = clock();
cout << "链表sort时间:" << endList - beginList << endl;
cout << "算法库sort时间:" << endVector - beginVector << endl;
list模拟实现
--总览
(1) 在sgi版的stl中,它的成员变量只有一个头结点指针:
(2) 链表结点的类型声明:
(3) list的迭代器类型不是原生指针:
--迭代器的实现(重点)
容器使用迭代器进行遍历的代码:
void testMyList1()
{
list<int> lt;
lt.push_back(1);
lt.push_back(2);
lt.push_back(3);
list<int>::iterator it = lt.begin();
while (it != lt.end())
{
std::cout << *it << " ";
++it;
}
}
如果list的迭代器使用原生指针实现,即用链表的结点指针作为迭代器,会出现以下问题:
(1) 链表的各个结点位置一般不是连续的,++it无法找到下一个结点的位置
(2) it是Node*类型,结点指针,*it不能直接取得结点存的数据.
因此,list的迭代器是一个单独的类型,然后在该类型内部运算符重载++、*、!=
普通迭代器类型
(1) 我们用链表的一个结点指针作为迭代器的成员变量
template<class T>
struct ListIterator
{
typedef ListNode<T> Node; //简化结点类型写法
typedef ListIterator<T> Iterator;//简化迭代器类型写法
Node* _node;
ListIterator(Node* x)
:_node(x)
{}
}
(2) 在内部重载++、!=、*运算符
//要控制!= ++ 和 *,在迭代器类型里重载这些运算符
bool operator!=(const Iterator& it)
{
//比较结点地址是否相同
return this->_node != it._node;
}
//前置++
//让当前迭代器指向下一个结点
Iterator& operator++()
{
_node = _node->_next;
return *this;
}
//对迭代器类型进行*,返回的是结点保存的数据引用,可读可写
T& operator*()
{
return _node->_date;
}
(3) 特殊:->运算符
使用迭代器遍历数据时,我们可以把迭代器直接当成数据的地址来使用.
list<int>::iterator it = lt.begin();
while (it != lt.end())
{
std::cout << *it << " ";
++it;
}
如果list内部存的数据,是结构体类型呢?
struct A
{
int _a;
int _b;
A(int a = 0, int b = 0)
:_a(a)
,_b(b)
{}
};
//测试迭代器内重载的operator->()
void testMyList3()
{
list<A> lt;
lt.push_back(A(0, 0));
lt.push_back(A(1, 1));
lt.push_back(A(2, 2));
list<A>::iterator it = lt.begin();
while (it != lt.end())
{
std::cout << *it << std:endl;
++it;
}
}
*it的结果是A类型数据,无法用cout直接打印
能否支持以下使用:
std::cout << it->_a << " " << it->_b<< std::endl;
用起来就像it是结点内数据的指针
//在迭代器类型内部重载->运算符,用于链表存放结构体类型的清况
T* operator->()
{
return &(this->operator*());
}
代码解析:
(a) 它是迭代器类型的成员函数,只有迭代器类型对象才能调用它.
(b) this->operator*(),返回该迭代器指向的具体数据
(c) 外面加&,最终返回 【迭代器指向的数据】的地址
(d) it -> _a 本质是it -> -> _a
it->调用it.operator->(),返回A数据地址
然后再 结构体地址->结构体成员.
编译器为了可读性,这块地方特殊处理,不需要写2个箭头
const迭代器类型
使用const迭代器遍历的例子:
//专门用来打印链表数据的函数
void testMyList2(const list<int>& lt)
{
//const对象会调用const迭代器
list<int>::const_iterator cit = lt.begin();
while (cit != lt.end())
{
std::cout << *cit << " ";
++cit;
}
}
const迭代器和普通迭代器区别是const迭代器不能用*或者->修改数据,但普通迭代器可以,
迭代器的operator*()只有返回值T&和const T&不同,
operator->()只有返回值T和const T不同,
把返回值作为模板参数传入,可以在list中通过传T&或者const T&等确定迭代器类型
//const迭代器和普通迭代器复用同一个迭代器类型,将不同的返回值作为模板参数传入
typedef ListIterator<T,T&, T*> iterator;
typedef ListIterator<T, const T&, const T*> const_iterator;
迭代器整体代码
list迭代器类型:
template<class T,class Reference, class Pointer>
struct ListIterator
{
typedef ListNode<T> Node; //简化结点类型
typedef ListIterator<T,Reference, Pointer> Iterator; //简化迭代器类型
Node* _node;
ListIterator(Node* x)
:_node(x)
{}
//要控制!= ++ 和 *,在迭代器类型里重载这些运算符
bool operator!=(const Iterator& it)
{
//比较结点地址是否相同
return this->_node != it._node;
}
//前置++
//让当前迭代器指向下一个结点
Iterator& operator++()
{
_node = _node->_next;
return *this;
}
//后置++
//让当前迭代器指向下一个结点,返回++之前的迭代器
Iterator operator++(int)
{
Iterator ret = *this;
_node = _node->_next;
return ret;
}
//前置--
//让当前迭代器指向前一个结点
Iterator& operator--()
{
_node = _node->_pre;
return *this;
}
//后置--
//让当前迭代器指向前一个结点,返回--之前的迭代器
Iterator operator--(int)
{
Iterator tmp = *this;
_node = _node->_pre;
return tmp;
}
//对迭代器类型进行*,返回的是结点保存的数据引用
Reference operator*()
{
return _node->_date;
}
//it->调用it.operator->()
//返回数据的指针,用于T是结构体类型的情况
Pointer operator->()
{
return &( operator*() );
}
};
list内部:
//const迭代器和普通迭代器复用同一个迭代器类型,将不同的返回值作为模板参数传入
typedef ListIterator<T,T&, T*> iterator;
typedef ListIterator<T, const T&, const T*> const_iterator;
iterator begin()
{
return iterator(_head->_next);
}
const_iterator begin()const
{
return const_iterator(_head->_next);
}
iterator end()
{
return iterator(_head);
}
const_iterator end()const
{
return const_iterator(_head);
}
--在任意位置插入删除
//删除pos位置的结点,返回删除结点的下一个结点位置
iterator erase(iterator pos)
{
Node* cur = pos._node;//当前要删除的结点
Node* pre = cur->_pre;
Node* next = cur->_next;
//链接pre和next
pre->_next = next;
next->_pre = pre;
//删除结点
delete cur;
return iterator(next);
}
//在pos位置前插入val,返回新插入结点的迭代器位置
iterator insert(iterator pos, const T& val)
{
Node* newNode = new Node(val);
Node* next = pos._node;
Node* pre = next->_pre;
//链接newNode和pre
newNode->_pre = pre;
pre->_next = newNode;
//链接newNode和next
newNode->_next = next;
next->_pre = newNode;
return iterator(newNode);
}
--构造函数
默认构造
//构造函数,初始化头结点,并让next和pre指向自己
list()
{
_head = new Node;
_head->_next = _head;
_head->_pre = _head;
}
迭代器区间构造
//用一段迭代器区间来构造链表
template<class InputIterator>
list(InputIterator first, InputIterator last)
{
//由于是模板,可以用vector、list、string等区间来构造
_head = new Node;
_head->_next = _head;
_head->_pre = _head;
while (first != last)
{
push_back(*first);
++first;
}
}
拷贝构造
list的拷贝构造是深拷贝
方法一: 通过遍历已有链表lt,将数据不停尾插给新链表.
方法二:复用迭代器区间构造一个tmp,交换tmp与新链表的成员变量,
必须保证tmp所得头结点有效
//拷贝构造1
list(const list<T>& lt)
{
_head = new Node;
_head->_next = _head;
_head->_pre = _head;
const_iterator it = lt.begin();
while (it != lt.end())
{
push_back(*it);
++it;
}
}
//拷贝构造2
list(const list<T>& lt)
{
_head = new Node;
_head->_next = _head;
_head->_pre = _head;
list<T> tmp(lt.begin(), lt.end());
//直接交换哨兵位头结点
std::swap(tmp._head, _head);
}
--析构函数
clear()用于清空有效结点,clear()调用erase()释放结点空间.
//析构函数
~list()
{
clear();
delete _head;
}
//清空结点,头结点除外
void clear()
{
iterator it = begin();
while (it != end())
{
it = erase(it);
}
}
--赋值运算符重载
形参进行拷贝构造,把需要的数据交给形参lt1,
然后直接交换形参和当前对象lt2的成员变量.
形参lt1销毁时也会顺便把原空间带走.
//=运算符重载 lt2 = lt1
list<T>& operator=(list<T> lt1)
{
//交换哨兵位头结点
std::swap(lt._head, _head);
return *this;
}
//测试各种构造函数 以及 =运算符重载
void testMyList5()
{
std::vector<int> v;
v.push_back(1);
v.push_back(2);
//迭代器区间构造
list<int> lv(v.begin(), v.end());
for (auto x : lv)
std::cout << x << " ";
std::cout << std::endl;
//拷贝构造
list<int> lt2 = lv;
for (auto x : lt2)
std::cout << x << " ";
std::cout << std::endl;
//赋值运算符重载
list<int> lt3;
lt3.push_back(9);
lt3.push_back(19);
lt2 = lt3;
for (auto x : lt2)
std::cout << x << " ";
std::cout << std::endl;
}