当前位置: 首页 > news >正文

【c++】:list模拟实现“任意位置插入删除我最强ƪ(˘⌣˘)ʃ“

 

 

文章目录

  • 前言
  • 一.list的基本功能的使用
  • 二.list的模拟实现
  • 总结

 


前言

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

 

 

一、list的基本功能的使用

我们先看看list有哪些接口:

d3be1d9622334da892c7d91cadcbcb28.png

其实list的接口并不多,对于链表而言我们在数据结构中就学过,无非就是头插尾插,头删尾删以及任意位置的插入删除等,下面我们就讲解一下一些常用的接口该如何使用:

首先要注意包list的头文件#include <list>

void test1()
{list<int> ls;ls.push_back(1);ls.push_back(2);ls.push_back(3);ls.push_back(4);list<int>::iterator it = ls.begin();while (it != ls.end()){cout << *it << " ";++it;}cout << endl;
}
int main()
{test1();return 0;
}

 上面是尾插push_back接口的使用和迭代器的使用,迭代器还是与前面的容器一样都是这样的用法,但是他们的底层实现已经天差万别了,我们模拟的时候会详细的介绍。

ced550a493de493baf8420b740ec5c4e.png

接下来我们头插一个99.

c770065b00b64626bf2595a74329e16b.png size是链表中的元素个数,empty是判断链表是否为空:

2cfa3b97a41249719b31dad16d5e20be.png

front返回链表中第一个节点值的引用,back返回链表中最后一个节点的引用。

a51bfdc2491e47b4b27708a2804670b8.png

void test2()
{list<int> ls;ls.push_back(1);ls.push_back(2);ls.push_back(3);ls.push_back(4);for (auto e : ls){cout << e << " ";}cout << endl;ls.pop_back();ls.pop_front();for (auto e : ls){cout << e << " ";}cout << endl;
}
int main()
{//test1();test2();return 0;
}

 pop_back是尾删,pop_front是头删。接下来我们再演示一下插入,删除,清空就结束。

clear():将list中的有效元素清空:

80ededd1e8cf4e5d88f6c8c0cf794bef.png

 insert():

b80af466edc64ada94c024018c49e4b2.png

 我们已经学过vector,可以发现这里insert接口参数都是一样的用迭代器进行插入:

d6755af81d774f7485dd8fdb94278281.png

erase也是同理:

d7a81824080d416c856d7ccfb511eda6.png 在这里我们为什么--了一下end()才删掉最后一个元素呢?因为end()是哨兵位的头结点,我们不能将头结点删除:

7b15e4edf0be4936991b205a7a0e4862.png

 由于list底层是双向带头循环链表,所以头结点的前一个就是最后一个元素。

 我们之前讲过vector的迭代器失效问题,那么erase会导致迭代器失效吗?答案是一定会失效,因为pos位置都被删掉了,要解决这个办法我们只需要把pos后的迭代器给it即可,如下图:

void test3()
{list<int> ls;ls.push_back(1);ls.push_back(2);ls.push_back(3);ls.push_back(4);list<int>::iterator it = ls.begin();while (it != ls.end()){ls.erase(it);++it;}cout << endl;
}
int main()
{//test1();//test2();test3();return 0;
}

我们的目的是依次删除链表中的元素,然后当我们运行起来:

0eab2aa9e2ab47f09de53c0e915a4da4.png

这时候我们像刚刚将的那样解决一下:

c30bc1037eca48ad8b3ebf2ca836d222.png 这也证明了在erase一个pos位置后,这个位置的迭代器会失效。

当然还有一些功能对我们刷题很有用,如下:

96d221dfcd674e129adac9d34ea055ba.png

 这些功能有链表的拼接,合并,反转等大家刷题的时候想用可直接查看原文档。

接下来我们就进入模拟list的环节。

 二、list的模拟实现

同样我们先看一下STL源码:

d29f86a1faf449a2a6ed3fb576ae922b.png

 我们可以看到对于单个节点的实现源码中用了struct,对于c++中是用struct还是用class是视情况而定的,如果你想要这个类里面的东西全部公开那就用struct,因为struct中的成员默认就是公有的。

