STL_list

一、有关list的介绍

  1. list是可以在常数范围内在任意位置进行插入和删除的序列式容器,并且该容器可以前后双向迭代
  2. list的底层是双向链表结构,双向链表中每个元素存储在互不相关的独立节点中,在节点中通过指针指向其前一个元素和后一个元素。
  3. Iist与forward_list非常相似,最主要的不同在于forward_list是单链表,只能朝前迭代,以让其更简单高效。
  4. 与其他的序列式容器相比(array,vector,deque),Iist通常在任意位置进行插入、移除元素的执行效率更好。
  5. 与其他序列式容器相比,Iist和forward_list最大的缺陷是不支持任意位置的随机访问,比如:要访问list的第6个元素,必须从已知的位置(比如头部或者尾部)迭代到该位置,在这段位置上迭代需要线性的时间开销;list还需要一些额外的空间,以保存每个节点的相关联信息(对于存储类型较小元素的大list来说这可能是一个重要的因素)
  6. list中的sort和算法库中sort的区别

在C++中,有两种不同的 sort 函数:一种是 list 容器的成员函数 sort,另一种是算法库中的 std::sort。它们在功能和应用上有一些区别。

  1. list 中的 sort:

    • list 是 C++ 标准库中的双向链表容器,它具有自己的成员函数 sort,可以用于对链表中的元素进行排序。
    • list::sort 使用的是链表特有的插入、删除操作,因此适用于链表这种数据结构。它的时间复杂度为 O(N*logN)。
    • 由于链表的特性,其插入、删除操作的时间复杂度为 O(1),因此 list::sort 可以高效地对链表进行排序。
  2. 算法库中的 std::sort:

    • std::sort 是 C++ 标准库中的算法,可以用于对各种容器(如数组、向量等)中的元素进行排序。
    • std::sort 使用的是比较和交换操作,适用于随机访问迭代器所表示的数据结构(比如数组、向量等)。它的时间复杂度也为 O(N*logN)。
    • 对于随机访问迭代器所表示的数据结构,std::sort 是一种通用的排序算法,可以适用于各种数据结构。

总的来说,list::sort 适用于对链表进行排序,而 std::sort 适用于对各种支持随机访问的数据结构进行排序,例如数组、向量等。选择哪种排序方式取决于你使用的数据结构以及具体的排序需求。

迭代器的类型:

虽然不管是算法库中的sort还是list中的sort,在传参时都使用了模板,理论上是可以传递任意类型的迭代器,但是它们在实现函数时,可能使用了  只有特定迭代器  才能使用的操作,导致如果传递的迭代器不支持这个功能,该函数就无法使用。

例如,在库sort实现时,使用了指针相减来确定元素距离,而对于链表来说,它的指针的指向不是顺序的而是随机的,用指针相减是毫无意义的。

那我们在使用时,如果确定传递怎样的迭代器呢?

实际上,在文档中命名迭代器时会有提示。

例如在算法库中sort迭代器命名为 RandomAccessIterator - 随机访问迭代器。所以在使用前可以查看一下文档。

迭代器从功能分类上可以分为五种主要类型:

  1. 输入迭代器(Input Iterator):

    只能用于读取数据,一般用于单向遍历容器中的元素,例如 std::vectorstd::list 等。
  2. 输出迭代器(Output Iterator):

    只能用于写入数据,也是单向的,只能逐个元素地写入数据。
  3. 单向迭代器(Forward Iterator):

    可以读取和写入数据,支持在序列中向前移动,例如单向链表。
  4. 双向迭代器(Bidirectional Iterator):

    除了具备前向迭代器的功能外,还支持在序列中向后移动,例如 std::list
  5. 随机访问迭代器(Random Access Iterator):

    提供了对迭代器进行算术操作的功能,可以在 O(1) 的时间复杂度内实现随机访问和跳跃式遍历,例如数组。

需要注意的是,我们传递的迭代器只要能够满足该函数所需的功能即可,不是一定要传特定的迭代器才行。例如,双向迭代器和随机访问迭代器含有单向迭代器的功能,所以这两个迭代器是可以传递给所使用的函数的。

二、list的模拟实现

list的数据结构:

// 带头双向循环链表的一个节点
template<class T>
struct list_node
{// 成员变量list_node<T>* _prev;list_node<T>* _next;T _data;// 构造函数list_node(const T& x = T()):_data(x), _prev(nullptr), _next(nullptr){}
};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;
private:// 头节点的指针 node* _head;
};

