【C++】list的介绍及使用 | 模拟实现list(万字详解)

news2024/9/20 18:30:37

目录

一、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清空

注:

1.因为list不是连续的结构,它是指针串起来的链表,所以不支持随机访问,“方括号+下标”的访问方式不能用了!

2.我们要遍历list的话就用范围for 或者 迭代器。

3.和vector一样,list里没有专门再实现find,要想查找就用<algorithm>下的find。

在调用find时,用第二种方式:

因为find是函数模板,不是vector里的。

在学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的类设成public,这样类外才能访问到。如果不写public的话,默认权限是private。

或者设计成struct{};,struct的默认访问权限是public。

构造函数

先实现 节点的构造函数:

我们想要达到的效果是:构造出的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& x = 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& x = 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& x = 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不同于string、vector,它不是一块连续的存储空间,++是无法走到下一个节点的;节点是自定义类型,没法单纯地进行解引用。

对此的解决方案是:

封装一个list迭代器类,在类里将++、!=、*等运算符进行重载。

这样,就能让list迭代器 像string的一样直接使用,而不用关心它的底层实现。

接下来,我们先把 __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的指针
};

注:

1.typedef是不受访问限定符控制的,访问限定符仅控制成员变量、成员函数的权限。

若是在类里定义typedef,其作用域仅限于类里;若是在类外定义,其作用域为整个文件。

2.__list_iterator类 要定义在list类 的前面,因为编译是从上往下的顺序,在list里遇到iterator只会往上找,不会往下找。

3.__list_iterator类 是定义在 list类 的外面的!不是list的内部类。

为了保证iterator的封装性,我们把它单独定义为了一个类。node、list、iterator三个类是分开定义的。

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

这里补充说明一下,返回值为什么不能写成iterator:

因为被重命名成iterator的对象是_ _list_iterator<T>,而不是 __list_iterator。后者是模板,前者是模板被实例化出的类型。

两者本质是不一样的,注意区分!我们用的iterator,不是模板了,它是指向具体类型的指针。

begin()与end()

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

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

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

operator++

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

注意:

后置加加的占位符就是int(这是规定)。不要写Node这种 其他类型。

//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的成员变量        
}

注:这里的形参一定要被const修饰!

说到const修饰形参,我们就趁此回顾下这个知识点。

如果是传值传参,那没必要用const修饰。反正形参只是实参的拷贝,它的改变压根不会影响形参,所以对于实参而言,形参有没有被const修饰都一样。

如果是传指针传参or引用传参,那尽量给形参加上const修饰!

记住这句话:从现在起,要养成好习惯!传指针传参or传值传参时,加上const修饰形参!当然,前提是函数内不改变形参~

因为,const修饰的形参,既能接受普通实参,又能接收const实参。而未经const修饰的形参,只能接收普通实参,无法接受const实参。(因为权限大的能调用权限小的,而反过来不行)

所以说,使用引用传参时,如果函数内不作改变,那尽量采用const引用传参。

测试

测试一下:

#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*());
}

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

刚刚实现的operator*:

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

所以,operator->就相当于返回了&(node->_data),即指向T的指针。

在使用时,iterator->XX,其实是编译器做了省略之后的结果。

它原本的样子是iterator-> ->XX,两个箭头,可这样的话,代码可读性太差了,于是编译器做了优化,省略了一个箭头

那实际上,应为( iterator.operator->() ).operator->() XX。对operator->做了两次调用。

第一次调用,返回一个指向T的指针。就相当于返回了一个原生指针,再用这个原生指针调用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,编译器就能自动匹配。

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

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

拿iterator的情况举例:

首先,程序的编译进行到了第一部分,lt要调用begin(),这里的lt没有被const修饰,那它的迭代器就是iterator,lt所调用的begin()函数 也是返回值为iterator的那个。(此时程序由第一部分跳至二)

而iterator追根溯源,找到当初被重命名的对象:__list _iterator<T,T&,T*>。(此时程序由第二部分跳至三)

__list _iterator<T,T&,T*>回到当初定义它的结构体 那边,此时<T,T&,T *>作为模板参数跟<class T,class Ref,class Ptr>一一匹配。

结构体中的Ref被自动匹配为T&,Ptr则被匹配为T*。(此时程序由第三部分跳至四)

