当前位置: 首页 > article >正文

c++STL——vector的使用和模拟实现

文章目录

  • vector的使用和模拟实现
    • vector的使用
    • vector介绍
    • 重点接口的讲解
      • 迭代器部分
      • 默认成员函数
      • 空间操作
      • 增删查改操作
      • 迭代器失效问题(重要)
        • 调整迭代器
    • vector的模拟实现
      • 实现的版本
      • 模拟实现
        • 结构
        • 预先处理的函数
          • 尾插函数push_back
          • swap函数
          • 赋值重载
          • size函数
          • reserve函数
        • 迭代器
        • 默认成员函数
          • 默认构造
          • 普通构造
          • 拷贝构造
          • 析构函数
        • 容量操作
        • 容量 、判空
          • resize函数
        • 修改操作
          • 尾删
          • insert函数
          • clear函数
          • erase函数
        • 打印函数(针对不同容器)

vector的使用和模拟实现

vector的使用

vector介绍

对于STL中各类容器的学习其实是很相似的,因为c++的封装性。虽然是不同的容器,但是c++标准库在实现的时候是对各类的容易实现了一些一样的接口,我们只需要关注其封装的接口的使用即可。所以各类容器的操作是很类似的。

而对于vector其实是一个类模板,其底层的实现本质还是顺序表。只不过与string的底层实现是略有区别。更大的不同是vector中存储的元素不仅仅是一些内置类型,也可以是类,如string,甚至是vector类。

当然学习STL容易是先学会如何使用其对应接口,我们得学会查阅文档:https://cplusplus.com/reference/vector/vector/

重点接口的讲解

迭代器部分

vector的接口其实没有string实现的那么多。因为string是更早写进标准库中的,这是历史遗留问题。

对于string,我们在增删的时候,更多的是传入对应的位置,也就是下标。而当我们查阅vector使用的文档的时候,我们发现参数竟然是使用迭代器的:
在这里插入图片描述
erase函数,可以传一个迭代器的位置,也可以传迭代器指向的一段区间(左闭右开)。

其实迭代器的使用和string是一样的的,有八种。end型的迭代器都是指向最后一个有效元素的后一个位置。

使用的话重点掌握beginrbeginendrend这四个就可以了。

默认成员函数

函数使用
vector()(重点)无参构造
vector(size_type n, const value_type& val =value_type())构造并初始化n个val
vector (const vector& x); (重点)拷贝构造
vector (InputIterator first, InputIterator last);使用迭代器进行初始化构造
~vector()析构 编译器会自行调用
vector& operator= (const vector& x);赋值重载

这里面有很多不认识的符号,下面给出这些符号的对应表:
在这里插入图片描述
我们直到,vector其实是一个类模板,内部的数据类型其实都是由模板参数T来替代的。但是为了代码的可读性更好,所以对一些常用的类型取别名。

我们只需要知道常用的那几个就可以了。

而还有一个很奇怪的构造函数:vector (InputIterator first, InputIterator last),使用迭代器进行初始化构造。这个InputIterator是什么呢?

其实这是一个模板参数的声名,template< class InputIterator >,声名这一个模板参数是因为在构造一个vector的时候,我们很可能需要用别的迭代器进行构造。
举一个很常见的例子:

有时候我们想对链表(STL中的list)中的数据进行排序。但是链表排序其实是效率较低的。所以我们会经常的使用链表的迭代器区间来构造一个vectorvector的本质是顺序表,使用顺序表排序是比较高效率的。然后排好序后再依次将数据覆盖回链表中。

当然这个迭代器也可以是指向数组的:
在这里插入图片描述
在这里我们可以认为是数组也有自己的迭代器。

空间操作

函数使用
size获取数据个数
capacity获取容量大小
empty判断是否为空
resize(重点)改变vector的size
reserve (重点)改变vector的capacity

重点我们来看看resizereserve的使用。

对于resize:
在这里插入图片描述
这是调整数据个数的函数。如果传入的n是小于当前数据个数,那么就会删除数据。
如果n大于当前数据个数,会往后续新加入的位置插入数据,空间不够的时候会扩容。

这个插入的数据是带有缺省参数的,即value_type val = value_type();。这个其实是调用了匿名类的默认构造函数

