STL剖析(二):容器底层数据结构及常见用法

news2024/12/27 0:16:23

一.概述

本文主要聚焦于STL容器,STL完整的容器分类体系如下所示,下文将逐一对各个容器底层的数据结构以及常见用法进行介绍。

容器

测试环境Ubuntu 22.04 g++ 11.3.0

二.顺序容器

顺序容器都对应着线性数据结构

2.1 array

array的使用需要引入头文件<array>,它表示固定大小的数组,与C风格的数组一致。在定义array时,需要指定类型T和数组大小N,即array<T,N>,array创建及常见用法如下:

#include <iostream>
#include <array>

using namespace std;

int main()
{
    array<int, 3> arr1;
    // 对array填充特定的值
    arr1.fill(5); // arr1: {5, 5, 5}
    // 遍历数组方式一
    for (int a : arr1) {
        cout << a << " ";
    }
    cout << endl; // 5 5 5
    //遍历数组方式二
    for (auto it = arr1.begin(); it != arr1.end(); it++) {
        cout << *it << " ";
    }
    cout << endl; // 5 5 5

    // 定义时指定初值
    array<int, 3> arr2 = {2, 5, 4};
    // 交换两个数组的内容,前提是类型和数组大小都要相同
    arr1.swap(arr2); // arr1: {2, 5, 4}, arr2: {5, 5, 5}
    // 对数组进行排序

    array<double, 2> arr3 = { 3.14, 2.56 };
    // 获取数组的大小
    cout << arr3.size() << endl; // 2
    // 获取数组的第一个元素元素
    cout << arr3.front() << endl; // 3.14
    // 获取数组的最后一个元素
    cout << arr3.back() << endl; // 2.56
    // 获取数组的首地址
    cout << arr3.data() << endl;

    return 0;
}

2.2 vector

vector表示动态数组,同array一样,vector中的元素也是连续存储在内存中的,但vector的大小是可变的。在使用vector时需要引入头文件<vector>,一般而言使用vector时仅需要指定数据类型T即可。

需要注意的是,vector并不是每添加一个元素就分配一次新的存储空间,实际上,当vector分配的容量全部填满元素后再添加新元素时,vector的分配器会重新分配两倍容量的新空间,然后将元素挪过去。vector示意图如下:

vector

图源自侯捷老师的《STL源码剖析》,侵权删。

注意:各家C++的实现可能略有区别,上述的重新分配规则不一定适用,但可以确定的是vector分配容量时一般都要未雨绸缪,即容量要比实际大小更大,否则需要重新分配

vector的创建及常见用法示例如下:

#include <iostream>
#include <vector>

using namespace std;

int main()
{
    // 创建空vector
    vector<int> arr1;
    // vector末端插入元素
    arr1.push_back(1);
    arr1.push_back(3);
    arr1.push_back(5);
    arr1.push_back(7);
    arr1.push_back(9);

    // 创建时指定初值
    vector<int> arr2 = { 1,2,3 };

    // 创建时指定大小和默认值
    vector<int> arr3(3, 5); // arr3: {5, 5, 5}

    // 遍历方式一
    for (int a : arr1) {
        cout << a << " ";
    }
    cout << endl; // 1 3 5 7 9 

    // 遍历方式二
    for (auto it = arr2.begin(); it != arr2.end(); it++) {
        cout << *it << " ";
    }
    cout << endl; // 1 2 3

    // 索引vector元素
    cout << arr1[1] << endl; // 3
    // 获取vector的第一个和最后一个元素
    cout << arr2.front() << " " << arr2.back() << endl; // 1 3

    // 判断数组是否为空
    cout << arr1.empty() << endl; // 0

    // 获取数组的大小和实际容量
    cout << arr1.size() << " " << arr1.capacity() << endl; // 5 8
    // 测试是否2倍扩张容量
    int n = 4;
    for (int i = 0; i < n; i++)
        arr1.push_back(i);
    cout << arr1.size() << " " << arr1.capacity() << endl; // 9 16

    // 清空vector的元素
    arr1.clear();
    // 插入元素,需要通过迭代器指定位置
    arr3.insert(arr3.begin(), 0); // arr3: {0, 5, 5, 5}
    // 删除vector末端的元素
    arr3.pop_back(); // arr3: {0, 5, 5}
    // 删除vector指定位置的元素,需要通过迭代器指定位置
    arr3.erase(arr3.begin());
    return 0;
}

