侯捷 C++ STL标准库和泛型编程 —— 3 容器(序列式容器)

news2024/11/18 16:48:39

3 容器

3.1 容器结构分类

分类:序列式容器 Sequence Container,关联式容器 Associative Container

  • 序列式容器:按照放入的次序进行排列

    image-20230818103748215
    • Array 数组,固定大小
    • Vector 向量,会自动扩充大小
    • Deque 双向队列,双向都可以扩充
    • List 链表,双向链表
    • Forward-List 链表,单向链表
  • 关联式容器:有 keyvalue,适合快速的查找

    STL中实现使用红黑树(高度平衡二叉树)哈希表

    • Set,key 就是 value,元素不可重复

    • Map,keyvalue 是分开的,元素不可重复

    • Multi~,元素是可以重复的

    • Unordered~,HashTable Separate Chaining

      image-20230818103522538

其中 ArrayForward-ListUnordered~ 都是C++11的

3.2 序列式容器

3.2.1 array
测试
image-20230819103001457
#include <array>
#include <iostream>
#include <ctime> 
#include <cstdlib> //qsort, bsearch, NULL

void test_array() {
    cout << "\n test_array().......... \n";

    // 创建一个包含long型元素的array容器,ASIZE为数组的大小
    array<long, ASIZE> c;

    // 记录开始时间
    clock_t timeStart = clock();

    // 填充数组 c 中的元素,使用 rand() 生成随机数
    for (long i = 0; i < ASIZE; ++i) {
        c[i] = rand();
    }
    // 输出填充数组所花费的毫秒数
    cout << "milli-seconds : " << (clock() - timeStart) << endl;

    // 输出数组的大小、第一个元素、最后一个元素、起始地址
    cout << "array.size()= " << c.size() << endl;
    cout << "array.front()= " << c.front() << endl;
    cout << "array.back()= " << c.back() << endl;
    cout << "array.data()= " << c.data() << endl;

    // 获取目标值
    long target = get_a_target_long();

    // 记录开始时间
    timeStart = clock();
    // 使用标准库的 qsort 函数(快排)对数组 c 进行排序
    ::qsort(c.data(), ASIZE, sizeof(long), compareLongs);

    // 使用标准库的 bsearch 函数(二分查找)在排序后的数组中搜索目标值
    long* pItem = (long*)::bsearch(&target, c.data(), ASIZE, sizeof(long), compareLongs);
    // 输出排序和搜索所花费的毫秒数
    cout << "qsort()+bsearch(), milli-seconds : " << (clock() - timeStart) << endl;

    // 如果找到目标值,输出该值;否则输出未找到消息
    if (pItem != NULL)
        cout << "found, " << *pItem << endl;
    else
        cout << "not found! " << endl;
}

运行结果:

image-20230818113016596

随机数据填充容器:47ms;排序和搜索:187ms


深度探索

C++TR1下(比较简单):

template <typename _Tp, std::size_t _Nm>
struct array
{
	typedef _Tp value_type;
	typedef _Tp* pointer;
	typedef value_type* iterator; // 迭代器为_Tp*


	value_type _M_instance[_Nm ? _Nm : 1]; // 如果_Nm为0,就分配一个空间

	iterator begin() { return iterator(&_M_instance[0]); }
	iterator end() { return iterator(&_M_instance[_Nm]); }
	...
};

GCC4.9下(复杂且无益处):

image-20230827201155808
// GCC4.9通过多个typedef以下面的逻辑创建的array里的data
typedef int T[100]; // T即类型int[100] 
T c; // 与int c[100]一样
3.2.2 vector
测试
image-20230819102940829
#include <vector>
#include <stdexcept>
#include <string>
#include <cstdlib> //abort()
#include <cstdio>  //snprintf()
#include <iostream>
#include <ctime> 
#include <algorithm> 	//sort()