很多人会疑惑,如果是int等内置类型也能这样使用吗?答案是当然可以。在以往我们会认为,只有自定义类型才会有默认构造函数。但其实在c++内,对于内置类型也是可以有默认构造函数的:
在这里插入图片描述
如果我们进行初始化了,那么值当然就是初始化的值。但是如果我们像上面参数显示的那样去调用默认构造,我们发现不传参的时候默认值是0,传参的时候就是将参数的值给变量。这个用法和自定义类型是一样的。所以我们不用担心自定义类型会使用不了的问题。

对于reserve:
reserve函数就是预留空间,因为c++标准没有明确规定一些细节,导致不同平台对于其实现是有差异的。

vs编译器下坚决不缩容,只会扩容,且扩容大致是1.5倍。
而g++编译器是会缩容的,扩容的方式是很标准的2倍扩容。

增删查改操作

函数说明
push_back(重点)尾插
pop_back (重点)尾删
find查找(注意这个是算法模块实现,不是vector的成员接口)
insert在position之前插入val
erase删除position位置的数据
swap交换两个vector的数据空间
operator[](重点) 像数组一样访问

只需要注意的是inserterase的操作是要传入对应位置的迭代器。还有就是vector内并没有像string内实现find接口,我们要使用的话就得在算法库algorithm中去调用。

而其余的用法是很简单的,自行查阅文档即可。

迭代器失效问题(重要)

string中也有迭代器,但是我们并没有说迭代器失效的问题。这是因为对于string我们更偏向于用下标来访问内部数据。就连删除插入等操作也是用下标进行访问元素的。而vector却是用迭代器实现的,而在实现的时候发现了一些问题,这些问题统称为迭代器失效,下面让我们一起来看看:

在这里我们先说明一个事情:vector的正向迭代器其实就是数据类型的原生指针。

1.迭代器变为野指针,原来指向的空间被释放
这种问题通常出现在vector容量被修改的时候。我们在模拟实现string的时候就对容量进行修改的时候,是需要新开辟一个空间,然后再将原来空间进行释放。是没有像c语言中realloc一样功能的函数的。

问题就出现在这里:
在这里插入图片描述
当然如果要进行缩容也是一样的。所以插入和删除函数在实现的时候,就考虑到这个问题,进行了修正。使得这两个功能能够正常的使用并且达到想要的效果。

2.非法使用迭代器和非法访问元素
这个点也是需要非常注意的,下面我们举一个例子看看:

现有vector v1,指向内容是{1,2,2,3,4,4,5}

我们来看一下下面这个代码:

int main(){vector<int>:: iterator it = v1.begin();while(it != v1.end()){if(*it % 2){erase(it);*it = -1;}++it;}return 0;
}

乍一看没啥问题,但其实问题很大。

首先这个代码在不同平台下的结果是不一样的。在vs2022上是会断言报错的。而在g++编译器上能够正常运行,但是达不到想要的效果。

我们先来说g++下的情况:
运行结果为 1 -1 3 -1 5,这是为什么呢?
这是因为我们非法使用迭代器了:

当遍历到第一个2的时候,就会进行删除操作,那后续的数据会被移动到前面来,数据变成{1,2,3,4,4,5}。原来的2的位置被后面一个2顶上来了。但此时原来的空间并没有被销毁,而正向迭代器的本质是原生指针,所以指向的仍然是原来的那个位置,也就是后顶上来的2的位置,然后对此时位置进行修改,数组变成{1,-1,3,4,4,5}。然后++it会走到3的那个位置。

然后以此类推,最后变成了输出的结果。变成-1的位置就是为了告诉我们,如果使用这个代码去删除偶数项,会有被遗漏的偶数。这其实也是迭代器失效的一个方面。就是会导致访问元素出现问题。

如果在vs2022下:
编译器会直接断言报错。这是因为vs编译器做了严格的检查,如果再执行了删除和插入操作后,迭代器会失效,一般是不能访问的。所以编译器内部自动检查是否有修正迭代器的情况。如果没有就会报错。因为编译器认为这样子是非法访问。

