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

news2024/9/28 11:21:11

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的底层实现,接下来要学习基于红黑树的容器。

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

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

相关文章

qt鼠标常用事件

和上一个案例相同,也是做了提升&#xff0c;换了相同父类,但是方式有所不同 先在widget.ui中加入label标签,此时其父类为QLabel,然后想实现鼠标在QLabel上的捕获。所以我们需要把QLabel提升为自己的框架&#xff0c;然后自定义框架后&#xff0c;我们就可以自己捕获信息了。然…

遗传算法总结(迭代版本2:附带MATLAB例题代码)

目录 基本概念&#xff1a; 具体例子 1.我们先对图像进行抽象化&#xff1a; 2.我们将得到的六串数字进行扁平化处理&#xff1a; 3.解释即后续操作​编辑 基本步骤&#xff1a; 总结 例题1&#xff1a; 例题2&#xff1a; 基本概念&#xff1a; 染色体&#xff08;Ch…

uniapp 文字超出多少字,显示收起全文按钮效果demo(整理)

收起展开 <template><view class"font30 color000 mL30 mR30"><text :class"showFullText ? : clamp-text">{{ text }}</text><view v-if"showToggleBtn && text.length > 42" click"toggleShowFu…

基于JAVA的服装店库存管理系统 开源项目

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 数据中心模块2.2 角色管理模块2.3 服装档案模块2.4 服装入库模块2.5 服装出库模块 三、系统设计3.1 用例设计3.2 数据库设计3.2.1 角色表3.2.2 服装档案表3.2.3 服装入库表3.2.4 服装出库表 四、系统展示五、核心代码5.…

小白入门基础 - tomcat

一&#xff1a;前言 Tomcat 服务器是一个免费的开放源代码的 Web 应用服务器&#xff0c;属于轻量级应用服务器&#xff0c;在中小型系统和并发访问用户不是很多的场合下被普遍使用&#xff0c;是开发和调试JSP 程序的首选。对于一个初学者来说&#xff0c;可以这样认为&#x…

解决“SQLServer 添加数据库,报Error 5118“错误

当将把一个SQLServer的数据库文件*.MDF和日志文件*.LDF&#xff0c;从电脑A拷贝到电脑B&#xff0c;然后在电脑B上&#xff0c;使用Microsoft SQL Server Management Studio添加该*.MDF文件&#xff0c;有时报"Error 5118"错误&#xff0c;如图(1)所示&#xff1a; 图…

【C程序设计】C指针

学习 C 语言的指针既简单又有趣。通过指针&#xff0c;可以简化一些 C 编程任务的执行&#xff0c;还有一些任务&#xff0c;如动态内存分配&#xff0c;没有指针是无法执行的。所以&#xff0c;想要成为一名优秀的 C 程序员&#xff0c;学习指针是很有必要的。 正如您所知道的…

Hadoop精选18道面试题(附回答思路)

1.简述Hadoop1和Hadoop2的架构异同 HDFS HA(High Availablity)一旦Active节点出现故障&#xff0c;就可以立即切换到Standby节点&#xff0c;避免了单点故障问题。加入了对zookeeper支持实现比较可靠的高可用。YARN将MapReduce1.0中的资源管理调度功能分离出来形成了YARN&…

Mac/Window 如何下载安装 Pycharm 2023

文章目录 1. 下载 Pycharm2. jiebra 工具下载3. jiebra 工具安装3.1 Window 端3.2 Mac 端 1. 下载 Pycharm Pycharm 下载官网&#xff0c;可以下载的是最新版的 Pycharm&#xff0c;但不保证可以jihuo&#xff1b; 如果需要保证可用的&#xff0c;建议直接下载资源&#xff…

@Transactional 注解的12种失效场景

请直接看原文: 原文链接:啪&#xff01;啪&#xff01;Transactional 注解的12种失效场景&#xff0c;这坑我踩个遍-腾讯云开发者社区-腾讯云 (tencent.com) ------------------------------------------------------------------------------------------------------------…

C++完成Query执行sql语句的接口封装和测试

