【C++】解锁<list>的正确姿势

> 🍃 本系列为初阶C++的内容,如果感兴趣,欢迎订阅🚩
> 🎊个人主页:[小编的个人主页])小编的个人主页
> 🎀 🎉欢迎大家点赞👍收藏⭐文章
> ✌️ 🤞 🤟 🤘 🤙 👈 👉 👆 🖕 👇 ☝️ 👍
目录
🐼前言
🐼认识list
🐼list的迭代器失效问题⭐️
🐼list的模拟实现
🚩定义链表节点结构
🚩定义list类
🚩正向迭代器实现⭐️
🚩迭代器使用
🚩insert操作
🚩erase操作
🚩增删
🚩list构造
🚩析构函数
🚩反向迭代器实现⭐️
🐼全部源码
🐼总结
🐼前言
👐在之前的容器<string>,<vector>中,我们遇到的底层物理空间都是连续的,在list中,由于底层物理空间不连续,但是逻辑上是连续的,此时底层是如何实现的呢❓迭代器的行为又是什么样呢❓小编这篇文章👇带你从0认识并掌握使用list并了解list的底层结构。
🐼认识list
我们可以借助Cplusplus来查看list类的一些常用接口(list类中的其它接口小伙伴们可以根据我给的链接在需要时进行查询)。
🌻list类的构造:
以及第一个构造空的初始化构造。
🌻list iterator的使用:
这里可以先简单将迭代器理解成一个指针,该指针指向list中的某个节点。在模拟实现时我们可以再谈。
🌻容量操作
🌻访问元素操作
list支持访问头部和尾部元素,不支持随机访问,因为效率太低。但是像vector支持随机访问。List不支持operator[]
🌻增删查改操作
和<vector>不同的是,list支持在头部和尾部操作,因为效率很高,<vector>不支持在头部操作。
其余操作,大家可以查文档。
🐼list的迭代器失效问题⭐️
在<vector>中我们认为insert需要扩容和erase后,原来的迭代器就失效了,不能继续使用。list稍微有一些不同。首先迭代器失效即迭代器所指向的节点的无效,即该节点被删除了。因为list的底层结构为带头结点的双向循环链表,因此在list中进行插入时是不会导致list的迭代器失效的,只有在删除时才会失效,并且失效的只是指向被删除节点的迭代器,其他迭代器不会受到影响。本质上还是由于vector物理空间是连续的,扩容等操作需要发生空间搬移,而list物理空间不连续,迭代器指向的那块空间没有发生改变。
举个例子:
void Test_lsg_list08() {int array[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };list<int> l1(array, array + sizeof(array) / sizeof(array[0]));auto it = l1.begin();while (it != l1.end()){// erase()函数执行后,it所指向的节点已被删除,因此it无效,在下一次使用it时,必须先给// 其赋值l1.erase(it);++it;}//改正list<int> l2(array, array + sizeof(array) / sizeof(array[0]));auto it = l2.begin();while (it != l2.end()){it = l2.erase(it);//返回下一个元素的迭代器}}erase删除It迭代器之后,it位置的迭代器失效了,需要重新更新it,才能继续使用。
🐼list的模拟实现
list的底层使用的是双向循环带头链表。如果有不清楚的小伙伴,可以看这篇文章,双向循环带头链表。
🚩定义链表节点结构
//定义节点结构 template<class T> struct List_Node {List_Node<T>* _next;//指向下一个节点的指针List_Node<T>* _prev;//指向前一个节点的指针T _data;List_Node(const T& x = T()):_next(nullptr),_prev(nullptr),_data(x){} };定义链表节点结构,并对每个节点进行构造初始化,避免是垃圾值。
🚩定义list类
我们知道list是双向循环带头链表,在list类拿一个头结点来维护一个list对象,并且我们希望统计list中元素的个数,list类就可以这样定义了:
template<class T> class list {typedef List_Node<T> Node;typedef List_Node<T>* pNode; public:void empty_init(){_head = new Node(-1);_head->_next = _head;_head->_prev = _head;}list(){empty_init();}private:pNode _head;size_t _size; };✅代码解析:
完成了对空list类对象的初始化,本质是双向循环带头链表。
🚩正向迭代器实现⭐️
下面,我们完成list迭代器的创建工作:
我们知道迭代器目的是不暴露底层结构,不管是<vector><list><tree>等,对于不同的容器遍历的使用方式都是一样的,而迭代器的行为就是像指针一样,有的迭代器就是指针,不需要我们封装,像<vector><string>,而有的迭代器需要我们封装,像<list>等,这正是我们的STL设计迭代器的目的,不暴露底层结构,对于不同容器间一套相同的访问方式。
在list类中,如果我们还希望迭代器能访问双链表中的元素,即*访问到当前节点保存的值,++访问到下一个节点。如果单靠Node*作为迭代器,那解引用是Node,++也访问不到下一个节点,这显而易见没有这么简单。既然迭代器行为是具有像指针一样的东西,那么如果我们就能对迭代器进行封装,可以重载*和++以及更多的迭代器操作。
正向迭代器非const版本实现:
template <class T>struct List_iterator{typedef List_Node<T> Node;typedef List_Node<T>* pNode;pNode _Node;List_iterator(Node* node):_Node(node){}T& operator*(){return _Node->_data;}//前置++List_iterator<T>& operator++(){_Node = _Node->_next;return *this;}//后置++List_iterator<T> operator++(int){List_iterator<T> tmp = *this;_Node = _Node->_next;return tmp;}bool operator!=(const List_iterator<T>& it){return _Node != it._Node;}T* operator->(){return &_Node->_data;}};✅代码解析:
迭代器支持*,我们就重载一份*操作符来访问元素的值,迭代器支持++,--,我们就重载一份++,--,让迭代器的行为能够支持++,--。这样,不暴露底层结构,我们就能完成一套相同的访问操作。而list迭代器本质还是一个Node*的指针,只不过我们进行了封装。
我们根据上述的思路再实现正向迭代器const版本。
template <class T> struct const_List_iterator {typedef List_Node<T> Node;typedef List_Node<T>* pNode;pNode _Node;const_List_iterator(Node* node):_Node(node){}const T& operator*() const{return _Node->_data;}//前置++const_List_iterator<T>& operator++() {_Node = _Node->_next;return *this;}const_List_iterator<T> operator++(int) {List_iterator<T> tmp = *this;_Node = _Node->_next;return tmp;}bool operator!=(const const_List_iterator<T>& it){return _Node != it._Node;}const T* operator->() const{return &_Node->_data;} };只需要把权限缩小到const。
但是这样写,代码有点冗余了,因为我们只想控制const和非const在<list>中,那么如果我们能够在迭代器实例化时传参时传入对应的参数,因为他们都是一系列共用的迭代器家族,只是权限上有差异。因此,我们可以用函数模版多加两个参数来避免代码冗余性。
//用模版方法来控制const和非const迭代器 template <class T,class Ref,class Ptr> struct List_iterator {typedef List_Node<T> Node;typedef List_Node<T>* pNode;typedef List_iterator<T, Ref, Ptr> Self;pNode _Node;List_iterator(Node* node):_Node(node){}//迭代器具有像指针一样的行为,可以解引用Ref operator*(){return _Node->_data;}//指针可以通过->访问其所指空间成员,因此迭代器类中必须重载oprator->()Ptr 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实例化时,传入对应的参数,list_iterator就能实例化出不同的迭代器版本。
🚩<list>迭代器使用
//传参来控制const迭代器和非const迭代器 typedef List_iterator<T,T&,T*> iterator; typedef List_iterator<T,const 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); }这里通过传参来控制const迭代器和非const迭代器,以构造匿名对象的形式来作为返回值,更简洁,也更好。
🚩insert操作
此处在pos位置和双链表中插入元素的逻辑一样,只不过pos此时是用迭代器封装的。
//insert后pos位置迭代器失效· void insert(iterator pos, const T& x) {pNode newnode = new Node(x);//prev newnode curpNode cur = pos._Node;pNode prev = cur->_prev;prev->_next = newnode;newnode->_next = cur;newnode->_prev = prev;cur->_prev = newnode;++_size;//pos = iterator(newnode);//如果想继续使用,更新pos位置的迭代器 }注意:这里插入操作,pos位置的迭代器并没有失效,只不过逻辑上在下一个位置了,如果需要让pos指向新插入的节点,可以显式地更新它,如 pos = iterator(newnode)
;iterator(newnode)是构造一个新的迭代器匿名对象,并将其赋值给 pos
。🚩
erase操作erase pos位置后,pos位置的迭代器被删除了,即失效了,不能继续使用。不过erase后,返回下一个元素位置的迭代器。
iterator erase(iterator pos){pNode cur = pos._Node;pNode next = cur->_next;pNode prev = cur->_prev;prev->_next = next;next->_prev = prev;delete cur;--_size;return iterator(next);//返回下一个元素的迭代器}✅代码解析:
实现方式和双向带头循环带头链表删除某个pos节点是一样的。
对比一下<vector> <list>insert和erase操作后迭代器失效问题:
<vector>insert操作,数据可能需要扩容,那么指向pos位置的迭代器就失效了;而<list>insert操作,pos位置的迭代器没有删除,只是逻辑上发生了变化,因此没有失效;
<vector><list>erase操作由于pos位置迭代器都删除了,因此都失效了。不过,erase后,都要返回下一个元素位置的迭代器。
🚩增删
有了insert和erase后,头插头删,尾插尾删都很方便。
void push_back(const T& x){insert(end(), x);}void pop_back(){erase(--end());}void push_front(const T& x){insert(begin(), x);}void pop_front(){erase(begin());}我们需要注意的是,<vector>中,没有对头部的操作,因为要挪动数据,效率很低。
🚩list构造
我们这里实现一下分别实现拷贝构造,赋值运算符重载,以及用一段迭代器区间构造,和initializer_list的构造
void empty_init() {_head = new Node(-1);_head->_next = _head;_head->_prev = _head; }//拷贝构造 //lt2(lt1) list(const list<T>& it) {empty_init();for (auto& e : it){push_back(e);} }void swap(list<T>& it){std::swap(_head, it._head);std::swap(_size, it._size);} //赋值运算符重载list<T>& operator=(list<T> it){swap(it);return *this;}template <class InputIterator> list(InputIterator first, InputIterator last) {empty_init();while (first != last){push_back(*first);first++;} }list(initializer_list<T> il) {empty_init();for (auto& e : il){push_back(e);} }✅代码解析:
拷贝构造先调用empty_init为*this开辟头结点,再直接尾插。
有了拷贝构造就可以直接写赋值赋值运算符重载。
用一段迭代器区间构造,我们来遍历这段迭代器区间,然后完成尾插工作。
initializer_list调用empty_init为this开辟头结点,再拿il中的元素进行尾插。
🚩析构函数
析构函数是对有资源的对象完成销毁和清理工作.
明确一下,此处有资源的包括每个节点,以及头结点。
void clear() {iterator it = begin();while(it != end()){it = erase(it);//erase后更新迭代器,防止迭代器失效;it++;} }~list() {clear();delete _head;_head = nullptr; }✅代码解析:
先释放<list>类对象中头结点后的所有元素,最后,释放头结点。
🚩反向迭代器实现⭐️
首先我们来了解一下适配器的概念:
适配器(Adapter)是一个设计模式,用于解决两个不兼容接口之间的兼容性问题。适配器模式允许你通过创建一个适配器类来“转换”一个类的接口,使其能够与另一个类的接口兼容。
简单可以理解成类模版之间的复用
而我们已经实现了正向迭代器,反向迭代器的行为跟正向迭代器没有什么不同,解引用可以取元素,迭代器++,--支持移动。无非就是反向迭代器的++是正向迭代器的--,反向迭代器的--是正向迭代器的++,逻辑上是相反的。
因此我们可以使用正向迭代器作为适配器,来适用于反向迭代器的实现。
我们先简单实现一下👉:
#pragma oncetemplate<class Iterator,class Ref,class Ptr>struct Reverse_iterator{typedef Reverse_iterator<Iterator, Ref, Ptr> Self;Reverse_iterator(Iterator it):_it(it){}//迭代器支持解引用/*Ref operator*(){return *_it;}*///返回前一个元素的值Ref operator*(){Iterator tmp = _it;tmp--;return *tmp;}Ptr operator->(){return &(operator*());}//迭代器支持移动Self& operator++(){--_it;return *this;}Self operator++(int){Self tmp(*this);--_it;return tmp;}Self& operator--(){++_it;return *this;}Self operator--(int){Self tmp(*this);++_it;return tmp;}//迭代器支持比较bool operator==(const Self& it){return _it == it._it;}bool operator!=(const Self& it){return _it != it._it;}Iterator _it; };反向迭代器的成员变量是<iterator>类型,用<iterator>作为适配器。
调用只需要调用适配器的接口,只需要注意逻辑上方向的问题,反向迭代器的++是正向迭代器的--。
这里有个问题,就是为什么反向迭代器解引用是访问前一个数据❓
这里设计本质是希望对称,即你的end()是我的rbegin(),你的begin()是我的rend()
因此这样从rbegin开始遍历,由于第一个位置是头结点,只能访问前面一个元素,也就是4,然后3,2,1,直到rbegin==rend
因此我们有了反向迭代器,对所有容器都可以使用,前提是只要提供了它的正向迭代器,我们拿它的正向迭代器适配出对应的反向迭代器。
因此在<list>中,我们构造出反向迭代器的rbegin(),rend()const和非const版本。
//反向迭代器 typedef Reverse_iterator< iterator, T&, T*> reverse_iterator; typedef Reverse_iterator< const_iterator, const T&, const T*> const_reverse_iterator;reverse_iterator rbegin() {return reverse_iterator(end()); }reverse_iterator rend() {return reverse_iterator(begin()); }const_reverse_iterator rbegin() const {return const_reverse_iterator(end()); }const_reverse_iterator rend() const {return const_reverse_iterator(begin()); }<list>中传入模版参数,Reverse_iterator这样实例化。其中第一个参数以iterator作为适配器。
🐼全部源码
list.h👇👇
#pragma once#include<iostream>
#include<list>
#include"iterator.h"
using namespace std;namespace lsg
{//定义节点结构template<class T>struct List_Node{List_Node<T>* _next;//指向下一个节点的指针List_Node<T>* _prev;//指向前一个节点的指针T _data;List_Node(const T& x = T()):_next(nullptr),_prev(nullptr),_data(x){}};两个迭代器版本(高度相似)//template <class T>//struct List_iterator//{// typedef List_Node<T> Node;// typedef List_Node<T>* pNode;// // pNode _Node;// List_iterator(Node* node)// :_Node(node)// {}// T& operator*()// {// return _Node->_data;// }// //前置++// List_iterator<T>& operator++()// {// _Node = _Node->_next;// return *this;// }// //后置++// List_iterator<T> operator++(int)// {// List_iterator<T> tmp = *this;// _Node = _Node->_next;// return tmp;// }// // bool operator!=(const List_iterator<T>& it)// {// return _Node != it._Node;// }// T* operator->()// {// return &_Node->_data;// }//};//template <class T>//struct const_List_iterator//{// typedef List_Node<T> Node;// typedef List_Node<T>* pNode;// pNode _Node;// const_List_iterator(Node* node)// :_Node(node)// {}// const T& operator*() const// {// return _Node->_data;// }// //前置++// const_List_iterator<T>& operator++() // {// _Node = _Node->_next;// return *this;// }// const_List_iterator<T> operator++(int) // {// List_iterator<T> tmp = *this;// _Node = _Node->_next;// return tmp;// }// bool operator!=(const const_List_iterator<T>& it)// {// return _Node != it._Node;// }// const T* operator->() const// {// return &_Node->_data;// }//};//用模版方法来控制const和非const迭代器template <class T,class Ref,class Ptr>struct List_iterator{typedef List_Node<T> Node;typedef List_Node<T>* pNode;typedef List_iterator<T, Ref, Ptr> Self;pNode _Node;List_iterator(Node* node):_Node(node){}//迭代器具有像指针一样的行为,可以解引用Ref operator*(){return _Node->_data;}//指针可以通过->访问其所指空间成员,因此迭代器类中必须重载oprator->()Ptr 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;}};template<class T>class list{typedef List_Node<T> Node;typedef List_Node<T>* pNode;public:/* typedef List_iterator<T> iterator;typedef const_List_iterator<T> const_iterator;*///传参来控制const迭代器和非const迭代器typedef List_iterator<T,T&,T*> iterator;typedef List_iterator<T,const T&,const T*> const_iterator;//反向迭代器typedef Reverse_iterator< iterator, T&, T*> reverse_iterator;typedef Reverse_iterator< const_iterator, const T&, const T*> const_reverse_iterator;reverse_iterator rbegin(){return reverse_iterator(end());}reverse_iterator rend(){return reverse_iterator(begin());}const_reverse_iterator rbegin() const{return const_reverse_iterator(end());}const_reverse_iterator rend() const{return const_reverse_iterator(begin());}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);}void empty_init(){_head = new Node(-1);_head->_next = _head;_head->_prev = _head;}list(){empty_init();_size = 0;}void clear(){iterator it = begin();while(it != end()){it = erase(it);//erase后更新迭代器,防止迭代器失效;it++;}}~list(){clear();delete _head;_head = nullptr;}//拷贝构造//lt2(lt1)list(const list<T>& it){empty_init();for (auto& e : it){push_back(e);}}list(initializer_list<T> il){empty_init();for (auto& e : il){push_back(e);}}template <class InputIterator>list(InputIterator first, InputIterator last){empty_init();while (first != last){push_back(*first);first++;}}void swap(list<T>& it){std::swap(_head, it._head);std::swap(_size, it._size);}list<T>& operator=(list<T> it){swap(it);return *this;}/*void push_back(const T& x){pNode newnode = new Node(x);pNode tail = _head->_prev;tail->_next = newnode;newnode->_prev = tail;_head->_prev = newnode;newnode->_next = _head;}*/void push_back(const T& x){insert(end(), x);}void pop_back(){erase(--end());}void push_front(const T& x){insert(begin(), x);}void pop_front(){erase(begin());}size_t size() const{return _size;}//insert后pos位置迭代器失效·void insert(iterator pos, const T& x){pNode newnode = new Node(x);//prev newnode curpNode cur = pos._Node;pNode prev = cur->_prev;prev->_next = newnode;newnode->_next = cur;newnode->_prev = prev;cur->_prev = newnode;++_size;//pos = iterator(newnode);//如果想继续使用,更细pos位置的迭代器}iterator erase(iterator pos){pNode cur = pos._Node;pNode next = cur->_next;pNode prev = cur->_prev;prev->_next = next;next->_prev = prev;delete cur;--_size;return iterator(next);//返回下一个元素的迭代器}private:pNode _head;size_t _size;};
}
Reverse_iterator.h👇👇
#pragma oncetemplate<class Iterator,class Ref,class Ptr>struct Reverse_iterator{typedef Reverse_iterator<Iterator, Ref, Ptr> Self;Reverse_iterator(Iterator it):_it(it){}//迭代器支持解引用/*Ref operator*(){return *_it;}*///返回前一个元素的值Ref operator*(){Iterator tmp = _it;tmp--;return *tmp;}Ptr operator->(){return &(operator*());}//迭代器支持移动Self& operator++(){--_it;return *this;}Self operator++(int){Self tmp(*this);--_it;return tmp;}Self& operator--(){++_it;return *this;}Self operator--(int){Self tmp(*this);++_it;return tmp;}//迭代器支持比较bool operator==(const Self& it){return _it == it._it;}bool operator!=(const Self& it){return _it != it._it;}Iterator _it;
};
🐼总结
通过<list>的认识以及模拟实现,加深了我们对迭代器的认识,迭代器支持++.--,比较,解引用,随机访问等等操作,我们知道了迭代器行为是像指针一样的东西,迭代器提供了一种统一的方式来访问容器中的元素,而无需关心容器的具体实现细节,在<list>中我们专门封装了一个iterator类来模拟迭代器的行为。
而介绍了适配器后,我们通过正向迭代器<iterator>来适配出反向迭代器<Reverse_iterator>类,通过类模版之间的复用实现了两个不同接口的兼容性。
我们也更加感受到了函数模版的魅力,通过模版参数,来减少很多逻辑上重复的代码,比如const对象和非const对象迭代器的实例化,我们控制实参,就能实例化出权限不同的迭代器版本。
感谢你耐心地阅读到这里,你的支持是我不断前行的最大动力。如果你觉得这篇文章对你有所启发,哪怕只是一点点,那就请不吝点赞👍,收藏⭐️,关注🚩吧!你的每一个点赞都是对我最大的鼓励,每一次收藏都是对我努力的认可,每一次关注都是对我持续创作的鞭策。希望我的文字能为你带来更多的价值,也希望我们能在这个充满知识与灵感的旅程中,共同成长,一起进步。再次感谢你的陪伴,期待与你在未来的文章中再次相遇!⛅️🌈 ☀️
相关文章:
【C++】解锁<list>的正确姿势
> 🍃 本系列为初阶C的内容,如果感兴趣,欢迎订阅🚩 > 🎊个人主页:[小编的个人主页])小编的个人主页 > 🎀 🎉欢迎大家点赞👍收藏⭐文章 > ✌️ 🤞 …...
Qt中的事件
写一个 可以拖动的按钮 DraggablePushButton.h 头文件 #ifndef DRAGGABLEPUSHBUTTON_H #define DRAGGABLEPUSHBUTTON_H#include <QPushButton> #include <QMouseEvent>class DraggablePushButton : public QPushButton {Q_OBJECTpublic:explicit DraggablePushBu…...
变化检测相关论文可读list
一些用得上的: 遥感变化检测常见数据集https://github.com/rsdler/Remote-Sensing-Change-Detection-Dataset/ 代码解读:代码解读 | 极简代码遥感语义分割,结合GDAL从零实现,以U-Net和建筑物提取为例 NeurIPS2024: https://mp.w…...
Ansible中playbook的变量
变量 playbook的变量有以下几种 在playbook中用户自定义的变量远程主机中由Ansible收集的变量在文件模板中使用的上述两种变量把任务结果作为一个变量使用,叫注册变量用户在执行playbook时,通过命令行传入的变量,叫做额外变量 在playbook中…...
亚信安全正式接入DeepSeek
亚信安全致力于“数据驱动、AI原生”战略,早在2024年5月,推出了“信立方”安全大模型、安全MaaS平台和一系列安全智能体,为网络安全运营、网络安全检测提供AI技术能力。自2024年12月DeepSeek-V3发布以来,亚信安全人工智能实验室利…...
相似性图相关性重构网络用于无监督跨模态哈希
《Similarity Graph-correlation Reconstruction Network for unsupervised cross-modal hashing》 摘要1. 引言2. 相关工作2.1. 监督跨模态哈希方法2.2. 无监督跨模态哈希方法 3. 方法论3.1 问题定义3.2 特征提取3.3 模态内关系图构建3.4. 局部关系图重置3.5. 跨模态关系图构建…...
【Bug】属性 PackageVersion 应在所有目标框架中具有单个值,但却具有以下值
文章目录 问题问题代码原因解决处理Bug的具体步骤 问题 严重性 代码 说明 项目 文件 行 禁止显示状态 错误(活动) NU1105 无法读取“x”的项目信息: 属性 PackageVersion 应在所有目标框架中具有单个值,但却具有以下值: 1.0.0, 1.0.5 x (net8.0-android), x (net8.…...
C++ Primer 类型转换
欢迎阅读我的 【CPrimer】专栏 专栏简介:本专栏主要面向C初学者,解释C的一些基本概念和基础语言特性,涉及C标准库的用法,面向对象特性,泛型特性高级用法。通过使用标准库中定义的抽象设施,使你更加适应高级…...
【CS61A 2024秋】Python入门课,全过程记录P7(Week13 Macros至完结)【完结撒花!】
文章目录 关于新的问题更好的解决方案Week13Mon Macros阅读材料Lab 11: Programs as Data, MacrosQ1: WWSD: QuasiquoteQ2: If ProgramQ3: Exponential PowersQ4: Repeat Wed SQL阅读材料Disc 11: MacrosQ1: Mystery MacroQ2: Multiple AssignmentQ3: Switch Optional Contest:…...
SSH隧道+Nginx:绿色通道详解(SSH Tunnel+nginx: Green Channel Detailed Explanation)
SSH隧道Nginx:内网资源访问的绿色通道 问题背景 模拟生产环境,使用两层Nginx做反向代理,请求公网IP来访问内网服务器的网站。通过ssh隧道反向代理来实现,重点分析一下nginx反代的基础配置。 实验环境 1、启动内网服务器的tomca…...
LabVIEW用户界面设计原则
在LabVIEW开发中,用户界面(UI)设计不仅仅是为了美观,它直接关系到用户的操作效率和体验。一个直观、简洁、易于使用的界面能够大大提升软件的可用性,尤其是在复杂的实验或工业应用中。设计良好的UI能够减少操作错误&am…...
Datawhale 数学建模导论二 2025年2月
第6章 数据处理与拟合模型 本章主要涉及到的知识点有: 数据与大数据Python数据预处理常见的统计分析模型随机过程与随机模拟数据可视化 本章内容涉及到基础的概率论与数理统计理论,如果对这部分内容不熟悉,可以参考相关概率论与数理统计的…...
SQL CASE表达式的用法
SQL CASE表达式的用法 一、CASE表达式的基础语法简单CASE表达式搜索CASE表达式 二、简单CASE表达式的应用示例三、搜索CASE表达式的应用示例四、CASE表达式在聚合函数中的应用五、嵌套CASE表达式的应用 今天在也无力用到了CASE表达式,于是有了这篇博客,C…...
趣味魔法项目 LinuxPDF —— 在 PDF 中启动一个 Linux 操作系统
最近,一位开源爱好者开发了一个LinuxPDF 项目(ading2210/linuxpdf: Linux running inside a PDF file via a RISC-V emulator),它的核心功能是在一个 PDF 文件中启动并运行 Linux 操作系统。它通过巧妙地使用 PDF 文件格式中的 Ja…...
win32汇编环境,窗口程序使用跟踪条(滑块)控件示例一
;运行效果 ;win32汇编环境,窗口程序使用跟踪条(滑块)控件示例一 ;生成2条横的跟踪条,分别设置不同的数值范围,设置不同的进度副度的例子 ;直接抄进RadAsm可编译运行。重要部分加备注。 ;下面为asm文件 ;>>>>>>>>>>>>>>>>>…...
mars3d接入到uniapp的时候ios上所有地图的瓦片都无法加载解决方案
用的是【Mars3d】官网的uniapp的仓库,安卓没有问题,但是ios的不行 相关链接 mars3d-uni-app: uni-app技术栈下的Mars3D项目模板 解决方案:感觉所有图片请求全被拦截了 uniapp的ios内核不允许跨域,需要先把瓦片下载后转base64&…...
使用 Notepad++ 编辑显示 MarkDown
Notepad 是一款免费的开源文本编辑器,专为 Windows 用户设计。它是替代记事本(Notepad)的最佳选择之一,因为它功能强大且轻量级。Notepad 支持多种编程语言和文件格式,并可以通过插件扩展其功能。 Notepad 是一款功能…...
wordpress主题制作
工具/原料 <P><BR>使用divcss语言编写的html静态页面一个</P> <P>Macromedia Dreamweaver软件<BR></P> WordPress主题结构分析 1 1、index.php首页模板(最基本) ---- 1、header.php头部 ---- 2、sidebar.php侧边…...
MybatisPlus常用增删改查
记录下MybatisPlus的简单的增删改查 接口概述 Service和Mapper区别 Mapper简化了单表的sql操作步骤(CRUD),而Serivce则是对Mapper的功能增强。 Service虽然加入了数据库的操作,但还是以业务功能为主,而更加复杂的SQL…...
Citus的TPCC、TPCH性能测试
Citus的TPCC、TPCH性能测试 文章目录 Citus的TPCC、TPCH性能测试测试的目的适用范围测试环境架构信息硬件配置操作系统软件版本 测试结果TPCC测试测试结果TPCH测试测试结果 一、环境部署1.1、安装BenchmarkSQL1.2、PostgreSQL安装1.3、nmon部署1.4、TPC-H测试的生成数据工具安装…...
Go 语言接口详解
Go 语言接口详解 核心概念 接口定义 在 Go 语言中,接口是一种抽象类型,它定义了一组方法的集合: // 定义接口 type Shape interface {Area() float64Perimeter() float64 } 接口实现 Go 接口的实现是隐式的: // 矩形结构体…...
Linux-07 ubuntu 的 chrome 启动不了
文章目录 问题原因解决步骤一、卸载旧版chrome二、重新安装chorme三、启动不了,报错如下四、启动不了,解决如下 总结 问题原因 在应用中可以看到chrome,但是打不开(说明:原来的ubuntu系统出问题了,这个是备用的硬盘&a…...
什么?连接服务器也能可视化显示界面?:基于X11 Forwarding + CentOS + MobaXterm实战指南
文章目录 什么是X11?环境准备实战步骤1️⃣ 服务器端配置(CentOS)2️⃣ 客户端配置(MobaXterm)3️⃣ 验证X11 Forwarding4️⃣ 运行自定义GUI程序(Python示例)5️⃣ 成功效果作为经典的损失函数,在处理干净数据时表现优异,但在面对包含异常值的噪声数据时,其对大误差的二次惩罚机制往往导致模型参数…...
C++:多态机制详解
目录 一. 多态的概念 1.静态多态(编译时多态) 二.动态多态的定义及实现 1.多态的构成条件 2.虚函数 3.虚函数的重写/覆盖 4.虚函数重写的一些其他问题 1).协变 2).析构函数的重写 5.override 和 final关键字 1&#…...
python爬虫——气象数据爬取
一、导入库与全局配置 python 运行 import json import datetime import time import requests from sqlalchemy import create_engine import csv import pandas as pd作用: 引入数据解析、网络请求、时间处理、数据库操作等所需库。requests:发送 …...
Modbus RTU与Modbus TCP详解指南
目录 1. Modbus协议基础 1.1 什么是Modbus? 1.2 Modbus协议历史 1.3 Modbus协议族 1.4 Modbus通信模型 🎭 主从架构 🔄 请求响应模式 2. Modbus RTU详解 2.1 RTU是什么? 2.2 RTU物理层 🔌 连接方式 ⚡ 通信参数 2.3 RTU数据帧格式 📦 帧结构详解 🔍…...
快速排序算法改进:随机快排-荷兰国旗划分详解
随机快速排序-荷兰国旗划分算法详解 一、基础知识回顾1.1 快速排序简介1.2 荷兰国旗问题 二、随机快排 - 荷兰国旗划分原理2.1 随机化枢轴选择2.2 荷兰国旗划分过程2.3 结合随机快排与荷兰国旗划分 三、代码实现3.1 Python实现3.2 Java实现3.3 C实现 四、性能分析4.1 时间复杂度…...
[USACO23FEB] Bakery S
题目描述 Bessie 开了一家面包店! 在她的面包店里,Bessie 有一个烤箱,可以在 t C t_C tC 的时间内生产一块饼干或在 t M t_M tM 单位时间内生产一块松糕。 ( 1 ≤ t C , t M ≤ 10 9 ) (1 \le t_C,t_M \le 10^9) (1≤tC,tM≤109)。由于空间…...






