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

news2025/7/7 17:34:06

文章目录

  • 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得使用就可以实现了,这是十分简单的。

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

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

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

相关文章

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( 更改仓…

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、平时如何使…

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

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

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

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

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

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

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;苹果式比…

进程(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 参数规模实现了多模态能力的突破。 核心特性升级&…

每日算法-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;

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

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

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

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

量子通信应用:量子安全物联网(三)协议融合

第一部分:引言与概述 1.1 量子安全物联网的背景与必要性 随着物联网(IoT)设备的爆炸式增长(预计2030年全球连接设备超750亿台),传统安全机制(如RSA、ECC加密)正面临量子计算的颠覆性威胁。量子计算机的Shor算法可在多项式时间内破解非对称加密体系,而Grover算法则对…

鸿蒙API15 “一多开发”适配:解锁黄金三角法则,开启高效开发新旅程

一、引言 在万物互联的时代浪潮中&#xff0c;鸿蒙操作系统以其独特的 “一多开发” 理念&#xff0c;为开发者打开了一扇通往全场景应用开发的新大门。“一多开发”&#xff0c;即一次开发&#xff0c;多端部署 &#xff0c;旨在让开发者通过一套代码工程&#xff0c;就能高效…