下面的迭代器我们可以看到很复杂,这就与我们刚开始所说的迭代器的底层实现已经发生了巨大改变,我们讲到迭代器会详细的介绍,这里就先讲解list中的成员:

8f2b91410083430d9b76f8510751289a.png

4578a4c5897549ada7109686d5005fb2.png 我们可以看到库中对于list也是用空间配置器开空间的与vector一样,然后在list中有一个node的成员,这个是什么呢?看下图:

809151142ded4930aa9aa2cd633cfda3.png

 link_type其实就是list_node*的重命名,也就是说实际上是一个链表节点的指针。然后通过我们之前对双向带头循环链表的知识不难判断这里就是哨兵位头结点,接下来我们看一下list的构造函数:

ef167793ca42400da551ef338255e428.png

这个empty_initialize是什么呢?看下图:

e8793f60f437428aa806bdecd661f5af.png

原来这就是一个对头节点进行初始化的函数,先开一个头结点,然后让这个头结点的前后指针都指向自己。

看完了源码现在我们就正式的开始模拟实现:

#pragma once
#include <iostream>
using namespace std;
#include <assert.h>namespace sxy
{template<class T>struct list_node{list_node<T>* _prev;list_node<T>* _next;T _data;};template<class T>class list{public:typedef list_node<T> node;void empty_init(){_head = new node;_head->_next = _head;_head->_prev = _head;}private:node* _head;};void test1(){}
}

 首先我们为了防止和库中命名冲突将要写的list放入命名空间,因为list要容纳任意类型所以我们必须用模板参数,对于一个链表节点我们和库中一样都用struct,有一个该类型的prev指针和一个next指针,然后还有一个模板类型的data变量。接下来我们创建list类,由于要经常用到节点指针所以我们直接将节点重命名为node,然后创建一个哨兵位的头结点_head,我们也像库中实现的那样专门用一个函数去初始化这个头节点,初始化头结点的步骤是:先给_head指针开一个节点空间,然后将这个节点的前后两个指针都指向自己。

对于构造函数,我们直接初始化一下头结点即可:

        list(){empty_init();}

 接下来我们直接写一个push_back接口:

        void push_back(const T& x){node* tail = _head->_prev;node* newnode = new node(x);tail->_next = newnode;newnode->_prev = tail;newnode->_next = _head;_head->_prev = newnode;}

1a7008777e9c44ada38a1c1bfbbe8c2a.png

 对于尾插来说,就是将最后一个节点的next连接新节点,新节点的prev连上前一个节点,然后再将新节点与头结点相连形成循环即可。我们可以看到给newnode开空间的时候我们初始化为x,那么这个时候一定要想到list_node的构造函数是否写了,因为我们没写所以给list_node写一个带缺省值的构造函数:

    struct list_node{list_node(const T& x = T()):_data(x),_next(nullptr),_prev(nullptr){}list_node<T>* _prev;list_node<T>* _next;T _data;};

在这里缺省值一定是T类型的匿名对象,因为list是针对任何类型的,用匿名对象做缺省值会去调用其类型的默认构造。 

这样我们的尾插应该是成功了,如下图:

d490e54fa6eb43d4ac07b5dfd47da51c.png

 下面我们开始实现迭代器:

e8817b247da6450b94e1b07a17590aa5.png

如上图,对于list的迭代器我们不能再像vector一样简单的让指针加加减减了,因为链表中每个节点的地址是不连续的,指针++并不是下一个节点的地址,这个时候我们看看库中是如果搞定迭代器的:

