【STL】list模拟实现
vector模拟实现
- 一、接口大框架函数声明速览
- 二、结点类的模拟实现
- 1、构造函数
- 三、迭代器类的模拟实现
- 1、迭代器类存在的意义
- 2、迭代器类的模板参数说明
- 3、构造函数
- 4、++运算符的重载(前置和后置)
- (1)前置++
- (2)后置++
- 5、–运算符的重载(前置和后置)
- (1)前置--
- (2)后置--
- 5、==运算符的重载
- 6、!=运算符的重载
- 7、*运算符的重载
- 8、->运算符的重载
- 四、list的模拟实现
- 1、默认成员函数
- (1)构造函数
- (2)拷贝构造函数
- (3)赋值运算符重载函数
- i、写法一:老式写法
- ii、写法二:现代写法
- (4)析构函数
- 2、迭代器相关的函数
- (1)begin和end
- 3、访问容器相关函数
- (1)front和back
- 4、插入、删除函数
- (1)插入insert函数
- (2)删除erase函数
- 5、push_back和pop_back
- 6、push_front和pop_front
- 7、其他函数
- (1)size
- (2)resize
- (3)clear
- (4)empty
- (5)swap
- (6)Print函数
- 五、代码汇总
一、接口大框架函数声明速览
#pragma oncenamespace JRH
{// list当中的结点template<class T>struct _list_node{// 成员变量T _val; // 数据域_list_node<T>* _prev; // 前驱指针_list_node<T>* _next; // 后继指针// 成员函数_list_node(const T& val = T()); // 构造函数};// list迭代器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* pnode); // 构造函数// 运算符重载的各类函数self operator++(); // 前置self operator--();self operator++(int); // 后置self operator--(int);bool operator==(const self& s) const;bool operator!=(const self& s) const;Ref operator*();Ptr operator->();};// listtemplate<class T>class list{public:// 重命名typedef _list_node<T> node; // 结点typedef _list_iterator<T, T&, T*> iterator; // 迭代器typedef _list_iterator<T, const T&, const T*> const_iterator; // const迭代器public:// 成员函数// 1、默认成员函数list(); // 构造list(const list<T>& ltnode); // 拷贝构造list<T>& operator=(const list<T>& ltnode); // 拷贝赋值~list(); // 析构// 2、迭代器相对应函数iterator begin(); // 开始iterator end(); // 结尾const_iterator begin() const; // const开始const_iterator end() const; // const结尾// 3、访问容器T& front(); // 访问头T& back(); // 访问尾const T& front(); // const访问头const T& back(); // const访问尾// 4、插入、删除函数void insert(iterator pos, const T& x); // 插入iterator erase(iterator pos); // 删除void push_back(const T& x); // 尾插void pop_back(); // 尾删void push_front(const T& x); // 头插void pop_front(); // 头删// 5、其他函数size_t size() const; // 容量大小void resize(size_t n, const T& val = T()); // 扩容void clear(); // 清空bool empty() const; // 判空 void swap(list<T>& lt); // 交换private:// 成员变量node* _phead; // 指向头结点的指针};
}
二、结点类的模拟实现
list是一个带头双向循环链表,我们如下图所示:
因此,我们若要实现list,则首先需要实现一个结点类。而一个结点需要存储的信息有:数据、前一个结点的地址、后一个结点的地址,于是该结点类的成员变量也就出来了(数据、前驱指针、后继指针)。
构造节点只需要构造一个节点即可(利用构造函数),释放结点只需要析构结点即可(利用析构函数)。
1、构造函数
结点类的构造函数直接根据所给数据构造一个结点即可,构造出来的结点的数据域存储的就是所给数据,而前驱指针和后继指针均初始化为空指针即可。而若构造结点时未传入数据,则默认以list容器所存储类型的默认构造函数所构造出来的值为传入数据。
_list_node(const T& val = T()) // 构造函数:_val(val),_prev(nullptr),_next(nullptr){}
三、迭代器类的模拟实现
1、迭代器类存在的意义
string和vector对象都将其数据存储在了一块连续的内存空间,我们通过指针进行自增、自减以及解引用等操作,就可以对相应位置的数据进行一系列操作,因此string和vector当中的迭代器就是原生指针。
但是对于list来说,其各个结点在内存当中的位置是随机的,并不是连续的,我们不能仅通过结点指针的自增、自减以及解引用等操作对相应结点的数据进行操作。
而迭代器的意义就是,让使用者可以不必关心容器的底层实现,可以用简单统一的方式对容器内的数据进行访问。
既然list的结点指针的行为不满足迭代器定义,那么我们可以对这个结点指针进行封装,对结点指针的各种运算符操作进行重载,使得我们可以用和string和vector当中的迭代器一样的方式使用list当中的迭代器。list迭代器类,实际上就是对结点指针进行了封装,对其各种运算符进行了重载,使得结点指针的各种行为看起来和普通指针一样。
2、迭代器类的模板参数说明
这里我们所实现的迭代器类的模板参数列表当中为什么有三个模板参数?
template<class T, class Ref, class Ptr>
在list的模拟实现当中,我们typedef了两个迭代器类型,普通迭代器和const迭代器。
typedef _list_iterator<T, T&, T*> iterator; // 迭代器typedef _list_iterator<T, const T& const T*> const_iterator; // const迭代器
迭代器类的模板参数列表当中的Ref和Ptr分别代表的是引用类型和指针类型。即当我们使用普通迭代器时,编译器就会实例化出一个普通迭代器对象;当我们使用const迭代器时,编译器就会实例化出一个const迭代器对象。说白了就是为了区分普通迭代器和const迭代器的。
3、构造函数
我们构造函数其实只有一个结点指针,其构造函数直接根据所给结点指针构造一个迭代器对象即可。
_list_iterator(node* pnode) // 构造函数:_pnode(pnode){}
4、++运算符的重载(前置和后置)
self的定义如下:
typedef _list_iterator<T, Ref, Ptr> self;
(1)前置++
先让结点指针指向后一个结点,然后再返回“自增”后的结点指针即可。
self operator++() // 前置{_pnode = _pnode->_next; // 先让结点指针指向后一个节点return *this; // 返回自增后的结点指针}
(2)后置++
先记录当前结点指针的指向,然后让结点指针指向后一个结点,最后返回“自增”前的结点指针即可。
self operator++(int) // 后置{self tmp(*this); // 记录当前指针的指向_pnode = _pnode->_next; // 让结点指针指向后一个节点return tmp; // 返回自增前的结点指针}
5、–运算符的重载(前置和后置)
(1)前置–
先让结点指针指向前一个结点,然后再返回“自减”后的结点指针即可。
self operator--(){_pnode = _pnode->_prev; // 先让结点指针指向前一个节点return *this; // 返回自减后的结点指针}
(2)后置–
先记录当前结点指针的指向,然后让结点指针指向前一个结点,最后返回“自减”前的结点指针即可。
self operator--(int){self tmp(*this); // 记录当前指针指向_pnode = _pnode->_prev; // 让结点指针指向前一个节点return tmp; // 返回自减前的结点指针}
5、==运算符的重载
当使用==运算符比较两个迭代器时,我们实际上想知道的是这两个迭代器是否是同一个位置的迭代器,也就是说,我们判断这两个迭代器当中的结点指针的指向是否相同即可。
bool operator==(const self& s) const{return _pnode == s._pnode; // 判断两个结点指针指向是否相同}
6、!=运算符的重载
!=运算符刚好和==运算符的作用相反,我们判断这两个迭代器当中的结点指针的指向是否不同即可。
bool operator!=(const self& s) const{return _pnode != s._pnode; // 判断两个结点指针指向是否不同}
7、*运算符的重载
当我们使用解引用操作符时,是想得到该位置的数据内容。因此,我们直接返回当前结点指针所指结点的数据即可,但是这里需要使用引用返回,因为解引用后可能需要对数据进行修改。
Ref operator*(){return _pnode->_val; // 返回结点指针所指结点的数据}
8、->运算符的重载
对于->运算符的重载,我们直接返回结点当中所存储数据的地址即可。
为什么会有这个->运算符重载呢?原因是因为我们之前写过一个日期类,我们用日期类的时候是里面有很多自定义的变量,所以就需要我们进行箭头的指向,我们如下代码:当我们拿到一个位置的迭代器时,我们可能会使用->运算符访问Date的成员:
// main.cclist<Date> lt;Date d1(2021, 8, 10);Date d2(1980, 4, 3);Date d3(1931, 6, 29);lt.push_back(d1);lt.push_back(d2);lt.push_back(d3);list<Date>::iterator pos = lt.begin();cout << pos->_year << endl; //输出第一个日期的年份
所以我们只需要返回地址即可,我们写如下代码:
Ptr operator->(){return &_pnode->_val; // 返回结点指针所指结点的数据的地址}
但肯定是有缺陷的,按照这种重载方式的话,这里使用迭代器访问日期类当中的成员变量时不是应该用两个->吗?
这里本来是应该有两个->的,第一个箭头是pos ->去调用重载的operator->返回Date* 的指针,第二个箭头是Date* 的指针去访问对象当中的成员变量_year。但是一个地方出现两个箭头,程序的可读性太差了,所以编译器做了特殊识别处理,为了增加程序的可读性,省略了一个箭头。
四、list的模拟实现
1、默认成员函数
(1)构造函数
list是一个带头双向循环链表,在构造一个list对象时,直接申请一个头结点,并让其前驱指针和后继指针都指向自己即可。
list() // 构造{_phead = new node; // 申请一个头结点_phead->_next = _phead; // 后继指向自己_phead->_prev = _phead; // 前驱指向自己}
(2)拷贝构造函数
拷贝构造函数就是根据所给list容器,拷贝构造出一个对象。对于拷贝构造函数,我们先申请一个头结点,并让其前驱指针和后继指针都指向自己,然后将所给容器当中的数据,通过遍历的方式一个个尾插到新构造的容器后面即可。
list(const list<T>& ltnode) // 拷贝构造lt2(lt1){_phead = new node; // 创建一个新的头结点_phead->_next = _phead; // head后继结点指向本身_phead->_prev = _phead; // head前驱结点指向本身for (const auto& e : ltnode){push_back(e); // 将容器ltnode中的值一个个push_back到_head结点后}}
(3)赋值运算符重载函数
两种写法:
i、写法一:老式写法
先调用clear函数将原容器清空,然后将容器lt当中的数据,通过遍历的方式一个个尾插到清空后的容器当中即可。
// 1、老式写法list<T>& operator=(const list<T>& ltnode) // 拷贝赋值{if (this != <node) // 防止自己给自己拷贝{clear(); // 先清空容器for (auto& e : ltnode){push_back(e); // 将ltnode中数据全部尾插到清空的容器中}return *this; // 支持连续赋值}}
ii、写法二:现代写法
首先利用编译器机制,故意不使用引用接收参数,通过编译器自动调用list的拷贝构造函数构造出来一个list对象,然后调用swap函数将原容器与该list对象进行交换即可。
// 2、现代写法list<T>& operator=(const list<T>& ltnode){swap(ltnode); // 交换这两个对象return *this; // 支持连续赋值}
(4)析构函数
首先调用clear函数清理容器当中的数据,然后将头结点释放,最后将头指针置空即可。
~list() // 析构{clear(); // 清空容器delete _phead; // 删除申请的节点_phead = nullptr; // 置空}
2、迭代器相关的函数
begin函数返回的是第一个有效数据的迭代器,end函数返回的是最后一个有效数据的下一个位置的迭代器。
(1)begin和end
对于list这个带头双向循环链表来说,其第一个有效数据的迭代器就是使用头结点后一个结点的地址构造出来的迭代器,而其最后一个有效数据的下一个位置的迭代器就是使用头结点的地址构造出来的迭代器。(最后一个结点的下一个结点就是头结点)
iterator begin() // 开始{return iterator(_phead->_next); // 头结点的下一个结点}iterator end() // 结尾{return iterator(_phead); // 头结点}const_iterator begin() const // const开始{return const_iterator(_phead->_next); // 头结点的下一个结点}const_iterator end() const // const结尾{return const_iterator(_phead); // 头结点}
3、访问容器相关函数
(1)front和back
front和back函数分别用于获取第一个有效数据和最后一个有效数据,因此,实现front和back函数时,直接返回第一个有效数据和最后一个有效数据的引用即可。重载一对用于const对象的front函数和back函数,因为const对象调用front和back函数后所得到的数据不能被修改。
T& front() // 访问头{return *begin(); // 返回第一个有效数据的引用}T& back() // 访问尾{return *(--end()); // 返回头结点的引用}const T& front() const // const访问头{return *begin(); // 返回第一个有效数据的const引用}const T& back() const // const访问尾{return *(--end()); // 返回头结点的const引用}
4、插入、删除函数
(1)插入insert函数
insert函数可以在所给迭代器之前插入一个新结点。
步骤为:先有cur的结点,其_prev是prev结点,再创建一个新结点,新结点的_next是cur,cur的_prev是新结点。prev的_next是新结点,新结点的-prev是prev结点。
void insert(iterator pos, const T& x) // 插入{assert(pos._pnode); // 确保合法性node* cur = pos._pnode; // cur结点为当前所处的结点位置node* prev = cur->_prev; // prev结点为当前结点的前一个结点node* newnode = new node(x);// 链接newnode->_next = cur;cur->_prev = newnode;prev->_next = newnode;newnode->_prev = prev;}
(2)删除erase函数
erase函数可以删除所给迭代器位置的结点。
iterator erase(iterator pos) // 删除{assert(pos._pnode); // 检测合法性assert(pos != end()); // 不能删除头结点node* cur = pos._pnode; // cur结点为当前结点node* prev = cur->_prev; // prev结点为前一个结点node* next = cur->_next; // next结点为后一个结点delete cur; // 删除当前节点// 建立关系prev->_next = next;next->_prev = prev;return iterator(next); // 返回所给迭代器pos的下一个迭代器}
5、push_back和pop_back
push_back和pop_back函数分别用于list的尾插和尾删,在已经实现了insert和erase函数的情况下,我们可以通过复用函数来实现push_back和pop_back函数。push_back函数就是在头结点前插入结点,而pop_back就是删除头结点的前一个结点。
void push_back(const T& x) // 尾插{insert(end(), x); // 头结点前插入结点}void pop_back() // 尾删{erase(--end()); // 删除头结点的前一个节点}
6、push_front和pop_front
当然,用于头插和头删的push_front和pop_front函数也可以复用insert和erase函数来实现。
push_front函数就是在第一个有效结点前插入结点,而pop_front就是删除第一个有效结点。
void push_front(const T& x) // 头插{insert(begin(), x); // 在第一个有效结点前插入结点}void pop_front() // 头删{erase(begin()); // 删除第一个有效节点}
7、其他函数
(1)size
size函数用于获取当前容器当中的有效数据个数,因为list是链表,所以只能通过遍历的方式逐个统计有效数据的个数。
size_t size() const // 容量大小{int sz = 0; // 统计的有效容量大小个数const_iterator it = begin(); // 开头元素while (it != end()) // 通过遍历累加{++sz;++it;}return sz; // 返回有效大小的个数}
(2)resize
函数规则:
- 若当前容器的size小于所给n,则尾插结点,直到size等于n为止。
- 若当前容器的size大于所给n,则只保留前n个有效数据。
实现resize函数时,不要直接调用size函数获取当前容器的有效数据个数,因为当你调用size函数后就已经遍历了一次容器了,而如果结果是size大于n,那么还需要遍历容器,找到第n个有效结点并释放之后的结点。
这里实现resize的方法是,设置一个变量len,用于记录当前所遍历的数据个数,然后开始变量容器,在遍历过程中:
- 当len大于或是等于n时遍历结束,此时说明该结点后的结点都应该被释放,将之后的结点释放即可。
- 当容器遍历完毕时遍历结束,此时说明容器当中的有效数据个数小于n,则需要尾插结点,直到容器当中的有效数据个数为n时停止尾插即可。
void resize(size_t n, const T& val = T()) // 扩容{iterator it = begin(); // 获取第一个有效数据的迭代器size_t len = 0; // 记录当前所遍历的数据个数while (len < n && it != end()){len++;it++;}if (len == n) // 说明容器当中的有效数据个数大于或是等于n{while (it != end()) // 只保留前n个数据{it = erase(it); // 每次删除后接收下一个数据的迭代器}}else // 说明容器当中的有效数据个数小于n{while (len < n) // 尾插数据为val的结点,直到容器当中的有效数据个数为n{push_back(val);len++;}}}
(3)clear
clear函数用于清空容器,我们通过遍历的方式,逐个删除结点,只保留头结点即可。
void clear() // 清空{iterator it = begin();while (it != end()) // 逐个删除{it = erase(it);}}
(4)empty
empty函数用于判断容器是否为空,我们直接判断该容器的begin函数和end函数所返回的迭代器,是否是同一个位置的迭代器即可。(此时说明容器当中只有一个头结点)
bool empty() const // 判空 {return end() == begin();}
(5)swap
swap函数用于交换两个容器,list容器当中存储的实际上就只有链表的头指针,我们将这两个容器当中的头指针交换即可。
void swap(list<T>& lt) // 交换{std::swap(_phead, lt._phead);}
(6)Print函数
用来进行验证的代码。
void Print(const list<int>& lt){list<int>::const_iterator it = lt.begin();while (it != lt.end()){std::cout << *it << " ";++it;}std::cout << std::endl;}
五、代码汇总
main.cc
#include"stl_list.h"int main()
{JRH::list<int> lt;lt.push_back(1);lt.push_back(2);lt.push_back(3);JRH::Print(lt);return 0;
}
stl_list.h
#pragma once
#include<iostream>
#include<cassert>namespace JRH
{// list当中的结点template<class T>struct _list_node{// 成员变量T _val; // 数据域_list_node<T>* _prev; // 前驱指针_list_node<T>* _next; // 后继指针// 成员函数_list_node(const T& val = T()) // 构造函数:_val(val),_prev(nullptr),_next(nullptr){}};// list迭代器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* pnode) // 构造函数:_pnode(pnode){}// 运算符重载的各类函数self operator++() // 前置{_pnode = _pnode->_next; // 先让结点指针指向后一个节点return *this; // 返回自增后的结点指针}self operator--(){_pnode = _pnode->_prev; // 先让结点指针指向前一个节点return *this; // 返回自减后的结点指针}self operator++(int) // 后置{self tmp(*this); // 记录当前指针的指向_pnode = _pnode->_next; // 让结点指针指向后一个节点return tmp; // 返回自增前的结点指针}self operator--(int){self tmp(*this); // 记录当前指针指向_pnode = _pnode->_prev; // 让结点指针指向前一个节点return tmp; // 返回自减前的结点指针}bool operator==(const self& s) const{return _pnode == s._pnode; // 判断两个结点指针指向是否相同}bool operator!=(const self& s) const{return _pnode != s._pnode; // 判断两个结点指针指向是否不同}Ref operator*(){return _pnode->_val; // 返回结点指针所指结点的数据}Ptr operator->(){return &_pnode->_val; // 返回结点指针所指结点的数据的地址}};// listtemplate<class T>class list{public:// 重命名typedef _list_node<T> node; // 结点typedef _list_iterator<T, T&, T*> iterator; // 迭代器typedef _list_iterator<T, const T&, const T*> const_iterator; // const迭代器public:// 成员函数// 1、默认成员函数list() // 构造{_phead = new node; // 申请一个头结点_phead->_next = _phead; // 后继指向自己_phead->_prev = _phead; // 前驱指向自己}list(const list<T>& ltnode) // 拷贝构造lt2(lt1){_phead = new node; // 创建一个新的头结点_phead->_next = _phead; // head后继结点指向本身_phead->_prev = _phead; // head前驱结点指向本身for (const auto& e : ltnode){push_back(e); // 将容器ltnode中的值一个个push_back到_head结点后}}// 1、老式写法list<T>& operator=(const list<T>& ltnode) // 拷贝赋值{if (this != <node) // 防止自己给自己拷贝{clear(); // 先清空容器for (auto& e : ltnode){push_back(e); // 将ltnode中数据全部尾插到清空的容器中}return *this; // 支持连续赋值}} 2、现代写法//list<T>& operator=(const list<T>& ltnode)//{// swap(ltnode); // 交换这两个对象// return *this; // 支持连续赋值//}~list() // 析构{clear(); // 清空容器delete _phead; // 删除申请的节点_phead = nullptr; // 置空}// 2、迭代器相对应函数iterator begin() // 开始{return iterator(_phead->_next); // 头结点的下一个结点}iterator end() // 结尾{return iterator(_phead); // 头结点}const_iterator begin() const // const开始{return const_iterator(_phead->_next); // 头结点的下一个结点}const_iterator end() const // const结尾{return const_iterator(_phead); // 头结点}// 3、访问容器T& front() // 访问头{return *begin(); // 返回第一个有效数据的引用}T& back() // 访问尾{return *(--end()); // 返回头结点的引用}const T& front() const // const访问头{return *begin(); // 返回第一个有效数据的const引用}const T& back() const // const访问尾{return *(--end()); // 返回头结点的const引用}// 4、插入、删除函数void insert(iterator pos, const T& x) // 插入{assert(pos._pnode); // 确保合法性node* cur = pos._pnode; // cur结点为当前所处的结点位置node* prev = cur->_prev; // prev结点为当前结点的前一个结点node* newnode = new node(x);// 链接newnode->_next = cur;cur->_prev = newnode;prev->_next = newnode;newnode->_prev = prev;}iterator erase(iterator pos) // 删除{assert(pos._pnode); // 检测合法性assert(pos != end()); // 不能删除头结点node* cur = pos._pnode; // cur结点为当前结点node* prev = cur->_prev; // prev结点为前一个结点node* next = cur->_next; // next结点为后一个结点delete cur; // 删除当前节点// 建立关系prev->_next = next;next->_prev = prev;return iterator(next); // 返回所给迭代器pos的下一个迭代器}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()); // 删除第一个有效节点}// 5、其他函数size_t size() const // 容量大小{int sz = 0; // 统计的有效容量大小个数const_iterator it = begin(); // 开头元素while (it != end()) // 通过遍历累加{++sz;++it;}return sz; // 返回有效大小的个数}void resize(size_t n, const T& val = T()) // 扩容{iterator it = begin(); // 获取第一个有效数据的迭代器size_t len = 0; // 记录当前所遍历的数据个数while (len < n && it != end()){len++;it++;}if (len == n) // 说明容器当中的有效数据个数大于或是等于n{while (it != end()) // 只保留前n个数据{it = erase(it); // 每次删除后接收下一个数据的迭代器}}else // 说明容器当中的有效数据个数小于n{while (len < n) // 尾插数据为val的结点,直到容器当中的有效数据个数为n{push_back(val);len++;}}}void clear() // 清空{iterator it = begin();while (it != end()) // 逐个删除{it = erase(it);}}bool empty() const // 判空 {return end() == begin();}void swap(list<T>& lt) // 交换{std::swap(_phead, lt._phead);}private:// 成员变量node* _phead; // 指向头结点的指针};void Print(const list<int>& lt){list<int>::const_iterator it = lt.begin();while (it != lt.end()){std::cout << *it << " ";++it;}std::cout << std::endl;}
}
相关文章:
【STL】list模拟实现
vector模拟实现 一、接口大框架函数声明速览二、结点类的模拟实现1、构造函数 三、迭代器类的模拟实现1、迭代器类存在的意义2、迭代器类的模板参数说明3、构造函数4、运算符的重载(前置和后置)(1)前置(2)后…...
常用的文件系统、存储类型小整理
最近接触到了五花八门的文件系统、存储类型,名词听得头大,趁假期整理学习一番~ 名称OSSFastDFSJuiceFSCIFSCephFSEFSNFS全称Object Storage Service (对象存储服务)Fast Distributed File System (快速分布式文件系统)Juice File System (Juice 文件系统…...
Java写标准输出进度条
学Java这么久了,突发奇想写一个 进度条 玩玩,下面展示一下成功吧! Java代码实现如下 public class ProcessBar {public static void main(String[] args) {//进度条StringBuilder processBarnew StringBuilder();//进度条长度int total100;/…...
leetcode 算法 69.x的平方根(python版)
需求 给你一个非负整数 x ,计算并返回 x 的 算术平方根 。 由于返回类型是整数,结果只保留 整数部分 ,小数部分将被 舍去 。 注意:不允许使用任何内置指数函数和算符,例如 pow(x, 0.5) 或者 x ** 0.5 。 示例 1&#…...
【golang】24、go get 和 go mod:indrect 与 go mod tidy
文章目录 go get 会执行如下操作: 操作 go.mod 文件(add、update、remove)下载依赖到 $GOPATH/pkg/mod 中若已安装,则更新该包,到最新版本 试验前置准备:首先删除已下载的依赖,rm -rf $GOPATH…...
AI算法工程师-非leetcode题目总结
AI算法工程师-非leetcode题目总结 除了Leetcode你还需要这些实现nms旋转矩形IOU手动实现BN手动实现CONV实现CrossEntropyLoss 除了Leetcode你还需要这些 希望大家留言,我可以进行补充。持续更新~~~ 实现nms import numpy as np def nms(dets, threshold):x1 dets…...
2.6:冒泡、简选、直插、快排,递归,宏
1.冒泡排序、简单选择排序、直接插入排序、快速排序(升序) 程序代码: 1 #include<stdio.h>2 #include<string.h>3 #include<stdlib.h>4 void Bubble(int arr[],int len);5 void simple_sort(int arr[],int len);6 void insert_sort(int arr[],in…...
FastDFS安装并整合Openresty
FastDFS安装并整合Openresty 一、安装环境准备【CentOS7.9】二、FastDFS--tracker安装2.1.下载fastdfs2.2.FastDFS安装环境2.3.安装FastDFS依赖libevent库2.4.安装libfastcommon2.5.安装 libserverframe 网络框架2.6.tracker编译安装2.7.安装之后文件目录介绍2.8.错误处理2.9.配…...
93 log4j-slf4j-impl 搭配上 log4j-to-slf4j 导致的 StackOverflow
前言 呵呵 最近想要 做一个 mongo 低版本的客户端读取高版本的服务端传递过来的数据造成的一个错误的时候, 出现了这样的问题 引入了 mongo-java-driver 之后, 使用相关 api 的时候会触发 com.mongo.internal.connection.BaseCluser 的初始化, 其依赖的 Loggers 间接的依赖…...
客户端会话技术-Cookie
一、会话技术 1.1 概述 会话:一次会话中包含多次**请求和响应** 一次会话:浏览器第一次给服务器资源发送请求,此时会话建立,直到有一方断开为止 会话的功能:在一次会话的范围内的多次请求间,共享数据 …...
rsa加密登录解决方案
1.问题 账密登录方式中用户输入密码后,把账号、密码通过http传输到后端进行校验,然而密码属于敏感信息,不能以明文传输,否则容易被拦截窃取,因此需要考虑如何安全传输密码 2.解决方案 使用rsa加密方式,r…...
速盾:海外服务器用了cdn还是卡怎么办
海外服务器使用CDN卡顿问题的解决办法 在如今互联网高速发展的时代,海外服务器成为了许多企业和个人用户的首选,因为它能够提供更高的带宽和更稳定的网络连接。然而,尽管海外服务器在网络性能方面表现出色,但在使用过程中仍然可能…...
[python-opencv] PNG 裁切物体
拿到一组图PNG的图,边缘有点太宽了,需要裁切一下,为了这个需求,简单复习一下基本语法。 1. 读取PNG的4个通道 image cv.imread(image_path, cv.IMREAD_UNCHANGED) 附参数说明: IMREAD_UNCHANGED -1 返…...
机器学习——有监督学习和无监督学习
有监督学习 简单来说,就是人教会计算机学会做一件事。 给算法一个数据集,其中数据集中包含了正确答案,根据这个数据集,可以对额外的数据希望得到一个正确判断(详见下面的例子) 回归问题 例如现在有一个…...
MySQL单主模式部署组复制集群
前言 本篇文章介绍MySQL8.0.27版本的组复制详细搭建过程,教你如何快速搭建一个三节点的单主模式组复制集群。 实际上,MySQL组复制是MySQL的一个插件 group_replication.so,组中的每个成员都需要配置并安装该插件,配置和安装过程…...
【大厂AI课学习笔记】【1.5 AI技术领域】(10)对话系统
对话系统,Dialogue System,也称为会话代理。是一种模拟人类与人交谈的计算机系统,旨在可以与人类形成连贯通顺的对话,通信方式主要有语音/文本/图片,当然也可以手势/触觉等其他方式 一般我们将对话系统,分…...
【ARM 嵌入式 编译系列 2.7 -- GCC 编译优化参数详细介绍】
请阅读【嵌入式开发学习必备专栏 】 文章目录 GCC 编译优化概述常用优化等级-O1 打开的优化选项-O2 打开的优化选项-O3 打开的优化选项-Os 打开的优化选项优化技术使用优化选项的注意事项GCC 编译优化概述 GCC(GNU Compiler Collection)包含了用于C、C++、Objective-C、Fort…...
《剑指 Offer》专项突破版 - 面试题 38、39 和 40 : 通过三道面试题详解单调栈(C++ 实现)
目录 面试题 38 : 每日温度 面试题 39 : 直方图最大矩形面积 方法一、暴力求解 方法二、递归求解 方法三、单调栈法 面试题 40 : 矩阵中的最大矩形 面试题 38 : 每日温度 题目: 输入一个数组,它的每个数字是某天的温度。请计算每天需要等几天才会…...
动态规划C语言
#include <stdio.h> #include <stdlib.h> //0-1背包问题是一种经典的组合优化问题, //问题描述为:有一个给定容量的背包和一组具有不同价值和重量的物品,如何选择物品放入背包中,以使得背包中物品的总价值最大化&…...
基于微信小程序的校园二手交易平台
博主介绍:✌程序员徐师兄、7年大厂程序员经历。全网粉丝12w、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ 🍅文末获取源码联系🍅 👇🏻 精彩专栏推荐订阅👇…...
K8S系列文章之 [使用 Alpine 搭建 k3s]
官方文档:K3s - 轻量级 Kubernetes | K3s 官方描述,可运行在 systemd 或者 openrc 环境上,那就往精简方向走,使用 alpine 做系统。与 RHEL、Debian 的区别,主要在防火墙侧;其他基础配置需求类似࿰…...
计算机视觉 | OpenCV 实现手势虚拟控制亮度和音量
Hi,大家好,我是半亩花海。在当今科技飞速发展的时代,我们身边充斥着各种智能设备,然而,如何更便捷地与这些设备进行交互却是一个不断被探索的课题。本文将主要介绍一个基于 OpenCV 的手势识别项目,通过手势…...
python28-Python的运算符之三目运算符
Python可通过if语句来实现三目运算符的功能,因此可以近似地把这种if语句当成三目运算符。作为三目运算符的f语句的语法格式如下 True_statements if expression else False_statements 三目运算符的规则是:先对逻辑表达式expression求值,如果逻辑表达式…...
高德 API 10009
问题 笔者使用高德地图所提供的API接口,访问接口报错 {"info":"USERKEY_PLAT_NOMATCH","infocode":"10009","status":"0","sec_code_debug":"d41d8cd98f00b204e9800998ecf8427e"…...
Go 语言中如何大小端字节序?int 转 byte 是如何进行的?
嗨,大家好!我是波罗学。 本文是系列文章 Go 技巧第十五篇,系列文章查看:Go 语言技巧。 我们先看这样一个问题:“Go 语言中,将 byte 转换为 int 时是否涉及字节序(endianness)&#x…...
论文阅读——MP-Former
MP-Former: Mask-Piloted Transformer for Image Segmentation https://arxiv.org/abs/2303.07336 mask2former问题是:相邻层得到的掩码不连续,差别很大 denoising training非常有效地稳定训练时期之间的二分匹配。去噪训练的关键思想是将带噪声的GT坐标…...
JPEG图像的压缩标准(1)
分3个博客详细介绍JPEG图像的压缩标准,包含压缩和解压缩流程,熵编码过程和文件存储格式。 一、JPEG压缩标准概述 JPEG压缩标准由国际标准化组织 (International Organization for Standardization, ISO) 制订,用于静态图像压缩。JPEG标准包…...
数解 transformer 之 self attention transformer 公式整理
句子长度为n;比如2048,或1024,即,一句话最多可以是1024个单词。 1, 位置编码 可知,E是由n个列向量组成的矩阵,每个列向量表示该列号的位置编码向量。 2, 输入向量 加入本句话第一个单词的词嵌入向量是, 第…...
ubuntu22.04@laptop OpenCV Get Started
ubuntu22.04laptop OpenCV Get Started 1. 源由2. 步骤3. 预期&展望4. 参考资料 1. 源由 OpenCV在学校的时候接触过,不过当时专注在物理、研究方面,没有好好的学习下。 这次借后续视频分析刚性需求,对OpenCV做个入门的学习和研读&#…...
【Java】苍穹外卖 Day01
苍穹外卖-day01 课程内容 软件开发整体介绍苍穹外卖项目介绍开发环境搭建导入接口文档Swagger 项目整体效果展示: 管理端-外卖商家使用用户端-点餐用户使用当我们完成该项目的学习,可以培养以下能力: 1. 软件开发整体介绍 作为一名软件开…...
Ivanti Pulse Connect Secure VPN SSRF(CVE-2023-46805)漏洞
免责声明:文章来源互联网收集整理,请勿利用文章内的相关技术从事非法测试,由于传播、利用此文所提供的信息或者工具而造成的任何直接或者间接的后果及损失,均由使用者本人负责,所产生的一切不良后果与文章作者无关。该…...
GPT-4:比ChatGPT3.5好得多,但它有多好你知道么?
GPT-4简介 GPT-4是一款由OpenAI开发的人工智能语言模型,它是ChatGPT3.5的升级版。GPT-4拥有更强大的学习能力、更高的生成质量和更广泛的知识覆盖范围,被誉为人工智能技术的重要突破。 GPT-4与ChatGPT3.5的对比 1. 学习能力 GPT-4采用了更多的神经网…...
测试:JMeter如何获取非json格式的响应参数
JMeter如何获取非json格式的响应参数 在 JMeter 中获取非 JSON 格式的响应参数通常涉及使用后置处理器来提取这些参数。以下是一些常见的方法来获取不同类型的响应数据: 正则表达式提取器: 适用于提取文本、HTML、XML 等格式中的特定文本。使用正则表达…...
2024年刘谦魔术大揭秘,其中竟用到了约瑟夫环?
目录 前言 魔术过程 揭秘过程 结尾 前言 不知道昨天春晚时刘谦的魔术大家看了没有,相信大家跟我一样也很疑惑,所以爆肝一天我得出了结论。如果你觉得还不错的话,记得点赞收藏,分享给更多的朋友看。 魔术过程 整个魔术可以分…...
openssl3.2 - update debian12‘s default openssl to openssl3.2
文章目录 openssl3.2 - update debian12s default openssl to openssl3.2概述笔记回到debian12自带的openssl版本从源码编译安装最新版的openssl配置ssl访问END openssl3.2 - update debian12’s default openssl to openssl3.2 概述 在debian12虚拟机中编译了openssl3.2(ope…...
VUE2和VUE3区别对比一览
## Vue3总结 ### 官方文档 * [Vue3](https://v3.cn.vuejs.org/api/options*data.html) * [Vue2](https://vuejs.bootcss.com/api/) ### Vue3相对于Vue2的语法特性#### 1.获取数据 * vue2 javascript export default {data() {return {name: myName,}},mounted() {console.log(t…...
Linux - updatedb 命令
1. 功能 updatedb 命令用来创建或更新slocate命令所必需的数据库文件。updatedb 命令的执行过程较长,因为在执行时它会遍历整个系统的目录树,并将所有的文件信息写入 slocate 数据库文件中。 补充说明:slocate 本身具有一个数据库ÿ…...
云计算市场分析
目录 一、云计算市场概述 1.1 概述 二、国外云计算厂商 2.1 亚马逊AWS 2.2 微软AzureAzure 2.3 Apple iCloud 三、国内云计算厂商 3.1 阿里云 3.2 腾讯云 3.3 华为云 3.4 百度智能云 一、云计算市场概述 1.1 概述 云计算从出现以来,其发展就非常迅速。以…...
前端JavaScript篇之call() 和 apply() 的区别?
目录 call() 和 apply() 的区别? call() 和 apply() 的区别? 在JavaScript中,call()和apply()都是用来改变函数中this指向的方法,它们的作用是一样的,只是传参的方式不同。 call()方法和apply()方法的第一个参数都是…...
Java设计模式大全:23种常见的设计模式详解(三)
本系列文章简介: 设计模式是在软件开发过程中,经过实践和总结得到的一套解决特定问题的可复用的模板。它是一种在特定情境中经过验证的经验和技巧的集合,可以帮助开发人员设计出高效、可维护、可扩展和可复用的软件系统。设计模式提供了一种在设计和编码过程中的指导,它用于…...
汇编语言程序设计(二)十六位汇编框架、子程序与堆栈
寄存器 如下是16位通用寄存器,存储在cpu硬件中 AX 返回值 AX寄存器分为两部分 AH和AL AH 高8位 存储功能号 AL 低8位 存储返回码 以下是一个AX寄存器应用: mov ax,4c00h 4c给高位AL,00低位AL,16进制要以h结尾 BX CX 计数…...
K8S之标签的介绍和使用
标签 标签定义标签实操1、对Node节点打标签2、对Pod资源打标签查看资源标签删除资源标签 标签定义 标签就是一对 key/value ,被关联到对象上。 标签的使用让我们能够表示出对象的特点,比如使用在Pod上,能一眼看出这个Pod是干什么的。也可以用…...
网络请求库axios
一、认识Axios库 为什么选择axios? 功能特点: 在浏览器中发送 XMLHttpRequests 请求在 node.js 中发送 http请求支持 Promise API拦截请求和响应转换请求和响应数据 补充: axios名称的由来? 个人理解没有具体的翻译. axios: ajax i/o system 二、axios发送请求 1.axios请求…...
程序设计语言的组成
程序设计语言的组成 程序设计语言基本上由数据、运算、控制、传输组成 数据成分 数据是程序操作的对象,具有存储类别、类型、名称、作用域和生存期等属性 从不同角度可将数据进行不同的划分。 数据类型的分类如下: 按程序运行过程中数据的值能否改…...
论文精读的markdown模板——以及用obsidian阅读网页资料做笔记
# The Investigation of S-P Chart Analysis on the Test Evaluations of Equality Axiom Concepts for Sixth Graders Tags: #/unread 本体论: 背景起源和发展 包含要素 # # # 可关联要素 # # # 逻辑 意义: 方法论: 方法论是一…...
LCP 30. 魔塔游戏
LCP 30. 魔塔游戏 难度: 中等 题目: 小扣当前位于魔塔游戏第一层,共有 N 个房间,编号为 0 ~ N-1。每个房间的补血道具/怪物对于血量影响记于数组 nums,其中正数表示道具补血数值,即血量增加对应数值;负数表示怪物造…...
RCE(命令执行)知识点总结最详细
description: 这里是CTF做题时常见的会遇见的RCE的漏洞知识点总结。 如果你觉得写得好并且想看更多web知识的话可以去gitbook.22kaka.fun去看,上面是我写的一本关于web学习的一个gitbook,当然如果你能去我的github为我的这个项目点亮星星我会感激不尽htt…...
[英语学习][27][Word Power Made Easy]的精读与翻译优化
[序言] 译者的这次翻译非常好. 对what与从句的嵌套用法, 进行了精准的翻译. 这次的记录, 也是对我自己的一次翻译经验的提升. 但是唯一遗憾的是"derivation"没有翻译好. [英文学习的目标] 提升自身的英语水平, 对日后编程技能的提升有很大帮助. 希望大家…...
Jupyter Notebook如何在E盘打开
Jupyter Notebook如何在E盘打开 方法1:方法2: 首先打开Anaconda Powershell Prompt, 可以看到默认是C盘。 可以对应着自己的界面输入: 方法1: (base) PS C:\Users\bella> E: (base) PS E:\> jupyter notebook方法2&#x…...
显示器校准软件:BetterDisplay Pro for Mac v2.0.11激活版下载
BetterDisplay Pro是一款由waydabber开发的Mac平台上的显示器校准软件,可以帮助用户调整显示器的颜色和亮度,以获得更加真实、清晰和舒适的视觉体验。 软件下载: BetterDisplay Pro for Mac v2.0.11激活版下载 以下是BetterDisplay Pro的主要…...