1、迭代器

这里迭代器的底层一定是一个Node*的指针,但是不能直接将Node*定义为iterator,因为Node*是一个指针,属于内置类型,当我们使用++iterator时,将相当于将Node*指针向后移动一个Node大小的空间,而list每一个节点存放的地址并不是连续的,所以这时候的++操作是不行的。

注:对于string和vector来说,他俩的底层是使用一个可变空间的数组,每一个元素存放的地址是连续的,不需要对++操作符进行重载,所以他俩可以直接使用原生指针作为迭代器,不用进行封装。

为了达到++iterator的功能是移动到下一个节点处,我们需要重载++操作符,因为对于内置类型才能进行重载操作符,所以我们要将Node*封装成为一个自定类型,进而对++操作符进行重载。

先大概看一下对于操作符 * 和 ++ 的重载,后面会详细讲解:

reference operator*() const { return node->data; }self& operator++() 
{ node = node->_next;return *this;
}

所以迭代器的框架可以定义为:

struct __list_iterator
{// 成员变量typedef list_node<T> node;typedef __list_iterator<T> self; // 因为__list_iterator名字较长,可以在内部重命名一下node* _node;
};

这个自定义类型也需要有一个构造函数:

__list_iterator(node* n):_node(n)
{}
  • 迭代器中对操作符的重载

然后再__list_iterator自定义类型内部对各种操作符进行重载:

// 解引用操作符:返回的是node指向的值,要求可以被修改,所以返回值要使用引用
Ref& operator*()
{return _node->_data;
}// 前置++
self& operator++()
{_node = _node->_next;return *this;
}// 后置++
self operator++(int)
{self ret(*this);_node = _node->_next;return ret;
}// 前置--
self& operator--()
{_node = _node->_prev;return *this;
}// 后置--
self operator--(int)
{self ret(*this);_node = _node->_prev;return ret;
}bool operator!=(const self& s)
{ return _node != s._node;
}bool operator==(const self& s)
{return _node == s._node;
}
  • const迭代器的设计

使用场景:

void func(const list<int>& lt)
{list<int>::iterator it = lt.begin();while (it != lt.end()){(*it) *= 2;cout << *it << " ";++it;}cout << endl;
}

会出现以下错误:

原因是因为,lt是一个const的对象,则会用一个const的对象调用begin和end,而实现的begin()和end()都是普通对象才能够调用的,所以,我们还要重载一个const对象使用的begin和end函数。

iterator begin() const
{return iterator(_head->_next);
}

但是,如果只是使用const修饰begin()和end(),会出现这样的问题:我们使用const对象作为参数,主要是不想更改对象的内容的,但是现在,这段代码还是可以改变该对象的内容的。

这是因为,这个begin()返回的迭代器(_head->_next)是一个普通迭代器,并没有使用const进行修饰,所以可以修改迭代器指向的内容。

分析:

需要注意的是,虽然begin()使用const进行修饰,也能创建出一个普通的迭代器(非const):const修饰的是 list<int>* this 即 const *this,所以const修饰的是this指针指向的_head,所以_head的常性的,同时_head也是一个指针,用const修饰代表着_head的指向不能再改变了,但是_head指向的内容是可以改变的,即_head->_next是可以改变的,非const修饰的,所以由_head->_next创建出来的迭代器也是非const修饰的。

所以还要让这两个函数的返回值设置成一个const迭代器,要求不能通过这个const迭代器修改其指向的值。

错误结构:

不能直接在普通迭代器前面加上一个const成为一个const迭代器,这种写法是保证迭代器本身不能被修改,我们想要实现的效果是迭代器指向的内容不能被修改

注意:这里的typedef进行重命名时不是进行简单的替换,typedef const iterator const_iterator;并不等于 typedef const node* const_iterator,而应该是 typedef node* const const_iterator 

所以const修饰的是node的指针,并不是修饰node指针指向的内容。例如:

int main()
{int a = 2;int c = 1;typedef int* PA;const PA b = &a;*b += 2;printf("%d", a);return 0;
}

我们发现可以通过b修改 a 的值,从a = 2,变成a = 4。并且:

int main()
{int a = 2;int c = 1;typedef int* PA;const PA b = &a;b = &c;return 0;
}