ff2322357ebf4081a14da2e2359ea974.png 通过源码我们发现list迭代器的++实际上是让此节点变成下一个节点,这个时候我们应该能明白,我们直接用一个节点指针就能搞定迭代器了,用这个指针我们就可以让节点变成下一个节点或者上一个节点。

    template<class T>struct list_iterator{typedef list_node<T> node;typedef list_iterator<T> self;node* _node;list_iterator(node* n):_node(n){}T& 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;}};

 我们首先从迭代器的构造函数开始讲解:

        list_iterator(node* n):_node(n){}

 因为我们的迭代器本质是一个链表的节点,所以我们的构造函数的参数也是一个节点,初始化就是用这个节点初始化即可。

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

 解引用符号本质就是找到其节点所保存的数据,所以返回data即可,返回类型用T&可以减少拷贝。

        self& operator++(){_node = _node->_next;return *this;}

 前置加加就比较简单了,对于迭代器的++其实就是指向下一个节点,所以我们让节点变成next即可,由于这里++后还是个迭代器我们必须返回其迭代器,而迭代器类型为list_iterator<T>太过繁琐我们直接typedef为self,所以返回self&即可。

        self operator++(int){self tmp(*this);_node = _node->_next;return tmp;}

对于后置++而言我们要用一个tmp变量,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;}

 解决完迭代器后一定要在list类中重新定义一下迭代器,不然名字太长了,如下:

eacaeb73b5c84232b56b4dda3a78e06a.png

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

 因为我们的迭代器实现是用struct重新创建的,所以返回的时候我们不能像之前那样直接返回头结点,我们返回的类型必须是迭代器,所以对于begin我们返回一个匿名对象并且用头结点的下一个节点初始化。而end就用头结点即可,如下图:

2efe1ecef12c4ef198ee62f740d562e6.png

下面我们试试迭代器是否能正常使用:

5f04aa41299f4222ad75f2a101971b0c.png 很明显是可以使用的,我们之前实现过string,vector,迭代器都有相应的const版本,那么我们这个迭代器如何实现const版本呢?

09935d67b0314ce8a560369b9d08d472.png

 我们看到对于const对象现在确实不能使用迭代器,我们可以这样吗?

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

f8baf63fce2e4e7c9ef94a6f9a0ddb95.png

 我们看到确实能正常编译了,但是对于const迭代器而言我们的目的是不可以修改迭代器所指向的内容,而迭代器本身可以++ --等,那么现在符合我们的预期吗,如下图:

f5d8d88fccb54a50878ddefc0ebd63f5.png

 对于const对象来讲现在竟然可以修改其内容了,所以上面我们写的const迭代器一定是错误的,那么该怎么办呢?

ca958e41fe7c4661b4d8caf05a6f1deb.png

 同样上图中对于const迭代器的使用也是不正确的,如果将const位于迭代器之前这样迭代器就无法++ --了,也是达不到我们的预期的。

所以我们给出第一种解决办法:

    template<class T>struct list_const_iterator{typedef list_node<T> node;typedef list_const_iterator<T> self;node* _node;list_const_iterator(node* n):_node(n){}const T& 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;}};

const迭代器的不同点就是对于*解引用的内容不可以被修改,所以我们将operator*的返回值设为const T&,这样是可以的。

        typedef list_node<T> node;typedef list_iterator<T> iterator;typedef list_const_iterator<T> const_iterator;iterator begin(){return iterator(_head->_next);}iterator end(){return iterator(_head);}const_iterator begin() const{return const_iterator(_head->_next);}const_iterator end() const{return const_iterator(_head);}

e48ce18547504a368215681cf7772c42.png

但是这样实现会不会太浪费了呢?const与普通迭代器的区别只是operator的返回值不同,其他的函数都是一样的,有什么办法可以只用普通迭代器就能既完成普通的又完成const迭代器呢?答案是有的,我们之前看源码可以看到源码中的迭代器的模板参数比我们多了两个,这就是解决这类问题的。如下:

    template<class T,class Ref>struct list_iterator{typedef list_node<T> node;typedef list_iterator<T,Ref> self;node* _node;list_iterator(node* n):_node(n){}Ref 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;}};
        typedef list_node<T> node;typedef list_iterator<T,T&> iterator;typedef list_iterator<T,const T&> const_iterator;iterator begin(){return iterator(_head->_next);}iterator end(){return iterator(_head);}const_iterator begin() const{return const_iterator(_head->_next);}const_iterator end() const{return const_iterator(_head);}

