STL标准库与泛型编程(侯捷)笔记3

STL标准库与泛型编程(侯捷)

本文是学习笔记,仅供个人学习使用。如有侵权,请联系删除。

参考链接

Youbute: 侯捷-STL标准库与泛型编程

B站: 侯捷 - STL

Github:STL源码剖析中源码 https://github.com/SilverMaple/STLSourceCodeNote/tree/master

Github:课程ppt和源码 https://github.com/ZachL1/Bilibili-plus

文章目录

  • STL标准库与泛型编程(侯捷)
    • 16 vector深度探索
    • 17 array、forward list深度探索
    • 18 deque、queue和 stack深度探索(上)
      • 容器deque
    • 19 deque、queue和 stack深度探索(下)
      • 容器queue
      • 容器stack
      • queue和stack,关于其iterator和底层结构
    • 后记

介绍vector, array, deque, queue, stack的底层实现

16 vector深度探索

Vector将元素复制到内部的dynamic array中。元素之间总是存在一定的顺序,所以vector是一种有序集合(ordered collection)。 Vector支持随机访问,因此只要知道位置,你可以在常量时间内访问任何一个元素。 Vector提供随机访问迭代器,所以适用于任何STL算法。

如果你在末端附加或删除元素,vector的效率相当好。但如果你在前端或中段安插或删除元素,效率就不怎么样了,因为作用点之后的每一个元素都必须移到另一位置,而每一次移动都得调用assignment操作符。

Vector是动态数组,它会进行扩充,但不是原地扩充,而是当数组空间用完时,它会在某个地方重新分配内存空间(原来的两倍大小),再把先前的内容搬过去。

vector底部有三个指针,start, finish, end_of_storage

在C++中,std::vector 是一个动态数组,它的底层通常包含三个指针成员:startfinishend_of_storage

  1. start: 这是指向数组的第一个元素的指针,表示 vector 的起始位置。

  2. finish: 这是指向数组最后一个元素的下一个位置的指针,表示 vector 当前存储的元素范围结束位置。注意,finish 指向的位置是当前存储元素的末尾的下一个位置,而不是最后一个元素的位置。

  3. end_of_storage: 这是指向 vector 内存空间的末尾的下一个位置的指针。当 vector 的元素数量接近其容量时,end_of_storage 可能指向当前分配内存的末尾。如果要添加更多的元素导致超过当前分配的内存空间,vector 可能需要重新分配更大的内存块,然后 end_of_storage 会被更新为新的内存块的末尾。

这些指针的作用是帮助 vector 进行动态内存管理。startfinish 之间的部分是当前 vector 存储的元素,而 finishend_of_storage 之间的部分是 vector 预分配但尚未使用的内存空间。当 vector 的元素数量增加时,它可能需要重新分配内存,然后更新 end_of_storage 指针,以确保有足够的空间来容纳更多的元素。

在这里插入图片描述

现在看看两倍空间增长是怎么做的

下面是push_back的实现,当前分配的空间用完的时候,会调用insert_aux函数

在这里插入图片描述

insert_aux关于空间两倍增长的部分如下图所示

const size_type len = old_size != 0 ? 2 * old_size : 1;

下面的try catch里面就是具体的从旧位置把元素拷贝到新位置的源代码,分别是:把原vector的内容拷贝到新vector,然后为新元素赋值,新的finish指针后移,如果有insert函数,还需要把insert后面的元素复制过来。

后面还会把原来旧的vector空间释放掉

在这里插入图片描述

vector’s iterator

list的迭代器设计为一个class,这里vector的迭代器就是一个指针T *, 然后借助前面介绍的iterator_traits的偏特化(范围的偏特化,从接收T变成接收指针T *),会把associated types里面的 value_type直接指定为T,而不是基于类的迭代器的I::value_type。下图是GNU C++2.9版本的内容

在这里插入图片描述

下面是对GNU C++4.9版本的vector的介绍:多了几个类,但是izeof(vector<T>)还是12B,因为vector继承自_Vector_base, 它里面有一个_Vector_impl的成员,它里面的data是三个指针,在32位电脑里面一个指针是4B,因此在32位电脑上vector的大小是12B。

在这里插入图片描述