当然对于上面那段代码,如果被删除的元素在末尾也是会出现问题,因为删除后数量减一,但是又要执行++it操作,那么it会越界。也是会触发断言报错的。

调整迭代器

这些都是迭代器失效的问题。为了 防止这种现象发生,我们就得调整迭代器的值。实际上vs编译器也是这么做的。

对于插入操作,编译器会返回新插入的元素的第一个的迭代器。插入操作可能会插入一个怨怒是,有可能插入多个元素。对此返回的是插入元素的第一个位置的迭代器。

对于删除操作,返回的是删除元素的最后一个的后一个元素的新位置的迭代器:
如1 3 5 6 7,删除3 和 5 ,变成 1 6 7,返回的就是指向6的迭代器。如果删除的最后一个元素正好是原本空间中的最后一个元素,那么返回的迭代器其实是数组结束位置。

所以要想真正的能把上面例子种数组的偶数全部删除,需要改进代码:

int main(){vector<int>:: iterator it = v1.begin();while(it != v1.end()){if(*it % 2){it = v1.erase(it);}else ++it;}return 0;
}

这样子就能在vs的编译器上跑起来了。

vector的模拟实现

当然要想更好的学会使用vector,我们也是需要了解如何对vector中的一些重要功能进行实现的。

实现的版本

c++只是规定了容器对应的功能应该完成什么样的效果,但是并没有明确要求应该如何实现。所以不同的版本实现是有一些区别的。

vs2022中的实现其实是非常复杂的,涉及到内存池等内容。由于当前还未学习内存池等相关技术,所以并不适合模仿。而我们可以查看一下g++编译器的底层是如何实现的:
在这里插入图片描述
这个是g++编译器下实现版本的比较早期的源代码,我们发现protected成员里面有三个迭代器,分别是startfinishend_of_storage

我们再翻看一下迭代器是怎么实现的:
在这里插入图片描述
正向迭代器其实就是value_type*这个指针,也就是数据类型的指针。所以对于vector来讲,其正向迭代器就是原生指针。

而以往我们在实现顺序表的时候,都是一个指针配合整形数据空间、容量进行管理内容。但是在vector的源代码中我们发现是通过指针管理的。

start就是指向开头数据的指针,finish其实是有效数据的后一个位置,end_of_storage指示容量,就是当前已有空间的后一个位置。

我们实现的版本主要是这个版本。

模拟实现

源码放在我的码云上了:vector imitate achievement

既然是使用指针实现的,那我们就特别需要注意指针的一些问题,特别是野指针。需要我们能够正确的操作这几个指针变量。

结构

vector是一个类模板,和string不太一样。所以我们声名的是一个类模板,需要定义模板参数。使用模板的话就尽量不要将函数的声名和定义进行分离了,因为会导致链接错误。

所以我们采取以下策略:
MyVector这个命名空间内定义类模板,将短小多次调用的函数放在类里面进行定义。因为默认内联,方便多次调用。而代码量比较长的就放在类外进行定义。

预先处理的函数

还是一样的,有一些函数由于会被很多次的调用,所以我们需要先处理一下。

尾插函数push_back

尾插函数是非常重要的。特别是在写构造函数的时候,我们可以提前开好空间,然后将需要的数据依次插入到vector指向的空间中。

所以我们可以实现一下尾插函数:

void push_back(const T& x){if (_end_of_storage == _finish) {reserve((size() == 0) ? 4 : size() * 2);}*_finish = x;++_finish;
}

这里的reserve函数虽然还没写,但是当前符合逻辑就可以。只要能在调用方尾插函数前写完就好。

当然目前先不写的原因是会有特殊情况,这一点我们等下会说。

swap函数

这个交换函数最大的目的就是为了方便进行深拷贝,其实现也是非常简单:

void swap(vector<T>& v) {std::swap(_start, v._start);  std::swap(_finish, v._finish);std::swap(_end_of_storage, v._end_of_storage); 
}

我们直接调用标准库内的交换函数就可以了。

赋值重载

这个是非常重要的,在等一下的reserve函数中也是需要调用。

这个方法很简单的,在上一节string实现深拷贝的优化中我们就已经实现过了,所以就不再过多赘述。

T& operator=(vector<T> v) {swap(v);return *this;
}