5e4114e4fb374f7ca9f361fee0e5d73f.png

 我们直接多给一个模板参数Ref,让operator *的返回值为Ref,然后我们在list中typedef的时候将list_iterator<T,T&>为普通迭代器,list_iterator<T,const T&>为const迭代器,这样一来一旦有const对象调用迭代器就会调用这个const T&的参数,然后普通迭代器中operator *的返回值就变成了constT&.对于迭代器来说,要么是原生指针,要么是自定义类型对原生指针的封装,模拟指针的行为。

0373a6c075f840af900b98a56763be60.png

 对于上图中这样的自定义类型我们是无法去直接打印链表中的值的,如下图:

dfb5545b1fe54d1eb851a4e1489473f8.png

当然也确实可以打印,但是会比较麻烦,比如下面这样:

a1806b02e14f4e919b1730e46b95eafa.png

e2f9e3a390f845c4a7d66a5dcc5567c1.png 所以我们想要让自定义类型的访问也方便应该让迭代器重载->操作符,不然就会像上图中圈出来的那样访问太过麻烦,而库中对于这方面的解决办法也是多加一个模板参数,如下:

    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* n):_node(n){}Ref 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;}Ptr operator->(){return &_node->_data;}bool operator!=(const self& it){return _node != it._node;}bool operator==(const self& it){return _node == it._node;}};
        typedef list_node<T> node;typedef list_iterator<T,T&,T*> iterator;typedef list_iterator<T,const T&,const T*> const_iterator;

这样就解决了上述我们所说的问题。

接下来我们实现insert接口:

        void insert(iterator pos, const T& x){node* cur = pos._node;node* prev = cur->_prev;node* newnode = new node(x);newnode->_next = cur;cur->_prev = newnode;newnode->_prev = prev;prev->_next = newnode;}

b33144d86ba24e05b814757e76c34bd7.png

对于插入我们并不陌生,只需要将新节点插入到pos的前一个节点和pos位置中间即可,让新节点的next连接pos位置的节点,pos位置的节点的prev连接新节点,再将新节点与prev节点相连即可。

实现了insert,我们就可以复用实现头插尾插了:

        void push_back(const T& x){insert(end(), x);}void push_front(const T& x){insert(begin(), x);}

 接下来我们实现erase接口:

        iterator erase(iterator pos){assert(pos!=end());node* prev = pos._node->_prev;node* tail = pos._node->_next;prev->_next = tail;tail->_prev = prev;delete pos._node;return iterator(tail);}

c9a5e7a5cec942c683f7c3198448b1bd.png

 对于erase,首先我们是不可以删除哨兵位的头结点的,所以我们断言一下,end()的位置就是头结点的位置。删除pos位置就是将pos 的前一个节点和pos的后一个节点相互连接即可。然后delete掉pos位置的节点,前面我们讲过为了防止erase后迭代器失效我们要返回pos位置节点的下一个节点,所以我们返回一个迭代器的匿名对象,用pos的下一个节点初始化即可。

有了erase接口我们就可以复用头删,尾删了:

        void pop_front(){erase(begin());}void pop_back(){erase(_head->_prev);}

下面实现一下clear()接口:

        void clear(){iterator it = begin();while (it != end()){it = erase(it);}}