GNU C++4.9版本的vector的iterator呢?

下图所示的设计很复杂,经过层层推导,可以看到_Iterator _M_current变量的类型也是_Tp*即上面GNU C++2.9版本的T*类型。

在这里插入图片描述

而且,GNU C++4.9版本的vector的iterator不再是G++2.9版本的指针T*,而是vector<T>::iterator,如下图箭头所指。

在这里插入图片描述

17 array、forward list深度探索

容器array

TR1:C++ TR1(Technical Report 1,技术报告 1)是一个介于 C++03 标准和 C++11 标准之间的阶段性技术报告,旨在为 C++ 标准库引入新功能和库组件提供一种机制。TR1 提供了一些 C++ 标准库的扩展,以及一些新的库组件,作为对 C++03 标准库的增强。

TR1 中的一些建议和组件后来被纳入 C++11 标准,成为了正式的 C++ 标准库的一部分。C++11 标准以及之后的版本中包括了 TR1 中的一些组件,例如 <memory>, <functional>, <random>, <tuple> 等。

这里介绍TR1版本的array实现

在这里插入图片描述

array的用法,需要指定array的大小

array<int, 10> myArray;
auto ite = myArray.begin();
ite += 3;
cout << *ite;

array的实现中没有构造函数和析构函数,GNU C++(g++) 2.9版本中直接拿原生指针当作迭代器, g++2.9版本的vector也是这种实现。

GNU C++ 4.9版本的array

在这里插入图片描述

容器forward_list

单向链表,下面的slide配合list学习。

std::forward_list 是 C++ 标准模板库(STL)中的一种单向链表容器。与 std::list 不同,std::forward_list 是一个单向链表,每个元素只保存指向下一个元素的指针,而不保存指向前一个元素的指针。

以下是一些 std::forward_list 的主要特点和操作:

特点:

  1. 单向链表: std::forward_list 是一个单向链表,每个元素只有指向下一个元素的指针。
  2. 没有 size() 函数: 由于是单向链表,std::forward_list 没有提供直接获取元素个数的 size() 函数。获取元素个数需要遍历整个链表。
  3. 高效插入和删除: 在链表中间插入或删除元素的操作比在其他序列容器中更加高效。
  4. 不支持随机访问: 由于是单向链表,std::forward_list 不支持通过索引直接访问元素。

在这里插入图片描述

18 deque、queue和 stack深度探索(上)

容器deque

std::deque(双端队列,deque 意为 double-ended queue)是 C++ 标准模板库(STL)中的一个容器,它提供了动态数组的功能,允许在两端进行高效的插入和删除操作。以下是关于 std::deque 的一些主要特点和概念:

  1. 双端操作: std::deque 允许在队列的两端(前端和后端)进行高效的插入和删除操作,而不仅仅是在尾部。

  2. 动态数组: std::deque 的内部结构通常是由多个固定大小的块组成的,每个块中存储一定数量的元素。这种结构允许在两端执行常数时间的插入和删除操作,而不需要像动态数组那样移动大量元素。

  3. 迭代器: std::deque 提供随机访问迭代器,可以在常数时间内对其元素进行随机访问。这使得 std::deque 在许多算法和操作中能够高效地使用。

  4. 不要求连续存储:std::vector 不同,std::deque 不要求元素在内存中是连续存储的。这使得在 std::deque 头部和尾部执行插入和删除操作更加高效。

  5. 大小可变: std::deque 的大小可以动态调整,因此它可以根据需要动态分配和释放内存。

总体而言,std::deque 是一个强大的容器,适用于需要在两端进行高效插入和删除操作的情况。它提供了动态数组的灵活性,同时避免了由于频繁在中间插入或删除元素而引起的性能问题。

在这里插入图片描述

deque的内部:

首先有一个map(暂且称为控制中心),里面存储指针,指针指向各个缓冲区buffer(或者叫做node),缓冲区是数组,具体存储数据。缓冲区是连续的,但是不同的缓冲区之间不是连续的,所以称为分段连续。如下图所示,下图中有5个 buffer。

对于下图中的迭代器中的cur,first,last,node四个元素,first和last分别是一个buffer的起始和结束,node是控制中心中哪个buffer的指针,cur指向的是具体的元素值。

