【玩转c++】List讲解和模拟底层实现

news2024/9/28 10:24:36
本期主题:list的讲解和模拟实现
博客主页: 小峰同学
分享小编的在Linux中学习到的知识和遇到的问题
小编的能力有限,出现错误希望大家不吝赐

1.list的介绍和使用

1.1.list的介绍

1.list是可以在常数范围内在任意位置进行插入和删除的序列式容器,并且该容器可以前后双向迭代。 2.list的底层是双向链表结构,双向链表中每个元素存储在互不相关的独立节点中,在节点中通过指针指向 其前一个元素和后一个元素。 3. list与forward_list非常相似:最主要的不同在于forward_list是单链表,只能朝前迭代,已让其更简单高 效。 4. 与其他的序列式容器相比(array,vector,deque),list通常在任意位置进行插入、移除元素的执行效率 更好。 5. 与其他序列式容器相比,list和forward_list最大的缺陷是不支持任意位置的随机访问,比如:要访问list 的第6个元素,必须从已知的位置(比如头部或者尾部)迭代到该位置,在这段位置上迭代需要线性的时间 开销;list还需要一些额外的空间,以保存每个节点的相关联信息(对于存储类型较小元素的大list来说这 可能是一个重要的因素)

1.2.list的使用

1.2.1.成员函数

这个和vector的差不多

1.2.2.迭代器

和vector相似
从此时开始就要知道迭代器的重要性了,不支持下标访问了,这时候迭代器就很重要了。
所有的容器真正的,统一的遍历方式就是迭代器。3

1.2.3.容量相关

1.2.4.访问相关

1.2.5.修改相关

1.2.6.操作相关

2.List的模拟实现

2.1.源码

源码
#pragma once
#include <utility>
#include<iostream>
#include<assert.h>
using namespace std;

namespace zxf2
{

    template<class T>
    struct list_node
    {
        //共有成员变量
        list_node* _next;
        list_node* _prev;
        T _data;

        //每个节点的构造函数。
        list_node(const T& val = T())
            :_next(nullptr)
            ,_prev(nullptr)
            ,_data(val){}
    };


    //这里实现的迭代器是普通版本的,那如果想要一个const版本的迭代器怎么办?
    //const list_iterator<T> it = lt.begin();//这样  显然不是我们想要的结果,
    //上述的写法只能是it 不能改,但是 const迭代器是 *it不能改动。
    //为什么vector模拟实现的时候 直接 定义了 : typedef const T* const_iterator;自然就是实现了 it可以改,而 *it不能改的目的,(利用了指针的特性)
    //
    //template<class T>
    //struct list_iterator
    //{
    //    //迭代器本质上是在控制节点的指针 _pnode;
    //    //是对节点(node)指针的封装。
    //    typedef list_node<T> node;
    //    typedef list_iterator<T> self;//迭代器类型重命名。
    //    //成员变量
    //    node* _pnode;
    //    //迭代器在创建的时候必须初始化
    //    //使用节点的指针来构造
    //    list_iterator(node* pnode){
    //        _pnode = pnode;
    //    }


    //    //前置++ ,--; 前置可以返回引用,也就返回迭代器本身。
    //    self& operator++()
    //    {
    //        _pnode = _pnode->_next;
    //        return *this;
    //    }
    //    self& operator--()
    //    {
    //        _pnode = _pnode->_prev;
    //        return *this;
    //    }

    //    //后置++ , -- ;不能返回引用。
    //    self operator++(int)
    //    {
    //        self tmp(*this);//出了函数tmp会被销毁的。
    //        _pnode = _pnode->_next;
    //        return tmp;
    //    }
    //    self operator--(int)
    //    {
    //        self tmp(*this);//出了函数tmp会被销毁的。
    //        _pnode = _pnode->_prev;
    //        return tmp;
    //    }
    //    //所以一般使用前置++,减少拷贝照成的空间和时间浪费。

    //    //判断两个迭代器是否相同就是迭代器里面的成员变量的地址是否相同。
    //    bool operator!=(const self& lt)const
    //    {
    //        return _pnode != lt._pnode;
    //    }
    //    bool operator==(const self& lt)const
    //    {
    //        return _pnode == lt._pnode;
    //    }

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