注:对于vector,索引任意位置的元素的时间复杂度为 O ( 1 ) O(1) O(1),往末端插入或删除末端元素的时间复杂度为 O ( 1 ) O(1) O(1),往其它位置插入或删除其它位置元素的时间复杂度为 O ( n ) O(n) O(n)

2.3 deque

deque表示双向队列,是一种双向开口的"连续"空间。要使用deque需要引入头文件<deque>,并指定数据类型T

连续只是用户看起来,实际deque的存储并非连续的,而是由一系列单独分配的固定大小的数组组成,另外,deque中还需要记录模块(中控器)来维护记录各个段的信息

与vector比起来,deque的扩展更廉价,因为它不涉及将现有元素复制到新的内存位置。deque的示意图如下所示,其中map便是中控器,其中每个元素都指向一段连续的空间,称之为缓冲区,缓冲器才是用来存储数据的。从下图可以看出,deque还需要维护start和finish两个迭代器,分别指向第一缓冲区的第一个元素和最后缓冲区的最后一个元素。此外,这两个迭代器还需要指向map,否则无法在不同的缓冲区间转移。

deque

图源自stackoverflow: What really is a deque in STL?

deque的创建以及常见用法示例如下:

#include <iostream>
#include <deque>

using namespace std;

int main()
{
    deque<int> d = {1, 3, 5, 8, 10};
    //在deque开头插入元素
    d.push_front(0); // d: {0,1,3,5,8,10}
    // 删除deque开头的元素
    d.pop_front(); // d: {1,3,5,8,10}
    // 在deque结尾插入元素
    d.push_back(7); // d: {1,3,5,8,10,7}
    //删除deque结尾处的元素
    d.pop_back(); // d: {1,3,5,8,10}
    
    // 在指定位置插入元素,会返回一个指向插入元素的迭代器
    auto it = d.begin();
    it++;
    it = d.insert(it, 11); // d: {1,11,3,5,8,10}
    for (; it != d.end();it++)
    {
        cout << *it << " ";
    }
    cout << endl; // {11,3,5,8,10}
    // 删除指定范围的元素
    d.erase(d.begin() + 2, d.begin() + 5); // {1,11,10}

    //获取deque开头和结尾的元素
    cout << d.front() << " " << d.back() << endl; // 1 10
    // 获取deque的大小
    cout << d.size() << endl; // 3
    
    return 0;
}

注:deque在开头和结尾可以进行元素的快速插入和删除。

2.4 list

list表示环形双向链表,链表最大的特点的大小可变且存储非连续。要使用list需要引入头文件<list>,创建对象时需要传入数据类型T

STL中list节点结构示意图如下图所示,其中包含两个指针prevnext分别指向当前节点的前一个和后一个节点,data存储当前节点的数据。

list_node

图源自侯捷老师的《STL源码剖析》,侵权删。

基于节点的结构,list的可视化示意图如下所示,可以看到,由于环状要求,还需要一个空白节点(红框标记)。

list

list的创建与常见用法如下所示:

#include <iostream>
#include <list>
#include <algorithm>

using namespace std;

int main()
{
    // 创建list并初始化
    list<int> l = {3, 5, 7, 9};
    // 在list前面添加一个数值为1的节点
    l.push_front(1);
    // 在list后面添加一个数值为11的节点
    l.push_back(11);

    // 遍历链表
    for(int a:l){
        cout << a << " ";
    }
    cout << endl; // 1 3 5 7 9 11

    // 删除链表头的元素
    l.pop_front(); // l: {3,5,7,9,11}
    // 删除链表尾的元素
    l.pop_back(); // l:{3,5,7,9}
    // 寻找链表中大于6的第一个元素
    auto it = upper_bound(l.begin(), l.end(), 6);
    // 链表中插入元素
    if(it != l.end())
        l.insert(it, 6); // l: {3,5,6,7,9}
    // 删除满足某些特殊条件的元素
    l.remove_if([](const int x)
                { return x < 5;
    }); // l: {5,6,7,9}
    // 删除值等于某个特定值的元素
    l.remove(7); // l:{5,6,9}

    list<int> l1 = {3, 1, 7, 10};
    // 对链表进行排序
    l1.sort(); // l1: {1,3,7,10}
    // 归并两个有序链表
    l.merge(l1); // l:{1,3,5,6,7,9,10}
    //对链表进行逆序
    l.reverse(); // l:{10,9,7,6,5,3,1}

    // 获取链表的大小
    cout << l.size() << endl; // 7
    // 获取链表头的元素
    cout << l.front() << endl; // 10
    // 获取链表尾的元素
    cout << l.back() << endl; // 1
    return 0;
}