// 测试函数,接受一个引用类型的长整型参数
void test_vector(long& value)
{
    cout << "\ntest_vector().......... \n";
     
    vector<string> c;  	// 创建一个字符串类型的向量
    char buf[10];
    
    clock_t timeStart = clock();	// 记录开始时间							
    for(long i=0; i< value; ++i)	// 循环插入随机生成的字符串
    {
        try {
            snprintf(buf, 10, "%d", rand());	// 将随机整数转换为字符串
            c.push_back(string(buf));     	// 将字符串添加到向量中
        } // 这里是处理异常,如内存不够
        catch(exception& p) {
            cout << "i=" << i << " " << p.what() << endl;	
            // 输出出现异常的信息以及对应的索引值
            // 曾經最高 i=58389486 then std::bad_alloc
            abort();	// 异常处理后中止程序
        }
    }
    cout << "milli-seconds : " << (clock()-timeStart) << endl;	// 输出填充向量花费时间
    cout << "vector.max_size()= " << c.max_size() << endl;	// 输出向量的最大容量
    cout << "vector.size()= " << c.size() << endl;	// 输出向量的实际大小
    cout << "vector.front()= " << c.front() << endl;	// 输出向量的首元素
    cout << "vector.back()= " << c.back() << endl;	// 输出向量的末尾元素
    cout << "vector.data()= " << c.data() << endl;	// 输出向量地址
    cout << "vector.capacity()= " << c.capacity() << endl << endl;	// 输出向量的容量

    // 直接find来查找————次序查找
    string target = get_a_target_string();	// 获取一个目标字符串
    {
        timeStart = clock();	// 记录开始时间
        auto pItem = find(c.begin(), c.end(), target);	// 在向量中查找目标字符串
        cout << "std::find(), milli-seconds : " << (clock()-timeStart) << endl;  
        
        if (pItem != c.end())
            cout << "found, " << *pItem << endl << endl;	// 输出找到的目标字符串
        else
            cout << "not found! " << endl << endl;	// 输出未找到目标字符串
    }

    // 先排序再二分法查找
    {
        timeStart = clock();	// 记录开始时间
        sort(c.begin(), c.end());	// 对向量中的字符串进行排序
        cout << "sort(), milli-seconds : " << (clock()-timeStart) << endl; 
        
        timeStart = clock();	    
        string* pItem = (string*)::bsearch(&target, (c.data()), 
                                           c.size(), sizeof(string), compareStrings); 
        cout << "bsearch(), milli-seconds : " << (clock()-timeStart) << endl; 
       
        if (pItem != NULL)
            cout << "found, " << *pItem << endl << endl;	// 输出在排序后向量中找到的目标字符串
        else
            cout << "not found! " << endl << endl;	// 输出在排序后向量中未找到目标字符串
    }
    
    c.clear();	// 清空向量中的数据
    test_moveable(vector<MyString>(),vector<MyStrNoMove>(), value);	// 调用另一个函数进行测试
}

这是 array 在后面插入元素,其中若空间 capacity 不够,其会进行两倍扩充——即空间不够时会将原来的空间 *2

c.push_back(string(buf));

运行结果:

随机数据填充容器:3063ms;直接搜索:0ms(运气很好);排序后二分查找:2765ms


深度探索

GCC2.9下:

一共3个指针:startfinishend_of_storage

所以 sizeof(vector<int>)12

image-20230827163726770
template <class T, class Alloc = alloc>
class vector
{
public:
	typedef T value_type;
	typedef value_type* iterator; // 迭代器就是T*
	typedef value_type& reference;
	typedef size_t size_type;
protected:
	iterator start;
	iterator finish;
	iterator end_of_storage;
public:
	iterator begin() { return start; }
	iterator end() { return finish; }
	size_type size() const { return size_type(end() - begin()); }
	size_type capacity() const { return size_type(end_of_storage - begin()); }
	bool empty() const { return begin() == end(); }
	reference operator[](size_type n) { return *(begin() + n); }
    // 所有连续储存的容器都有[]的重载
	reference front() { return *begin(); }
	reference back() { return *(end() - 1); }
}

vector 每次成长会大量调用元素的拷贝构造函数和析构函数,是一个大成本

void push_back(const T& x)
{
    if (finish != end_of_storage) // 还有备用空间
    {
        construct(finish, x); // 全局函数
        ++finish;
    }
    else // 无备用空间
        insert_aux(end(), x);
}