其实就是调用拷贝构造,构造一个v,使得v就是需要赋值的数据。当然当前我们也没有实现拷贝构造。但是不要急,我们当前主要还是得厘清逻辑。保证当前接口主逻辑不会出错即可。

使用这个方法最大的有点就是不用担心自己给自己赋值。如果要自己开空间来操作赋值,就得先删掉空间,再来赋值。但是当自己给自己赋值的时候,就会导致原有数据被销毁。所以需要判断这个特殊情况。

size函数

这个没太多好说的,返回容量就好。

size_t size() const{return _finish - _start;
}
reserve函数

因为空间的浪费不会太大,所以为了效率更高,防止频繁扩容,所以我们采用vs底层一样的实现方法,reserve坚决不缩容。

template<class T>
void vector<T>::reserve(size_t n) {if (n <= size()) return; size_t old_size = size(); T* tmp = new T[n]; //复制for (int i = 0; i < old_size; ++i) { tmp[i] = _start[i]; }delete[] _start; _start = tmp; _finish = _start + old_size;  _end_of_storage = _start + n; 
}

由于代码量还是比较长的,我们放在类外面进行定义。

这里会有几个很容易出错的点:
1.管理内容的三个指针失效
此时我们不再是使用整形变量管理空间。而是使用指针。使用指针最怕的就是野指针。当我们扩容的时候,又需要将旧空间进行释放。那就会导致_finishend_of_storage两个指针变成野指针。所以需要调整这两个指针的位置。

但是很多人这么写的:

    _start = tmp; _finish = _start + size();  _end_of_storage = _start + n; 

这样子会出问题。因为size返回的是当前_start_finish的位置之差。但是我们发现一个事情,就是当我们先让_start指向新空间的时候,_finish会指向被释放的空间。那么这样算出来的size肯定不对。

还有就是从代入表达式的角度看,size()返回的是_start - _finish,代入表达式,
_finish = _start + _start - _finish = _finish,这根本没有改变啊。

所以我们得先记录一下_start_finish原本的差值,也就是有效数据个数old_size,然后再以此进行调整。源码中也是这么干的:
在这里插入图片描述
所以我们就学习这个方法进行调整这三个管理指针。

还有一个问题就是复制,在之前模拟实现string的时候我们是使用memcpy函数进行操作的。但是再vector中万万不能。

假设我们现在声名的是一个vector< string >,如果使用的是memcpy函数,就是将里面内容一个字节一个字节拷贝过去,这个方式是浅拷贝。那一旦碰到有指向资源的数据类型如string那就糟了,那资源是没有办法复制过去的。又或者是vector,也是有指向资源。这是万万不能的。具体内容可以看类和对象章节。

所以复制部分是要调用赋值重载的。这也就是为什么我们要先写赋值重载函数,就是怕内部存储的也是vector(自己写的),那就需要调用其赋值重载函数,那我们就得提供。

迭代器
//实现正向迭代器用的
typedef T* iterator;
typedef const T* const_iterator;//iterator
iterator begin() {return _start;
}
iterator end() {return _finish;
}
const_iterator cbegin() const { return _start;
}
const_iterator cend() const {return _finish;
}

迭代器就是原生指针,实现非常简单。

默认成员函数
默认构造
vector()
{}

我们会在定义三个管理指针的时候给定缺省参数nullptr,所以不需要存储任何东西的时候就不需要进行任何操作,所以无参构造函数这样写即可。

普通构造
vector(size_t n, const T& val = T()) {reserve(n);while (_finish != _end_of_storage){push_back(val);   ++_finish; }
}vector(int n, const T& val = T()) {reserve(n);while (_finish != _end_of_storage) {push_back(val);}
}template<class InputIterator>
vector(InputIterator first, InputIterator last) {while (first != last) {push_back(*first);++first;}
}

这里的const T& val = T()是调用默认构造函数,前面部分已经讲过了。
这里有三个函数,最后一个是迭代器区间构造。

有人看到前面两个仅仅是n参数类型不同,为什么要多次一举写多一个呢?

