0
点赞
收藏
分享

微信扫一扫

京东销量(销额)数据分析:2023年9月京东奶粉行业品牌销售排行榜

niboac 2023-10-28 阅读 36

目录

一、list的介绍及使用

什么是list?

list的基本操作

增删查改

获取list元素

不常见操作的使用说明

​编辑

接合splice

​编辑

移除remove

去重unique

二、模拟实现list

大框架

构造函数

尾插push_back

迭代器__list_iterator

list的迭代器要如何跑起来

iterator的构造函数

begin()与end()

operator++

operator*

operator!=

测试

operator->

const迭代器

增删查改

insert()

❗erase()

pop_back()

完整代码

List.h

test.cpp


一、list的介绍及使用

什么是list?

1.list是可以在常数范围内在任意位置进行插入和删除的序列式容器,并且该容器可以前后双向迭代

2.其底层是双向链表结构

双向链表中每个元素存储在互不相关的独立节点中,在节点中通过指针指向其前一个元素和后一个元素。

3.list与forward_list非常相似。

最主要的不同在于forward_list是单链表,只能朝前迭代。(这也使得它更加得简单高效)

4.与其他的序列式容器相比(array,vector,deque),list通常在任意位置进行插入、移除元素的执行效率 更好。

5.与其他序列式容器相比,list和forward_list最大的缺陷是不支持任意位置的随机访问

比如:要访问list的第6个元素,必须从已知的位置(如头部 或 尾部)迭代到该位置,在这段位置上迭代需要线性的时间开销。

list还需要一些额外的空间,以保存每个节点的相关联信息。(对于存储类型较小元素的大list来说,这可能是一个重要的因素)

list的基本操作

包头文件:<list>

 

就和我们用过的vector一样,list也是个类模板:

#include<iostream>
#include<list>
using namespace std;

int main() {
list<int> lt;
return 0;
}

list的大部分操作,我们不需要记忆,因为很多用起来和vector、string的差不多,就算忘了,直接查文档即可。所以这里就一笔带过。

增删查改

函数名功能
assign覆盖
push_front头插
pop_front头删
push_back尾插
pop_back尾删
insert插入
erase删除
swap交换
resize改变大小
clear清空

在学vector时,我们在insert和erase处,讲到了迭代器失效的问题。这个问题是否同样存在于list中呢?

实际上,list中进行insert操作是不存在迭代器失效的问题的,而erase操作则会有。

因为insert仅需要改变指针间的指向关系即可,不存在变成野指针的问题:

而erase会导致 指向被删节点的指针 变成野指针的问题,所以存在迭代器失效的情况。

获取list元素

函数名功能
front返回list的第一个节点中值的引用
back返回list的最后一个节点中值的引用

不常见操作的使用说明

这些操作很少用,但由于很多我们之前没见过,所以这里也做下说明。

接合splice

splice意为“接合”,即把一个链表接合到另一个链表上去。

示例:

int main() {
list<int> l1(4,0);
for (auto e : l1) {
cout << e << " ";
}
cout << endl;

list<int> l2(2, 1);
for (auto e : l2) {
cout << e << " ";
}
cout << endl;

l1.splice(l1.begin(), l2);
for (auto e : l1) {
cout << e << " ";
}
cout << endl;
return 0;
}
移除remove

remove可以移除所有的待找元素。

示例:

int main() {
list<int> lt;
lt.push_back(1);
lt.push_back(1);
lt.push_back(1);
lt.push_back(2);
lt.push_back(3);
lt.push_back(4);
lt.remove(1);
for (auto e : lt) {
cout << e << " ";
}
return 0;
}

相关函数remove_if()用于删除 满足特定条件的元素。

去重unique

unique仅对 有序的元素集合 有效,所以,在使用unique前 要先对集合排序!

示例:

int main() {
list<int> lt;
lt.push_back(4);
lt.push_back(1);
lt.push_back(9);
lt.push_back(2);
lt.push_back(2);
lt.push_back(4);
lt.sort();
lt.unique();
for (auto e : lt) {
cout << e << " ";
}
cout << endl;
return 0;
}

其余还有:合并merge、排序sort、逆置reverse,就不详讲了,最重要的是 要培养阅读文档的能力。

二、模拟实现list