const_iterator同理。

同时这里要注意,因为模板参数是三个:

template<class T,class Ref,class Ptr >

所以我们在typedef时,也要注意同样写三个参数,不然没法与模板参数匹配:

typedef __list_iterator<T,T&, T*> iterator;   //√
typedef __list_iterator<T,T&> iterator;       //×
typedef __list_iterator<T,T*> iterator;       //×

我当初对typedef __ list_iterator<T,T&, T*> iterator;非常不理解,当时误以为,它可以拆分成 typedef __ list_iterator<T&> iterator;或者typedef __list_iterator<T *> iterator;

现在才明白,这个并不是拆分来看的,这里之所以要写T,T&,T*,是为了和参数模板一一匹配。

实现:

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& x = 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;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1135678.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Java八股文 ---Java并发篇

线程安全 线程安全就是多个线程去执行某类&#xff0c;这个类始终能表现出正确的行为&#xff0c;那么这个类就是线程安全的 我们判断是否要处理线程安全问题&#xff0c;就看有没有多个线程同时访问一个共享变量 能不能保证操作的原子性&#xff0c;考虑atomic包下的类够不够我…

DASCTF X CBCTF 2023

一、justpaint 1.先是压缩包密码爆破&#xff0c;密码为11452&#xff0c;然后开始代码审计&#xff0c;发现是一个线性的神经网络。 源代码如下&#xff1a; import torch import torch.nn as nn import numpy as np import matplotlib.pyplot as plt from PIL import Ima…

JVM重点

文章目录 0. 运行流程1. 内存区域划分1.1 堆1.2 Java 虚拟机栈1.3 程序计数器1.4 方法区 2. 类加载机制类加载过程2.1 加载2.2 验证2.3 准备2.4 解析2.5 初始化双亲委派模型 3. 垃圾回收机制3.1 垃圾判断算法3.1.1 引用计数算法3.1.2 可达性分析算法 3.2 垃圾回收算法3.2.1 标记…

01 # 手写 new 的原理

new 做了什么? 在构造器内部创建一个新的对象这个对象内部的隐式原型指向该构造函数的显式原型让构造器中的 this 指向这个对象执行构造器中的代码如果构造器中没有返回对象&#xff0c;则返回上面的创建出来的对象 手写 new 的过程 new 是一个运算符&#xff0c;只能通过函…

Redis Cluster高可用集群原理

目录 一、Redis Cluster和哨兵对比二、槽位定位算法三、集群节点间的通信机制四、集群选举原理五、网络抖动六、Redis集群为什么至少需要三个master节点&#xff0c;并且推荐节点数为奇数&#xff1f;七、集群没有过半机制会出现脑裂数据丢失问题八、跳转重定位九、集群对批量操…

封神工具:腾讯云服务器价格计算器,精准报价一键计算!

腾讯云服务器价格计算器可以一键计算出云服务器的精准报价&#xff0c;包括CVM实例规格价格、CPU内存费用、公网带宽收费、存储系统盘和数据盘详细费用&#xff0c;腾讯云百科txybk.com分享腾讯云价格计算器链接入口、使用方法说明&#xff1a; 腾讯云服务器价格计算器 打开腾…

行业追踪,2023-10-25

自动复盘 2023-10-25 凡所有相&#xff0c;皆是虚妄。若见诸相非相&#xff0c;即见如来。 k 线图是最好的老师&#xff0c;每天持续发布板块的rps排名&#xff0c;追踪板块&#xff0c;板块来开仓&#xff0c;板块去清仓&#xff0c;丢弃自以为是的想法&#xff0c;板块去留让…

[奇奇怪怪符号]输入法手动输入希腊字母

日常写材料总要输入一些奇奇怪怪符号&#xff0c;虽然知道有一张表&#xff0c;可以在上面选&#xff0c;但是在文本编辑器上找到并打开也不是一件容易的事情&#xff0c;&#xff08;功能多的编辑器就很臃肿&#xff0c;令人眼花缭乱的标签&#xff09; 很想要简洁的编辑器&a…

文件加密软件(2023十大文件加密软件排行榜)