这是因为当我们想要这样写的时候vector(5, 4);,会导致一个问题,因为不写第二个的那个版本,5在编译器中默认为int,4也为int,那么第一个就不是那么的匹配。而传给迭代器区间的时候会更加匹配一些,所以编译器会调用迭代器区间构造的那个。所以我们需要多写一个。而使用其他类型的时候就不会有这个问题。

拷贝构造
vector(const vector<T>& v) {reserve(v.size());const_iterator it = v.cbegin();while (it != v.cend()) {push_back(*it);++it;}
}

这里没有采用以往的那个深拷贝的优化方法。因为在string实现中,我们可以把传入的string的指向字符串的指针拿去构造出一个一样的string。而我们在这里并没有实现这么一个函数,因为只有字符出啊怒这样做是比较方便。所以我们直接自己开空间进行尾插即可。

析构函数
~vector() {delete[] _start;_start = _finish = _end_of_storage = nullptr; 
}

析构函数比较简单,就不再赘述。

容量操作

现在来对容量的操作进行实现

容量 、判空
size_t capacity() const {return _end_of_storage - _start;
}bool empty() const {return (_start == _finish);
}

数据个数已经在预处理部分处理过了,所以只需要实现一下容量和判空即可。

resize函数
template<class T> 
void vector<T>::resize(size_t n, const T& val) {  //声名与定义分离时 定义中不能有缺省参数if (n <= size()) {_finish = _start + n; }else {if (_finish + n > _end_of_storage) { size_t old_size = size();size_t old_capacity = capacity();reserve( old_size + n > old_capacity * 2 ? (old_size + n) : old_capacity * 2);}while (_finish != _end_of_storage) { push_back(val); } }
}

根据文档的要求进行实现即可。注意是否需要删除数据以及扩容即可。

修改操作
尾删
void pop_back() {assert(!empty());   --_finish;
}

需要注意是否为空,否则无法删除。

insert函数
template<class T>
typename vector<T>::iterator vector<T>::insert
(typename vector<T>::iterator pos, const T& val) { assert(pos <= _finish);assert(pos >= _start);size_t old_size = size();size_t posdiff = pos - _start;//必须写这个 要不然迭代器失效了if (_finish == _end_of_storage) {reserve((old_size == 0) ? 4 : 2 * old_size);pos = _start + posdiff;}//挪动数据typename vector<T>::iterator it = end() - 1;while (it >= pos) {*(it + 1) = *it;--it;}*pos = val;++_finish;return pos;
}

很多人会疑问,为什么要加typename这个关键字呢?这是因为我们是在类外面定义这个函数。而这个类此时还没有实例化,那要从里面取东西是需要特别注意的。对于iterator,编译器会不知道这是一个变量还是类型名称。所以加上typename就是告诉编译器这是一个类型。

注意一下之前讲过的迭代器失效的问题即可。

clear函数

这个函数只对数据进行清空,但是不进行缩容:

void clear() {_finish = _start;
}
erase函数

这个函数实现了两个版本:

template<class T>
typename vector<T>::iterator vector<T>::erase
(typename vector<T>::iterator pos) {assert(pos >= _start);assert(pos <= _finish);//不考虑缩容typename vector<T>::iterator it = pos;while (it != end()) {*it = *(it + 1);++it;}--_finish;return pos;
}template<class T>
typename vector<T>::iterator vector<T>::erase
(typename vector<T>::iterator first, typename vector<T>::iterator last) {assert(first <= last);assert(first >= _start);assert(last <= _finish);assert(!(first == end() && last == end()));vector<T>::iterator prev = first; vector<T>::iterator rear = last + 1;while (rear != end()) {*prev = *rear;++prev;++rear;}_finish = prev;return first;
}

当然insert函数也是可以实现迭代器区间插入的(我忘记了哈哈哈哈哈),逻辑都不难,最主要的就是注意一下删除的位置是否合法(通过断言报错),然后实现数据挪动逻辑。然后需要注意迭代器失效得问题,要通过返回值来修正。

最好是通过画图来赋值写代码,然后考虑一下一些特殊位置即可。

打印函数(针对不同容器)
template<class Container>
void Print_container(Container& con) { for (auto& x : con) { cout << x << " "; }cout << endl;  
}

通过传入容器以及范围for得使用就可以实现了,这是十分简单的。

