当前位置: 首页 > 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的文件系统进行一些更改。实验目的熟悉华为网络设备文件系统的管理。掌握华为网…...

日语AI面试高效通关秘籍:专业解读与青柚面试智能助攻

在如今就业市场竞争日益激烈的背景下&#xff0c;越来越多的求职者将目光投向了日本及中日双语岗位。但是&#xff0c;一场日语面试往往让许多人感到步履维艰。你是否也曾因为面试官抛出的“刁钻问题”而心生畏惧&#xff1f;面对生疏的日语交流环境&#xff0c;即便提前恶补了…...

VB.net复制Ntag213卡写入UID

本示例使用的发卡器&#xff1a;https://item.taobao.com/item.htm?ftt&id615391857885 一、读取旧Ntag卡的UID和数据 Private Sub Button15_Click(sender As Object, e As EventArgs) Handles Button15.Click轻松读卡技术支持:网站:Dim i, j As IntegerDim cardidhex, …...

微软PowerBI考试 PL300-选择 Power BI 模型框架【附练习数据】

微软PowerBI考试 PL300-选择 Power BI 模型框架 20 多年来&#xff0c;Microsoft 持续对企业商业智能 (BI) 进行大量投资。 Azure Analysis Services (AAS) 和 SQL Server Analysis Services (SSAS) 基于无数企业使用的成熟的 BI 数据建模技术。 同样的技术也是 Power BI 数据…...

FFmpeg 低延迟同屏方案

引言 在实时互动需求激增的当下&#xff0c;无论是在线教育中的师生同屏演示、远程办公的屏幕共享协作&#xff0c;还是游戏直播的画面实时传输&#xff0c;低延迟同屏已成为保障用户体验的核心指标。FFmpeg 作为一款功能强大的多媒体框架&#xff0c;凭借其灵活的编解码、数据…...

从零开始打造 OpenSTLinux 6.6 Yocto 系统(基于STM32CubeMX)(九)

设备树移植 和uboot设备树修改的内容同步到kernel将设备树stm32mp157d-stm32mp157daa1-mx.dts复制到内核源码目录下 源码修改及编译 修改arch/arm/boot/dts/st/Makefile&#xff0c;新增设备树编译 stm32mp157f-ev1-m4-examples.dtb \stm32mp157d-stm32mp157daa1-mx.dtb修改…...

学习STC51单片机32(芯片为STC89C52RCRC)OLED显示屏2

每日一言 今天的每一份坚持&#xff0c;都是在为未来积攒底气。 案例&#xff1a;OLED显示一个A 这边观察到一个点&#xff0c;怎么雪花了就是都是乱七八糟的占满了屏幕。。 解释 &#xff1a; 如果代码里信号切换太快&#xff08;比如 SDA 刚变&#xff0c;SCL 立刻变&#…...

用机器学习破解新能源领域的“弃风”难题

音乐发烧友深有体会&#xff0c;玩音乐的本质就是玩电网。火电声音偏暖&#xff0c;水电偏冷&#xff0c;风电偏空旷。至于太阳能发的电&#xff0c;则略显朦胧和单薄。 不知你是否有感觉&#xff0c;近两年家里的音响声音越来越冷&#xff0c;听起来越来越单薄&#xff1f; —…...

C++使用 new 来创建动态数组

问题&#xff1a; 不能使用变量定义数组大小 原因&#xff1a; 这是因为数组在内存中是连续存储的&#xff0c;编译器需要在编译阶段就确定数组的大小&#xff0c;以便正确地分配内存空间。如果允许使用变量来定义数组的大小&#xff0c;那么编译器就无法在编译时确定数组的大…...

push [特殊字符] present

push &#x1f19a; present 前言present和dismiss特点代码演示 push和pop特点代码演示 前言 在 iOS 开发中&#xff0c;push 和 present 是两种不同的视图控制器切换方式&#xff0c;它们有着显著的区别。 present和dismiss 特点 在当前控制器上方新建视图层级需要手动调用…...

【MATLAB代码】基于最大相关熵准则(MCC)的三维鲁棒卡尔曼滤波算法(MCC-KF),附源代码|订阅专栏后可直接查看

文章所述的代码实现了基于最大相关熵准则(MCC)的三维鲁棒卡尔曼滤波算法(MCC-KF),针对传感器观测数据中存在的脉冲型异常噪声问题,通过非线性加权机制提升滤波器的抗干扰能力。代码通过对比传统KF与MCC-KF在含异常值场景下的表现,验证了后者在状态估计鲁棒性方面的显著优…...