文件加密软件已成为企业和个人保护文件安全的重要工具。本文将介绍2023年十大文件加密软件的排行榜&#xff0c;以帮助大家了解和选择适合自己的文件加密软件。 本文是根据软件的功能性、安全性、易用性和创新性等多方面指标进行评选&#xff0c;以体现各大文件加密软件的实力和…

C++多态的认识与理解

多态的概念 通俗来说&#xff0c;多态就是多种形态。具体点就是去完成某个行为&#xff0c;当不同的对象去完成时会产生出不同的状态。 比方说买高铁票时&#xff0c;如果你是学生的话&#xff0c;买票就有优惠。如果你是军人的话&#xff0c;就可以优先买票。普通人的话&…

聚观早报 |2024年春节连休8天;RTE2023开幕

【聚观365】10月26日消息 2024年春节连休8天 RTE2023开幕 一加12首发“东方屏” 微软公布2024财年第一财季财报 Alphabet Q3业绩好于预期 2024年春节连休8天 国务院办公厅发布关于2024年部分节假日安排的通知。2024年春节&#xff0c;2月10日至17日放假调休&#xff0c;共…

Linux ———— 用户-组

Linux是一个多用户多任务的操作系统。 用户&#xff08;user&#xff09;&#xff1a; 在Linux系统中&#xff0c;用户是一个拥有独立空间、权限和身份的实体。每个用户都有一个唯一的用户名和用户ID。用户可以登录到系统、读取、写入、执行文件&#xff0c;并按照预设的权限进…

Pytorch使用torch.utils.data.random_split拆分数据集,拆分后的数据集状况

对于这个API,我最开始的预想是从 猫1猫2猫3猫4狗1狗2狗3狗4 中分割出 猫1猫2狗4狗1 和 猫4猫3狗2狗3 ,但是打印结果和我预想的不一样 数据集文件的存放路径如下图 测试代码如下 import torch import torchvisiontransform torchvision.transforms.Compose([torchvision.tran…

算法通关村-黄金挑战K个一组反转

大家好我是苏麟 , 今天带来K个一组反转 , K个一组反转 可以说是链表中最难的一个问题了&#xff0c;每k 个节点一组进行翻转&#xff0c;请你返回翻转后的链表。k 是一个正整数&#xff0c;它的值小于或等于链表的长度。如果节点总数不是 k 的整数倍&#xff0c;那么请将最后…

索引模型的常见数据结构

索引的出现是为了提高查询效率,三种常见、也比较简单的数据结构 哈希表有序数组搜索树 哈希表 哈希表是一种以键 - 值&#xff08;key-value&#xff09;存储数据的结构&#xff0c;我们只要输入待查找的键即 key&#xff0c;就可以找到其对应的值即 Value。哈希的思路很简单…

Python实验项目4 :面对对象程序设计

1&#xff1a;运行下面的程序&#xff0c;回答问题。 &#xff08;1&#xff09;说明程序的执行过程&#xff1b; &#xff08;2&#xff09;程序运行结果是什么&#xff1f; # &#xff08;1&#xff09;说明程序的执行过程&#xff1b; # &#xff08;2&#xff09;程序运行…

Python在不同场景下的并发编程方案选择

目录 一、多线程 二、多进程 三、异步IO 四、优缺点分析 五、注意事项 总结 并发编程是软件开发中的重要一环&#xff0c;它允许程序同时处理多个任务&#xff0c;提高程序的运行效率和响应速度。Python作为一种流行的编程语言&#xff0c;提供了多种并发编程方案。 一、…

source insight 使用过程中问题点总结

1. //1 //2 不现实大小的注释。选中Special comment styles即可。

vector详解

迭代器 vector维护的是一个连续线性空间。普通指针可以满足条件作为vector的迭代器。 template <typename T, typename Allocalloc> class vector { public: using value_type T; using iterator value_type*; }; vector::iterator //int* vector::iterator //char* …

HFP协议分析

HFP 全称为Hands-Free Profile&#xff0c;通俗的说就是蓝牙电话协议&#xff0c;可以通过指定好的AT command来控制通话的接听、挂断、拒接等 看协议的一些约定格式 在HFP协议文档里面有一个约定&#xff0c;这里贴出来&#xff0c;每种不同的标识代表不同的意思&#xff0c…