到此所有的操作就完成了,想要更详细代码的可以进入我的码云空间获取。

相关文章:

c++STL——vector的使用和模拟实现

文章目录 vector的使用和模拟实现vector的使用vector介绍重点接口的讲解迭代器部分默认成员函数空间操作增删查改操作迭代器失效问题(重要)调整迭代器 vector的模拟实现实现的版本模拟实现结构预先处理的函数尾插函数push_backswap函数赋值重载size函数reserve函数 迭代器默认成…...

git更新的bug

文章目录 1. 问题2. 分析 1. 问题 拉取了一个项目后遇到了这个问题&#xff0c; nvocation failed Server returned invalid Response. java.lang.RuntimeException: Invocation failed Server returned invalid Response. at git4idea.GitAppUtil.sendXmlRequest(GitAppUtil…...

github | 仓库权限管理 | 开权限

省流版总结&#xff1a; github 给别人开权限&#xff1a;仓库 -> Setting -> Cllaborate -> Add people GitHub中 将公开仓库改为私有&#xff1a;仓库 -> Setting -> Danger Zone&#xff08;危险区&#xff09; ->Change repository visibility( 更改仓…...

MQTT客户端核心架构解析:clients.h源码深度解读

MQTT客户端核心架构解析&#xff1a;clients.h源码深度解读 一、头文件概览与设计哲学 clients.h作为MQTT客户端核心数据结构定义文件&#xff0c;体现了以下设计原则&#xff1a; 分层架构&#xff1a;网络层/协议层/业务层解耦状态管理&#xff1a;通过状态机实现复杂协议…...

uniapp自定义底部导航栏,解决下拉时候顶部空白的问题

一、背景 最近使用uniapp开发微信小程序&#xff0c;因为使用了自定义的顶部导航栏&#xff0c;所以在ios平台上&#xff08;Android未测试&#xff09;测试的时候&#xff0c;下拉的时候会出现整个页面下拉并且顶部留下大片空白的问题 二、任务&#xff1a;解决这个问题 经…...

C++学习之密码学知识

目录 1.文档介绍 2.知识点概述 3.项目准备 4.序列化介绍 5.项目中基础组件介绍 6.基础模块在项目中作用 7.项目中其他模块介绍 8.加密三要素 9.对称加密和非堆成加密 10.对称和非对称加密特点 11.堆成加密算法des 12.des对称加密算法 13.对称加密算法aes 14.知识点…...

力扣 797. 所有可能的路径

题目 给你一个有 n 个节点的 有向无环图&#xff08;DAG&#xff09;&#xff0c;请你找出所有从节点 0 到节点 n-1 的路径并输出&#xff08;不要求按特定顺序&#xff09; graph[i] 是一个从节点 i 可以访问的所有节点的列表&#xff08;即从节点 i 到节点 graph[i][j]存在一…...

第二篇:linux之Xshell使用及相关linux操作

第二篇&#xff1a;linux之Xshell使用及相关linux操作 文章目录 第二篇&#xff1a;linux之Xshell使用及相关linux操作一、Xshell使用1、Xshell安装2、Xshell使用 二、Bash Shell介绍与使用1、什么是Bash Shell(壳)&#xff1f;2、Bash Shell能干什么&#xff1f;3、平时如何使…...

全自动驾驶(FSD,Full Self-Driving)自动驾驶热点技术的成熟之处就是能判断道路修复修路,能自动利用类似“人眼”的摄像头进行驾驶!值得学习!

全自动驾驶&#xff08;FSD&#xff0c;Full Self-Driving&#xff09;软件是自动驾驶领域中的热点技术&#xff0c;其核心目标是实现车辆在各种复杂交通环境下的安全、稳定、高效自动驾驶。FSD软件的技术核心涉及多个方面的交叉技术&#xff0c;下面将详细分析说明其主要核心技…...

SpringBoot项目动态加载jar 实战级别

网上也找到类似的文章&#xff0c;但是基本都不到实用级别&#xff0c;就是不能直接用。在参照网上的文章及与AI沟通N次后终于完善可以在实际项目上 创建jar文件动态加载类 Component Slf4j public class PluginRegistry {Autowiredprivate GenericApplicationContext applicat…...