注:list插入和和删除元素的时间复杂度为 O ( 1 ) O(1) O(1)

2.5 forward_list

foward_list为单向链表,其行为与list类似,只不过只能单向访问。要使用forward_list需要引入头文件<forward_list>,创建对象时需要指定数据类型参数T

forward_list的示意图如下所示:

forward_list

forward_list的创建与常见用法如下所示:

#include <iostream>
#include <forward_list>

using namespace std;

int main()
{
    // 创建对象并初始化
    forward_list<int> l = {2, 4, 6, 8};
    // 在链表开头插入一个元素
    l.push_front(0); // l {0,2,4,6,8}
    // 删除链表头的元素
    l.pop_front(); // {2,4,6,8}
    // 在链表中指定位置后插入元素,位置通过迭代器来指定
    auto it = l.begin();
    it++;
    l.insert_after(it, 10); // {2,4,10,6,8}
    return 0;
}

forward_list的好多操作与list类似,这里不做重复赘述。

三.关联容器

关联容器底层的数据结构是红黑树。

3.1 红黑树

红黑树是一种平衡二叉搜索树,其示意图如下所示:

rbtree

红黑树需要满足如下规则:

  • 每个节点不是红色就是黑色。
  • 根节点需为黑色。
  • 红节点的子节点不能为红色。
  • 任一节点至NULL(树尾端)的任何路径所包含的黑色节点数须相同。

当新节插入时,若未符合上述规则,则需要调整颜色并旋转树形,使得其仍然是保持上述性质的平衡二叉搜索树。

红黑树中节点是有序的,即每个节点的左子树中各个节点的值都小于根节点,右子树中所有节点的值都大于根节点

红黑树的插入、删除和查找操作的时间复杂度都是 O ( log N ) O(\text{log}N) O(logN)

限于篇幅,本文不对红黑树进行展开讲解,后续将会写一篇专门的博客来介绍红黑树。

3.2 容器说明

对于set、multiset、map、multimap四种容器,其底层的数据结构都是红黑树,其中:

  • set表示集合,其中包含一系列无重复的键(key)。set的使用需要引入头文件<set>,另外还需要指定键的数据类型T和排序准则Compare,注意后者不是必须的,只有当需要自定义键的排序准则时才需要显式指定。
  • multiset与set类似,但其可以包含重复的键
  • map表示字典,即map中每个元素是一个键唯一的键值对。map的使用需要引入头文件<map>,另外需要指定键(key)和值(value)的数据类型KeyT,此外其同样可以指定排序准则Compare
  • multimap与map类似,但其允许键值对的键重复

上述四种容器中,set和map用的比较多,下面仅介绍两者的用法。

set的创建和常见用法示例如下:

#include <iostream>
#include <set>

using namespace std;

int main()
{
    // 创建并初始化,注意set会自动去重
    set<int> s = {1, 3, 3, 5, 5};
    cout << s.size() << endl; // 3
    // 插入元素
    s.insert(7); // s: {1,3,5,7}
    // 删除指定位置的元素
    s.erase(s.begin()); // s: {3,5,7}
    // 删除特定的键
    s.erase(7); // s:{3,5}
    // 寻找集合中特定的key的数量(非1即0)
    cout << s.count(3) << " " << s.count(7) << endl; // 1 0

    s.insert(8);
    s.insert(0);
    s.insert(4); // s: {0,3,4,5,8}
    // 返回第一个不小于给定键的元素的迭代器
    auto it = s.lower_bound(6);
    if(it != s.end())
        cout << *it << endl; // 8
    // 返回第一个大于给定键的元素的迭代器
    it = s.upper_bound(2);
    if(it !=s.end())
        cout << *it << endl; // 3
    return 0;
}