b的指向不能被改变。

所以这样进行定义是不行的:const修饰的是b指针,而不是b指向的内容。

正确结构

我们可以对按照普通迭代器一样,再创建一个const类型的迭代器。

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){}// 解引用操作符:返回的是node指向的值,要求不可以被修改,所以返回值要使用常量引用const T& operator*(){return _node->_data;}// 前置++self& operator++(){_node = _node->_next;return *this;}// 后置++self operator++(int){self ret(*this);_node = _node->_next;return ret;}// 前置--self& operator--(){_node = _node->_prev;return *this;}// 后置--self operator--(int){self ret(*this);_node = _node->_prev;return ret;}bool operator!=(const self& s){return _node != s._node;}bool operator==(const self& s){return _node == s._node;}
};

 但是,我们发现,这两个迭代器只有解引用操作符的返回值类型不同,其余的函数和变量都相同,代码比较冗余。

我们可以再使用一个模板参数进行改进,当我们在创建一个迭代器时可以将迭代器的类型作为一个参数进行创建。

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){}// 解引用操作符:返回的是node指向的值,要求可以被修改,所以返回值要使用引用Ref operator*(){return _node->_data;}};typedef __list_iterator<T, T&> iterator;
typedef __list_iterator<T, const T&> const_iterator;

这样就可以使用一个模板完成两个类才能完成的工作。

const对象调用const类型的begin(),begin()函数返回一个const类型的迭代器(注意:const类型的迭代器不是const对象,其本身是可以修改的,是其指向的内容不能被修改),这样设计就能完成前面的使用场景了。

  • -> 操作符的重载

使用场景:

struct AA
{int _a1;int _a2;AA(int a1, int a2):_a1(a1), _a2(a2){}
}

<<操作符右操作数为AA类型的一个对象(lt指向list第一个对象也就是AA,所以lt就是AA类型的一个指针),由于没有对<<进行重载,所以不能进行打印。

所以解决办法要么我们对<<操作符进行重载;

要么用这种方法进行使用,但用起来不方便,在C语言中,我们可以通过->操作符访问一个自定义类型指针的成员变量,所以我们也可以对->操作符进行重载。

T* operator->()
{return &_node->data;
}

可以这样使用:

使用的时候可以这样使用,但是通过分析好像有点不妥:

我们发现  it.operator->() 得到的只是AA*,并没有得到_a1,如果想要得到_a1,还要在此基础上再加一个->才能得到_a1,即 (it.operator->())->_a1,it->->_a1.

但事实上我们可以只用一个->就可以访问到_a1,其实是编译器进行了优化,省略了一个箭头。

同时为保证重载的operator->() 也有const的版本,也会像上面,那样再添加一个模板参数,用来实例化operator->() 不同的迭代器。

template<class T, class Ref, class Ptr>
struct __list_iterator
{// 成员变量typedef list_node<T> node;typedef __list_iterator<T, Ref, Ptr> self;node* _node;Ptr operator->(){return &_node->data;}
};typedef __list_iterator<T, T&, T*> iterator;
typedef __list_iterator<T, const T&, const T*> const_iterator;

2、list相关函数实现

1)Modifiers

Ⅰ. insert

这里的insert的操作就是在一个带头双向循环链表中插入一个节点:

void insert(iterator pos, const T& x)
{node* cur = pos._node;node* prev = cur->_prev;node* new_node = new node(x);prev->_next = new_node;new_node->_prev = prev;new_node->_next = cur;cur->_prev = new_node;
}

同时也要注意迭代器失效的问题,这里需要设置一个返回值,用来更新外部迭代器的位置。

iterator insert(iterator pos, const T& x)
{node* cur = pos._node;node* prev = cur->_prev;node* new_node = new node(x);prev->_next = new_node;new_node->_prev = prev;new_node->_next = cur;cur->_prev = new_node;return new_node;
}

与之逻辑相配的函数是push_back()和push_front(),因此这两个函数的实现可以对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* next = pos._node->_next;prev->_next = next;next->_prev = prev;delete pos._node;return iterator(next);
}

注意这里的迭代器失效问题,会导致野指针的问题,所以同样设置一个返回值。

与之逻辑相配的函数是pop_back()和pop_front(),因此这两个函数的实现可以对erase进行复用:

void pop_back()
{erase(--end());
}void pop_front()
{erase(begin());
}


2)默认成员函数

