list底层的简单实现(万字长文详解!)
list底层的简单实现
文章目录
- list底层的简单实现
- list_node的实现!
- list_node的构造函数
- list的迭代器!——重点!
- list迭代器的成员变量
- 迭代器的构造函数
- * 重载
- 前置++ 重载
- 后置++ 重载
- 前置-- 重载
- 后置-- 重载
- != 重载
- == 重载
- -- 重载
- list的const迭代器——重点
- 迭代器之-> 重载——重点
- list的成员变量
- list的构造函数(无参)
- insert
- begin
- end
- const版本的begin
- const版本的end
- push_back
- 原始的写法
- 复用的写法
- push_front
- swap
- 构造函数(带参)
- 拷贝构造
- 传统写法
- 现代写法
- 赋值重载
- 传统写法
- 现代写法
- erase
- pop_front
- pop_back
- clear
- 析构函数
- size
- empty
- 代码展示
list_node的实现!
要实现链表我们首先就要实现节点的结构体!
struct list_node
{list_node* _next;list_node* _prev;T _data;
};
list_node的构造函数
list_node(const T& x):_data(x),_next(nullptr),_prev(nullptr)
{
}
list的迭代器!——重点!
任何一个容器的迭代器都是内嵌类型!都要受到域的影响!
list的迭代器和string和vector不一样,并不是一个原生的指针!
typedef node* iterator;
为什么我们不可以像上面怎么写呢?
因为我们要求迭代器行为要类似指针!——1.解引用得到数据 2.支持++/–
像是我们解引用,我们就要得到这个节点的数据!但是如果我们将上面的这个迭代器解引用了我们其实得到的是一个节点!我们自己还得去x.data用来获得数据!,这就不符合迭代器的定义了!
因为节点本身不是连续的!所以上面的迭代器也不支持++/–
以前string和vector能使用原生指针的原因是因为存储的物理结构都是连续的!所以可以直接支持原生指针能完成迭代器行为!
所以list的迭代器是通过类的封装+函数重载实现的!
用函数重载来实现相似的行为!
list迭代器的成员变量
template<class T>
struct __list_iterator
{typedef list_node<T> node;typedef __list_iterator<T> self;//方便添加模板参数node* _pnode;
}
迭代器的构造函数
__list_iterator(node* p):_pnode(p)
{}
使用一个节点用来构造一个迭代器
* 重载
T& operator*()
{return _pnode->_data;
}
因为我们一般都是解引用后可以修改数据的值!
所以我们要引用返回
前置++ 重载
self& operator++()
{_pnode = _pnode->_next;return *this;
}
这个是迭代器++ 要返回的是这个迭代器的类型__list_iterator!
不要忘记加上 否则就是类型不完全!也不是node*
还是一个前置++ ,返回引用可以减少拷贝构造
后置++ 重载
self operator++(int)
{node* cur = _pnode;_pnode = _pnode->_next;return self(cur);
}
后置的定义为 先用后 ++
本质就是一个先将原来的节点构造成一个迭代器然后返回
本身再移动到下一个节点!
实际上使用的是我们返回后的新创建的迭代器!
而不是原来的迭代器
前置-- 重载
self& operator--()
{_pnode = _pnode->_prev;return *this;
}
后置-- 重载
self operator--(int)
{node* cur = _pnode;_pnode = _pnode->_prev;return self(cur);
}
!= 重载
bool operator!=(const self& it)const
{return _pnode != it._pnode;
}
虽然传的参数类型是__list_iterator
但是实际上比较的是他们里面封装的pnode
== 重载
bool operator==(const self& it)const
{return _pnode == it._pnode;
}
– 重载
self& operator--()
{_pnode = _pnode->_prev;return *this;
}
list的const迭代器——重点
像是string和vector因为其迭代器的本质就是原生指针!所以const迭代器其实也是十分的简单的!但是list不一样!它不是一个原生的指针!是以一个内部类!这就导致了一些问题
如果我们仅仅在普通的迭代器前面加上const
const list<int>iterator cit;
这个const保护的其实是cit本身!
而不是cit指向的内容!
const T* p1; T* const p2;
上面的行为要类似于p1,而不是p2
p1是本身可以修改!但是p1指向的内容却是不可修改的!
为了能够实现我们预想的行为我们真正要对修改的是返回值!
像是对于 *的重载我们原先返回的是 它的引用
现在我们应该返回的是 它的const T& 从而来进行权限的缩写!
那么我们是不是可以写一个如下的代码呢
const T operator*()const {return _pnode->data; }
让const调用这个const重载 让非const调用非const重载
不行!我们不能对解引用进行重载!
为什么?答案是虽然上面的都可行了但是 我们会发现const的对象无法去调用++了
const的对象无法去调用非const的成员函数
那我们如果实现一个const版本的++呢?
self& operator++()const {_pnode = _pnode->_next;return *this; }
可行吗?当然是不可行的!因为这个const修饰的是this指针!this指针指向的内容就无法修改了!就没办法做到修改pnode了!
那如何解决这个问题呢?——就是不要让迭代器本身const!
方法1 ——重新写一个类这两个类的唯一区别就是他们的解引用是不一样的!
一个返回引用,一个返回常引用 其他都是完全一致的!
//迭代器 template<class T> struct __list_const_iterator {typedef list_node<T> node;node* _pnode;__list_iterator(node* p):_pnode(p){}const T operator*()const{return _pnode->data;}//唯一的区别返回常引用__list_iterator<T>& operator++();__list_iterator<T> operator++(int);__list_iterator<T>& operator--();__list_iterator<T> operator--(int);bool operator!=(const __list_iterator& it);bool operator==(const __list_iterator& it);Ptr operator->() };
然后使用这个类去实现const版本的list函数调用接口例如begin和end
typedef __list_const_iterator const_iterator const_iterator begin()const {return const_iterator(_head->_next); } const_iterator end()const {return const_iterator(_head); }
然后我们就可以通过这个类来实现const的迭代器!使用的时候const和非const调用的就是两个完全不同的类了!
但是这样子实在是太繁琐了!为了一个不同而写一个类
所以有了方法二 ——使用类模板来实现!即使是同一个类模板 但是使用的参数不一样的时候也就是完不同的类!
vector<int>; vector<string>; vector<vector<string>>:
虽然都是同一个类模板,这三种都是不同的类!
所以我们可以添加一个模板参数!
template<class T, class Ref>//Ref 引用 struct __list_iterator {typedef list_node<T> node;typedef __list_iterator<T, Ref> self;//方便添加模板参数node* _pnode;__list_iterator(node* p):_pnode(p){}Ref operator*()//使用第二个模板参数{return _pnode->_data;}self& operator++();self operator++(int);self& operator--();self operator--(int);bool operator!=(const self& it)const;bool operator==(const self& it)const;Ptr operator->() }; class list {typedef list_node<T> node; private:node* _head; public:typedef __list_iterator<T, T&> iterator;typedef __list_iterator<T, const T&> const_iterator; } typedef __list_iterator<T,T&> iterator;//迭代器 typedef __list_iterator<T, const T&> const_iterator;//const 迭代器
这样子我们就通过复用来简洁的完成const的迭代器!
迭代器之-> 重载——重点
template<class T, class Ref,class Ptr>//多引入一个模板参数
struct __list_iterator
{typedef list_node<T> node;typedef __list_iterator<T, Ref,Ptr> self;Ptr operator->(){return &_pnode->_data;}
}
为什么我们需要 重载 -> 呢?
当我遇到如下的如下的结构
struct pos {int _row;int _col;pos(int row = 0, int col = 0){_row = row;_col = col;} };
当我们把它插入链表
void test_list() {list<pos> lt;lt.push_back(pos(1, 2));lt.push_back(pos(2, 3));lt.push_back(pos(3, 4));lt.push_back(pos(3, 5));list<pos>::iterator it = lt.begin();while (it != lt.end()){cout << (*it)._col << " " << (*it)._row << endl;++it;} }
如果我们想要访问那么就不得不先解引用!
但是我们一般在使用指针的时候都是使用 - > 更加符合我们的使用习惯!
但是当看到重载的时候或许会觉得很奇怪会什么要返回的是数据的指针呢?
it->_col; //转换成函数调用 it.operator->();//返回值是一个地址一个pos* 类型的指针
那为什么是 pos* 类型的指针后面可以直接跟上成员变量呢?
其实这是因为编译器给我们进行了简化处理
真实的情况 应该是 it->->_col转换成函数调用就是it.operator->()- > _col这样的情况但是这样子又不符合我们的日常使用的习惯和为了更好的可读性!所以编译器就简化了一个 ->
it->->_col;//当然了我们也不可以怎么使用就是了 it.operator->()->_col//但是如果显示的去调用的话我们也可以怎么使用
如果我们不引入第三个模板参数会怎么样
T* operator->() {return &_pnode->_data; }
看起来好像也毫无问题啊!
但是当我们遇到const对象的时候!
struct pos {int _row;int _col;pos(int row = 0, int col = 0){_row = row;_col = col;} }; void print(const list<pos>& lt) {list<pos>::const_iterator it = lt.begin();while (it != lt.end()){++it->_col;cout << it->_col << " " << it->_row << endl;++it;}} void test_list() {list<pos> lt;lt.push_back(pos(1, 2));lt.push_back(pos(2, 3));lt.push_back(pos(3, 4));lt.push_back(pos(3, 5)); }
我们发现const对象下仍然可以通过-> 来修改数据!这就和 我们上面const迭代器讲的 * 的重载是一个道理!
对于const迭代器 - > 的返回值应该是 一个 const指针!用来保护指针指向的内容!
所以我们才要引入第三个模板参数来给 - > 使用!或者就是再写一个类!
typedef __list_iterator<T,T&,T> iterator;//迭代器typedef __list_iterator<T, const T&,const T*> const_iterator;//const 迭代器
这样子迭代器才算是真正的完成了!
list的成员变量
class list
{typedef list_node<T> node;
public:typedef __list_iterator<T,T&,T> iterator;//迭代器typedef __list_iterator<T, const T&,const T*> const_iterator;//const 迭代器
private:node* _head;size_t _size;
}
因为写list_node 不方便所以我们一般会进行重命名
list的构造函数(无参)
void empty_initialize()
{_head = new node(T());_head->_next = _head;_head->_prev = _head;_size = 0;
}
list()
{empty_initialize();
}
链表的底层是一个带头双向循环的链表
开始的头是一个哨兵位所以要new一个!
因为node 的构造函数是带参的构造,没有默认构造!
所以我们要给一个值进行初始化!不可以填0 什么的!
因为我们无法保证这个数据类型是一个内置类型!
所以我们要用T() 来进行初始化!如果是一个自定义类型那么T()就是一个匿名对象,就会去调用这个类的默认构造来初始化!
insert
iterator insert(iterator pos, const T& x = T())
{node* newnode = new node(x);node* cur = pos._pnode;//取到了这个节点的指针!node* prev = cur->_prev;newnode->_next = cur;newnode->_prev = prev;prev->_next = newnode;cur->_prev = newnode;++_size;return iterator(cur);//返回当前这个新插入的元素的迭代器
}
begin
iterator begin()
{return iterator(_head->_next);
}
iterator是一个内部类!
假如有无参构造 那么iterator() 就是一个匿名对象!
iterator(_head-> _next) 就是用 _head-> _next初始化这个对象! begin() 是指向第一个元素的迭代器!
_head是哨兵位
也可以先创建一个对象然后返回!
iterator begin() {iterator it(_head->_next);return it; }
end
iterator end()
{return iterator(_head);
}
end指向的是最后一个元素的下一个元素!那么最后一个元素的下一个元素就是哨兵位!
const版本的begin
const_iterator begin()const
{return const_iterator(_head->_next);
}
const版本的end
const_iterator end()const
{return const_iterator(_head);
}
push_back
原始的写法
void push_back(const T& x)
{node* newnode = new node(x);node* tail = _head->_prev;tail->_next = newnode;newnode->_next = _head;newnode->_prev = tail;_head->_prev = newnode;++_size;
}
要改变 四个指针
newnode的prev和next
tail的next 和 head的prev!
其中head的prev要在要保存成tail才能能修改!否则newnode的prev就找不到原来链表的最后一个元素了了!
如果不保存tail就要最后修改_head->prev!
复用的写法
void push_back(const T& x)
{insert(end(), x);//在end的前面插入就是尾插
}
push_front
void push_front(const T& x)
{insert(begin(), x);//在begin的前面插入就是头插!
}
swap
void swap(list<T>& lt)
{std::swap(_head, lt._head);std::swap(_size, lt._size);
}
交换两个链表对象!
只要交换头结点和大小
构造函数(带参)
template<class InputIterator>
list(InputIterator first, InputIterator last)
{empty_initialize();while (first != last){push_back(*first);++first;}
}
可以复用pushbak来完成一个带参的构造函数
拷贝构造
传统写法
list(const list<T>& lt)
{empty_initialize();//先初始化!创造哨兵位for (const auto& e : lt)//使用使用范围for之前要先实现const迭代器{push_back(e); //进行深拷贝}//范围for就是会自动的从lt里面取 其迭代器然后解引用得到T类型的数据//如果不使用auto& 一旦T是一个自定义类型 可能会多次发生深拷贝!极大的影响效率!
}
现代写法
list(const list<T>& lt)
{empty_initialize();list<T> temp(lt.begin(), lt.end());swap(temp);
}
现代写法是利用带参的构造来完成深拷贝!然后交换彼此的头结点来完成一次拷贝构造!
但是开始一定要完成一次初始化!
否则开始的我们我们要初始化的这个头结点是一个随机值,temp一旦出了作用域后就会立刻调用析构函数,析构一个野指针会导致程序崩溃!
或者我们能不能不能在初始化列表里面将_head初始化成nullptr呢?也不行!因为我们写的析构函数是默认有哨兵位的情况下进行析构的!所以必须进行初始化!
赋值重载
传统写法
//传统写法
iterator& operator=(const list<T>& lt)
{if (this != <){clear();//每次赋值前都去清除掉原理的for (const auto& e : lt){push_back(e);}}return *this;
}
现代写法
iterator& operator=(list<T> lt)
{swap(lt);return *this;
}
这个写法是利用了拷贝构造! 传值传参会调用拷贝构造构造一个新的对象!lt
然后我们可以直接利用swap交换头结点!
而且出了作用域后lt也会调用析构函数来释放原来对象的资源!
erase
iterator erase(iterator pos)
{assert(pos != end());//不能等于哨兵位!node* cur = pos._pnode;node* next = cur->_next;node* prev = cur->_prev;prev->_next = next;next->_prev = prev;delete cur;--_size;return iterator(next);//返回删除的节点的下一个节点的迭代器
}
先把前后两个节点保存下来
然后改变前一个节点的next
改变后一个节点的prev
最后释放掉原来的节点!
pop_front
void pop_front()
{erase(begin());//删除掉头结点
}
pop_back
void pop_back()
{erase(--end());//end()是最后一个节点的下一个!所以要-- 是其指向最后一个节点!
}
clear
void clear()
{iterator it = begin();while (it != end()){it = erase(it);//erase之后不能++ 因为已经发生了迭代器失效!}
}
clear和析构的区别在于 clear不清头结点!
析构函数
~list()
{clear();delete _head;_head = nullptr;
}
析构函数记得要清楚头结点!
size
size_t size()const
{return _size;
}
关于list的size,因为有的源码可能不提供_size这个成员变量 使用的是遍历一遍然后返回!时间复杂度为O(N)
所以使用list的size需要谨慎!
empty
bool empty()const
{return _size == 0;
}
也可以使用
bool empty()const {return _head->_next == _head->_prev; }
来判断是否为空
代码展示
#pragma once
#include<iostream>
#include<assert.h>
namespace My_STL
{template<class T>struct list_node{list_node* _next;list_node* _prev;T _data;list_node(const T& x):_data(x),_next(nullptr),_prev(nullptr){}};template<class T,class Ref,class Ptr>struct __list_iterator{typedef list_node<T> node;typedef __list_iterator<T, Ref, Ptr> self;node* _pnode;__list_iterator(node* p):_pnode(p){}Ref operator*(){return _pnode->_data;}Ptr operator->(){return &_pnode->_data;}self& operator++(){_pnode = _pnode->_next;return *this;}self operator++(int){node* cur = _pnode;_pnode = _pnode->_next;return self(cur);}self& operator--(){_pnode = _pnode->_prev;return *this;}self operator--(int){node* cur = _pnode;_pnode = _pnode->_prev;return self(cur);}bool operator!=(const self& it)const{return _pnode != it._pnode;}bool operator==(const self& it)const{return _pnode == it._pnode;}};template<class T>class list{typedef list_node<T> node;private:node* _head;size_t _size;public:typedef __list_iterator<T,T&,T> iterator;typedef __list_iterator<T, const T&,const T*> const_iterator;void empty_initialize(){_head = new node(T());_head->_next = _head;_head->_prev = _head;_size = 0;}list(){empty_initialize();}template<class InputIterator>list(InputIterator first, InputIterator last){empty_initialize();while (first != last){push_back(*first);++first;}}void swap(list<T>& lt){std::swap(_head, lt._head);std::swap(_size, lt._size);}list(const list<T>& lt){empty_initialize();list<T> temp(lt.begin(), lt.end());swap(temp);}~list(){clear();delete _head;_head = nullptr;}iterator& operator=(list<T> lt){swap(lt);return *this;}void clear(){iterator it = begin();while (it != end()){it = erase(it);}}iterator insert(iterator pos, const T& x = T()){node* newnode = new node(x);node* cur = pos._pnode;node* prev = cur->_prev;newnode->_next = cur;newnode->_prev = prev;prev->_next = newnode;cur->_prev = newnode;++_size;return iterator(cur);}iterator erase(iterator pos){assert(pos != end());node* cur = pos._pnode;node* next = cur->_next;node* prev = cur->_prev;prev->_next = next;next->_prev = prev;delete cur;--_size;return iterator(next);}void push_back(const T& x){insert(end(), x);}void push_front(const T& x){insert(begin(), x);}void pop_front(){erase(begin());}void pop_back(){erase(--end());}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);}size_t size()const{return _size;}bool empty()const{ //return _head->_next == _head->_prev;return _size == 0;}};};
相关文章:

list底层的简单实现(万字长文详解!)
list底层的简单实现 文章目录list底层的简单实现list_node的实现!list_node的构造函数list的迭代器!——重点!list迭代器的成员变量迭代器的构造函数* 重载前置 重载后置 重载前置-- 重载后置-- 重载! 重载 重载-- 重载list的const迭代器——…...

学习Linux只要学会这个命令就够了!
大家好,我是良许。 这段时间又是搬家,又是找新办公室,现在终于安顿下来了,有时间给大家分享干货了。 今天给大家介绍一个 Linux 超级实用命令,有了这个命令,你就可以愉快使用 Linux 上几乎所有常用命令了…...

javascript基础
javascript基础 1概述: JavaScript是目前web开发中不可缺少的脚本语言,js不需要编译即可运行,运行在客户端,需要通过浏览器来解析执行JavaScript代码。 诞生于1995年,当时的主要目的是验证表单的数据是否合法。 JavaS…...

【游戏逆向】某游戏技能库分析
技能库的分析大多是从技能名字入手的,然后再通过传入职业或者ID等信息去到库中去取当前角色的可用技能。下面我们来对《**明月刀》中的技能库进行分析。 首先通过CE对技能名字进行搜索,得到较少的结果,分别对结果进行修改,并再次…...

Pytorch深度学习常用预训练网络模型的下载地址
Resnet:model_urls {‘resnet18’: ‘https://download.pytorch.org/models/resnet18-5c106cde.pth‘,‘resnet34’: ‘https://download.pytorch.org/models/resnet34-333f7ec4.pth‘,‘resnet50’: ‘https://download.pytorch.org/models/resnet50-19c8e357.pth‘,‘resnet…...

毕业设计 基于51单片机自动智能浇花系统设计
基于51单片机自动智能浇花系统设计1、毕业设计选题原则说明(重点)2、项目资料2.1 系统框架2.2 系统功能3、部分电路设计3.1 STC89C52单片机最小系统电路设计3.2 按键电路设计3.3 水泵控制电路设计4、部分代码展示4.1 数码管位选程序4.2 ad0832数据读取程…...

熟悉常用的 Linux 操作和 Hadoop 操作
文章目录前言一、常用命令集合1、cd命令:切换目录1、切换到目录/usr/local2、切换回上级目录3、切换到当前登录Linux系统的用户的自己的文件夹2、ls命令:查看文件与目录3、mkdir命令:创建目录4、rmdir命令:删除空的目录5、cp 命令…...

Vue2项目总结-电商后台管理系统
Vue2项目总结-电商后台管理系统 去年做的项目,拖了很久,总算是打起精力去做这个项目的总结,并对Vue2的相关知识进行回顾与复习 各个功能模块如果有过多重复冗杂的部分,将会抽取部分值得记录复习的地方进行记录 一:项目…...

【二】一起算法---队列:STL queue、手写循环队列、双端队列和单调队列、优先队列
纸上得来终觉浅,绝知此事要躬行。大家好!我是霜淮子,欢迎订阅我的专栏《算法系列》。 学习经典算法和经典代码,建立算法思维;大量编码让代码成为我们大脑的一部分。 ⭐️已更系列 1、基础数据结构 1.1、链表➡传送门 1…...

<Linux>环境变量
环境变量 文章目录环境变量一、基本概念二、常见环境变量三、查看环境变量的方法四、测试PATH五、测试HOME六、测试SHELL七、环境变量相关的命令八、环境变量的组织方式九、命令行参数十、通过代码获得环境变量十一、通过系统调用获取环境变量十二、环境变量通常是具有全局属性…...

【MySQL】下载(超详细教程)
目录 First-下载 Second-安装 Third-检测是否安装 Last-总结 First-下载 首先 ,我们一步一步跟着我的操作来,不能越步骤,很容易报错,就芭比Q了。 第一步直接进入这个网址:MySQL :: MySQL 社…...

再探pytorch的Dataset和DataLoader
本文从分类、检测、分割三大任务的角度来剖析pytorch得dataset和dataloader源码,可以让初学者深刻理解每个参数的由来和使用,并轻松自定义dataset。思考:在探究Dataset和DataLoader之前,需要明白一个事情,就是当我们不…...

【2023.3.18 美团校招】
文章目录1. 小美剪彩带2. 最多修改两个字符,生成字典序最小的回文串1. 小美剪彩带 题意:找出区间内不超过k种数字子数组的最大长度 使用双指针的方式,用哈希表来统计每个数出现次数。在双指针移动的过程中,动态的维护区间内不同数…...

程序员必须知道的HTML常用代码有哪些?
HTML 即超文本标记语言,是目前应用最为广泛的语言之一,是组成一个网页的主要语言。在现今这个 HTML5 华丽丽地占领了整个互联网的时候,如果想要通过网页抓住浏览者的眼球光靠因循守旧是不行的,程序猿们需要掌握一些必须知道的 HTM…...

多目标家庭行为检测--人脸识别模块构建
文章目录前言原理项目结构编码配置主控函数人脸采集模块特征提取识别测试前言 2023-3-18 天小雨,午觉舒适程度5颗星。任务完成指数2颗星。续接上文:《MidiaPipe stgcn(时空图卷积网络)实现人体姿态判断(单目标&#x…...

RocketMQ重复消费问题的原因
文章目录 概览消息发送异常时重复发送消费消息抛出异常消费者提交offset失败服务端持久化offset失败主从同步offset失败重平衡清理长时间消费的消息总结概览 消息发送异常时重复发送 首先,我们来瞅瞅RocketMQ发送消息和消费消息的基本原理。 如图,简单说一下上图中的概念: …...

proxy详细介绍与使用
proxy详细介绍与使用 proxy 对象用于创建一个对象的代理,是在目标对象之前架设一个拦截,外界对该对象的访问,都必须先通过这个拦截。通过这种机制,就可以对外界的访问进行过滤和改写。 ES6 原生提供 Proxy 构造函数,…...

基于YOLOv5的舰船检测与识别系统(Python+清新界面+数据集)
摘要:基于YOLOv5的舰船检测与识别系统用于识别包括渔船、游轮等多种海上船只类型,检测船舰目标并进行识别计数,以提供海洋船只的自动化监测和管理。本文详细介绍船舰类型识别系统,在介绍算法原理的同时,给出Python的实…...

【C#】List数据去重
系列文章 【C#】单号生成器(定义编号规则、流水号、产生业务单号) 本文链接:https://blog.csdn.net/youcheng_ge/article/details/129129787 【C#】日期范围生成(构建本周开始、结束日期) 本文链接:https…...

避免踩坑,教给你VSCode中最常用到的6项功能
这里为程序员介绍VSCode中包含的许多令人兴奋的Tips。 1. 插件市场中免费下载使用CodeGeeX插件 AI辅助编程工具CodeGeeX,是完全免费,开源开放给所有开发者使用。程序员普遍反应使用这个插件后,代码编写效率提升2倍以上。 CodeGeeX插件拥有…...

ThingsBoard开源物联网平台智慧农业实例快速部署教程(Ubuntu、CentOS适用)
ThingsBoard部署教程文档 文章目录ThingsBoard部署教程文档1. JDK环境安装2. 安装thingsBoard2.1 ThingsBoard软件包安装2.2 PostgreSQL安装2.3 PostgreSQL初始化配置3. 修改ThingsBord的配置4. 运行安装脚本测试5. 访问测试6. 导入一个仪表盘库6.1 导出仪表盘并导入自己的项目…...

【Java Spring基本问题】记录面试题宝典中自己不熟悉的Spring问题
文章目录Spring Bean定义装配Spring Bean生命周期Spring Bean容器Spring 循环依赖Spring 事务Autowired和ResourceSpring Bean定义装配 参考文章 1. 定义Spring Bean的三种方式 XML文件定义Spring Bean JavaConfig定义Spring Bean Component注解定义SpringBean 2. 装配Spri…...

I2C协议简介 Verilog实现
I2C协议 IIC 协议是三种最常用的串行通信协议(I2C,SPI,UART)之一,接口包含 SDA(串行数据线)和 SCL(串行时钟线),均为双向端口。I2C 仅使用两根信号线…...

服务器被DDoS攻击,怎么破?
文章目录前言网站受到DDoS的症状判断是否被攻击查看网络带宽占用查看网络连接TCP连接攻击SYN洪水攻击防御措施TCP/IP内核参数优化iptables 防火墙预防防止同步包洪水(Sync Flood)Ping洪水攻击(Ping of Death)控制单个IP的最大并发…...

实现完全二叉树
文章目录1、树概念及结构2、孩子兄弟表示法3、二叉树3.1、二叉树的概念3.2、特殊的二叉树3.3、二叉树的存储4、堆的性质5、数组结构实现完全二叉树1、结构体的定义2、初始化堆3、销毁堆4、交换函数5、向上调整函数6、插入数据7、向下调整函数8、删除堆顶数据函数9、判断是否空堆…...

【独家】华为OD机试 - 矩阵最值(C 语言解题)
最近更新的博客 华为od 2023 | 什么是华为od,od 薪资待遇,od机试题清单华为OD机试真题大全,用 Python 解华为机试题 | 机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南华为od机试,独家整理 已参加机试人员的实战技巧本期题目:矩阵最值 题目 给定一个仅包…...

C++模板(进阶)
文章目录非类型模板参数类模板的特化类模板的概念函数模板特化类模板的特化全特化偏特化参数的进一步限制模板的分离编译模板的优缺点非类型模板参数 模板参数分类型形参与非类型形参. 类型形参: 出现在模板参数列表中,跟在class,typename之类的参数类型名称. 非类型形参: 就是…...

【数据分析之道(二)】列表
文章目录专栏导读1、列表介绍2、访问列表中的值3、列表增加和修改4、删除元素5、列表函数6、列表方法专栏导读 ✍ 作者简介:i阿极,CSDN Python领域新星创作者,专注于分享python领域知识。 ✍ 本文录入于《数据分析之道》,本专栏针…...

架构师必须要掌握的大小端问题
一、什么是大端和小端 所谓的大端模式,就是高位字节排放在内存的低地址端,低位字节排放在内存的高地址端。 所谓的小端模式,就是低位字节排放在内存的低地址端,高位字节排放在内存的高地址端。 简单来说:大端——高尾端,小端——低尾端 举个例子,比如数字 0x12 34 56 78…...

2023年ACM竞赛班 2023.3.20题解
目录 瞎编乱造第一题 瞎编乱造第二题 瞎编乱造第三题 瞎编乱造第四题 瞎编乱造第五题 不是很想编了但还是得编的第六题 不是很想编了但还是得编的第七题 还差三道题就编完了的第八题 还差两道题就编完了的第九题 太好啦终于编完了 为啥一周六天早八阿 瞎编乱造第一题…...