map的创建和常见用法示例如下:

#include <iostream>
#include <map>

using namespace std;

int main()
{
    // 创建并初始化
    map<string, int> wc = {{"tom", 18}, {"andy", 12}};
    // 使用[]可以获取特定key对应的元素
    cout << wc["tom"] << endl; // 18
    // 使用[]时当不存在指定的key时,则会插入给定键的节点,若未指定值则值会设置为Value的默认值
    wc["jake"]; // {{"andy", 12}, {"jake":0}, {"tom", 18}}

    // 获取map中元素的个数
    cout << wc.size() << endl; // 3
    // 插入的另一种方式
    wc.insert({"smith", 22});

    auto it = wc.find("sala");
    if(it == wc.end()) // not find
        cout << "not find" << endl;
    else
        cout << "find" << endl;
    return 0;
}

注:map中存在很多查找操作与set类似,故没有重复介绍。

四.无序关联容器

无序关联容器底层的数据结构是哈希表。

4.1 哈希表

哈希表(散列表)在插入、删除、查找等操作上具有平均复杂度为 O ( 1 ) O(1) O(1),其数据结构示意图如下:

hash table

图源:hast-table

假定数组有 m m m个桶(bucket),哈希表通过哈希函数(hash function)计算key映射到索引(哈希代码),然后将元素放置到索引对应的桶。

但使用哈希函数可以回带来一个问题,哈希碰撞,即不同的元素被映射到相同的位置。目前解决哈希碰撞最常见的方法为开链(separate chaining),即数组每个桶都维护一个list,通过哈希函数可以为key分配一个桶,然后可以在桶对应list上进行相应的插入、删除和查找操作,若list足够短,速度仍然是非常快的

限于篇幅,后面写博客详细介绍。

4.2 容器说明

unordered_set、unordered_multiset、unordered_map和unordered_multimap的底层数据结构都是哈希表,其中

  • unordered_set:包含一组唯一的键(key),使用需要引用<unordered_set>,并指定键的数据类型。
  • unordered_multiset:与unordered_set类似,但键可以重复。使用需要引用<unordered_multiset>
  • unordered_map:包含一组键(key)唯一的键值对,引用需要导入<unordered_map>,并指定键和值的数据类型。
  • unordered_multimap:与unordered_map类似,但键可以重复。使用需要引用<unordered_multimap>

注意:上述容器还可以指定哈希函数Hash和判断键是否相等的函数KeyEqual

上述四种容器中,unordered_set和unordered_map用的比较多,下面仅介绍两者的用法。

unordered_set的创建和常见用法如下所示:

#include <iostream>
#include <unordered_set>

using namespace std;

int main()
{
    // 创建并初始化
    unordered_set<int> us = {3, 3, 4, 4, 7, 9, 1};
    // 获取元素个数
    cout << us.size() << endl; // 5
    // 获取哈希表的桶个数
    cout << us.bucket_count() << endl; // 13
    // 判断容器是否为空
    cout << us.empty() << endl; // 0
    // 返回特定桶的元素数
    cout << us.bucket_size(0) << endl; // 0
    // 返回每个桶的平均元素数
    cout << us.load_factor() << endl; // 0.384615

    // 往哈希表中插入元素
    us.insert(10); // us: {}
    auto it = us.find(9);
    if(it != us.end()) // find key!!!
        cout << "find key!!!" << endl;
    else
        cout << "not find" << endl;

    unordered_set<int> us1 = {11, 8, 9};
    us.merge(us1); // us:{11,8,10,1,9,7,4,3}
    return 0;
}

unordered_map的创建和常见用法如下所示:

#include <iostream>
#include <unordered_map>

using namespace std;

int main()
{
    unordered_map<string, double> grades = {{"math", 85}, {"chinese", 77}};
    // 插入特定的键与值
    grades["english"] = 90;
    // 插入特定的元素
    grades.insert({"biology", 85.5});
    // 获取特定键对应的值
    cout << grades["math"] << endl; // 85
    // 返回特定键的元素个数,非1即0
    cout << grades.count("phyics") << " " << grades.count("math") << endl; // 0 1
    return 0;
}