Ⅰ. destructor

在模拟实现析构函数前,先介绍一下clear()函数。

  • clear()

Removes all elements from the list container (which are destroyed), and leaving the container with a size of 0.--移除所有元素,不包括头节点。

这里为了防止迭代器失效的问题,可以使用erase的返回值(返回删除当前节点的下一个)。

// 不清除头节点
void clear()
{iterator it = begin();while (it != end()){it = erase(it);}
}

也可以使用下面这种写法。

// 不清除头节点
void clear()
{iterator it = begin();while (it != end()){erase(it++);}
}

注意这里并不是通过 it 进行删除的,而是使用 it++ 返回的一个拷贝值进行删除。

  •  析构函数

可以对clear()函数进行复用,并删除头节点即可。

~list()
{clear();delete _head;_head = nullptr;
}
Ⅱ. constructor
  • 构造函数
// 构造函数
list()
{_head = new node;_head->_next = _head;_head->_prev = _head;
}

这里手动创建了一个头节点。

当然也可以使用迭代器进行构造一个对象,这里可以复用push_back()函数,但是在使用push_back之前,需要有一个头节点。因为后面也要使用到初始化list,让list有一个头节点,所以可以将这个步骤写成一个函数empty_init():

void empty_init()
{_head = new node;_head->_next = _head;_head->_prev = _head;
}
// 迭代器区间的初始化
template<class Iterator>
list(Iterator first, Iterator last)
{// 使用push_back之前需要有头节点empty_init();while (first != last){push_back(*first);++first;}
}

  • 拷贝构造函数

第一种方法:根据右边的节点,一个一个创建出节点值相同的list

void empty_init()
{_head = new node;_head->_next = _head;_head->_prev = _head;
}// 拷贝构造函数(传统写法)
list(const list<T>& lt)
{empty_init();for (auto e : lt){push_back(e);}
}

第二种:使用迭代器区间的构造函数构造一个对象,再交换两者的指针

void empty_init()
{_head = new node;_head->_next = _head;_head->_prev = _head;
}void swap(list<T>& temp)
{std::swap(_head, temp._head);
}
// 拷贝构造函数(使用swap交换头指针)
list(const list<T>& lt)
{empty_init();// 使用迭代器区间构建一个对象list<T> temp(lt.begin(), lt.end());// 交换指针swap(temp);
}

  • operator=

第一种,传统写法:

// 传统写法
list<T>& operator=(const list<T>& lt)
{if (this != &lt){clear(); // 删除被赋值list中的值for (auto& e : lt) // 插入值{push_back(e);}}return *this;
}

第二种写法:

list<T>& operator=(list<T> lt)
{swap(lt);return *this;
}

注意:不能将对象的引用作为参数。要与被拷贝对象的一个拷贝进行交换(传值调用了拷贝构造函数),如果使用引用会导致右边被赋值的对象被改变。

3)iterator

typedef __list_iterator<T, T&, T*> iterator;
typedef __list_iterator<T, const T&, const T*> const_iterator;
Ⅰ. begin()

这里的细节在前面已经讲过了。

iterator begin() 
{return iterator(_head->_next);
}const_iterator begin() const
{return const_iterator(_head->_next);
}
Ⅱ. end()
iterator end()
{return iterator(_head);
}const_iterator end() const
{return const_iterator(_head);
}

3、反向迭代器

1)反向迭代器的介绍

反向迭代器是一种迭代器,它可以在容器中从后向前遍历元素。与正向迭代器不同的是,反向迭代器的++操作符是让迭代器指向前一个元素,--操作符是让迭代器指向后一个元素。反向迭代器通常用于需要从后往前遍历容器的算法和场景中。

反向迭代器和正向迭代器在使用上有相似之处,它们都可以通过解引用运算符来访问当前迭代器指向的元素,也可以通过比较运算符来判断迭代器之间的大小关系。但是,它们之间也存在一些区别和联系。

区别:

  1. 步进方向不同:正向迭代器的步进方向是从前向后,而反向迭代器的步进方向是从后向前。

  2. 支持的操作不同:由于反向迭代器的步进方向不同,因此它们不支持正向迭代器的某些操作,例如递增和递减操作等。

  3. 消耗的空间不同:反向迭代器需要保存一个正向迭代器来实现其功能,因此它们通常会消耗更多的空间。