从0开始 实现一遍list,可以让我们清晰地认识list的结构。

大框架

先把大框架搭出来,再慢慢填充血肉。

list是链表结构,本身要定义出一个类去描述它;而list的每个节点list_node,也需要定义一个类来描述。

 
namespace jzy 
{
template<class T>
class list_node
{
public:
list_node<T>* _pre;  
list_node<T>* _next;
T _data;
};

template<class T>
class list
{
typedef list_node<T> Node;   //list_node太长了,改成Node用着顺手
public:

private:
Node* _head;   //list是带头结点的链表,所以成员变量只需一个头节点
};
}

构造函数

先实现 节点的构造函数:

我们想要达到的效果是:构造出的list,内含一个头点node,这个node已被初始化过。

如图:list为双向循环链表,其node被初始化:

既然想要node在创建伊始就被初始化,那我们可以直接写个node的构造函数:

class list_node
{
public:
T _data;
list_node<T>* _pre;
list_node<T>* _next;

list_node(const T T())
:_data(x)
,_pre(nullptr)
,_next(nullptr)
{}
};

这样,我们每创建出一个node,就是被初始化过的了。

class list_node
{
public:
T _data;
list_node<T>* _pre;
list_node<T>* _next;

list_node(const T T())
:_data(x)
,_pre(nullptr)
,_next(nullptr)
{}
};

再实现list的构造函数:

因为是带头双向循环列表,所以创建伊始,就有个哨兵位的头节点。

namespace jzy
{
template<class T>
class list_node
{
public:
T _data;
list_node<T>* _pre;
list_node<T>* _next;

list_node(const T T())
:_data(x)
,_pre(nullptr)
,_next(nullptr)
{}
};

template<class T>
class list
{
typedef list_node<T> Node;
public:
list() {
_head = new Node;
_head->_next = _head;
_head->_pre = _head;
}

private:
Node* _head;
};
}

尾插push_back

步骤:

1.找到尾节点tail

2.创建newNode

3.改变_head、tail的指针与newNode的指向关系

void push_back(const T& val) {  
  Node* tail = _head->_pre;
  Node* newNode = new Node(val); //实例化出一个值为val的node

  tail->_next = newNode;   //改变指针的指向关系
  newNode->_pre = tail;
  newNode->_next = _head;
  _head->_pre = newNode;
}

迭代器__list_iterator

list的迭代器要如何跑起来

我们之前实现过string、vector的迭代器,它们的迭代器都是原生指针,当时我们说过:“就当作指针来用”,可以解引用,也可以++。

所以迭代器用起来真的很方便:

vector<int>::iterator it=v.begin();
while(it!=v.end()){
cout<<*it<<" ";
it++;
}

list的迭代器本质也同样是一个指向node的指针。但是!list的迭代器不能 解引用和++,所以目前没法跑起来。

接下来,我们先把 __list_iterator类 的框架搭出来,然后挨个实现里面的运算符。

list迭代器的框架:

template<class T>
class __list_iterator
{
public:
  typedef list_node<T> Node;     //这俩名字都太长了,typedef个短点儿的,好使
  typedef __list_iterator<T> iterator;

  Node* node;     //list迭代器本质是一个指向node的指针
};

iterator的构造函数

template<class T>
class __list_iterator    
{        
public:
  typedef list_node<T> Node;
  typedef __list_iterator<T> iterator;  
  Node* node;

  //构造函数:当iterator被构造出来时,其实就是构造了一个指向特定节点的指针
  __list_iterator(Node* pn)   //这里小心,返回值不能写成iterator
      :node(pn)
  {}
};

begin()与end()

begin()为第一个元素的位置,end()为最后一个元素的后一个位置:

因为begin与end返回的是list的位置,所以要把这两个函数实现在list类里,而不是__list_iterator里。

iterator begin() {
  return iterator(_head->_next);   //返回匿名对象
}

iterator end() {
return iterator(_head);
}

operator++

++操作包括:前置++、后置++。两者区分点在于后置++有占位符int。

//operator++
//前置++
iterator& operator++() {   //为什么返回类型是iterator? 就跟int++完还是int一样,迭代器++完,还得是迭代器。。。
  node = node->_next;
  return *this;  
}

//后置++
iterator operator++(int) {    
  __list_iterator<T> copy_it(*this);  
  node = node->_next;
  return copy_it;
}