所有的容器都维护了两个begin() 和 end(), 就是下图中的迭代器start和finish,它里面也有上述四个元素。

在这里插入图片描述

下面看一下class deque的内容,它里面的数据有:

iterator start; // 16B, 
iterator finish; // 16B
map_pointer map;  // 4B,上面所说的控制中心
size_type map_size;  // 4B, 控制中心这个vector的大小

上面的16 + 16 + 4 + 4 = 40B,这就是一个class deque创建出来的对象本身大小有40B(32位机器上),至于deque里面存储的数据占用的空间大小,是动态分配获得的,和对象本身大小无关。

再看下图的右上角,其中一个模板参数位 BufSiz, 这是GNU C++2.9版本,可以用来指定缓冲区的大小。

在这里插入图片描述

下图是iterator的成员(方框框起来的部分),可以看到map_pointer是 T**,它本身是一个指针,所以cur, first, last, node共计4 * 4B = 16B(在32位电脑上指针是4B大小),这个就是上面iterator的大小。

下图中有

typedef  random_access_iterator_tag iterator_category;

指定迭代器的类型是随机存取的类型,这就是deque对外表现:提供随机访问迭代器,可以在常数时间内对其元素进行随机访问。其实通过上面的学习,我们知道deque底层不是连续的,它是分段连续的。

在这里插入图片描述

下面看deque的insert函数

insert可以在指定位置插入元素,这一页ppt展示的是插入的位置在deque的头或尾,直接调用push_front和push_back,对于中间的插入放到下一页ppt中的insert_aux函数讲。

在这里插入图片描述

下面是insert_aux函数的具体讲解

由于deque是连续的,并且是可以双向扩充的,当插入一个元素(对应安插点)时,就要看一下,哪端元素少,要决定元素的移动是向前还是向后,然后再插入新值,这样速度更快。

在这里插入图片描述

19 deque、queue和 stack深度探索(下)

先看一下deque的函数:

reference front()
{
    return *start;
}

reference back()
{
    iterator tmp = finish; // 迭代器tmp赋值为finish,finish指向最后一个元素的下一个位置
    --tmp;// 迭代器--,往前移动一格
    return *tmp; // 此时返回的是最后一个元素
}

size_type size() const
{
    return finish - start; // -操作符重载
    // 
}

这里是从源代码中的operator-的重载,摘录如下:

// _Deque_iterator里面的四个值
_Tp* _M_cur;
_Tp* _M_first;
_Tp* _M_last;
_Map_pointer _M_node;

difference_type operator-(const _Self& __x) const {
    return difference_type(_S_buffer_size()) * (_M_node - __x._M_node - 1) +
      (_M_cur - _M_first) + (__x._M_last - __x._M_cur);
}
// 每个buffer容纳的元素个数,buffer_size
inline size_t __deque_buf_size(size_t __size) {
  return __size < 512 ? size_t(512 / __size) : size_t(1);
}

static size_t _S_buffer_size() { return __deque_buf_size(sizeof(_Tp)); }

在这里插入图片描述

下面看deque如何模拟连续空间,主要是deque iterators的功劳

下面的_M_node - __x._M_node - 1中的_M_node指的是上图中的迭代器中的node节点,_M_node - __x._M_node - 1指的是控制中心两个node相减,得到是首尾buffer之间buffer的个数。(__x._M_last - __x._M_cur)指的是起始buffer的元素个数,就是下图中的97,98,99所在buffer的计算,这个buffer中右侧是元素,左侧是空白(目前还没有存数据)。(_M_cur - _M_first)指的是末尾buffer的元素个数,这个buffer左侧是数据,右侧是空白。

// 两个迭代器之间的距离:指的是多少个元素在两个迭代器之间
difference_type operator-(const _Self& __x) const {
    return difference_type(_S_buffer_size()) * (_M_node - __x._M_node - 1) +
      (_M_cur - _M_first) + (__x._M_last - __x._M_cur);
}

在这里插入图片描述

下面看++和–操作符重载:移动一个位置