template <class T, class Alloc>
void vector<T, Alloc>::insert_aux(iterator position, const T& x){
if (finish != end_of_storage){ // insert_aux还会被其他函数调用所以还有检查
    // 在‘备用空间起始处’构建一个元素以vector最后一个元素为初值
    // insert_aux也可能被insert调用,元素插入位置不定
    construct(finish, *(finish - 1));
    ++finish;
    T x_copy = x;
    copy_backward(position, finish - 2, finish - 1);
    *position = x_copy;
}
else{
    const size_type old_size = size();
    const size_type len = old_size != 0 ? 2 * old_size : 1;
    // 原大小为0,则分配1;否则,分配原大小的2倍
    
    iterator new_start = data_allocator::allocate(len);
    iterator new_finish = new_start;
    try{
        // 拷贝安插点前的原内容
        new_finish = uninitialized_copy(start, position, new_start);
        construct(new_finish, x);
        ++new_finish;
        // 拷贝安插点后的原内容
        new_finish = uninitialized_copy(position, finish, new_finish);
    }
    catch (...){
        destroy(new_start, new_finish);
        data_allocator::deallocate(new_start, len);
        throw;
    }
    // 解构并释放原vector
    destroy(begin(), end());
    deallocate();
    // 调整迭代器,指向新vector
    start = new_start;
    finish = new_finish;
    end_of_storage = new_start + len;
}

GCC4.9下变得复杂:

image-20230827174519929

且迭代器也变得乱七八糟,舍近求远,何必如此!!

image-20230827175349603
3.2.3 list
测试
image-20230819103100219
// 同理
void test_list(long& value)
{ 
    ...
        
    list<string> c;  // 创建一个字符串列表  	
    char buf[10];  // 字符串缓冲区
	
    ...
		
    string target = get_a_target_string();  // 获取目标字符串		
    timeStart = clock();		
    auto pItem = find(c.begin(), c.end(), target);  // 在列表中查找目标字符串						
    cout << "std::find(),milli-seconds : " << (clock()-timeStart) << endl;  // 输出查找时间		
	
    ...
    	
    timeStart = clock();		
    c.sort();  // 对列表进行排序						
    cout << "c.sort(), milli-seconds : " << (clock()-timeStart) << endl;  // 输出排序时间		    	

    c.clear();  // 清空	 
}

注意: c.sort(); 是容器自带的排序函数,如果容器自带肯定是要比全局的排序函数好的

list 同样也是用 c.push_back(string(buf)); 往里添加元素的

运行结果:

image-20230819105152408

随机数据填充容器:3265ms;直接搜索:16ms;排序:2312ms


深度探索

GCC2.9

image-20230822105307837
// list class
template <class T, class Alloc = alloc>
class list
{
protected:
	typedef __list_node<T> list_node;
public:	
	typedef list_node* link_type;
	typedef __list_iterator<T, T&, T*> iterator; // 迭代器,每一个容器都会 typedef
	// 只传一个参数就行了 不理想
protected:
	link_type node; // 一个 __list_node<T> 的指针
...
};

// 节点 class
template <class T>
struct __list_node
{
	typedef void* void_pointer; // 每次用还要转换类型 不理想
	void_pointer prev;
	void_pointer next;
	T data;
};

除了 array,vector 这样是连续存储的容器,其他容器的 iterator 都是智能指针,其有大量的操作符重载 —— 模拟指针

基本上所有的 iterator 都有下面5typedef 和一大堆操作符重载

// iterator class
template <class T, class Ref, class Ptr>
struct __list_iterator
{
	typedef __list_iterator<T, T&, T*> self;
	typedef bidirectional_iterator_tag iterator_category; // (1)双向迭代器	
	typedef T value_type; // (2)迭代器所指对象的类型
	typedef Ptr pointer; // (3)迭代器所指对象的指针类型
	typedef Ref reference; // (4)迭代器所指对象的引用类型
	typedef __list_node<T>* link_type;
	typedef ptrdiff_t difference_type; // (5)两个迭代器之间的距离类型

	link_type node; // iterator本体,一个指向__list_node<T>的指针

	reference operator*() const { return (*node).data; }
	pointer operator->() const { return &(operator*()); }
	self& operator++() // ++i
    {
        node = (link_type)((*node).next); // 移到下一个节点
        return *this; 
    }
	self operator++(int) // i++ 为了区分加上了一个参数其实无用
    {
        self tmp = *this; 
        ++*this; 
        return tmp; 
    }
	...
};

注意:self operator++(int){...}self tmp = *this; 中,由于先调用了 = 唤起了 copy ctor 用以创建 tmp 并以 *this 为初值,所以不会唤起 operator* —— *this 已经被解释为 ctor 的参数

下面的 ++*this; 同理

与 int 类似:iterator 可以连续前++,但不能连续后++

image-20230822173147636image-20230822173354379

所以前++是返回引用,后++返回值

因为要符合前闭后开原则,所以在 list 尾端加上了一个空白节点

image-20230827092146933

GCC4.9中做出了改进:

  • 迭代器模板参数从三个 --> 只有一个
  • 节点 class 中的前后指针类型从 void* --> _LIst_node_base*
image-20230827091438719

在GCC4.9中 sizeof(list<int>)8

在GCC2.9中 sizeof(list<int>)4

3.2.4 forward_list
测试
image-20230819103623779
// 同理
void test_forward_list(long& value)
{
    ...
     
    forward_list<string> c;  // 创建一个前向列表  	
    char buf[10];  // 字符串缓冲区
			
    ...
    
    
    string target = get_a_target_string();  // 获取目标字符串	
    timeStart = clock();	
    auto pItem = find(c.begin(), c.end(), target);  // 在前向列表中查找目标字符串	
    cout << "std::find(),milli-seconds : " << (clock()-timeStart) << endl;  // 输出查找时间		
	
    ...
    	
    timeStart = clock();		
    c.sort();  // 进行排序					
    cout << "c.sort(), milli-seconds : " << (clock()-timeStart) << endl;  // 输出排序时间		
	
    c.clear();  // 清空	 
}

注意:forward_list 只有 c.push_front(); 且没有 forward_list.back() forward_list.size()

运行结果:

image-20230819110505646

随机数据填充容器:3204ms;直接搜索:15ms;排序:2656ms

深度探索

list 相似,略

image-20230827201331283
3.2.6 deque
测试
image-20230819103846501

类似vector,两边都能扩充,实际上是分段连续的

其是通过 map(是一个vector,但在扩充时会 copy 到中间)里的指针指向各个 bufferbuffer 里再存数据,每个 buffer 的大小一致,每次扩充都是扩充一个指针指向一个新的 buffer

image-20230819111424969
void test_deque(long& value)
{
    ...
     
    deque<string> c;  // 创建一个双端队列  	
    char buf[10];  // 字符串缓冲区
	
    ...
    
    string target = get_a_target_string();  // 获取目标字符串	
    timeStart = clock();	
    auto pItem = find(c.begin(), c.end(), target);  // 在队列中查找目标字符串	
    cout << "std::find(),milli-seconds : " << (clock()-timeStart) << endl;  // 输出查找时间		
	
    ...
    	
    timeStart = clock();		
    sort(c.begin(), c.end());  // 对队列进行排序					
    cout << "sort(),milli-seconds : " << (clock()-timeStart) << endl;  // 输出排序时间		
	
    c.clear();  // 清空队列
}

运行结果:

image-20230819112747434

随机数据填充容器:2704ms;直接搜索:15ms;排序:3110ms

下面的 stackqueue 内部都是一个 deque,所以技术上这两个可以看作容器适配器 Container Adapter


深度探索

GCC2.9

template <class T, class Alloc = alloc, size_t BufSiz = 0>
class deque
{
public:
	typedef T value_type;
	typedef __deque_iterator<T, T&, T*, BufSiz> iterator;
	typedef size_t size_type;
	typedef T* pointer;
protected:
	typedef pointer* map_pointer; // T** 指向指针的指针
protected:
	iterator start;
	iterator finish;
	map_pointer map;
	size_type map_size;
    // 两个迭代器:16*2,一个指针:4,一个size_t:4,一共40字节
public:
	iterator begin() { return start; }
	iterator end() { return finish; }
    size_type size() const { return finish - start; }
    ...
};

注意:第三个模板参数 size_t BufSiz = 0 有一个函数:

如果不为0,则 buffer size 就是传入的数据

如果为0,表示预设值,那么

如果 sz = sizeof(value_type) < 512,传回 512/sz
如果 sz = sizeof(value_type) >= 512,传回 1

迭代器四个指针,cur 指向当前元素,first 指向当前 buffer 的第一个元素,last 指向当前 buffer 的最后一个元素的下一个,node 指向当前 buffer 在 map(控制中心)的指针

image-20230828084817056
// deque迭代器
template <class T, class Ref, class Ptr, size_t BufSiz>
struct __deque_iterator
{
	typedef random_access_iterator_tag iterator_category; // (1)
	typedef T value_type; // (2)
	typedef Ptr pointer; // (3)
	typedef Ref reference; // (4)
	typedef size_t size_type;
	typedef ptrdiff_t difference_type; // (5)
	typedef T** map_pointer;
	typedef __deque_iterator self;

	T* cur;
	T* first;
	T* last;
	map_pointer node; // 指向指针的指针
    // 四个指针,一共16字节
	...
};

deque 中的 insert 函数:

iterator insert(iterator position, const T& x)
{
    if (position.cur == start.cur) // 插入点在deque最前端      
    {							// 交给push_front
        push_front(x);
        return start;
    }
    else if (position.cur == finish.cur) // 插入点在deque最尾端
    {								  // 交给push_front
        push_back(x);
        iterator tmp = finish;
        --tmp;
        return tmp;
    }
    else // 在中间插入
    {
        return insert_aux(position, x);
    }   
}

iterator insert_aux(iterator pos, const T& x)
{
    difference_type index = pos - start; // 安插点前元素个数
    value_type x_copy = x;
    if (index < size() / 2) // 安插点前的元素少————搬前面的
    {
        push_front(front());
        ...
        copy(front2, pos1, front1); // 搬元素
    }
    else // 安插点后的元素少————搬后面的
    {
        push_back(back());
        ...
        copy_backward(pos, back2, back1);
    }
    *pos = x_copy; // 安插点设新值
    return pos;
}

deque 模拟连续空间(deque iterator 的功能):

image-20230828093535797
  • -:两个位置之间的距离——前闭后开的元素个数

    image-20230828093602891

    两个位置之间的距离 = buffer_size * 两个位置之间 buffer 的数量 + 末尾位置到 buffer 前端的长度 + 起始位置到 buffer 末尾的长度

  • ++/--:注:下面带参数的是后++(i++)

    image-20230828183715764
  • +=/+

    self& operator+=(difference_type n)
    {
        difference_type offset = n + (cur - first);  
        if (offset >= 0 && offset < difference_type(buffer_size()))  
            // 若+了之后在缓冲区大小范围内
            cur += n;  // 直接移动迭代器 n 步
        else
        {
            difference_type node_offset = offset > 0 ? offset / difference_type(buffer_size()) 
                : -difference_type((-offset - 1) / buffer_size()) - 1;
            // 计算偏移的节点数,offset > 0判断是为了之后的-=/-
            // 这里(-offset - 1)后除buffer_size()再-1是为了offset==buffer_size()的情况
            set_node(node + node_offset);  // 调整节点,使迭代器指向正确的节点
            cur = first + (offset - node_offset * difference_type(buffer_size()));  // 调整迭代器位置
        }
        return *this;
    }
    
    self operator+(difference_type n) const
    {
        self tmp = *this;  // 复制当前迭代器
        return tmp += n;   // 返回向前移动 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); }
    

GCC4.9下:其实没必要这样

image-20230829210932604

G2.91 允许指派 buffer_size

G4.53 不允许了

3.2.7 stack,queque
测试

stack:

image-20230819104008973

queue:

image-20230819104029805

stackqueue 是通过 push()pop() 来放取元素的,且无iterator 的操作


深度探索

stackqueue 内部默认用 deque 来实现,所以有时候不会将这两个认为容器而是一个适配器

  • 底层函数可以使用 listdeque(deque默认更快)

  • queue 不能用 vector,stack 可以用 vector

  • set,map 都不能用

用时编译器可以通过的,但在具体使用函数时,若遇到底层容器没有这个函数时,就会报错

// queue
template<class T, class Sequence = deque<T>>
class queue
{
	...
protected:
	Sequence c; // 底层容器
public:
    // 都是通过底层容器来实现
	bool empty() const { return c.empty(); }
	size_type size() const { return c.size(); }
	reference front() { return c.front(); }
	const_reference front() const { return c.front(); }
	reference back() { return c.back(); }
	const_reference back() const { return c.back(); }
	void push(const value_type& x) { c.push_back(x); }
	void pop() { c.pop_front(); }
};

// stack
template<class T, class Sequence = deque<T>>
class stack
{
	...
protected:
	Sequence c; // 底层容器
public:
    // 都是通过底层容器来实现
	bool empty() const { return c.empty(); }
	size_type size() const { return c.size(); }
	reference top() { return c.back(); }
	const_reference top() const { return c.back(); }
	void push(const value_type& x) { c.push_back(x); }
	void pop() { c.pop_back(); }
};

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

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

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

相关文章

每日一题 1333. 餐厅过滤器(中等)

感觉应该算是简单题 思路&#xff1a; 按照他提出的要求进行筛选即可学习了 sort 函数中几个参数的用法&#xff0c; keylambda c:(c[1], c[0]) 的形式可以令在排序中当c[1]相等时按照c[0]大小进行排序&#xff0c;reverseTrue 实现降序排列 class Solution:def filterResta…

基于微信小程序的美食推荐系统设计与实现(源码+lw+部署文档+讲解等)

前言 &#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计划导师、全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战✌&#x1f497; &#x1f447;&#x1f3fb;…

element日历插件获取显示的第一天和最后一天

和重置日期内容显示 需求是要传入当前显示的第一天和最后一天来获取范围&#xff0c;再判断某个日期是否有内容标记 已知星期排版是固定的&#xff0c;第一天是星期天&#xff0c;最后一天是星期六。通过当月1号和最后一天再往前推算需要展示上个月几天&#xff0c;和下个月几…

Vue3 + TS 自动检测线上环境 —— 版本热更新提醒

&#x1f414; 前期回顾 编写 loading、加密解密 发布NPM依赖包&#xff0c;并实施落地使用_彩色之外的博客-CSDN博客 目录 &#x1f30d; 问题产生 &#x1f916; 性能效率 &#x1fa82; 新建 autoUpdate.ts &#x1f38b; 在App.vue使用 &#x1f30d; 问题产生 当用…

秋招搞个保底offer再说,我换赛道了。

作者&#xff1a;阿秀 InterviewGuide大厂面试真题网站&#xff1a;https://top.interviewguide.cn 这是阿秀的第「312」篇原创 小伙伴们大家好&#xff0c;我是阿秀。 转测开这个话题已经聊过很多次了&#xff0c;以前也分享过不少测开相关的面经&#xff1a; 后端太卷&#x…

Mac电脑视频处理工具 Topaz Video AI for mac

Topaz Video AI是一款强大而易用的视频处理软件&#xff0c;通过人工智能技术提供高质量的视频增强和编辑功能。它可以帮助用户改善视频的质量、修复缺陷、优化图像&#xff0c;并提供丰富的编辑选项&#xff0c;以满足个性化的视频处理需求。无论是专业摄影师、视频编辑人员&a…

vue+Vant,关闭Popup弹框,遮罩层并没有消失

遇到问题&#xff1a; 点击Popup弹框关闭按钮&#xff0c;弹框的遮罩不能正常关闭&#xff0c;如下图。经研究&#xff0c;排除了popup属性问题&#xff0c;最后只能删除代码排除法。 <!--弹框&#xff1a;选号--><van-popupv-model"showNumber"closeablero…

JS 内存泄漏与垃圾回收机制

一、什么是内存泄漏? 内存泄漏&#xff08;Memory Leak&#xff09;是指程序中己动态分配的堆内存由于某种原因程序未释放或无法释放&#xff0c;造成系统内存的浪费&#xff0c;导致程序运行速度减慢甚至系统崩溃等严重后果。通俗点就是指由于疏忽或者错误造成程序未能释放已…

指定名校牛导|肿瘤科医生赴哈佛大学麻省总医院访学进修

世界排名第一的哈佛大学&#xff0c;美国排名第一的研究型医院-麻省总医院&#xff0c;AIMBE fellow头衔的导师&#xff0c;这些条件均符合T医生要求的名校牛导标准。 T医生背景&#xff1a; 申请类型&#xff1a; 单位公派访问学者 工作背景&#xff1a; 三甲医院医生 教育…

在openwrt dnsmasq DHCP中为客户端分配不同的网关和DNS | 旁路由 禁止上网

环境&#xff1a;openwrt dnsmasq PS4/Switch 问题&#xff1a;为路由器下的设备分配不同的网关和DNS&#xff0c;禁止局域网设备上网 解决办法&#xff1a;修改dnsmasq配置文件 背景&#xff1a;Openwrt 的DHCP服务是使用dnsmasq实现的&#xff0c;他可以给内网的客户端设备…

如何减少爬虫产生的网络负载:爬取间隔和缓存控制策略

在进行Python爬虫开发时&#xff0c;我们需要注意控制爬取频率&#xff0c;以减少对目标网站的网络负载。本文将为您分享两种关键策略&#xff1a;爬取间隔和缓存控制。通过合理设置爬取间隔和使用缓存&#xff0c;您可以有效减少网络负载&#xff0c;同时保证数据的实时性和准…

Spring5应用之AOP动态代理开发

作者简介&#xff1a;☕️大家好&#xff0c;我是Aomsir&#xff0c;一个爱折腾的开发者&#xff01; 个人主页&#xff1a;Aomsir_Spring5应用专栏,Netty应用专栏,RPC应用专栏-CSDN博客 当前专栏&#xff1a;Spring5应用专栏_Aomsir的博客-CSDN博客 文章目录 前言何为动态代理…

使用Packet Tracer了解网络模型及Lab3 - 2

显示TCP/IP协议套件的元素 这些额外的条目在 TCP/IP 套件中扮演着各种角色。如果列出了地址解析协议 &#xff08;ARP&#xff09;&#xff0c;它将搜索 MAC 地址。DNS 负责将名称&#xff08;例如 www.osi.local&#xff09;转换为 IP 地址。其他 TCP 事件负责连接、商定通信…

DETR纯代码分享(九)transformer.py

一、定义DETR Transformer用于DETR模型 """ DETR Transformer class.Copy-paste from torch.nn.Transformer with modifications:* positional encodings are passed in MHattention* extra LN at the end of encoder is removed* decoder returns a stack of …

趣解设计模式之《当代毕加索小王》

〇、小故事 小王最近对画油画非常的感兴趣&#xff0c;尤其是当他参观完毕加索画展之后&#xff0c;更觉得自己有画画天赋了&#xff0c;“这画我自己也能画啊&#xff01;这以后一幅画随随便便买它2、3个亿&#xff0c;这不发财了&#xff01;”于是&#xff0c;他就开始着手…

Java——》IO

推荐链接&#xff1a; 总结——》【Java】 总结——》【Mysql】 总结——》【Redis】 总结——》【Kafka】 总结——》【Spring】 总结——》【SpringBoot】 总结——》【MyBatis、MyBatis-Plus】 总结——》【Linux】 总结——》【MongoD…

12KM02E-V0002 3EGM030300R0002 模块化和加固的边缘计算加速

12KM02E-V0002 3EGM030300R0002 模块化和加固的边缘计算加速 随着边缘人工智能解决方案的兴起&#xff0c;对实时洞察和自主决策的需求显著增长。这也带来了对变革性技术的高度需求&#xff0c;这些技术可以在坚固的边缘支持和提供最佳性能。为了应对技术革命&#xff0c;Prem…

C#开发 降.NET版本问题解决笔记

C#开发 降.NET版本问题记录 以下都是我在使用.NET4.7.2版本开发控件完成后又将控件代码降位使用.NET4.5时所遇到的问题&#xff0c;和解决方式 Struct构造函数&#xff1a;“必须将字段完全分配&#xff0c;然后控制权才能返回给调用者。” 原始的代码和错误如下&#xff1a…

PMP证书的价值如何?

2022年开始&#xff0c;PMP考试启用了新考纲&#xff0c;不光考试内容进行了大刀阔斧的改革&#xff0c;出题方式也进行了更新。除原有的PMBOK6和PMBOK7主考教材外&#xff0c;还增加了一本《敏捷实践指南》。 别小看新加的这本书&#xff0c;它虽然与PMBOK代表的预测法属于完…

史上最强,从0到1接口自动化测试落地实施,资深老鸟整理...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 为什么要做接口测…