operator*

T& operator*() {
return node->_data;
}

operator!=

bool operator!=(const iterator& it) {  
return node != it.node;     //node是it的成员变量        
}

测试

测试一下:

#include"List.h"
using namespace jzy;
void test1()
{
list<int> lt;
lt.push_back(1);
lt.push_back(2);
lt.push_back(3);
lt.push_back(4);

list<int>::iterator it = lt.begin();
while (it != lt.end()) {
cout << *it << " ";
it++;
}
}
int main()
{
test1();
return 0;
}

之前说过,范围for的底层就是替换成了迭代器。只要迭代器实现出来,那范围for也自然能用了。

operator->

迭代器模拟的是指针p,p在访问 自定义类型的成员 时,有两种方式:

1.(*p).XX

2.p->XX

我们已经实现了*运算符,现在就来完善下,实现箭头运算符。这样,迭代器就能通过->,访问自定义类型的成员了。

。。。

这个箭头运算符,我准备实现的时候i,发现一点头绪都没有。。。

所以我们还是看下源码,学习下大佬是怎么实现的吧。

源码:

T* operator->() {
  return &(operator*());
}

这个源码比较难理解,我来解释一下:

测试一下:

先在List.h里写一个自定义类型,然后我们用迭代器去访问name。

struct student     
{
  char name[100];
  size_t age;
  char tele[100];
};

test.cpp:

void test2() {
list<student> lt;
lt.push_back({ "Tom",12,"110" });
lt.push_back({ "Billy",10,"888" });

list<student>::iterator it = lt.begin();
while (it != lt.end()) {
cout << it->name << " ";     //->
it++;
}
}

const迭代器

我们来看下面这个情境:

void Print(const list<int> lt) {      //lt被const修饰
list<int>::iterator it = lt.begin();
while (it != lt.end()) {
cout << *it << " ";
it++;
}
}
void test3() {
list<int> lt;
lt.push_back(1);
lt.push_back(2);
lt.push_back(3);
lt.push_back(4);
Print(lt);
}

const对象lt 是没法调用普通迭代器的,因为权限小的 没法调用 权限大的。const对象要调用const迭代器。

那怎么实现const迭代器呢?

其实,const迭代器与普通迭代器 的内部实现是一样的,唯一区别就是普通迭代器返回T&,可读可写,而const迭代器返回const T&,可读不可写。

最笨的办法,就是copy一份刚刚的迭代器代码,然后给每个函数的返回值加上const修饰,再把名称改成_const __ list__iterator。

先来展示一下笨办法:

#pragma once
#include<iostream>
using namespace std;
namespace jzy
{
template<class T>
class list_node
{
……
};

template<class T>
class __list_iterator      
{        
……
};

template<class T>             //拷贝一份__list_iterator的代码
class _const__list_iterator   //所做的修改:1.把iterator换成const_iterator(换个名字而已) 2.在T&、T*前加上const
{        
public:
typedef list_node<T> Node;
typedef _const__list_iterator<T> const_iterator;          
Node* node;

//构造函数
_const__list_iterator(Node* pn)  
:node(pn)
{}

//operator++
//前置++
const_iterator& operator++() {  
node = node->_next;
return *this;
}

//后置++
const_iterator operator++(int) {
__list_iterator<T> copy_it(*this);
node = node->_next;
return copy_it;
}

const T& operator*() {
return node->_data;
}

//operator--
//前置--
const_iterator& operator--() {
node = node->_pre;
return *this;
}

//后置--
const_iterator operator--(int) {
__list_iterator copy_it(*this);
node = node->_pre;
return copy_it;
}

bool operator!=(const const_iterator& it) {
return node != it.node;            
}

const T* operator->() {
return &(operator*());
}
};

template<class T>
class list
{
public:
typedef list_node<T> Node;
typedef __list_iterator<T> iterator;
typedef _const__list_iterator<T> const_iterator;  

……

iterator begin() {
return iterator(_head->_next);  
}

const_iterator begin() const{     //begin()和end()也要实现const版本,供const对象调用
return const_iterator(_head->_next);  
}

iterator end() {
return iterator(_head);
}

const_iterator end() const{
return const_iterator(_head);
}

private:
Node* _head;
};
}