_Self& operator++() {
    ++_M_cur;
    if (_M_cur == _M_last) { // 到达一个buffer的边界
       // _M_node是控制中心的节点
      _M_set_node(_M_node + 1); // 跳转到下一个buffer
      _M_cur = _M_first;
    }
    return *this; 
}
_Self operator++(int)  {
    _Self __tmp = *this;
    ++*this;
    return __tmp;
}

_Self& operator--() {
    if (_M_cur == _M_first) {
      _M_set_node(_M_node - 1);
      _M_cur = _M_last;
    }
    --_M_cur;
    return *this;
}
_Self operator--(int) {
    _Self __tmp = *this;
    --*this;
    return __tmp;
}

// 从一个buffer跳转到另一个buffer
void _M_set_node(_Map_pointer __new_node) {
    _M_node = __new_node;
    _M_first = *__new_node; // 指向新buffer的start
    _M_last = _M_first + difference_type(_S_buffer_size()); //指向新buffer的last
}

在这里插入图片描述

deque号称是连续空间,迭代器可以跳过多个位置,所以迭代器提供+=的重载

当跳过多个位置跨越了当前buffer的边界,就会到另一个buffer,

_Self& operator+=(difference_type __n)
{
    difference_type __offset = __n + (_M_cur - _M_first);
     // 目标位置在同一个缓冲区buffer
    if (__offset >= 0 && __offset < difference_type(_S_buffer_size()))
      _M_cur += __n;
    else { // 目标位置不在同一个缓冲区buffer
      difference_type __node_offset =
        __offset > 0 ? __offset / difference_type(_S_buffer_size())
                   : -difference_type((-__offset - 1) / _S_buffer_size()) - 1;
      // 切换到正确的buffer
      _M_set_node(_M_node + __node_offset);
      // 切换至正确的元素
      _M_cur = _M_first + 
        (__offset - __node_offset * difference_type(_S_buffer_size()));
    }
    return *this;
}

_Self operator+(difference_type __n) const
{
    _Self __tmp = *this;
    return __tmp += __n;
}

在这里插入图片描述

-=的重载调用上文的+=

  _Self& operator-=(difference_type __n) { return *this += -__n; }
 
  _Self operator-(difference_type __n) const {
    _Self __tmp = *this;
    return __tmp -= __n;
  }
// 连续空间需要提供下标操作符[]
  reference operator[](difference_type __n) const { return *(*this + __n); }

另外这里补充一下this 和 *this的区别:

bool operator==(const _Self& __x) const { return _M_cur == __x._M_cur; }  
bool operator!=(const _Self& __x) const { return !(*this == __x); }

问题:这里的this是什么类型,*this是什么类型?

回答:

在C++中,this 是一个指向当前对象的指针。在成员函数中,this 指针被隐式地传递给成员函数,指向调用该成员函数的对象。因此,this 的类型是指向当前类类型的指针。

在上述代码中,*this 表示当前对象的解引用。由于 this 是指针,*this 就是该指针所指向的对象。因此,*this 的类型是当前类的对象类型。

具体来说,上述代码是一个用于比较不等于的运算符重载函数。*this 是当前对象,__x 是另一个对象。通过 *this == __x 这一比较操作,判断当前对象是否等于另一个对象。!(*this == __x) 表示当前对象不等于另一个对象,因此返回 true。这个重载函数的目的是实现 != 运算符,如果你已经在类中实现了 == 运算符,这个函数就是通过对 == 的结果取反来实现 !=

g++4.9版本的deque

deque的类图如下图所示,也是deque继承自一个deque base,deque base有一个deque impl实现,这个deque impl有一个迭代器deque iterator。

新版本不允许用户指定缓冲区的大小,使用默认值512.

在这里插入图片描述

deque里面有四个成员变量

_M_map // 指向控制中心(这是一个vector)map,控制中心控制一块一块的缓冲区
_M_map_size // 控制中心的大小
// 两个迭代器
_M_start // 指向deque所有元素的头
_M_finish // 指向deque所有元素的尾

这里补充控制中心这个vector扩充的规则:

当vector空间满的时候,会自动两倍扩充空间,并且把旧的元素拷贝进新的两倍空间中,这里实现的时候,拷贝到新vector的中间部分,从而实现vector中间部分不仅可以向后扩张(记录新的缓冲区),而且还可以向前扩张(记录新的缓冲区),如下图右侧,中间控制中心(灰色)。