unordered_map的好多用法与unordered_set类似,故不重复赘述。

五.容器适配器

容器适配器是依靠其底层容器来完成特定数据结构的功能。

5.1 堆栈

stack是一种先进后出的数据结构,其示意图如下所示:

stack

图源自侯捷老师的《STL源码剖析》,侵权删。

可以看出stack只允许在栈顶进行元素的插入(push)、删除(pop)和获取。(stack不允许遍历行为)

STL中要使用stack,需要引入头文件<stack>,并指定栈内数据类型T,stack的默认底层容器为deque。由于deque是双向队列,因此只要将deque的底部结构封闭,便能形成一个栈。

stack的创建以及常见用法为:

#include <iostream>
#include <stack>

using namespace std;

int main()
{
    stack<int> s;
    // 往栈中插入元素
    s.push(1);
    s.push(3);
    s.push(4);
    s.push(9);
    s.push(10);
    // 获取栈中元素的个数
    cout << s.size() << endl; // 5
    // 通过empty()来判断栈是否为空
    while(!s.empty()) // 
    {
        // 获取栈顶的元素
        cout << s.top() << " ";
        // 弹出栈顶的元素
        s.pop();
    }
    cout << endl; // 10 9 4 3 1

    return 0;
}

5.2 队列

queue是一种先进先出的数据结构,其示意图为:

queue

图源自侯捷老师的《STL源码剖析》,侵权删。

可以看出,queue允许从队首获取元素,从对尾插入元素。(queue不允许遍历行为)

要使用queue,需要引入头文件<queue>,并指定队列元素的数据类型T,queue的默认底层容器同样为deque。对于deque,只要封闭其底端的出口和首端的入口,便能轻易形成一个queue。

queue的创建及常见用法如下:

#include <iostream>
#include <queue>

using namespace std;

int main()
{
    queue<int> q;
    // 往队尾插入元素
    q.push(3);
    q.push(5);
    q.push(4);
    q.push(8);
    q.push(1);
    // 获取队列的大小
    cout << q.size() << endl; // 5
    // 通过empty判断队列是否为空
    while(!q.empty()){
        // 获取队首的元素
        cout << q.front() << " ";
        // 弹出队首的元素
        q.pop();
    }
    cout << endl; // 3 5 4 8 1
    return 0;
}

5.3 优先级队列

priority_queue表示具有元素优先级的队列,其示意图如下:

priority_queue

图源自侯捷老师的《STL源码剖析》,侵权删。

priority_queue的行为与queue类似,都是只能从队尾加入元素,从队首获取元素。另外,由于priority_queue中元素带有优先级,因此其内的元素并非依次按照入队的次序排列,而是按照元素的优先级权值来进行排序,权值高的排在前面

要使用priority_queue需要引用头文件<queue>,并指定元素类型<T>priority_queue的底层是利用一个最大堆(max-heap)完成的,而max-heap是一个以vector表现的完全二叉树。使用最大堆,可以满足优先级队列中权值高的排在前面的特性。

注意,priority_queue还可以指定权值大小的比较方法Compare

最大堆中根节点的值都不小于其孩子节点的值。

priority_queue的创建及常见用法如下:

#include <iostream>
#include <queue>

using namespace std;

int main()
{
    priority_queue<int> pq;
    int n = 10;
    for (int i = 0;i<n;i++){
        // 往优先级队列中插入元素
        pq.push(rand() % 20);
    }
    // 获取队列大小
    cout << pq.size() << endl; // 10
    // 通过empty判断队列是否为空
    while(!pq.empty())
    {
        // 获取队首元素
        cout << pq.top() << " ";
        // 弹出队首元素
        pq.pop();
    }
    cout << endl; // 17 15 15 13 12 9 6 6 3 1
    return 0;
}

六.参考资料

本文的完成参考了如下资料:

  • cppreference.com
  • cplusplus.com
  • 《STL源码剖析》侯捷著

以上便是本文的全部内容,要是觉得不错可以关注或点赞支持一下,有任何问题也敬请批评指正!!!。

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

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