一种改进的CFAR算法用于目标检测(解决多目标掩蔽)

摘要 恒虚警率&#xff08;CFAR&#xff09;技术在雷达自动检测过程中起着关键作用。单元平均&#xff08;CA&#xff09;CFAR算法在几乎所有的多目标情况下都会受到掩蔽效应的影响。最小单元平均&#xff08;SOCA&#xff09;CFAR算法仅当干扰目标位于参考窗口的前后方时才具有…...

无人机+智能监控:石油管道巡检迈入“空中智慧时代”

引言&#xff1a;安全与效率的双重革命 在广袤的沙漠、崎岖的山脉或人迹罕至的冻土带&#xff0c;石油管道的安全巡检曾是一项耗时耗力且风险极高的任务。如今&#xff0c;随着无人机巡检技术与视频监控管理平台的深度融合&#xff0c;石油行业正迎来一场智能化变革——从“人巡…...

Python内置函数---anext()

用于异步迭代器的核心工具&#xff0c;专为处理异步数据流设计。 1. 基本语法 await anext(async_iterator, default) 参数&#xff1a; async_iterator &#xff1a;实现了异步迭代协议的对象&#xff08;如异步生成器、异步迭代器类&#xff09;。 default &#xff08;可选…...

4.17学习总结

完成135. 分发糖果 - 力扣&#xff08;LeetCode&#xff09;的算法 学习了字节缓冲流和字符缓冲流&#xff0c;了解了底层的原理&#xff0c;...

【gpt生成-其一】以go语言为例,详细描述一下 ​:语法规范​​BNF/EBNF形式化描述

在 Go 语言中通过 EBNF 形式化描述语法规范需要结合语言规范文档的结构&#xff0c;以下是详细实现方法及标准规范示例&#xff1a; 一、Go 语法规范结构&#xff08;基于 Go 1.21 标准&#xff09; ebnf 复制 // 基础元素定义 letter "A" ... "Z&quo…...

用cython将python程序打包成C++动态库(windows+Vistual Studio2017平台)

作为一名程序员我们都知道Python的库可能要比C的丰富的多特别是在算法方面&#xff0c;但是有的时候我们的工程是用C开发的&#xff0c;我们又像用Python的这个库那怎么办呢&#xff1f;如果直接调.py程序&#xff0c;工程中代码有.py又有.cpp显得工程很杂乱。那么我么可以借助…...

三层交换机SVI功能(交换机虚拟接口)实现各个实训室电脑网络可互通,原本是独立局域网

三层交换机 SVI功能&#xff08;交换机虚拟接口&#xff09; 实现VLAN路由 需求 &#xff1a;各实训室使用独立局域网&#xff0c;即每个实训有自己的IP网段&#xff0c; 每个实训室只有内部互相访问。 需求&#xff1a;为了加强各实训室学生的交流&#xff0c;学校要求我们…...

class的访问器成员