    //template<class T>
    //struct list_const_iterator
    //{
    //    //迭代器本质上是在控制节点的指针 _pnode;
    //    //是对节点(node)指针的封装。
    //    typedef list_node<T> node;
    //    typedef list_const_iterator<T> self;//迭代器类型重命名。
    //    //成员变量
    //    node* _pnode;
    //    //迭代器在创建的时候必须初始化
    //    //使用节点的指针来构造
    //    list_const_iterator(node* pnode) {
    //        _pnode = pnode;
    //    }


    //    //前置++ ,--; 前置可以返回引用,也就返回迭代器本身。
    //    self& operator++()
    //    {
    //        _pnode = _pnode->_next;
    //        return *this;
    //    }
    //    self& operator--()
    //    {
    //        _pnode = _pnode->_prev;
    //        return *this;
    //    }

    //    //后置++ , -- ;不能返回引用。
    //    self operator++(int)
    //    {
    //        self tmp(*this);//出了函数tmp会被销毁的。
    //        _pnode = _pnode->_next;
    //        return tmp;
    //    }
    //    self operator--(int)
    //    {
    //        self tmp(*this);//出了函数tmp会被销毁的。
    //        _pnode = _pnode->_prev;
    //        return tmp;
    //    }
    //    //所以一般使用前置++,减少拷贝照成的空间和时间浪费。

    //    //判断两个迭代器是否相同就是迭代器里面的成员变量的地址是否相同。
    //    bool operator!=(const self& lt)const
    //    {
    //        return _pnode != lt._pnode;
    //    }
    //    bool operator==(const self& lt)const
    //    {
    //        return _pnode == lt._pnode;
    //    }

    //    const T& operator*()//这里是const迭代器和 普通迭代器真的区别,区别就这一点点。就是在解引用的时候,返回的是const类型还是普通类型。
    //        //返回const类型就是不能修改(const迭代器),返回普通类型就是可以修改(普通迭代器)。
    //    {
    //        return _pnode->_data;
    //    }
    //};


    //上面两个迭代器多少有点代码冗余。可以把他们合并在一起。

template<class T , class ref, class ptr>
struct list_iterator
{
    //迭代器本质上是在控制节点的指针 _pnode;
    //是对节点(node)指针的封装。
    typedef list_node<T> node;
    typedef list_iterator<T, ref ,ptr> self;//迭代器类型重命名。
    //成员变量
    node* _pnode;
    //迭代器在创建的时候必须初始化
    //使用节点的指针来构造
    list_iterator(node* pnode) {
        _pnode = pnode;
    }


    //前置++ ,--; 前置可以返回引用,也就返回迭代器本身。
    self& operator++()
    {
        _pnode = _pnode->_next;
        return *this;
    }
    self& operator--()
    {
        _pnode = _pnode->_prev;
        return *this;
    }

    //后置++ , -- ;不能返回引用。
    self operator++(int)
    {
        self tmp(*this);//出了函数tmp会被销毁的。
        _pnode = _pnode->_next;
        return tmp;
    }
    self operator--(int)
    {
        self tmp(*this);//出了函数tmp会被销毁的。
        _pnode = _pnode->_prev;
        return tmp;
    }

    bool operator!=(const self& lt)const//这里的const有什么用?
    {
        return _pnode != lt._pnode;
    }
    bool operator==(const self& lt)const
    {
        return _pnode == lt._pnode;
    }

    ref operator*()
    {
        return _pnode->_data;
    }
    ptr operator->()
    {
        return &_pnode->_data;
    }
    self operator+=(size_t n)
    {
        while (n--)
        {
            _pnode = _pnode->_next;
        }
        return *this;
    }

    self operator-=(size_t n)
    {
        while (n--)
        {
            _pnode = _pnode->_prev;
        }
        return *this;
    }

};
    template <class T>
    class list
    {
        //类名 list
        //类型 list<T>
        typedef list_node<T> node;
    public:
        //typedef list_iterator<T> iterator;
        //typedef list_const_iterator<T> const_iterator;
        typedef list_iterator<T, T& ,T*> iterator;
        typedef list_iterator<T, const T&, const T*> const_iterator;
        //创建出头节点
        void empty_list()
        {
            _phead = new node(T());
            _phead->_next = _phead;
            _phead->_prev = _phead;
        }

        //无参构造
        list()
        {
            empty_list();
        }