在这里插入图片描述

容器queue

std::queue 是 C++ 标准模板库(STL)中的一个容器适配器,用于实现先进先出(FIFO)的队列数据结构。它是建立在其他 STL 容器之上的一个封装,通常使用 std::deque 作为默认底层容器。

以下是 std::queue 的一些主要特点和操作:

  1. 先进先出(FIFO): std::queue 实现了队列的基本特性,确保最先进入队列的元素最先被移出。

  2. 容器适配器: std::queue 是一个容器适配器,意味着它不直接提供存储元素的能力,而是通过封装底层容器(默认是 std::deque)来实现队列的行为。

  3. 底层容器: 默认情况下,std::queue 使用 std::deque 作为底层容器,但你也可以通过模板参数来指定其他底层容器,比如 std::liststd::vector

  4. 入队和出队操作: std::queue 提供了 push 方法用于将元素入队,pop 方法用于将队首元素出队。

    std::queue<int> myQueue;
    myQueue.push(1);
    myQueue.push(2);
    myQueue.pop();
    
  5. 队首和队尾访问: front 方法返回队首元素的引用,back 方法返回队尾元素的引用。

    std::queue<int> myQueue;
    int frontElement = myQueue.front();  // 访问队首元素
    int backElement = myQueue.back();    // 访问队尾元素
    
  6. 大小和空判断: size 方法返回队列中的元素数量,empty 方法检查队列是否为空。

    std::queue<int> myQueue;
    bool isEmpty = myQueue.empty();  // 检查队列是否为空
    

std::queue 提供了一种方便的方式来操作队列,尤其是在需要实现先进先出的场景下。它隐藏了底层容器的实现细节,使得队列的使用更加简单。

queue的源代码如下图,它调用deque中的各种方法实现自己的功能

在这里插入图片描述

容器stack

std::stack 是 C++ 标准模板库(STL)中的一个容器适配器,用于实现后进先出(LIFO)的栈数据结构。它是建立在其他 STL 容器之上的一个封装,通常使用 std::deque 作为默认底层容器。

以下是 std::stack 的一些主要特点和操作:

  1. 后进先出(LIFO): std::stack 实现了栈的基本特性,确保最后入栈的元素最先被弹出。

  2. 容器适配器: std::stack 是一个容器适配器,意味着它不直接提供存储元素的能力,而是通过封装底层容器(默认是 std::deque)来实现栈的行为。

  3. 底层容器: 默认情况下,std::stack 使用 std::deque 作为底层容器,但你也可以通过模板参数来指定其他底层容器,比如 std::liststd::vector

  4. 入栈和出栈操作: push 方法用于将元素入栈,pop 方法用于将栈顶元素出栈。

    std::stack<int> myStack;
    myStack.push(1);
    myStack.push(2);
    myStack.pop();
    
  5. 栈顶访问: top 方法返回栈顶元素的引用。

    std::stack<int> myStack;
    int topElement = myStack.top();  // 访问栈顶元素
    
  6. 大小和空判断: size 方法返回栈中的元素数量,empty 方法检查栈是否为空。

    std::stack<int> myStack;
    bool isEmpty = myStack.empty();  // 检查栈是否为空
    

std::stack 提供了一种方便的方式来操作栈,尤其是在需要实现后进先出的场景下。它隐藏了底层容器的实现细节,使得栈的使用更加简单。

下面是stack的源代码,它也是调用deque的各个函数实现自己的功能

在这里插入图片描述

queue和stack,关于其iterator和底层结构

stack和queue都不允许遍历,不提供iterator

stack和queue都可以选择list或者deque作为底层结构,如下图所示,便是使用list作为底层结构

stack<string, list<string>> c;
queue<string, list<string>> c1;

在这里插入图片描述

stack可以选择vector作为底层结构

queue不能选择vector作为底层结构

在这里插入图片描述

stack和queue都不能选择set或map做底层结构

在这里插入图片描述

后记

截至2024年1月7日,学习完list, vector, deque, queue, stack的底层实现,接下来要学习基于红黑树的容器。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