联系:

  1. 都是迭代器:反向迭代器和正向迭代器都是STL迭代器的一种,都具有迭代器的基本特性。

  2. 具有相同的接口:反向迭代器和正向迭代器都具有相同的解引用和比较运算符等接口。

  3. 可以互相转换:由于反向迭代器是基于正向迭代器实现的,因此它们可以通过std::reverse_iterator构造函数进行互相转换。

2)反向迭代器的模拟实现

反向迭代器的++就是正向迭代器的-,反向迭代器的一就是正向迭代器的++,因此反向迭
代器的实现可以借助正向迭代器。

如果直接在正向迭代器的基础上更改个别操作符的重载方法,虽然能够实现反向迭代器的功能,但是方法比较笨拙。

标准库中对反向迭代器是通过适配器适配正向迭代器来实现的。

所以反向迭代器的底层就是正向迭代器:

template<class Iterator> // 使用一个正向迭代器适配出一个反向迭代器
struct Reverse_Iterator
{Iterator cur; // 底层是一个正向迭代器// 构造函数Reverse_Iterator(Iterator it):cur(it){}
};
// 正向迭代器
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<iterator, const T&, const T*> const_reverse_iterator;

有一些标准库中有以下规定(并不是所有的库都是这样实现的):

因此,在链表中rbegin()和rend()的模拟实现如下:

// 反向迭代器
reverse_iterator rbegin()
{return iterator(end());
}reverse_iterator rend()
{return iterator(begin());
}const_reverse_iterator rbegin() const
{return const_reverse_iterator(end());
}const_reverse_iterator rend() const
{return const_reverse_iterator(begin());
}
  • 反向迭代器对操作符的重载

反向迭代器的++操作符是让迭代器指向前一个元素,所以反向迭代器对++操作符的重载可以复用正向迭代器的 --操作符,同时加上前面对正向迭代器关于const问题的处理,最终重载的结果为:

template<class Iterator, class Ref, class Ptr> // 使用一个正向迭代器适配出一个反向迭代器
struct Reverse_Iterator
{typedef Reverse_Iterator<Iterator, Ref, Ptr> Self;Iterator cur; // 底层是一个正向迭代器// 构造函数Reverse_Iterator(Iterator it):cur(it){}Self& operator++(){--cur; // 调用正向迭代器的--操作符return *this;}Self operator++(int){Iterator tmp = *this;--cur; // 调用正向迭代器的--操作符return tmp;}
};

--操作符是让迭代器指向后一个元素,所以反向迭代器对--操作符的重载可以复用正向迭代器的 ++操作符:

Self& operator--()
{++cur; // 调用正向迭代器的++操作符return *this;
}Self operator--(int)
{Iterator tmp = *this;++cur; // 调用正向迭代器的++操作符return tmp;
}

== 和 != 的重载

bool operator!=(const Self& it)
{return cur != it.cur; // 调用正向迭代器的!=操作符
}bool operator==(const Self& it)
{return cur == it.cur; // 调用正向迭代器的==操作符
}

* 和 -> 的重载

因为我们选择让 实现的反向迭代器和正向迭代器的begin和end具有一定的对称性 ,所以这里对 * 操作符的重载并不能直接对正向迭代器的 * 操作符进行复用。

因为 rbegin 指向 end 的位置,直接使用正向迭代器的 * 操作符会出现错误,所以反向迭代器对 * 的重载应该返回 rbegin 前一个的元素:

Ref operator*()
{Iterator tmp = cur;--tmp;return *tmp; // 调用正向迭代器的解引用操作符
}

在前面正向迭代器部分,我们知道->操作符返回的是迭代器指向元素的指针,所以:

Ptr operator->()
{return &(operator*()); 
}

首先,让我们来解读 operator*() 函数的实现:

  1. 在函数内部创建一个临时的迭代器对象 temp,并将其初始化为当前迭代器对象 _it。
  2. 对 temp 进行递减操作符 -- 的操作,将其指向前一个元素。
  3. 返回 temp 所指向的元素对象 *temp。

这段代码的作用是返回当前迭代器所指向的元素。通过创建一个临时的迭代器对象并将其指向前一个元素,然后返回该元素,实现了迭代器的解引用操作。

接下来,我们来看 operator->() 函数的实现:

  1. 在函数内部调用 operator*() 函数,获取当前迭代器所指向的元素。
  2. 使用取地址符 & 取得该元素的指针,并返回该指针。