        void push_back(const T& val)
        {
            //node* tmp = new node(val);
            //tmp->_next = _phead;
            //tmp->_prev = _phead->_prev;
            //_phead->_prev->_next = tmp;
            //_phead->_prev = tmp;
            insert(iterator(_phead), val);
        }
        void push_front(const T& val)
        {
            //node* tmp = new node(val);
            //tmp->_next = _phead;
            //tmp->_prev = _phead->_prev;
            //_phead->_prev->_next = tmp;
            //_phead->_prev = tmp;
            insert(iterator(_phead->_next), val);
        }
        //单参数构造
        list(const T& val)
        {
            empty_list();
            push_back(val);
        }

        //迭代器构造的模板

        template<class input_iterator>
        list(input_iterator first, input_iterator last)
        {
            //创建头节点
            empty_list();
            while (first != last)
            {
                push_back(*first);//(*first)是 T/const T 类型的数据
                ++first;
            }
        }


//        //拷贝构造(古法) 里 lt1 = lt2;
//        list(const list<T>& lt)
//            //注意拷贝构造的参数也是引用,防止无线循环,
//        {
//            empty_list();
需要迭代器的支持
//            for (auto& e : lt){//注意这样的引用,const 可加可不加。
//                //引用1,防止e的空间浪费;2,list<list <T>>这种情况出现时,死循环。
//                push_back(e);
//            }
//        }

        void swap(list& lt)
        {
            std::swap(lt._phead, _phead);
        }


        ~list()
        {
            clear();
            delete _phead;
            _phead = nullptr;
        }

        void clear()
        {
            //注意这里只能是 iterator ,不能是 const_iterator 
            //const list 才会返回const_iterator,但是 const list 不能修改
            iterator first = begin();
            //while (first != end())
            //{
            //    erase(first);
            //    ++first;
            //}
            //上述写法是错误的
            while (first != end())
            {
                first = erase(first);
            }
        }
        size_t size()
        {
            return _size;
        }
        bool empty()
        {
            return _size == 0;
        }
        

        //lt1 =lt2;
        list<T> operator=(list<T> lt)
        {
            swap(lt);
            return *this;

        }
        //注意删除的时候要返回欲删除元素下一个元素的迭代器,
        iterator erase(iterator it)
        {
            assert(it != end());
            node* next = it._pnode->_next;
            node* prev = it._pnode->_prev;
            next->_prev = prev;
            prev->_next = next;
            delete it._pnode;
            return iterator(next);// 匿名对象, 这里的生命周期只有一行,不能引用返回。
            --_size;
        }


        iterator insert(iterator pos, const T& val)
        {
            node* newnode = new node(val);
            node* next = pos._pnode;
            node* prev = pos._pnode->_prev;
            newnode->_next = next;
            newnode->_prev = prev;
            prev->_next = newnode;
            next->_prev = newnode;
            return iterator(newnode);
            ++_size;
        }




        void pop_back()
        {
            erase(iterator(_phead->_prev));
        }

        void pop_front()
        {
            erase(iterator(_phead->_next));
        }

        //拷贝构造(现代) 里 lt1 = lt2;
        list(const list<T>& lt)
            //注意拷贝构造的参数也是引用,防止无线循环,
        {
            //无论什么构造,都要先创建头节点
            empty_list();
            list tmp(lt.begin(), lt.end());//先创建一个list类型的临时局部变量,出了作用域会被销毁。
            swap(tmp);
        }

        //这里也不能返回引用
        iterator begin()
        {
            return iterator(_phead->_next);//匿名对象,生命周期只有一行
        }
        const_iterator begin()const
        {
            return const_iterator(_phead->_next);//匿名对象,生命周期只有一行
        }

        iterator end()
        {
            return iterator(_phead);//匿名对象,生命周期只有一行
        }
        const_iterator end()const
        {
            return const_iterator(_phead);//匿名对象,生命周期只有一行
        }






    private:
        //list私有的成员
        node* _phead;
        size_t _size;
    };






}

2.2.常见问题

2.2.1. list的迭代器失效