常用服务器管理面板整理汇总

服务器管理面板是用于管理和控制服务器的软件&#xff0c;可以帮助管理员更轻松地进行服务器管理和维护。以下是几种常用的服务器管理面板&#xff1a; 1、宝塔面板【官网直达】 宝塔面板是一款服务器运维管理软件&#xff0c;支持Windows和Linux等操作系统&#xff0c;提供了…

《动手学深度学习》学习笔记 第6章 卷积神经网络

本系列为《动手学深度学习》学习笔记 书籍链接&#xff1a;动手学深度学习 笔记是从第四章开始&#xff0c;前面三章为基础知道&#xff0c;有需要的可以自己去看看 关于本系列笔记&#xff1a; 书里为了让读者更好的理解&#xff0c;有大篇幅的描述性的文字&#xff0c;内容很…

TypeScript 从入门到进阶之基础篇(六) 类型(断言 、推论、别名)| 联合类型 | 交叉类型

系列文章目录 TypeScript 从入门到进阶系列 TypeScript 从入门到进阶之基础篇(一) ts基础类型篇TypeScript 从入门到进阶之基础篇(二) ts进阶类型篇TypeScript 从入门到进阶之基础篇(三) 元组类型篇TypeScript 从入门到进阶之基础篇(四) symbol类型篇TypeScript 从入门到进阶…

鱼类识别Python+深度学习人工智能+TensorFlow+卷积神经网络算法

一、介绍 鱼类识别系统。使用Python作为主要编程语言开发&#xff0c;通过收集常见的30种鱼类&#xff08;‘墨鱼’, ‘多宝鱼’, ‘带鱼’, ‘石斑鱼’, ‘秋刀鱼’, ‘章鱼’, ‘红鱼’, ‘罗非鱼’, ‘胖头鱼’, ‘草鱼’, ‘银鱼’, ‘青鱼’, ‘马头鱼’, ‘鱿鱼’, ‘鲇…

如何批量自定义视频画面尺寸

在视频制作和编辑过程中&#xff0c;对于视频画面尺寸的调整是一项常见的需求。有时候&#xff0c;为了适应不同的播放平台或满足特定的展示需求&#xff0c;我们需要对视频尺寸进行批量调整。那么&#xff0c;如何实现批量自定义视频画面尺寸呢&#xff1f;本文将为您揭示这一…

github action

https://www.bilibili.com/video/BV1PM411B7um/?spm_id_frompageDriver&vd_sourcefd0f4be6d0a5aaa0a79d89604df3154a workflow pipeline

PyTorch 进阶指南,这个宝典太棒了

最新写了很多关于 Pytorch 的文章&#xff0c;主要针对刚刚接触 Pytorch 的同学&#xff0c;文章我给大家列出来了&#xff0c;喜欢可以从0开始学习&#xff1a; 小白学 PyTorch 系列&#xff1a;这一次&#xff0c;我准备了 20节 PyTorch 中文课程小白学 PyTorch 系列&#x…

【深度deepin】深度安装,jdk,tomcat,Nginx安装

目录 一 深度 1.1 介绍 1.2 与别的操作系统的优点 二 下载镜像文件及VM安装deepin 三 jdk&#xff0c;tomcat&#xff0c;Nginx安装 3.1 JDK安装 3.2 安装tomcat 3.3 安装nginx 一 深度 1.1 介绍 由深度科技社区开发的开源操作系统&#xff0c;基于Linux内核&#xf…

学完 Pinia 再也不想用 vuex 真香啊!!!!

&#x1f495;Pinia 注册 ✔ vue3 与 Pinia 注册 import { createApp } from vue import { createPinia } from pinia import App from ./App.vueconst app createApp()app.use(createPinia()) app.mount(#app)✔ vue2 与 Pinia 注册 import Vue from vue import App from …

java推荐系统:好友推荐思路

1.表的设计 表里面就两个字段&#xff0c;一个字段是用户id&#xff0c;另外一个字段是好友id&#xff0c;假如A跟B互为好友&#xff0c;那在数据库里面就会有两条数据 2.推荐好友思路 上面的图的意思是&#xff1a;h跟a的互为好友&#xff0c;a跟b&#xff0c;c&am…