在这个接口就体现出我们实现erase让其返回下一个节点的重要性了,如果不这样做这里迭代器失效是无法持续删除的。

        void clear(){iterator it = begin();while (it != end()){//it = erase(it);erase(it++);}}

 当然我们也可以用后置++,也可以完成c连续删除的任务。

接下来是析构函数:

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

 析构函数和clear的区别在于析构函数要释放哨兵位的头结点,释放后将头结点置为空。

接下来是用迭代器区间构造:

        template<class Iterator>list(Iterator first, Iterator last){empty_init();while (first != last){push_back(*first);++first;}}

 首先我们用迭代器区间构造是一个个push_back,而我们要用push_back()必须得有哨兵位的头结点,所以我们先对空初始化然后依次push_back()即可。对于迭代器区间构造我们之前说过,这里的迭代器可以是任意类型的比如string,vector,所以我们必须用一个模板参数Iterator。

拷贝构造函数:

        list(const list<T>& ls){empty_init();for (auto e : ls){push_back(e);}}

 对于拷贝构造函数,我们先讲头结点初始化,然后依次尾插ls的节点即可。

当然还有一种现代写法的拷贝构造函数:

        list(const list<T>& ls){empty_init();list<T> tmp(ls.begin(), ls.end());swap(tmp);}void swap(list<T>& ls){std::swap(_head, ls._head);}

 我们先开一个头结点然后初始化,用迭代器区间构造一个与ls对象一样的tmp链表,然后再将tmp和*this交换交换指针指向即可。

接下来是赋值重载:

        list<T>& operator=(list<T> ls){swap(ls);return *this;}

赋值重载就很简单了,首先我们是传值传参,ls是一份临时拷贝,我们直接用这个临时变量和*this交换即可,然后返回*this.

 以上就是list的完整模拟实现了。

下面我们放一张vector与list的对比图:

0a73dafcfc8f42c4aaf2fbf1eff7bf89.png

7be40035c27549a4bde1fb281216f710.png 

 


总结

list的模拟实现中最难理解的就是迭代器的构造,对于list的迭代器我们一定要多练习才能真正的掌握。

 

相关文章:

【c++】:list模拟实现“任意位置插入删除我最强ƪ(˘⌣˘)ʃ“

文章目录 前言一.list的基本功能的使用二.list的模拟实现总结前言 1. list是可以在常数范围内在任意位置进行插入和删除的序列式容器&#xff0c;并且该容器可以前后双向迭代。2. list的底层是双向链表结构&#xff0c;双向链表中每个元素存储在互不相关的独立节点中&#xff0…...

QT表格控件实例(Table Widget 、Table View)

欢迎小伙伴的点评✨✨&#xff0c;相互学习&#x1f680;&#x1f680;&#x1f680; 博主&#x1f9d1;&#x1f9d1; 本着开源的精神交流Qt开发的经验、将持续更新续章&#xff0c;为社区贡献博主自身的开源精神&#x1f469;‍&#x1f680; 文章目录前言一、图示实例二、列…...

第二章Vue组件化编程

文章目录模块与组件、模块化与组件化模块组件模块化组件化Vue中的组件含义非单文件组件基本使用组件注意事项使用 kebab-case使用 PascalCase组件的嵌套模板templateVueComponent一个重要的内置功能单文件组件Vue脚手架使用Vue CLI脚手架先配置环境初始化脚手架分析脚手架结构实…...

面试官:vue2和vue3的区别有哪些

目录 多根节点&#xff0c;fragment&#xff08;碎片&#xff09; Composition API reactive 函数是用来创建响应式对象 Ref toRef toRefs 去除了管道 v-model的prop 和 event 默认名称会更改 vue2写法 Vue 3写法 vue3组件需要使用v-model时的写法 其他语法 1. 创…...

【TopK问题】——用堆实现

文章目录一、TopK问题是什么二、解决方法三、时间复杂度一、TopK问题是什么 TopK问题就是从1000个数中找出前K个最大的数或者最小的数这样的类似问题。 不过并不要求这k个数字必须是有序的&#xff0c;如果题目有要求&#xff0c;则进行堆排序即可。 还有比如求出全国玩韩信…...

【Spring从成神到升仙系列 四】从源码分析 Spring 事务的来龙去脉

&#x1f44f;作者简介&#xff1a;大家好&#xff0c;我是爱敲代码的小黄&#xff0c;独角兽企业的Java开发工程师&#xff0c;CSDN博客专家&#xff0c;阿里云专家博主&#x1f4d5;系列专栏&#xff1a;Java设计模式、数据结构和算法、Kafka从入门到成神、Kafka从成神到升仙…...

使用Nginx反向代理OpenAI API

由于OpenAI的API在国内无法访问&#xff0c;所以可以通过海外服务器利用Nginx实现反向代理。 安装Nginx 这一步就不赘述了&#xff0c;不同的Linux系统安装方式略有不同&#xff0c;根据自己的服务器的系统自行百度即可。 OpenSSL创建证书 因为OpenAI的接口是https协议的&a…...

USB键盘实现——字符串描述符(四)

字符串描述符 字符串描述符内容解析和 HID鼠标 一致。 获取字符串描述符请求 标准设备请求 typedef struct __attribute__ ((packed)){union {struct __attribute__ ((packed)) {uint8_t recipient : 5; ///< Recipient type usb_request_recipient_t.uint8_t type …...

STM32的中断

目录 一、STM32中断概述 二、外部中断控制器EXTI 三、按键中断 四、串口中断 一、STM32中断概述 处理器中的中断在处理器中&#xff0c;中断是一个过程&#xff0c;即CPU在正常执行程序的过程中&#xff0c;遇到外部/内部的紧急事件需要处理&#xff0c;暂时中止当前程序的…...

Flink进阶篇-CDC 原理、实践和优化采集到Doris中

简介 基于doris官方用doris构建实时仓库的思路&#xff0c;从flinkcdc到doris实时数仓的实践。 原文 Apache Flink X Apache Doris 构建极速易用的实时数仓架构 (qq.com) 前提-Flink CDC 原理、实践和优化 CDC 是什么 CDC 是变更数据捕获&#xff08;Change Data Captur…...

看完这篇 教你玩转渗透测试靶机vulnhub——My File Server: 1

Vulnhub靶机My File Server: 1渗透测试详解Vulnhub靶机介绍&#xff1a;Vulnhub靶机下载&#xff1a;Vulnhub靶机安装&#xff1a;Vulnhub靶机漏洞详解&#xff1a;①&#xff1a;信息收集&#xff1a;②&#xff1a;FTP匿名登入&#xff1a;③&#xff1a;SMB共享服务&#xf…...

OpenHarmony实战STM32MP157开发板 “控制” Hi3861开发板 -- 中篇

一、前言 我们在 OpenHarmony实战STM32MP157开发板 “控制” Hi3861开发板 – 上篇 中介绍到了,App面板的开发,以及JS API接口的开发和调用。 那么本篇文章,会详解:BearPi-HM Nano开发板,如何实现数据上报和指令接收响应的。 看到这里,可能有同学可能已经知道思路了,因…...

【数据结构初阶】单链表

目录一、思路>>>>>>>>>>>>过程<<<<<<<<<<<<<<<1.打印2.尾插3.尾删4.头插5.头删6.查找7.指定位置后插入8.指定位置后删除9.链表的销毁二、整个程序1.SLTlist.c2.SLTlist.c一、思路 #define …...

多线程代码案例-阻塞队列

hi,大家好,今天为大家带来多线程案例--阻塞队列 这块知识点也很重要,要好好掌握呀~~~ &#x1f338;&#x1f338;&#x1f338;&#x1f338;&#x1f338;&#x1f338;&#x1f338;&#x1f338;&#x1f338;&#x1f338;&#x1f338;&#x1f338;&#x1f338;&#x…...

mysql的limit查询竟然有坑?

背景 最近项目联调的时候发现了分页查询的一个bug&#xff0c;分页查询总有数据查不出来或者重复查出。 数据库一共14条记录。 如果按照一页10条。那么第一页和第二页的查询SQL和和结果如下。 .png) 那么问题来了&#xff0c;查询第一页和第二页的时候都出现了11,12,13的记录…...

【Docker】MAC电脑下的Docker操作

文章目录安装Docker部署mysql 一主一从登录ChatGPT搞方案本地创建一个文件夹编辑docker-compose.yml文件启动检查并编排容器验证基于command的my.cnf配置的加载主数据库建一个用户给子数据库用于主从复制启动主从同步安装Docker 官网地址 https://www.docker.com/ 下载安装 验…...

【Python3】matplotlib,模块,进/线程,文件/xml,百度人脸api,hal/aiohttp/curl

文章目录1.matplotlib/时间复杂度/线性表&#xff1a;顺序表要求存储空间必须连续2.python模块导入&#xff1a;python3 -c ‘import sys;print(sys.path)’ 显示导入模块时会去哪些路径下查找3.进/线程&#xff1a;进/线程是不能随便创建&#xff0c;就像每招一个员工是有代价…...

异或相关算法

文章目录1. 异或的性质2. 题目一3. 题目二4. 题目三5. 题目四1. 异或的性质 我们知道&#xff0c;异或的定义是&#xff1a;相同为0&#xff0c;相异为1。所以也被称为无进位相加&#xff0c;根据这定义&#xff0c;我们可以得出三个性质&#xff1a; 1. N ^ N0。2. N ^ 0N。3…...

python 使用pyshp读写shp文件

安装 pip install pyshp 引入 import shapefile读取 sfshapefile.Reader("{路径名}",encodingutf-8) # 仅仅读取 shapes与shape shapessf.shapes() 返回值是一个列表&#xff0c;包含该文件中所有的”几何数据”对象shapesf.shape(0) Shape是第1个”几何数据”…...

eNSP FTP基础配置实验

关于本实验在本实验中&#xff0c;我们通过两台路由器来展示通过FTP在两台路由器之间传输文件。其中一台路由器AR2作为FTP服务器&#xff0c;另一台路由器AR1以FTP的方式登录AR2&#xff0c;并对AR2的文件系统进行一些更改。实验目的熟悉华为网络设备文件系统的管理。掌握华为网…...

堆及其多种接口与堆排序的实现

我们本期来讲解堆结构 目录 堆的结构 堆的初始化 堆的销毁 堆的插入 向上调整算法 堆的删除 向下调整算法 取堆顶元素 判断堆是否为空 堆中元素个数 堆排序 向下调整与向上调整效率计算 Top-K问题 全部代码 堆的结构 堆是一种用数组模拟二叉树的结构 逻辑结构是…...

JNI原理及常用方法概述

1.1 JNI(Java Native Interface) 提供一种Java字节码调用C/C的解决方案&#xff0c;JNI描述的是一种技术。 1.2 NDK(Native Development Kit) Android NDK 是一组允许您将 C 或 C&#xff08;“原生代码”&#xff09;嵌入到 Android 应用中的工具&#xff0c;NDK描述的是工具集…...

【Docker】之docker-compose的介绍与命令的使用

&#x1f341;博主简介 &#x1f3c5;云计算领域优质创作者   &#x1f3c5;华为云开发者社区专家博主   &#x1f3c5;阿里云开发者社区专家博主 &#x1f48a;交流社区&#xff1a;运维交流社区 欢迎大家的加入&#xff01; 文章目录docker-compose简介docker-compose基础…...

水果新鲜程度检测系统(UI界面+YOLOv5+训练数据集)

摘要&#xff1a;水果新鲜程度检测软件用于检测水果新鲜程度&#xff0c;利用深度学习技术识别腐败或损坏的水果&#xff0c;以辅助挑拣出新鲜水果&#xff0c;支持实时在线检测。本文详细介绍水果新鲜程度检测系统&#xff0c;在介绍算法原理的同时&#xff0c;给出Python的实…...

flask多并发

多线程 flask默认使用多进程处理请求&#xff0c;因此&#xff0c;是支持并发的。比如两个调用a.html和b.html&#xff0c; 请求a.html未运行完成&#xff0c;在浏览访问b.html不会阻塞。开两个不同浏览器&#xff0c;分别请求请求运行时间较长的a.html也不阻塞。只要不用一个…...

我用Python django开发了一个商城系统,已开源,求关注!

起始 2022年我用django开发了一个商城的第三方包&#xff0c;起名为&#xff1a;django-happy-shop。当时纯粹是利用业余时间来开发和维护这个包&#xff0c;想法也比较简单&#xff0c;Python语言做web可能用的人比较少&#xff0c;不一定有多少人去关注&#xff0c;就当是一个…...

大数据项目之数仓相关知识

第1章 数据仓库概念 数据仓库&#xff08;DW&#xff09;: 为企业指定决策&#xff0c;提供数据支持的&#xff0c;帮助企业&#xff0c;改进业务流程&#xff0c;提高产品质量等。 DW的输入数据通常包括&#xff1a;业务数据&#xff0c;用户行为数据和爬虫数据等 ODS: 数据…...

RK3588平台开发系列讲解(视频篇)RTP H264 码流打包详解

平台内核版本安卓版本RK3588Linux 5.10Android 12文章目录 一、单 NALU 封包方式二、组合封包方式三、分片封包方式沉淀、分享、成长,让自己和他人都能有所收获!😄 📢 H264 码流是放在 RTP 的有效载荷部分的。因此有效载荷前面的 RTP 头部跟码流本身是没有关系的,所以我…...

realloc的补充 柔性数组

&#x1f436;博主主页&#xff1a;ᰔᩚ. 一怀明月ꦿ ❤️‍&#x1f525;专栏系列&#xff1a;线性代数&#xff0c;C初学者入门训练&#xff0c;题解C&#xff0c;C的使用文章&#xff0c;「初学」C &#x1f525;座右铭&#xff1a;“不要等到什么都没有了&#xff0c;才下…...

【C语言】柔性数组

柔性数组1. 柔性数组介绍2. 柔性数组特点3. 用例3.1 代码一&#xff1a;3.2 代码二&#xff1a;4. 柔性数组优势&#xff1a;1. 柔性数组介绍 也许你从来没有听说过柔性数组&#xff08;flexible array&#xff09;这个概念&#xff0c;但是它确实是存在的。 C99 中&#xff0c…...

伪静态网站/如何开通自己的网站

iphone4锁屏键坏了图文教你修复 来源&#xff1a;互联网 作者&#xff1a;佚名 时间&#xff1a;03-07 16:18:30 【大 中 小】iphone4锁屏键经常使用&#xff0c;所以坏的可能性是很大而且不在少说&#xff0c;接下来为你详细分享一下解决方法&#xff0c;感兴趣的朋友可以参考…...

电子商务网站建设讯息/快速网站排名优化

tomcat超时解问题 在eclipse启动tomcat时遇到超时45秒的问题&#xff1a; 错误&#xff1a;Server Tomcat v7.0 Server at localhost was unable to start within 45 seconds 错误提示就是我们限定了部署的时间导致的错误。改动 workspace\.metadata\.plugins\org.eclipse.wst.…...

公众号开发的可行性/沙洋县seo优化排名价格

1.笔试常见的问题&#xff1f; 面试常见的问题上面给的面试题链接基本都有。我只提几点&#xff1a; 写SQL&#xff1a;写SQL很常考察group by、内连接和外连接。手写代码&#xff1a;手写代码一般考单例、排序、线程、消费者生产者。我建议排序算法除了冒泡排序&#xff0c;…...

兴义建设局网站/最常见企业网站有哪些

Monoid&#xff0c;独异点 独异点是一个&#xff0c;集合只包含单位元&#xff0c;单个二元关系运算的代数结构。 单位元&#xff0c;identity element 单位元是&#xff08;二元运算符&#xff09;集合中的一种特殊类型的元素&#xff0c;当与另一元素进行二元运算时&#xff…...

如何设计网站的首页/网站推广哪家好

下载到SlideMenu的源码&#xff0c;打开例子&#xff0c;发现有些错误&#xff0c;先把依赖的包给导入发现在BaseActivity有几个红叉&#xff0c;提示不能使用getActionBar...什么的分析一下其使用过程&#xff0c;首先 BaseActivity extends SlidingFragmentActivity为了兼容性…...

微信公众平台一定要找网站做吗/seo营销软件

文章目录 数据压缩分块传输范围请求多段数据总结额外知识上次我们谈到了 HTTP 报文里的 body,知道了 HTTP 可以传输很多种类的数据,不仅是文本,也能传输图片、音频和视频。 早期互联网上传输的基本上都是只有几 K 大小的文本和小图片,现在的情况则大有不同。网页里包含的信…...