相关文章

6.2 微服务-SpringBoot

目录 6.2.1 SpringBoot 6.2.1.1 什么是Spring Boot 6.2.1.2 SpringBoot的特点 6.2.2 快速入门 6.2.2.1 创建工程 6.2.2.2 引入依赖 6.2.2.3 启动类 6.2.2.4 controller 6.2.2.5 测试 6.2.3 注解与属性注入 6.2.3.1 注解 6.2.3.1.1 EnableAutoConfiguration 6.2.3.1…

leetcode 240. 搜索二维矩阵 II-java题解

题目所属分类 从右上角出发往下遍历 倒是也可以二分 原题链接 编写一个高效的算法来搜索 m x n 矩阵 matrix 中的一个目标值 target 。该矩阵具有以下特性&#xff1a; 每行的元素从左到右升序排列。 每列的元素从上到下升序排列。 代码案例&#xff1a; 输入&#xff1a;m…

加解密与HTTPS(4)

您好&#xff0c;我是湘王&#xff0c;这是我的CSDN博客&#xff0c;欢迎您来&#xff0c;欢迎您再来&#xff5e; 在互联网应用中&#xff0c;安全性问题已经越来越突出。从DDoS攻击、矿机劫持、乌云事件&#xff08;白帽子变成黑帽子&#xff09;&#xff0c;到窃听、偷拍、强…

Word控件Spire.Doc 【Table】教程(1):在 Word 中创建表格-C#VB.NET

Spire.Doc for .NET是一款专门对 Word 文档进行操作的 .NET 类库。在于帮助开发人员无需安装 Microsoft Word情况下&#xff0c;轻松快捷高效地创建、编辑、转换和打印 Microsoft Word 文档。拥有近10年专业开发经验Spire系列办公文档开发工具&#xff0c;专注于创建、编辑、转…

spring源码-资源资源加载器

Spring资源抽象Resource Spring对各种底层资源,比如文件系统中的一个文件&#xff0c;classpath上的一个文件&#xff0c;或者一个网络URL&#xff0c;统一抽象为接口Resource来表示 因为每个底层文件都可以以一个只读InputStream的方式打开&#xff0c;所以Resource接口继承…

ModuleNotFoundError: No module named ‘cs231n‘

在colab上完成cs231n的作业时发现&#xff0c;报了No module named cs231n’这个错误&#xff0c;查询后也没有找到合适的答案 仔细检查&#xff0c;发现是没有找到assignment1下的cs231n文件夹&#xff0c;然后去网站核对视频教程&#xff0c;发现没有搞错&#xff0c;视频中…

浮点数的储存

浮点数的储存一.浮点数的三段式&#xff08;S,E,M&#xff09;1.如何放入2.如何取出二.为什么浮点数不能直接比较三.解释第一个问题我们都知道整形在内存中是按照补码的形式储存的&#xff0c;但是浮点数的储存却和整数的截然不同&#xff0c;浮点数没有所谓是原反补并且浮点数…

SSM框架学习记录-MyBatisPlus_day01

1.入门案例与简介 MybatisPlus是基于MyBatis框架基础上开发的增强型工具&#xff0c;旨在简化开发、提供效率 未使用MybatisPlus时&#xff0c;在dao接口中的代码如下&#xff1a; Mapper public interface UserDao {Select("select * from user where id#{id}")publ…

冬日宅家选哪款投影仪比较好?极米H5陪你温暖过冬天

随着室外温度的逐步下降&#xff0c;寒冬也真的来了。相信对于许多朋友来说&#xff0c;宅家是冬季最惬意的时光&#xff0c;就是开着空调、电暖风、暖气&#xff0c;在温暖的室内&#xff0c;再打开投影仪&#xff0c;用超大屏追剧、看看电影&#xff0c;听听美妙的音乐&#…

光伏二次设备概述

概述 分布式光伏发电项目一般根据并网的电压等级分为380V和10KV。一般电压等级为380V低压并并网基本不涉及到什么二次产品&#xff0c;通常采用光伏并网柜就能解决&#xff0c;常见的并网设备为防孤岛保护装置和电能质量在线监测装置为主。而10KV并网的光伏容量一般处于1MWP到1…