Python笔记04-数据容器列表、元组、字符串、集合、字典

文章目录 listtuple 元组str序列&#xff08;切片&#xff09;setdict集合通用功能 Python中的数据容器&#xff1a; 一种可以容纳多份数据的数据类型&#xff0c;容纳的每一份数据称之为1个元素 每一个元素&#xff0c;可以是任意类型的数据&#xff0c;如字符串、数字、布尔等…

JavaWeb——新闻管理系统(Jsp+Servlet)之jsp新闻删除

java-ee项目结构设计 1.dao:对数据库的访问&#xff0c;实现了增删改查 2.entity:定义了新闻、评论、用户三个实体&#xff0c;并设置对应实体的属性 3.filter&#xff1a;过滤器&#xff0c;设置字符编码都为utf8&#xff0c;防止乱码出现 4.service:业务逻辑处理 5.servlet:处…

Qt/QML编程学习之心得:Linux下USB接口使用(25)

很多linux嵌入式系统都有USB接口,那么如何使用USB接口呢? 首先,linux的底层驱动要支持,在linux kernal目录下可以找到对应的dts文件,(device tree) usb0: usb@ee520000{compatible = "myusb,musb";status = "disabled";reg = <0xEE520000 0x100…

Unity中Shader序列帧动画(总结篇)

文章目录 前言一、半透明混合自定义调整1、属性面板2、SubShader中3、在片元着色器(可选)3、根据纹理情况自己调节 二、适配Build In Render Pipeline三、最终代码 前言 在前几篇文章中&#xff0c;我们依次解决了实现Shader序列帧动画所遇到的问题。 Unity中Shader序列图动画…

2.C++的编译:命令行、makefile和CMake

1. 命令行编译 命令行编译是指直接在命令行中输入以下指令&#xff1a; 预处理&#xff1a;gcc -E main.c -o main.i 编译&#xff1a;gcc -S main.i -o main.s 汇编&#xff1a;gcc -c main.s -o main.o 链接&#xff1a;gcc main.o -o main 命令汇总&#xff1a;gcc main.c …

LabVIEW开发自动读取指针式仪表测试系统

LabVIEW开发自动读取指针式仪表测试系统 在工业领域&#xff0c;尤其是煤矿、变电站和集气站等环境中&#xff0c;指针式仪表因其简单的结构、抗干扰能力强以及能适应高温高压等恶劣环境条件而被广泛应用于设备运行状态监视。然而&#xff0c;传统的人工读表方式不仅成本高昂&…

【Leetcode】移除后集合的最多元素数

目录 &#x1f4a1;题目描述 &#x1f4a1;思路 &#x1f4a1;总结 100150. 移除后集合的最多元素数 &#x1f4a1;题目描述 给你两个下标从 0 开始的整数数组 nums1 和 nums2 &#xff0c;它们的长度都是偶数 n 。 你必须从 nums1 中移除 n / 2 个元素&#xff0c;同时从 …

SpringMVC源码解析——HTTP请求处理(持续更新中)

在SpringMVC源码解析——DispatcherServlet的逻辑处理中&#xff0c;最后介绍到了org.springframework.web.servlet.DispatcherServlet的doDispatch方法中关于处理Web HTTP请求的核心代码是调用AbstractHandlerMethodAdapter类的handle方法&#xff0c;源码如下&#xff1a; /*…

04-微服务-Nacos

Nacos注册中心 国内公司一般都推崇阿里巴巴的技术&#xff0c;比如注册中心&#xff0c;SpringCloudAlibaba也推出了一个名为Nacos的注册中心。 1.1.认识和安装Nacos Nacos是阿里巴巴的产品&#xff0c;现在是SpringCloud中的一个组件。相比Eureka功能更加丰富&#xff0c;在…

[redis] redis的安装,配置与简单操作

一、缓存的相关知识 1.1 缓存的概念 缓存是为了调节速度不一致的两个或多个不同的物质的速度&#xff0c;在中间对速度较慢的一方起到加速作用&#xff0c;比如CPU的一级、二级缓存是保存了CPU最近经常访问的数据&#xff0c;内存是保存CPU经常访问硬盘的数据&#xff0c;而且…