1、在LXMysql.h 创建Query执行函数 //封装 执行sql语句 if sqllen 0 strlen获取字符长度bool Query(const char*sql,unsigned long sqllen0); 2、在LXMysql.cpp编写函数 bool LXMysql::Query(const char* sql, unsigned long sqllen){if (!mysql)//如果mysql没有初始化好{c…

功能强大且易于使用的视频转换软件—Avdshare Video Converter for Mac/win

在当今的数字时代&#xff0c;我们的生活离不开各种形式的媒体娱乐&#xff0c;而视频内容无疑是其中最为受欢迎的一种。然而&#xff0c;我们常常会遇到一些问题&#xff0c;比如我们在电脑上下载的视频无法在手机上播放&#xff0c;或是我们想将视频转换为其他格式以适应不同…

.NetCore部署微服务(一)

目录 前言 什么是微服务 微服务的优势 微服务的原则 创建项目 在Docker中运行服务 客户端调用 简单的集群服务 前言 写这篇文章旨在用最简单的代码阐述一下微服务 什么是微服务 微服务描述了从单独可部署的服务构建分布式应用程序的体系结构流程&#xff0c;同时这些…

【Linux】之搭建 PostgreSQL 环境

前言 在 Linux 系统下安装 PostgreSQL&#xff0c;可以选择快捷方便的 Docker 安装&#xff0c;但正常的服务器都是直接原生安装的&#xff0c;所以&#xff0c;这里我将讲解如何正常安装 PostgreSQL 以及安装之后的一些配置。如果想了解 Docker 安装的话&#xff0c;可以查看我…

二叉树的经典算法(算法村第八关青铜挑战)

二叉树里的双指针 所谓的双指针就是定义了两个变量&#xff0c;在二叉树中有需要至少定义两个变量才能解决问题。这两个指针可能针对一棵树&#xff0c;也可能针对两棵树&#xff0c;姑且也称之为“双指针”。这些问题一般与对称、反转和合并等类型题相关。 判断两棵树是否相…

004-Zynq实现SD卡存储灰度图片(彩色图片存储正点已开源)

文章目录 前言一、为什么参考ov7725照相机实验存储不了灰度&#xff1f;二、SD卡实现步骤1.配置Zynq核中的SD卡接口2.PS端勾选xilffs3.PS端代码4.读卡器读取SD卡结果呈现 总结 前言 最近在弄SD卡存储灰度图片&#xff0c;参考了正点原子的OV7725照相机实验&#xff0c;但发现最…

网安入门09-Sql注入(绕过方法梳理)

ByPass SQL注入ByPass是指攻击者通过各种手段绕过应用程序中已经实施的SQL注入防御措施&#xff0c;例如输入恶意数据、修改请求头等方式&#xff0c;绕过过滤、转义、限制等操作&#xff0c;从而成功地执行恶意SQL语句。攻击者使用SQL注入ByPass技术可以让应用程序的防御措施…

为什么Java中“1000==1000”为false,而”100==100“为true?

大家好&#xff0c;我是可乐。 在日常编程中&#xff0c;我们经常遇到一些看似简单却隐藏着复杂逻辑的问题。 比如&#xff0c;你是否想过为什么在 Java 中表达式10001000会返回 false&#xff0c;而 100100 却返回 true 呢&#xff1f; Integer a 100; Integer b 100; Sy…

王国维的人生三境界,这一生至少当一次傻瓜

一、人生三境界 古今之成大事业、大学问者&#xff0c;必经过三种之境界。“昨夜西风凋碧树&#xff0c;独上高楼&#xff0c;望尽天涯路。”此第一境也。“衣带渐宽终不悔&#xff0c;为伊消得人憔悴。”此第二境也。“众里寻他千百度&#xff0c;蓦然回首&#xff0c;那人却…

双向数据绑定详细解析(超详细)

文章目录 一、什么是双向绑定二、双向绑定的原理是什么理解ViewModel 三、实现双向绑定实现编译Compile依赖收集 参考文献 一、什么是双向绑定 我们先从单向绑定切入单向绑定非常简单&#xff0c;就是把Model绑定到View&#xff0c;当我们用JavaScript代码更新Model时&#xf…