【C++】list的使用方法和模拟实现
❤️欢迎来到我的博客❤️ |
前言
- list是可以在常数范围内在任意位置进行插入和删除的序列式容器,并且该容器可以前后双向迭代
- list的底层是双向链表结构,双向链表中每个元素存储在互不相关的独立节点中,在节点中通过指针指向其前一个元素和后一个元素
- list与forward_list非常相似:最主要的不同在于forward_list是单链表,只能朝前迭代,已让其更简单高效
- 与其他的序列式容器相比(array,vector,deque),list通常在任意位置进行插入、移除元素的执行效率更好
- 与其他序列式容器相比,list和forward_list最大的缺陷是不支持任意位置的随机访问,比如:要访问list的第6个元素,必须从已知的位置(比如头部或者尾部)迭代到该位置,在这段位置上迭代需要线性的时间开销;list还需要一些额外的空间,以保存每个节点的相关联信息(对于存储类型较小元素的大list来说这可能是一个重要的因素)
list相比于vector在insert和erase上有很大的区别
vector想在第五个位置插入数据可以直接+5
int main()
{vector<int> v1;v1.push_back(1);v1.push_back(2);v1.push_back(3);v1.push_back(4);v1.push_back(5);v1.push_back(7);v1.insert(v1.begin() + 5, 6);for (auto e : v1){cout << e << " ";}cout << endl;//输出结果为:1 2 3 4 5 6 7return 0;
}
而list想在第五个位置插入数据只能使用迭代器,不可以直接+5
int main()
{list<int> lt;lt.push_back(1);lt.push_back(2);lt.push_back(3);lt.push_back(4);lt.push_back(5);lt.push_back(7);//lt.insert(lt.begin() + 5, 10);auto it = lt.begin();for (size_t i = 0; i < 5; i++){++it;}lt.insert(it, 6);for (auto e : lt){cout << e << " ";}cout << endl;//输出结果为:1 2 3 4 5 6 7return 0;
}
list使用insert之后迭代器不会失效,因为结点的位置没有发生改变
但list使用erase之后迭代器就会失效,因为结点位置发生了改变(结点已经被删除)
排序相关
可以看到list单独提供了sort库里明明有一个sort为什么list还要单独提供呢?
我们来试试:
可以看到,当我们使用库里的sort的时候编译报错了
我们转到库定义可以看到,库里的sort用了一个last-first,原理是快排,需要三数取中,但在链表中就不适合三数取中的场景
迭代器从功能的角度是会分类的,他分为:单向、双向和随机
单向可以进行 ++
双向可以进行 ++/–
随机可以进行 ++/–/+/-
单向迭代器有:forward_list / unordered_map / unordered_set
双向迭代器有:list / map / set
随机迭代器有:vector / string / deque
在形参的名字中就暗示了我们适合用哪一种算法,比如reverse就适合用双向,find适合用单向,sort适合用随机
InputIterator(find):只写迭代器(他在单向迭代器的上面)也就是说单向 / 双向 / 随机都可以使用
双向迭代器(reverse):双向 / 随机可以使用
随机迭代器(sort):只有随机能用
容器的迭代器类型在文档中是有说明的:
list:
vector:
set:
forward_list:
在数据量大的情况下,我们可以把list拷贝到vector中,使用库里的排序,再把数据拷贝回去效率更高,我们来对比一下:
数据个数为一千万
int main()
{srand(time(0));const int N = 10000000;vector<int> v;v.reserve(N);list<int> lt1;list<int> lt2;for (int i = 0; i < N; i++){auto e = rand();lt2.push_back(e);lt1.push_back(e);}//拷贝到vectorint begin1 = clock();//拷贝for (auto e : lt1){v.push_back(e);}//排序sort(v.begin(), v.end());//拷贝回去size_t i = 0;for (auto& e : lt1){e = v[i++];}int end1 = clock();//直接使用list排序int begin2 = clock();lt2.sort();int end2 = clock();cout << "使用vector排序:" << end1 - begin1 << endl;cout << "直接使用list排序:" << end2 - begin2 << endl;return 0;
}
可以看到效率差了近十倍,所以在数据量特别大的时候就不要直接使用list排序了,排少量数据则可以直接使用
merge
可以将两个链表归并(前提是链表有序)
unique
去重(前提是链表有序)
remove
remove就是find+erase,如果要删除的值不存在则不进行任何操作
splice
可以把一个链表的内容转移到另一个链表(直接把结点拿走)
转移全部
int main()
{list<int> list1, list2;list<int>::iterator it;for (int i = 1; i <= 4; ++i){list1.push_back(i); //list1:1 2 3 4}for (int i = 1; i <= 3; ++i){list2.push_back(i * 10); //list2:10 20 30}cout << "list1转移前:";for (auto e : list1){cout << e << " ";}cout << endl;cout << "list2转移前:";for (auto e : list2){cout << e << " ";}cout << endl;it = list1.begin();++it;//2位置//把list2全部转移到list1中2之前的位置list1.splice(it, list2);cout << "list1转移后:";for (auto e : list1){cout << e << " ";}cout << endl;cout << "list2转移后:";for (auto e : list2){cout << e << " ";}cout << endl;return 0;
}
转移某一个结点
//转移某一个结点
list1.splice(it, list2,++list2.begin());
部分转移
//部分转移
list1.splice(it, list2,++list2.begin(),list2.end());
自己转移自己
//把第二位置转移到第一个位置的前面
list1.splice(list1.begin(),list1,++list1.begin());
注意:转移重叠位置会造成死循环
模拟实现list
基本框架
namespace List
{template<class T>struct list_node{list_node<T>* _next;list_node<T>* _prev;T _val;//构造函数list_node(const T& val = T()) //缺省值不能给0,因为T不一定是内置类型:_next(nullptr),_prev(nullptr),_val(val){}};template<class T>class list{typedef list_node<T> Node;public:list(){_head = new Node;//哨兵位指向自己_head->_prev = _head;_head->_next = _head;}//尾插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;}private:Node* _head;};void test1(){list<int> lt;lt.push_back(1);lt.push_back(2);lt.push_back(3);lt.push_back(4);}
}
尾插
//尾插
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;
}
迭代器
由于结点的地址不是连续的,那我们的迭代器该如何设置呢,这时候就要用到运算符重载
我们先来看迭代器的使用:
list<int>::iterator it = lt.begin();
while (it != lt.end())
{cout << *it << " ";++it;
}
cout << endl;
那么我们的begin应该返回的是第一个数据的位置,end在最后一个数据的下一个位置
迭代器是一个自定义类型,这个自定义类型是一个结点的指针,所以在list中他的迭代器需要自己设计而不是像vector或string那样使用原生指针(不同平台下实现方式不同,个别平台也可能对其进行二次封装,也可能不是用原生指针实现)作为迭代器,原因如下:
- list 的元素在内存中不是连续存储的,而是通过指针链接起来的。这意味着你不能简单地使用原生指针作为迭代器,因为原生指针只能访问连续内存块中的元素
- list 的迭代器需要提供额外的功能,如在链表中前进或后退,这些操作涉及到修改指针以跳转到下一个或上一个元素。原生指针不支持这些操作,因此需要自定义迭代器来实现
- 自定义迭代器可以针对链表的特性进行优化。例如,链表迭代器在插入或删除操作时,只需要调整相邻元素的指针,而不需要移动大量元素,这使得链表在某些操作上比数组更高效
- list中,插入或删除元素通常不会使迭代器失效(除非删除的是迭代器指向的元素),这与vector不同。自定义迭代器可以确保这些操作的正确性,而原生指针无法提供这种保证
- 自定义迭代器可以提供更好的封装性,隐藏链表的内部实现细节,这样,即使链表的实现发生变化,只要迭代器的接口保持不变,使用迭代器的代码就不需要修改
begin和end
template<class T>
class list
{typedef list_node<T> Node;
public:typedef __list_iterator<T> iterator;iterator begin(){//单参数的构造函数支持隐式类型转换//return _head->_next;return iterator(_head->_next);}iterator end(){//return _head;return iterator(_head);}list(){_head = new Node;//哨兵位指向自己_head->_prev = _head;_head->_next = _head;}private:Node* _head;
};
* / ++ / – / != / ==
template<class T>
struct __list_iterator
{typedef list_node<T> Node;Node* _node;//用一个结点的指针构造一个迭代器__list_iterator(Node* node):_node(node){}T& operator*(){//*it默认解引用是结点,我们不想要结点,我们要的是数据return _node->_val;}//迭代器++返回的还是迭代器__list_iterator<T>& operator++(){//我们想++做的是让他走向下一个结点//(原生指针是加一个结点的大小,这样并不能到下一个结点的位置)_node = _node->_next;return *this;}//后置++__list_iterator<T> operator++(int){__list_iterator<T> 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 __list_iterator<T>& it){//使用结点的指针来进行比较return _node != it._node;}bool operator==(const __list_iterator<T>& it){//使用结点的指针来进行比较return _node == it._node;}
};
这样我们的迭代器就可以正常使用了
void test1()
{list<int> lt;lt.push_back(1);lt.push_back(2);lt.push_back(3);lt.push_back(4);list<int>::iterator it = lt.begin();while (it != lt.end()){cout << *it << " ";++it;}cout << endl;
}int main()
{List::test1();return 0;
}
运行效果:
const迭代器
迭代器模拟的是指针的行为,指针有2种const指针:
1:const T* ptr1; (指针指向的内容不能修改)
2:T* const ptr2;(指针本身不能修改)
const迭代器模拟的是第一种指针的行为
我们可以通过一个类型来控制返回值,给不同的模板参数不同的实例化,他们就是不同的类
template<class T,class Ref>//添加一个class Ref参数
struct __list_iterator
{Ref operator*()//返回值改为Ref{return _node->_val;}
}template<class T>
class list
{typedef list_node<T> Node;
public:typedef __list_iterator<T,T&> iterator;//普通迭代器typedef __list_iterator<T, const T&> const_iterator;//const迭代器//提供const接口const_iterator begin() const{return const_iterator(_head->_next);}const_iterator end() const{return const_iterator(_head);}
};
->
T* operator->()
{return &_node->_val;
}
struct A
{A(int a1 = 0,int a2 = 0):_a1(a1),_a2(a2){}int _a1;int _a2;
};void test1()
{list<A> lt;lt.push_back(A(1, 1));lt.push_back(A(2, 2));lt.push_back(A(3, 3));lt.push_back(A(4, 4));list<A>::iterator it = lt.begin();while (it != lt.end()){cout << it->_a1 << " " << it->_a2 << endl;++it;}cout << endl;
}
运行效果
const迭代器,也需要添加一个模板参数
//typedef __list_iterator<T,T&,T*> iterator;//普通迭代器
//typedef __list_iterator<T, const T&,const T*> const_iterator;//const迭代器
template<class T,class Ref,class Ptr>
insert
insert默认都是在pos位置之前插入(任意位置都可以),所以insert不需要做检查
erase则需要检查(因为哨兵位的结点不可删)
//pos位置之前插入
iterator insert(iterator pos, const T& x)
{//首先得换成结点的指针,因为迭代器不好访问数据Node* cur = pos._node;Node* prev = cur->_prev; //前一个位置Node* newnode = new Node(x); //新结点prev->_next = newnode;newnode->_next = cur;cur->_prev = newnode;newnode->_prev = prev;return newnode;
}
库里面的insert返回的是新插入位置的迭代器,所以我们实现时和库里保持一致
erase
iterator erase(iterator pos)
{assert(pos != end());Node* cur = pos._node;Node* prev = cur->_prev;//前一个结点Node* next = cur->_next;//后一个结点prev->_next = next;next->_prev = prev;delete cur;return next; //返回下一个位置
}
注意:这里会存在迭代器失效的问题,因为pos位置的结点已经被我们释放掉了,所以我们需要返回下一个位置的迭代器,而不是void
代码复用
当我们实现完insert和erase之后,尾插、头插、尾删、头删都可以复用
尾插
//尾插
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;//复用//尾插 - 在哨兵位的前面插入insert(end(), x);
}
头插
void push_front(const T& x)
{//头插//在第一个位置插入,也就是begin的前面insert(begin(), x);
}
尾删
void pop_back()
{//尾删erase(--end());
}
头删
void pop_front()
{//头删erase(begin());
}
size
size有两种实现方法
方法一:
size_t size()
{size_t sz = 0;iterator it = begin();while (it != end()){++sz;++it;}return sz;
}
方法二:
增加一个_size成员初始化为0,每次插入++_size,每次删除–_size
size_t size()
{return _size;
}private:Node* _head;size_t _size;//增加成员
clear和析构
void clear()
{//clear - 清除所有数据,不清哨兵位iterator it = begin();while (it != end()){//这里erase之后就不能进行++了,因为他失效了//所以得接收返回值(返回值是下一个结点)it = erase(it);}
}
析构可以直接复用clear
~list()
{//析构clear();delete _head;_head = nullptr;
}
测试一下:
void test1()
{list<int> lt;lt.push_back(1);lt.push_back(2);lt.insert(lt.end(), 3);lt.push_back(4);lt.push_back(5);lt.erase(lt.begin());for (auto e : lt){cout << e << " ";}cout << endl;lt.push_front(1);for (auto e : lt){cout << e << " ";}cout << endl;lt.pop_front();lt.pop_back();for (auto e : lt){cout << e << " ";}cout << endl;lt.clear();lt.push_back(10);lt.push_back(20);for (auto e : lt){cout << e << " ";}cout << endl;cout << "size=" << lt.size() << endl;
}
运行效果
拷贝构造和empty_init
拷贝构造
// lt2(lt1)
list(const list<T>& lt)
{//初始化_head = new Node;//哨兵位指向自己_head->_prev = _head;_head->_next = _head;//T对象不确定,所以最好加上引用//遍历lt1,把lt1的数据插入到lt2for (auto& e : lt){push_back(e);}
}
empty_init
void empty_init()
{_head = new Node;//哨兵位指向自己_head->_prev = _head;_head->_next = _head;
}
复用
list()
{empty_init();
}// lt2(lt1)
list(const list<T>& lt)
{empty_init();//T对象不确定,所以最好加上引用//遍历lt1,把lt1的数据插入到lt2for (auto& e : lt){push_back(e);}
}
赋值和swap
void swap(list<T>& lt)
{std::swap(_head, lt.head);
}
赋值
list<T>& operator=(list<T> lt)
{//现代写法swap(lt);return *this;
}
测试一下
void test2()
{list<int> lt1;lt1.push_back(1);lt1.push_back(2);lt1.push_back(3);lt1.push_back(4);list<int> lt2(lt1);for (auto e : lt2){cout << e << " ";}cout << endl;list<int> lt3;lt3.push_back(10);lt3.push_back(20);lt3.push_back(30);lt3.push_back(40);lt1 = lt3;for (auto e : lt1){cout << e << " ";}cout << endl;
}
运行效果
完整代码
#pragma once
#include<iostream>
#include<assert.h>using namespace std;namespace List
{template<class T>struct list_node{list_node<T>* _next;list_node<T>* _prev;T _val;//构造函数list_node(const T& val = T()) //缺省值不能给0,因为T不一定是内置类型:_next(nullptr),_prev(nullptr),_val(val){}};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* node):_node(node){}Ref operator*(){//*it默认解引用是结点,我们不想要结点,我们要的是数据return _node->_val;}Ptr operator->(){return &_node->_val;}//迭代器++返回的还是迭代器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) const{//使用结点的指针来进行比较return _node != it._node;}bool operator==(const self& it) const{//使用结点的指针来进行比较return _node == it._node;}};template<class T>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;iterator begin(){//单参数的构造函数支持隐式类型转换//return _head->_next;return iterator(_head->_next);}iterator end(){//return _head;return iterator(_head);}const_iterator begin() const{//单参数的构造函数支持隐式类型转换//return _head->_next;return const_iterator(_head->_next);}const_iterator end() const{//return _head;return const_iterator(_head);}void empty_init(){_head = new Node;//哨兵位指向自己_head->_prev = _head;_head->_next = _head;}list(){empty_init();}// lt2(lt1)list(const list<T>& lt){empty_init();//T对象不确定,所以最好加上引用//遍历lt1,把lt1的数据插入到lt2for (auto& e : lt){push_back(e);}}void swap(list<T>& lt){std::swap(_head, lt._head);}list<T>& operator=(list<T> lt){//现代写法swap(lt);return *this;}~list(){//析构clear();delete _head;_head = nullptr;}void clear(){//clear - 清除所有数据,不清哨兵位iterator it = begin();while (it != end()){//这里erase之后就不能进行++了,因为他失效了//所以得接收返回值(返回值是下一个结点)it = erase(it);}}//尾插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;//复用//尾插 - 在哨兵位的前面插入insert(end(), x);}void push_front(const T& x){//头插//在第一个位置插入,也就是begin的前面insert(begin(), x);}void pop_back(){//尾删erase(--end());}void pop_front(){//头删erase(begin());}//pos位置之前插入iterator insert(iterator pos, const T& x){//首先得换成结点的指针,因为迭代器不好访问数据Node* cur = pos._node;Node* prev = cur->_prev; //前一个位置Node* newnode = new Node(x); //新结点prev->_next = newnode;newnode->_next = cur;cur->_prev = newnode;newnode->_prev = prev;return newnode;}iterator erase(iterator pos){assert(pos != end());Node* cur = pos._node;Node* prev = cur->_prev;//前一个结点Node* next = cur->_next;//后一个结点prev->_next = next;next->_prev = prev;delete cur;return next;}size_t size(){size_t sz = 0;iterator it = begin();while (it != end()){++sz;++it;}return sz;}private:Node* _head;};void Print(const list<int>& lt){list<int>::const_iterator it = lt.begin();while (it != lt.end()){//(*it) += 1;cout << *it << " ";++it;}cout << endl;}struct A{A(int a1 = 0,int a2 = 0):_a1(a1),_a2(a2){}int _a1;int _a2;};void test1(){list<int> lt;lt.push_back(1);lt.push_back(2);lt.insert(lt.end(), 3);lt.push_back(4);lt.push_back(5);lt.erase(lt.begin());for (auto e : lt){cout << e << " ";}cout << endl;lt.push_front(1);for (auto e : lt){cout << e << " ";}cout << endl;lt.pop_front();lt.pop_back();for (auto e : lt){cout << e << " ";}cout << endl;lt.clear();lt.push_back(10);lt.push_back(20);for (auto e : lt){cout << e << " ";}cout << endl;cout << "size=" << lt.size() << endl;}void test2(){list<int> lt1;lt1.push_back(1);lt1.push_back(2);lt1.push_back(3);lt1.push_back(4);list<int> lt2(lt1);for (auto e : lt2){cout << e << " ";}cout << endl;list<int> lt3;lt3.push_back(10);lt3.push_back(20);lt3.push_back(30);lt3.push_back(40);lt1 = lt3;for (auto e : lt1){cout << e << " ";}cout << endl;}
}
以上就是本篇文章的全部内容了,希望大家看完能有所收获
❤️创作不易,点个赞吧❤️ |
相关文章:

【C++】list的使用方法和模拟实现
❤️欢迎来到我的博客❤️ 前言 list是可以在常数范围内在任意位置进行插入和删除的序列式容器,并且该容器可以前后双向迭代list的底层是双向链表结构,双向链表中每个元素存储在互不相关的独立节点中,在节点中通过指针指向其前一个元素和后…...

【物联网实战项目】STM32C8T6+esp8266/mqtt+dht11+onenet+uniapp
一、实物图 前端uniapp效果图(实现与onenet同步更新数据) 首先要确定接线图和接线顺序: 1、stm32c8t6开发板连接stlinkv2下载线 ST-LINK V2STM323.3V3.3VSWDIOSWIOSWCLKSWCLKGNDGND 2、ch340串口连接底座(注意RXD和TXD的连接方式…...

Pyhton 二叉树层级遍历
class TreeNode:def __init__(self, val0, leftNone, rightNone):self.val valself.left leftself.right rightclass Solution:def levelOrder(self, root: Optional[TreeNode]) -> List[List[int]]:res []# 空节点,直接返回if not root:return resque [roo…...

Flutter 中的 FadeTransition 小部件:全面指南
Flutter 中的 FadeTransition 小部件:全面指南 在 Flutter 中,动画是一种吸引用户注意力并提供流畅用户体验的强大工具。FadeTransition 是 Flutter 提供的一个动画小部件,它允许子组件在不透明度上进行渐变,从而实现淡入和淡出效…...

缓存存储器:性能提升的关键
目录 基本原理 主存与缓存的地址映射 主存的替换策略 缓存的写操作策略 Pentium 4 的缓存组织 使用多级缓存减少缺失损失 结论 在计算机系统中,缓存存储器(Cache Memory)发挥着至关重要的作用。它充当处理器和主存之间的高速缓冲区&am…...

『大模型笔记』工程师的LLMs简介!
💡工程师的LLMs简介 ! 文章目录 1. Embeddings Conceptually(嵌入的概念)1.1. One-hot Encodings(独热编码)1.2. Embeddings(嵌入)2. LLM Basics(LLM 基础知识)3. Autoregressive LLMs(自回归LLMs)4. Where to go from here(何去何从?)5. 参考文献https://devo…...

Vue中的常用指令
Vue 会根据不同的【指令】,针对标签实现不同的【功能】 概念:指令(Directives)是 Vue 提供的带有 v- 前缀 的 特殊 标签属性。 为啥要学:提高程序员操作 DOM 的效率。 vue 中的指令按照不同的用途可以分为如下 6 大…...

百度页面奔跑的白熊html、css
一、相关知识-动画 1.基本使用:先定义再调用 2. 调用动画 用keyframes定义动画(类似定义类选择器) keyframes动画名称{ 0%{ width:100px; } 100%{ width:200px; } } 使用动画 div { width:200px; height:200px; background-…...

Day-02面向对象
一、匿名函数 和函数的作用一致,都是进行代码逻辑的封装, 区别1 在定义时,匿名函数可以不指定函数的名字 区别2 匿名函数执行实现简单的计算 区别3 匿名函数会自动将计算的结果返回 定义格式 lambda 参数1,参数2...:计算逻辑(参数的处理逻辑…...

Sentinel-2 哨兵二号数据介绍及下载
1 Sentinel-2简介 SENTINEL-2 is a European wide-swath, high-resolution, multi-spectral imaging mission. Sentinel-2 是高分辨率多光谱成像卫星,一颗卫星的重访周期为10天,两颗互补,重访周期为5天。分为2A和2B两颗卫星。2A于2015年6月…...

阿里智能信息数据挖掘复盘
(嘻嘻——不嘻嘻) 挫败呜呜呜,钉钉忘装,还要手机登录,迟到三分钟。 一上来就问项目,没有自我介绍。 第一篇的重要特征是什么直接忘记,正负样本比,过拟合的判断标准,特…...

Flutter中图片是怎么在flutter上呈现出来的?
在Flutter中,图片的呈现是通过使用Image组件来实现的。Image组件是一个用于加载和显示图片的Widget,可以从本地文件、网络URL或内存中加载图片,并在应用界面上进行渲染。 图片在Flutter中的呈现过程如下: 加载图片: 使…...

使用 CNN 训练自己的数据集
CNN(练习数据集) 1.导包:2.导入数据集:3. 使用image_dataset_from_directory()将数据加载tf.data.Dataset中:4. 查看数据集中的一部分图像,以及它们对应的标签:5.迭代数据集 train_ds࿰…...

自动控制: 最小二乘估计(LSE)、加权最小二乘估计(WLS)和线性最小方差估计
自动控制: 最小二乘估计(LSE)、加权最小二乘估计(WLS)和线性最小方差估计 在数据分析和机器学习中,参数估计是一个关键步骤。最小二乘估计(LSE)、加权最小二乘估计(WLS&…...

基于VMware安装Linux虚拟机
1.准备Linux环境 首先,我们要准备一个Linux的系统,成本最低的方式就是在本地安装一台虚拟机。为了统一学习环境,不管是使用MacOS还是Windows系统的同学,都建议安装一台虚拟机。 windows采用VMware,Mac则采用Fusion …...

6、phpjm混淆解密和php反序列化
题目:青少年雏形系统 1、打开链接也是一个登入面板 2、尝试了sqlmap没头绪 3、尝试御剑,发现一个www.zip 4、下载打开,有一个php文件打开有一段phpjm混淆加密 5、使用手工解混淆 具体解法链接:奇安信攻防社区-phpjm混淆解密浅谈…...

Codeforces Round 909 (Div. 3) E. Queue Sort(模拟 + 贪心之找到了一个边界点)
弗拉德找到了一个由 n 个整数组成的数组 a ,并决定按不递减的顺序排序。 为此,弗拉德可以多次执行下面的操作: 提取数组的第一个元素并将其插入末尾; 将个元素与前一个元素对调,直到它变成第一个元素或严格大于前一个…...

设计模式基础——设计原则介绍
1.概述 对于面向对象软件系统的设计而言,如何同时提高一个软件系统的可维护性、可复用性、可拓展性是面向对象设计需要解决的核心问题之一。面向对象设计原则应运而生,这些原则你会在设计模式中找到它们的影子,也是设计模式的基础。往往判…...

【校园网网络维修】当前用户使用的IP与设备重定向地址中IP不一致,请重新认证
出现的网络问题:当前用户使用的IP与设备重定向地址中IP不一致,请重新认证 可能的原因: 把之前登录的网页收藏到浏览器,然后直接通过这个链接进行登录认证。可能是收藏网址导致的ip地址请求参数不一致。 解决方法: 方法…...

如何找到docker的run(启动命令)
使用python三方库进行 需要安装python解释器 安装runlike安装包 pip3 install runlike 运行命令 runlike -p <container_name> # 后面可以是容器名和容器id,-p参数是显示自动换行实验 使用docker启动一个jenkins 启动命令为 docker run -d \ -p 9002:80…...

Spring如何管理Bean的生命周期呢?
我们都知道,在面试的过程中,关于 Spring 的面试题,那是各种各样,很多时候就会问到关于 Spring的相关问题,比如 AOP ,IOC 等等,还有就是关于 Spring 是如何管理 Bean 的生命周期的相关问题&#…...

Java网络编程:UDP通信篇
目录 UDP协议 Java中的UDP通信 DatagramSocket DatagramPacket UDP客户端-服务端代码实现 UDP协议 对于UDP协议,这里简单做一下介绍: 在TCP/IP协议簇中,用户数据报协议(UDP)是传输层的一个主要协议之一…...

HTML+CSS+JS简易计算器
HTMLCSSJS简易计算器 index.html <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>简易计算器</t…...

STM32使用ST-LINK下载程序中需要注意的几点
使用keil5的ST-link下载界面 前提是ST-LINK已经连接好,(下图中是没有连接ST-link设备),只是为了展示如何查看STlink设备是否连接的方式 下载前一定设置下载完成后自启动 这个虽然不是必须,但对立即看到新程序的现象…...

我和jetson-Nano的故事(12)——安装pytorch 以及 torchvision
在jetson nano中安装Anaconda、pytorch 以及 torchvision 1.Pytorch下载安装2.Torchvision安装 1.Pytorch下载安装 首先登录英伟达官网下载Pytorch安装包,这里以PyTorch v1.10.0为例 安装依赖库 sudo apt-get install libjpeg-dev zlib1g-dev libpython3-dev liba…...

「异步魔法:Python数据库交互的革命」(一)
Hi,我是阿佑,今天将和大家一块打开异步魔法的大门,进入Python异步编程的神秘领域,学习如何同时施展多个咒语而不需等待。了解asyncio的魔力,掌握Async SQLAlchemy和Tortoise-ORM的秘密,让你的数据库操作快如…...

探秘GPT-4o:从版本对比到技术能力的全面评价
随着人工智能技术的不断发展,自然语言处理领域的突破性技术——GPT(Generative Pre-trained Transformer)系列模型也在不断演进。最新一代的GPT-4o横空出世,引起了广泛的关注和讨论。在本文中,我们将对GPT-4o进行全面评…...

四川汇烁面试总结
自我介绍项目介绍、 目录 1.jdk和jre的区别? 2.一段代码的执行流程? 3.接口与抽象类的区别? 4.ArrayList与LinkList的区别? 5.对HashMap的理解? 6.常见的异常? 7.throw 和 throws 有什么区别? 8.…...

【小程序 按钮 表单 】
按钮 代码演示 xxx.wxml <view class"boss" hover-class"box"hover-start-time"2000"hover-stay-time"5000">测试文本<view hover-stop-propagation"true">子集</view><view>子集2</view>…...

高铁Wifi是如何接入的?
使用PC端的朋友,请将页面缩小到最小比例,阅读最佳! 在飞驰的高铁上,除了窗外一闪而过的风景,你是否好奇过,高铁Wifi信号如何连接的呢? 远动的火车可不能连接光纤吧,难道是连接的卫星…...