测试下,当我们尝试修改const对象:

很好,修改不了~

但是!上面的代码重复度太高了,并不是好的解决思路。库里的思路是:用模板实现const迭代器!

其实我们观察可以发现,iterator和const_iterator的实现中,只有operator* 和operator->的返回值不一样,普通迭代器的返回值是 T& 与T*,后者是const T&与const T *。

所以,可以把T&和T *用类模板Ref(reference)、Ptr(pointer)表示,即现在设三个模板参数:

template<class T,class Ref,class Ptr >

这样一来,我们传iterator / const_iterator,编译器就能自动匹配。

你现在一定很多问号,这一块的确不好理解。

我就着下面这张图,为你解释下为什么传三个模板参数就能起到自动匹配的效果:

实现:

template<class T,class Ref,class Ptr>
class __list_iterator    
{        
public:
  typedef list_node<T> Node;
  typedef __list_iterator<T,Ref,Ptr> iterator;
  Node* node;

  ……     //省略掉的部分都和之前的一样

  Ref operator*() {
      return node->_data;
  }

  ……

  Ptr operator->() {
      return &(operator*());
  }
};

不光是__list_iterator要改,list也要相应地作出改动:

template<class T>
class list
{
public:
  typedef list_node<T> Node;
  typedef __list_iterator<T,T&, T*> iterator;
  typedef __list_iterator<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);
  }

private:
  Node* _head;
};

增删查改

insert()

iterator insert(iterator pos, const T& val) {  
  Node* cur = pos.node;   //注意iterator和node的关系,node是它的成员
  Node* pre = cur->_pre;
  Node* pNew = new Node(val);  

  pre->_next = pNew;   //注意:指针是双向的
  pNew->_pre = pre;
  pNew->_next = cur;
  cur->_pre = pNew;

  return iterator(pNew);
}

有了insert(),我们就可以复用它来进行头插了:

void push_front(const T& val) {
  insert(begin(), val);
}

❗erase()

void erase(iterator pos) {
  assert(pos!= end());

  Node* cur = pos.node;
  Node* pre = cur->_pre;
  Node* next = cur->_next;
  pre->_next = next;
  next->_pre = pre;
  delete[] cur;
}

但是,这样写真的对吗?

还记得我们在vector那里,花了很大篇幅说到的insert/erase迭代器失效的问题吗?

这里也同样要考虑迭代器失效的问题。

修改后的正确版本:

iterator erase(iterator pos) {
  assert(pos!= end());

  Node* cur = pos.node;
  Node* pre = cur->_pre;
  Node* next = cur->_next;
  pre->_next = next;
  next->_pre = pre;
  delete[] cur;

  return iterator(next);
}

pop_back()

void pop_back() {
  Node* EndNode = end().node;
  Node* ToBeErase = EndNode->_pre;
  Node* pre = ToBeErase->_pre;
  pre->_next = EndNode;
  EndNode->_pre = pre;
  delete[] ToBeErase;
}

至于find(),你问我为啥不实现find()?没必要呀!算法库里就有find()模板,直接拿来用就好。

完整代码

List.h