class的访问器成员 本质是 class 的语法糖 等价于对象的defineProperty对象里面也能使用 class Product{constructor(count, price){this.count count;this.price price;}get total(){ // 相当于getterreturn this.count * this.price;}}const product new Product(10, 10…...

vue入门:路由 router

文章目录 介绍安装配置路由模式嵌套路由路由传参编程式导航路由懒加载 底层原理 介绍 vue2 vue router API vue3 vue router API Vue Router 是 Vue.js 的官方路由管理器&#xff0c;它允许你通过不同的 URL 显示不同的组件&#xff0c;从而实现单页面应用&#xff08;SPA&a…...

JVM详解(曼波脑图版)

(✪ω✪)&#xff89; 好哒&#xff01;曼波会用最可爱的比喻给小白同学讲解JVM&#xff0c;准备好开启奇妙旅程了吗&#xff1f;(๑˃̵ᴗ˂̵)و &#x1f4cc; 思维导图 ━━━━━━━━━━━━━━━━━━━ &#x1f34e; JVM是什么&#xff1f;&#xff08;苹果式比…...

Prometheus thanos架构

Thanos 是一个用于扩展 Prometheus 的高可用性和长期存储的解决方案。它通过整合多个 Prometheus 实例&#xff0c;提供了全局查询、长期存储、以及高可用性的能力。Thanos 的架构主要由以下几个核心组件组成&#xff1a; 1. Sidecar 功能&#xff1a; Sidecar 是与每个 Prom…...

进程(Process)和进程管理

李升伟 整理 进程和进程管理是操作系统的核心概念之一&#xff0c;涉及计算机资源的分配、调度和执行控制。以下是详细的解释&#xff1a; 1. 进程的定义 进程&#xff08;Process&#xff09;是正在执行的程序实例&#xff0c;是操作系统进行资源分配和调度的基本单位。它包…...

更强的视觉 AI!更智能的多模态助手!Qwen2.5-VL-32B-Instruct-AWQ 来袭

Qwen2.5-VL-32B-Instruct 是阿里巴巴通义千问团队于 2025 年 3 月 24 日开源的多模态大模型&#xff0c;基于 Apache 2.0 协议发布。该模型在 Qwen2.5-VL 系列的基础上&#xff0c;通过强化学习技术优化&#xff0c;以 32B 参数规模实现了多模态能力的突破。 核心特性升级&…...

Linux系统中的Perf总结

Linux系统中的Perf总结 Perf 是一个集成在 Linux 内核中的强大性能分析工具&#xff0c;在 Ubuntu 系统上尤为实用。它可以帮助用户监控和分析 CPU、内存、I/O 等性能指标。本文将一步步详解 Perf 在 Ubuntu 系统中的安装、使用方法及进阶技巧&#xff0c;带你从入门走向精通。…...

每日算法-250417

每日算法 - 20250417 记录今天的算法学习过程&#xff0c;包含三道 LeetCode 题目。 1005. K 次取反后最大化的数组和 题目 思路 贪心 解题过程 想要获得最大的数组和&#xff0c;我们的目标是尽可能地增大数组元素的总和。一种有效的贪心策略是&#xff1a;每次选择数组中绝…...

16.4B参数仅激活2.8B!Kimi-VL-A3B开源:长文本、多模态、低成本的AI全能选手

近日&#xff0c;月之暗面&#xff08;Moonshot AI&#xff09;开源了Kimi-VL系列模型&#xff0c;包含Kimi-VL-A3B-Instruct&#xff08;指令调优版&#xff09;和Kimi-VL-A3B-Thinking&#xff08;推理增强版&#xff09;。这两款模型以总参数16.4B、激活参数仅2.8B的轻量化设…...

山东大学软件学院创新项目实训开发日志(17)之中医知识历史问答历史对话查看功能完善

本次完善了历史对话的查看功能&#xff0c;可以让其正常显示标题 后端&#xff1a;在conversationDTO功能构造方法添加title 前端&#xff1a;在历时会话按钮添加标题title即可 前端效果展示&#xff0c;成功(&#xff3e;&#xff0d;&#xff3e;)V&#xff1a;...

关于Java集合中对象字段的不同排序实现方式

&#x1f4ca; 关于Java集合中对象字段的不同排序实现方式 #Java集合 #排序算法 #Comparator #性能优化 一、排序基础&#xff1a;两种核心方式对比 方式Comparable接口Comparator接口实现位置目标类内部实现独立类或匿名内部类排序逻辑自然排序&#xff08;固定规则&#xf…...

ZKmall开源商城静态资源管理:Nginx 配置与优化

ZKmall开源商城作为电商平台&#xff0c;其商品图片、前端资源等静态内容的高效管理与分发直接影响用户体验和系统性能。基于Nginx的静态资源管理方案&#xff0c;结合动静分离、缓存优化、安全加固、性能调优四大核心策略&#xff0c;可显著提升平台响应速度与稳定性。以下是具…...

Google Gemini 系列AI模型 的详细解析,涵盖其技术特点、版本差异、应用场景及优势

以下是 Google Gemini 系列AI模型 的详细解析&#xff0c;涵盖其技术特点、版本差异、应用场景及优势&#xff1a; 1. Gemini 系列概述 发布背景&#xff1a; Google于2023年推出 Gemini 系列模型&#xff0c;作为其多模态大模型的里程碑&#xff0c;旨在结合文本、图像、音频…...