这段代码的作用是返回一个指向当前迭代器所指向元素的指针。通过调用 operator*() 函数获取元素的引用,然后使用取地址符 & 获取该元素的指针,实现了迭代器的箭头操作符。

注意:

因为这里的迭代器使用了一个模板,所以不仅可以实现list的反向迭代器,也可以通过其他容器的正向迭代器适配出它的反向迭代器。


今天的分享就到这里了,如果,你感觉这篇博客对你有帮助的话,就点个赞吧!感谢感谢……

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/655997.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Docker容器引擎(6)

目录 一.什么是consul 解决什么问题&#xff1f; consul的模式&#xff1a; 二.consul 部署 consul服务器&#xff1a; 查看版本&#xff1a; 设置代理&#xff0c;在后台启动 consul 服务端&#xff1a; 进行后台启动&#xff1a; 查看集群信息&#xff1a; 访问页面…

云计算底层技术奥秘、磁盘技术揭秘、虚拟化管理、公有云概述

云计算基础 实验图例 虚拟化平台安装 创建虚拟机 1、创建虚拟机 2cpu&#xff0c;4G内存&#xff08;默认用户名: root 密码: a&#xff09; 2、验证 ecs 是否支持虚拟化 [rootecs ~]# grep -Po "vmx|svm" /proc/cpuinfovmx... ...[rootecs ~]# lsmod |grep kvm…

【算法专题】前缀和(附图解、代码)

&#x1f4d1;前言 本文主要是前缀和的文章&#xff0c;如果有什么需要改进的地方还请大佬指出⛺️ &#x1f3ac;作者简介&#xff1a;大家好&#xff0c;我是青衿&#x1f947; ☁️博客首页&#xff1a;CSDN主页放风讲故事 &#x1f304;每日一句&#xff1a;努力一点&…

Orion-14B-Chat-Plugin本地部署的解决方案

大家好,我是herosunly。985院校硕士毕业,现担任算法研究员一职,热衷于机器学习算法研究与应用。曾获得阿里云天池比赛第一名,CCF比赛第二名,科大讯飞比赛第三名。拥有多项发明专利。对机器学习和深度学习拥有自己独到的见解。曾经辅导过若干个非计算机专业的学生进入到算法…

WebSocket 整合 记录用法

WebSocket 介绍 WebSocket 是基于tcp的一种新的网络协议,可以让浏览器 和 服务器进行通信,然后区别于http需要三次握手,websocket只用一次握手,就可以创建持久性的连接,并进行双向数据传输 Http和WebSocket的区别 Http是短连接,WebSocket’是长连接Http通信是单向的,基于请求…

互联网加竞赛 基于深度学习的人脸表情识别

文章目录 0 前言1 技术介绍1.1 技术概括1.2 目前表情识别实现技术 2 实现效果3 深度学习表情识别实现过程3.1 网络架构3.2 数据3.3 实现流程3.4 部分实现代码 4 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 基于深度学习的人脸表情识别 该项目较…

HiveSQL题——排序函数(row_number/rank/dense_rank)

一、窗口函数的知识点 1.1 窗户函数的定义 窗口函数可以拆分为【窗口函数】。窗口函数官网指路&#xff1a; LanguageManual WindowingAndAnalytics - Apache Hive - Apache Software Foundationhttps://cwiki.apache.org/confluence/display/Hive/LanguageManual%20Windowin…

【Algorithms 4】算法(第4版)学习笔记 01 - 1.5 案例研究:union-find算法

文章目录 前言参考目录学习笔记1&#xff1a;动态连通性2&#xff1a;UF 实现 1&#xff1a;快速查找 quick-find2.1&#xff1a;demo 演示 12.2&#xff1a;demo 演示 22.3&#xff1a;quick-find 代码实现3&#xff1a;UF 实现 2&#xff1a;快速合并 quick-union3.1&#xf…

【Java 数据结构】二叉树

二叉树 1. 树型结构&#xff08;了解&#xff09;1.1 概念1.2 概念&#xff08;重要&#xff09;1.3 树的表示形式&#xff08;了解&#xff09;1.4 树的应用 2. 二叉树&#xff08;重点&#xff09;2.1 概念2.2 两种特殊的二叉树2.3 二叉树的性质2.4 二叉树的存储2.5 二叉树的…

【人工智能课程】计算机科学博士作业二