#pragma once
#include<iostream>
#include<algorithm>
#include<assert.h>
using namespace std;
namespace jzy
{
template<class T>
class list_node
{
public:
T _data;
list_node<T>* _pre;
list_node<T>* _next;

list_node(const T T())
:_data(x)
,_pre(nullptr)
,_next(nullptr)
{}
};

template<class T,class Ref,class Ptr>
class __list_iterator    
{        
public:
typedef list_node<T> Node;
typedef __list_iterator<T,Ref,Ptr> iterator;
Node* node;

//构造函数
__list_iterator(Node* pn)  
:node(pn)
{}

//operator++
//前置++
iterator& operator++() {
node = node->_next;
return *this;  
}

//后置++
iterator operator++(int) {    
__list_iterator<T,Ref,Ptr> copy_it(*this);  
node = node->_next;
return copy_it;
}

Ref operator*() {
return node->_data;
}

//operator--
//前置--
iterator& operator--() {
node = node->_pre;
return *this;
}

//后置--
iterator operator--(int) {
__list_iterator copy_it(*this);
node = node->_pre;
return copy_it;
}

bool operator!=(const iterator& it) {
return node != it.node;            
}

Ptr operator->() {
return &(operator*());
}
};

//template<class T>
//class _const__list_iterator     //所做的修改:1.把iterator换成const_iterator(换个名字而已) 2.在T& 前加上const
//{        
//public:
// typedef list_node<T> Node;
// typedef _const__list_iterator<T> const_iterator;          
// Node* node;

// //构造函数
// _const__list_iterator(Node* pn)  
// :node(pn)
// {}

// //operator++
// //前置++
// const_iterator& operator++() {  
// node = node->_next;
// return *this;
// }

// //后置++
// const_iterator operator++(int) {
// __list_iterator<T> copy_it(*this);
// node = node->_next;
// return copy_it;
// }

// const T& operator*() {
// return node->_data;
// }

// //operator--
// //前置--
// const_iterator& operator--() {
// node = node->_pre;
// return *this;
// }

// //后置--
// const_iterator operator--(int) {
// __list_iterator copy_it(*this);
// node = node->_pre;
// return copy_it;
// }

// bool operator!=(const const_iterator& it) {
// return node != it.node;            
// }

// const T* operator->() {
// return &(operator*());
// }
//};

template<class T>
class list
{
public:
typedef list_node<T> Node;
typedef __list_iterator<T,T&, T*> iterator;
typedef __list_iterator<T, const T&, const T*> const_iterator;

list() {
_head = new Node;
_head->_next = _head;
_head->_pre = _head;
}

void push_back(const T& val) {
Node* tail = _head->_pre;
Node* newNode = new Node(val);  

tail->_next = newNode;    
newNode->_pre = tail;
newNode->_next = _head;
_head->_pre = newNode;
}

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);
}

iterator insert(iterator pos, const T& val) { //注意iterator和node的关系,node是它的成员
Node* cur = pos.node;
Node* pre = cur->_pre;
Node* pNew = new Node(val);  
pre->_next = pNew;   //注意:指针是双向的
pNew->_pre = pre;
pNew->_next = cur;
cur->_pre = pNew;

return iterator(pNew);
}

void push_front(const T& val) {
insert(begin(), val);
}

iterator erase(iterator pos) {
assert(pos!= end());

Node* cur = pos.node;
Node* pre = cur->_pre;
Node* next = cur->_next;
pre->_next = next;
next->_pre = pre;
delete[] cur;

return iterator(next);
}

void pop_back() {
Node* EndNode = end().node;
Node* ToBeErase = EndNode->_pre;
Node* pre = ToBeErase->_pre;
pre->_next = EndNode;
EndNode->_pre = pre;
delete[] ToBeErase;
}


private:
Node* _head;
};

//这个是测试用的
struct student
{
char name[100];
size_t age;
char tele[100];
};
}

test.cpp

#include"List.h"
using namespace jzy;
void test1()
{
list<int> lt;
lt.push_back(1);
lt.push_back(2);
lt.push_back(3);
lt.push_back(4);

list<int>::iterator it = lt.begin();
while (it != lt.end()) {
cout << *it << " ";
it++;
}
cout << endl;

for (auto e : lt) {
cout << e << " ";
}
}
void test2() {
list<student> lt;
lt.push_back({ "Tom",12,"110" });
lt.push_back({ "Billy",10,"888" });
list<student>::iterator it = lt.begin();
while (it != lt.end()) {
cout << it->name << " ";
it++;
}
cout << endl;
}

void Print(const list<int>& lt) {
list<int>::const_iterator it = lt.begin(); //注意看它所调用的begin()函数!
while (it != lt.end()) {
//*it = 100;
cout << *it << " ";
it++;
}
}
void test3() {
list<int> lt;
lt.push_back(1);
lt.push_back(2);
lt.push_back(3);
lt.push_back(4);
Print(lt);
}
void test4() {
list<int> lt;
lt.push_back(1);
lt.push_back(2);
lt.push_back(3);
list<int>::iterator pos = lt.begin();
lt.insert(pos, 9);
lt.push_front(99);
list<int>::iterator pos2 = lt.begin();
lt.erase(pos2);
lt.pop_back();

for (auto e : lt) {
cout << e << " ";
}
}
int main()
{
//test1();
//test2();
//test3();
test4();
return 0;
}
举报

相关推荐

0 条评论