前面说过,此处大家可将迭代器暂时理解成类似于指针,迭代器失效即迭代器所指向的节点的无效,即该节 点被删除了。因为list的底层结构为带头结点的双向循环链表,因此在list中进行插入时是不会导致list的迭代 器失效的,只有在删除时才会失效,并且失效的只是指向被删除节点的迭代器,其他迭代器不会受到影响。
void TestListIterator1()
{
 int array[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
 list<int> l(array, array+sizeof(array)/sizeof(array[0]));
 auto it = l.begin();
 while (it != l.end())
 {
 // erase()函数执行后,it所指向的节点已被删除,因此it无效,在下一次使用it时,必须先给
其赋值
 l.erase(it); 
 ++it;
 }
}

// 改正
void TestListIterator()
{
 int array[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
 list<int> l(array, array+sizeof(array)/sizeof(array[0]));
 auto it = l.begin();
 while (it != l.end())
 {
 l.erase(it++); // it = l.erase(it);
 }
}

2.2.2.类模板中类名和类型的问题

类一般只有两种:
普通类 和 类模板
对于普通类: 类名就是类型,是相同的。
对于类模板:类名是类名,给T赋上对应的类型后才是类的类型
class BBB
{
    int _data;
};
//在这里类名就是类型
//类名:BBB
//类型:BBB

template<class T>
class AAA
{
    T _data;
};
//在这里
//类名是:AAA
//类型是:AAA<T>
但是在类模板里面可以使用类名去代表类型,但是在类外面类名就是类名,类型就是类型。
但是我个人习惯不喜欢,这个特例。

2.2.3.迭代器it的 -> 的访问问题

看一下示例代码
struct Pos
{
    int _row;
    int _col;

    Pos(int row = 0, int col = 0)
        :_row(row)
        , _col(col)
    {}
};

void print_list(const zxf2::list<Pos>& lt)
{
    zxf2::list<Pos>::const_iterator it = lt.begin();
    while (it != lt.end())
    {
        //it->_row++;

        //这里还能++ ,明明是 const类型的list<Pos>为啥里面的数据可以++,
        //因为这里又会出现一个问题 T*  和 const T* 的  问题,
        //const_iterator 对应的应该是 const T&(解引用的返回值不可修改)  和 const T* (it-> 返回值不可修改) 的问题。
        //iterator 对应的应该是 T& (解引用的返回值可修改)  和  T* (it-> 返回值不可修改) 的问题。


        //迭代器是什么?
        //功能类似于指针,可以 * 和 -> 访问指针元素的类模板
        //他是通过 对 T* 进行封装的得到的一个类。

        //typedef list_iterator<T, T&, T*> iterator;
        //普通迭代器:
        // 重载operator* 的时候 用T&返回。 
        // 重载operator->  的时候用 T* 返回
        //typedef list_iterator<T, const T&, const T*> const_iterator;
        //const迭代器:
        // operator* 的时候 用const T&返回。
        // operator-> 的时候用const T* 返回


         cout << it->_row << ":" << it->_col << endl;

        ++it;
    }
    cout << endl;
}

void test_list5()
{
    zxf2::list<Pos> lt;
    Pos p1(1, 1);
    lt.push_back(p1);
    lt.push_back(p1);
    lt.push_back(p1);
    lt.push_back(Pos(2, 2));
    lt.push_back(Pos(3, 3));

    // int* p  -> *p
    // Pos* p  -> p->
    zxf2::list<Pos>::iterator it = lt.begin();
    //list<Pos>::iterator it2 = it;
    while (it != lt.end())
    {
        it->_row++;
        //cout << it->;//错误写法。
        //cout << it.operator->();
        //it.operator->()->_col++;
        //cout << (&(*it))->_row << ":" << (*it)._col << endl;
        cout << it->_row << ":" << it->_col << endl;
        //cout << it.operator->()->_row << ":" << it->_col << endl;

        ++it;
    }
    cout << endl;

    print_list(lt);
}
首先第一个问题:operator->()如何重载的问题:
        ptr operator->()
            //ptr是 T* 类型或者 const T* 类型的节点里面存放数据的地址。
        {
            return &(_pnode->_data);
        }
我们说迭代器是一个类似于指针一样的东西,所以如果list节点数据是一个自定义类型不用->,但是如果list节点数据是一个结构体那么 -> 成员变量应该就可以访问到成员,所以应该 it -> 成员变量 就可以访问到。
但是根据我们迭代器->的重载来看的话它返回的是结构的指针,
所以按理来说访问成员应该是 it -> -> 成员变量
但是我们看到在实例中我们访问的时候 直接是使用 it -> 成员变量 ,即可
所以这里编译器做了优化。我们可以看到下面代码都可以实现,对结构体里面对象的访问
        cout << (&(*it))->_row << ":" << (*it)._col << endl;
        cout << it->_row << ":" << it->_col << endl;
        cout << it.operator->()->_row << ":" << it->_col << endl;
其实本质就是,编译器把 it -> -> _row 优化为 it ->_row
可以从:it.operator->()->_row 证明得出。
这里是编译器的一个特殊处理。在以后的智能指针中很常用。

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

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

相关文章

Good Idea, 利用MySQL JSON特性优化千万级文库表

&#x1f473;我亲爱的各位大佬们好&#x1f618;&#x1f618;&#x1f618; ♨️本篇文章记录的为 利用MySQL JSON特性优化千万级文库表 相关内容&#xff0c;适合在学Java的小白,帮助新手快速上手,也适合复习中&#xff0c;面试中的大佬&#x1f649;&#x1f649;&#x1f…

常见问题整理1

目录 偏差和方差 欠拟合underfitting 过拟合overfitting 梯度消失和梯度爆炸 归一化 偏差和方差 偏差&#xff1a;算法期望预测和真实预测之间的偏差程度。反应的是模型本身的拟合能力。 方差&#xff1a;度量了同等大小的训练集的变动导致学习性能的变化&#xff0c;刻画…

万字长文带你走进MySql优化(系统层面优化、软件层面优化、SQL层面优化)

文章目录系统层面优化采用分布式架构使用缓存使用搜索引擎软件层面优化调整 MySQL 参数配置定期清理无用数据创建索引创建索引普通索引唯一索引全文索引组合索引空间索引主键索引外键索引索引前缀适合创建索引的场景不适合创建索引的场景优化表结构分库分表SQL优化explain执行计…

Python3-File(文件) 方法

Python3 File(文件) 方法 open() 方法 Python open() 方法用于打开一个文件&#xff0c;并返回文件对象。 在对文件进行处理过程都需要使用到这个函数&#xff0c;如果该文件无法被打开&#xff0c;会抛出 OSError。 注意&#xff1a;使用 open() 方法一定要保证关闭文件对…

Nowcoder .链表分割

文章目录哨兵位节点哨兵位节点 链表分割 小于X 尾插到一个新链表 大于等于X 尾插到另一个链表 最后将两个链表链接起来 需要注意的细节&#xff1a;将第一个链表的尾与第二个链表的头相连接&#xff0c;再返回连接后的整个链表的头&#xff08;哨兵位头节点的下一个&#xff0…

ECharts 环形图组件封装

一、ECharts引入1.安装echarts包npm install echarts --save2.引入echarts这里就演示全局引入了&#xff0c;挂载到vue全局&#xff0c;后面使用时&#xff0c;直接使用 $echartsimport * as echarts from echarts Vue.prototype.$echarts echarts二、写echarts组件这里演示环…

【蓝桥杯嵌入式】PWM的设置,原理图解析与代码实现(第十一届省赛为例)——STM32

&#x1f38a;【蓝桥杯嵌入式】专题正在持续更新中&#xff0c;原理图解析✨&#xff0c;各模块分析✨以及历年真题讲解✨都在这儿哦&#xff0c;欢迎大家前往订阅本专题&#xff0c;获取更多详细信息哦&#x1f38f;&#x1f38f;&#x1f38f; &#x1fa94;本系列专栏 - 蓝…

Lombok 常用注解

文章目录简介MAVEN 依赖常用的注解1. Data 注解 :2. Setter 注解&#xff1a;3.Getter 注解&#xff1a;4.Log4j or Slf4j 注解5.NoArgsConstructor注解&#xff1a;6.AllArgsConstructor注解&#xff1a;7.RequiredArgsConstructor注解:8.Builder注解&#xff1a;9.Cleanup注解…

全国青少年软件编程(Scratch)等级考试一级真题——2019.12

青少年软件编程&#xff08;Scratch&#xff09;等级考试试卷&#xff08;一级&#xff09;分数&#xff1a;100 题数&#xff1a;37一、单选题(共25题&#xff0c;每题2分&#xff0c;共50分)1.下列关于舞台的描述&#xff0c;不正确的是&#xff1f;&#xff08; &#xff09…

DMHS搭建DMDSC 2节点集群同步到单库

DMHS搭建DMDSC 2节点集群同步到单库环境介绍1 安装DMOCI1.1 关闭数据库实例服务1.2 将DMOCI 复制到源端与目的端的数据库bin目录1.3 对数据库bin 执行目录文件更改用户属组和权限2 启动源数据库服务并配置数据库实例参数2.1 使用DMCSSM启动集群实例2.2 DMDSC源其中一个节点执行…

为何英格兰与苏格兰同属英国,却争端不断?

英国作为世界上的强国其组成部分是由大不列颠岛上的英格兰、威尔士和苏格兰以及爱尔兰岛东北部的北爱尔兰以及一系列附属岛屿。英国作为西欧的一个岛国&#xff0c;地理位置十分优越&#xff0c;位于欧洲大陆西北面的不列颠群岛。英国作为西欧的一颗不容忽视的“明星”&#xf…

【Unity风格化草地】概述风格化草地的实现方法

写在前面 最近本专业开始多很多事情了&#xff0c;要开始建模写论文了&#xff08;不然研究生毕不了业&#xff09;&#xff0c;TA方面的学习进度更慢了&#xff0c;&#xff0c;so sad。 废话不多说&#xff0c;这篇文章其实是个小总结&#xff0c;毕竟学习新东西就是先要当…

序列索引序列切片

六个标准数据类型中是序列的有&#xff1a;字符串&#xff08;String&#xff09;、列表&#xff08;List&#xff09;、元组&#xff08;Tuple&#xff09;。 通过索引和切片的方式可以访问序列中的元素。 一、序列索引 序列中的每一个元素都有一个属于自己的编号&#xff0c…

演唱会的火车票没了?Python实现12306查票以及zidong购票....

嗨害大家好&#xff01;我是小熊猫~ 不知道大家抢到演唱会的门票没有呢? 不管抢到没有&#xff0c;火车票也是很重要的哇 24小时抢票不间断的那种喔~ ~ ~ 不然可就要走路去了喔~ 准备工作 环境 Python 3.8Pycharm 插件 谷歌浏览器驱动 模块 需要安装的第三方模块&am…

1/4车、1/2车、整车悬架LQR仿真合集

目录 前言 1. 1/4悬架系统 1.1数学模型 1.2 LQR求解反馈阵阵(负反馈) 1.3仿真分析 2. 1/2悬架系统 2.1数学模型 2.2 LQR求解反馈阵阵(负反馈) 2.3仿真分析 3. 整车悬架系统 3.1数学模型 整车7自由度主动悬架数学模型 3.2 LQR求解反馈阵阵(负反馈) 3.3仿真分析 4…

MySQL(四)视图、存储过程、触发器

视图、存储过程、触发器视图检查选项视图的更新存储过程存储过程基本语法变量系统变量用户自定义变量局部变量if判断参数casewhile循环repeat循环loop循环cursor游标handler条件处理程序存储函数触发器视图 视图&#xff08;View&#xff09;是一种虚拟存在的表。视图中的数据…

ERP原理与应用教程(作业2)

1. 简答题 ERP是一个高度集成的信息系统&#xff0c;从管理信息集成的角度来看&#xff0c;从MRP到MRPII到ERP&#xff0c;是企业管理信息继承的不断扩展和深化。 所有成为ERP的软件都把MRPII作为其生产和控制模块&#xff0c;以MRPII为基础的计划功能在整个供应链的业务处理…

Composer最小稳定性问题

Composer最小稳定性问题主要指的是&#xff0c;在使用Composer安装某个特定的依赖包时&#xff0c;可能会出现一些不稳定的情况&#xff0c;比如依赖包的版本号发生变化&#xff0c;或者依赖包的某些功能发生变化&#xff0c;这样就会导致应用程序出现问题。为了解决这个问题&a…

【数据结构】栈和队列 (栈 栈的概念结构 栈的实现 队列 队列的概念及结构 队列的实现 栈和队列面试题)

文章目录前言一、栈1.1 栈的概念结构1.2栈的实现二、队列2.1队列的概念及结构2.2队列的实现三、栈和队列面试题总结前言 一、栈 1.1 栈的概念结构 栈也是一种线性表&#xff0c;数据在逻辑上挨着存储。只允许在固定的一端进行插入和删除元素。进行插入和删除操作的一端叫栈顶…

记录 UE5 完全重新构建 UE C++项目

不知道搞了什么&#xff0c;C项目的实时代码编译罢工了&#xff0c;搞了半天都修不好&#xff0c;只能又重建了 UE5 版本为 v5.1.1 删除以下文件夹 /Binaries /Intermediate /SavedBinaries 文件夹是编译后的模块 Intermediate 文件夹里是中间层的C代码&#xff0c;完全由ue…