使用TensorFlow1.x版本来实现手势识别任务中&#xff0c;并用图像增强的方式改进&#xff0c;基准训练准确率0.92&#xff0c;测试准确率0.77&#xff0c;改进后&#xff0c;训练准确率0.97&#xff0c;测试准确率0.88。 1 导入包 import math import warnings warnings.filt…

SpringBoot---创建项目

介绍 此项目SpringBoot使用的是2.6.1版本&#xff0c;由于这个项目使用的是maven聚合方式创建的&#xff0c;所以第二步是我在聚合方式下需要添加的依赖&#xff0c;完整的pom.xml内容放到了最下面。 第一步&#xff1a;创建Maven项目 这个里什么也不勾选&#xff0c;直接点…

JDK Locale的妙用:探索多语言和地区设置的强大功能

文章目录 前言应用场景国际化&#xff08;Internationalization&#xff09;格式化&#xff08;Formatting&#xff09;日期格式化数字格式化金额格式化百分比形式格式化 获取Locale信息 前言 JDK&#xff08;Java Development Kit&#xff09;的Locale类用于表示特定的地理、…

openGauss学习笔记-210 openGauss 数据库运维-常见故障定位案例-谓词下推引起的查询报错

文章目录 openGauss学习笔记-210 openGauss 数据库运维-常见故障定位案例-谓词下推引起的查询报错210.1 谓词下推引起的查询报错210.1.1 问题现象210.1.2 原因分析210.1.3 处理办法 openGauss学习笔记-210 openGauss 数据库运维-常见故障定位案例-谓词下推引起的查询报错 210.…

8-小程序数据promise化、共享、分包、自定义tabbar

小程序API Promise化 wx.requet 官网入口 默认情况下&#xff0c;小程序官方异步API都是基于回调函数实现的 wx.request({method: , url: , data: {},header: {content-type: application/json // 默认值},success (res) {console.log(res.data)},fail () {},complete () { }…

ANAPF有源电力滤波器选型计算——安科瑞赵嘉敏

配电系统中谐波电流的计算涉及很多因素。对于改造项目&#xff0c;可使用专业电能质量分析仪测得所需谐波数据&#xff1b;对于新建项目&#xff0c;设计人员并不能直接获得供电系统的的谐波数据&#xff0c;因此&#xff0c;我司研发人员通过众多不同行业、不同类型的项目&…

JSP仓储管理系统myeclipse定制开发SQLServer数据库网页模式java编程jdbc

一、源码特点 JSP仓储管理系统系统是一套完善的web设计系统&#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库 &#xff0c;系统主要采用B/S模式开发。开发环境为 TOMCAT7.0,Myeclipse8.5开发&#xff0c;数据库为SQLServer2008&#x…

扩展学习|一文明晰推荐系统应用开发核心技术发展

文献来源&#xff1a;Lu J, Wu D, Mao M, et al. Recommender system application developments: a survey[J]. Decision support systems, 2015, 74: 12-32. 主题&#xff1a;关于推荐系统应用开发的调查研究 关键词:推荐系统、电子服务个性化、电子商务、电子学习、电子政务 …

除了Adobe之外,还有什么方法可以将Excel转为PDF?

前言 Java是一种广泛使用的编程语言&#xff0c;它在企业级应用开发中发挥着重要作用。而在实际的开发过程中&#xff0c;我们常常需要处理各种数据格式转换的需求。今天小编为大家介绍下如何使用葡萄城公司的的Java API 组件GrapeCity Documents for Excel&#xff08;以下简…

BL808学习日志-3-DPI-RGB屏幕使用-LVGL D0

一、DPI-RGB驱动 BL808的手册上显示是支持RGB565屏幕显示输出的&#xff0c;但是一直没找到网上的使用例程。且官方的SDK显示也是能够使用的&#xff0c;只是缺少了驱动。这一部分驱动在SIPEED的SDK中已经内置了&#xff0c;今天就是简单的点亮一个800*480 RGB565的屏幕。 二、…

Java基础数据结构之Lambda表达式

一.语法 基本语法&#xff1a;(parameters)->expression或者(parameters)->{statements;} parameters&#xff1a;类似方法中的形参列表&#xff0c;这里的参数是函数式接口里面的参数。这里的参数可以明确说明&#xff0c;也可以不声明而由JVM隐含的推断。当只有一个推…