加强企业数据库安全的行为准则

现在大多数企业都拥有可靠的网络安全程序&#xff0c;这些程序利用多种控件来实现深度防御安全性。通过这些程序&#xff0c;企业服务器得到加固&#xff0c;企业端点得到保护&#xff0c;监控工具也得以部署。还能够消除来自端点设备的高度敏感信息&#xff0c;并整合企业系统…

基于jsp+sevlet+mysql实验室设备管理系统

基于jspsevletmysql实验室设备管理系统一、系统介绍二、功能展示1.通知公告(学生)2.实验设备借用申请(学生)3.设备借用记录(学生)4.实验室预约申请(老师)5.实验室预约记录(老师)6.实验设备借用申请(老师)7.设备借用记录(老师)8.通知公告(管理员)9.实验室管理(管理员)10.设备管理…

QA | 关于可编程信号发生器,您在使用中可能遇到的问题

Q1&#xff1a;为什么信号源插在电脑上会显示电压不足&#xff1f; A&#xff1a;通常需要比普通电脑USB接口能提供更大的功率&#xff0c;需要高达2.0A的电流&#xff0c;超出了许多老式 USB 端口的水平。可以通过多种方式满足这一要求。适配器、USB 3.0计算机/笔记本电脑端口…

python——Matplotlib之fill_between函数

Matplotlib是Python提供的一套基于NumPy的绘图工具包&#xff0c;用Python实现与MATLAB相似的命令API&#xff0c;十分适合交互式绘制图表&#xff0c;成为Python中应用非常广的绘图工具包之一。 在对数据可视化时&#xff0c;为了突出某一段数据需要对部分区域进行填充处理。…

用这4招优雅的实现Spring Boot 异步线程间数据传递

Spring Boot 自定义线程池实现异步开发相信看过文章都了解&#xff0c;但是在实际开发中需要在父子线程之间传递一些数据&#xff0c;比如用户信息&#xff0c;链路信息等等 比如用户登录信息使用ThreadLocal存放保证线程隔离&#xff0c;代码如下&#xff1a; /*** author 公…

认监委调整《有机产品认证目录》

认监委关于调整《有机产品认证目录》的公告为进一步完善有机产品认证制度&#xff0c;规范有机产品认证活动&#xff0c;促进有机产业发展&#xff0c;根据《有机产品认证管理办法》&#xff08;质检总局令第155号&#xff09;和《有机产品认证实施规则》&#xff08;认监委201…

大数据编程期末大作业

大数据编程期末大作业 文章目录大数据编程期末大作业一、Hadoop基础操作二、RDD编程三、SparkSQL编程四、SparkStreaming编程一、Hadoop基础操作 在HDFS中创建目录 /user/root/你的名字 例如李四同学 /user/root/lisi 首先我们需要启动hdfs&#xff0c;我们直接在终端输入如下命…

JavaScript for 循环

文章目录JavaScript for 循环JavaScript 循环使用for循环不同类型的循环For 循环语句 1语句 2语句 3For/In 循环JavaScript for 循环 循环可以将代码块执行指定的次数。 JavaScript 循环 如果您希望一遍又一遍地运行相同的代码&#xff0c;并且每次的值都不同&#xff0c;那么…

MCU-51:单片机DS18B20温度报警器

目录一、DS18B201.1 DS18B20介绍1.2 引脚及应用电路1.3 内部结构框图1.4 存储器结构二、单总线2.1 单总线介绍2.2 单总线电路规范2.3 单总线时序结构三、DS18B20操作流程四、DS18B20数据帧五、温度存储格式六、代码演示6.1 温度读取6.2 温度报警器注意&#xff1a;一定要看一、…

Qt OpenGL(04)Sierpinski 镂垫 3D 版

文章目录三维 Siepinski 镂垫相关代码main.cppHelp.hppWidget.hWidget.cpp顶点着色器片元着色器总结三维 Siepinski 镂垫 把前面的二维Sierpinski程序转换成一个生成三维Sierpinski镂垫的程序&#xff0c;也就是说要绘制的镂垫不再只是限制在一个平面里。我们可仿效对二维镂垫所…