STL--vector

目录

【本节目标】

1.vector的介绍及使用

1.1 vector的介绍

1.2 vector的使用及底层模拟实现

vector类中成员变量

1.2.1 vector的定义

1.2.2 vector iterator 的使用

1.2.3 vector 空间增长问题

1.2.3 vector 增删查改

1.2.4 vector 迭代器失效问题

1.2.5 使用memcpy拷贝问题


【本节目标】

1.vector的介绍及使用
2.vector深度剖析及模拟实现

1.vector的介绍及使用

1.1 vector的介绍

  1. vector是一个可变大小数组的序列容器
  2. 就如同数组一样,vector也采用的是连续存储空间来存储元素;又不同于数组,他的大小会被容器自动处理,大小会自动改变
  3. 本质上,vector使用动态分配数组来存储元素,新元素插入时,vector会重新分配一个内存更大的新数组,而将之前内存的元素都转移到这个数组中,释放之前的数组空间
  4. vector分配空间策略:vector会分配一些额外的空间以适应可能的增长,因为存储空间比实际需要的存储空间更大
  5. vector占用更多的存储空间,为了获得管理存储空间的能力,以一种有效方式动态增长
  6. vector在访问元素时会更加高效,尾删和尾插元素相对高效

1.2 vector的使用及底层模拟实现

vector类中成员变量

template<class T>
class vector
{public:......private:T* _start;    //指向数组内存的开始T* _finish;   //指向数组内存结束的下一个T* _endofstorage;    //数组内存当前的最大容量
}

1.2.1 vector的定义

(constructor)构造函数声明接口说明
vector()(重点)无参构造
vector(size_type n, const value_type& val = value_type())构造并初始化n个val
vector (const vector& x); (重点)拷贝构造
vector (InputIterator first, InputIterator last);使用迭代器进行初始化构造

无参构造 vector()

vector():_start(nullptr),_finish(nullptr),_endofstorage(nullptr)
{}

 构造并且初始化n个val  vector(size_type n, const value_type& val = value_type())

//初始化一定数量的相同数据
vector(size_t n, const T& val=T())
{//扩容reserve(n);for (size_t i = 0; i < n; i++){push_back(val);}
}//重载函数解决 vector<int> v1(10, 1);的调用错误
vector(int n, const T& val = T())
{//扩容reserve(n);for (size_t i = 0; i < n; i++){push_back(val);}
}

由于用上面第一个函数时,用如下的代码,可能会导致寻址错误,会与使用迭代器进行初始化构造相冲突,导致调用构造的时候会调用与使用迭代器进行初始化构造

vector<int> v1(10, 1);

由于类型转换,则会两次类型转换,与使用迭代器进行初始化构造,会只用依次匹配成功,则会选用与使用迭代器进行初始化构造

而下面代码则无影响

vector<int> v2(10, 1);
print_vector(v2);vector<char> v3(10, 'a');
print_vector(v3);

因而要解决寻址错误,则会直接重载一个会冲突的那个类型的函数

拷贝构造 vector (const vector& x)

//拷贝构造
//v1(v3)
vector(const vector<T>& v)
{//扩容reserve(v.capacity());//对于不确定的类型T,加上&for (auto& e : v){push_back(e);}
}

对于不确定的类型迭代器要加上&,将遍历v中数据插入到this中

使用迭代器进行初始化构造 vector (InputIterator first, InputIterator last)

//如果写iterator的话,会只允许vector的迭代器初始化
//类模板的成员函数是函数模板
//部分初始化
template<class Inputiterator>
vector(Inputiterator first, Inputiterator end)
{while (first != end){push_back(*first);first++;}
}

类模板的成员函数时函数模板

 C++11中初始化数组

//C++11大括号初始化数组vector<int> v1 = { 1,2,3,4,5,6,7,8,9,10 };
vector(initializer_list<T> il)
{//扩容reserve(il.size());//运用initializer_list 中的迭代器for (auto e : il){push_back(e);}
}

ininializer_list<T>类中可以实现大括号初始化数组,数组中的元素可以任意添加

// 隐式类型转换+优化
//vector<int> v1 = { 1,2,3,4,5,6,7,8,9,10 };
vector<int> v1{ 1,2,3,4,5,6,7,8,9,10 };

在C++11中也有这样的初始化方式

int i = 1;
// C++11,都可以实现
int j = { 1 };
int k{ 1 };

认识既可 

1.2.2 vector iterator 的使用

iterator的使用接口说明
begin +
end(重点)
获取第一个数据位置的iterator/const_iterator, 获取最后一个数据的下一个位置
的iterator/const_iterator
rbegin + rend获取最后一个数据位置的reverse_iterator,获取第一个数据前一个位置的
reverse_iterator

 iterator迭代器实现begin,end

typedef T* iterator;
typedef const T* const_iterator;//普通迭代器
iterator begin()
{return _start;
}iterator end()
{return _finish;
}//const迭代器
const_iterator begin() const
{return _start;
}const_iterator end() const
{return _finish;
}

迭代器的作用类似于指针,我这里以指针为例

1.2.3 vector 空间增长问题

容量空间接口说明
size获取数据个数
capacity获取容量大小
empty判断是否为空
resize(重点)改变vector的size
reserve (重点)改变vector的capacity

 1.capacity的代码在vs和g++下分别运行会发现,vs下capacity是按1.5倍增长的,g++是按2倍增长的。这个问题经常会考察,不要固化的认为,vector增容都是2倍,具体增长多少是根据具体的需求定义的。vs是PJ版本STL,g++是SGI版本STL。
2.reserve只负责开辟空间,如果确定知道需要用多少空间,reserve可以缓解vector增容的代价缺陷问题。
3.resize在开空间的同时还会进行初始化,影响size

 获取数据个数 size()

//大小
size_t size() const
{return _finish - _start;
}

获取容量大小 capacity()

size_t capacity() const
{return _endofstorage - _start;
}

const函数,指明this指向的对象数据不会被修改,并且const对象和普通对象都可以使用

 判断是否为空 empty()

//判断是否为空
bool empty()
{return _start == _finish;
}

改变vector的size resize()

//这里的T()指的是对象的默认构造,T是谁就是神的默认构造的值,int()=0
void resize(size_t n,const T& val=T())
{if (n > capacity()){//扩容reserve(n);//插入while (_finish < _start + n){*_finish = val;_finish++;}}else{//删除_finish = _start + n;}}

用if判断容量是否大于自身,从而判断是否需要扩容

resize函数的第二个参数的缺省值是用来初始化的,这个值由于不知道T的类型,用一个匿名对象T()来构造成功

改变vector的capacity reserve()

//扩容
void reserve(size_t n)
{if (n > capacity()){T *tmp = new T[n];	//开辟n个空间size_t old_size = size();//防止开辟内存时,_start更新以后,_finish与之不在同一段空间,提前拷贝//如果是string类型的vector扩容时,会导致string浅拷贝,两个内容会指向同一份空间//memcpy(tmp, _start, sizeof(T)*size());//将_start原有的数据内存空间拷贝到tmp中for (size_t i = 0; i < old_size; i++){tmp[i] = _start[i];}delete[] _start;_start = tmp;_finish = _start + old_size;_endofstorage = _start + n;}
}

如果是一个类类型的vector,如string时,开辟空间时可能会产生浅拷贝,memcpy导致的,memcpy是将内存的数据拷贝一份到另一段内存,但即两个内存的对象都指向同一个字符空间

1.2.3 vector 增删查改

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

尾插 push_back(const T& val)

//尾插
void push_back(const T& val)
{//扩容if (_finish == _endofstorage){//扩容2倍reserve(capacity() == 0 ? 4 : capacity() * 2);}*_finish = val;_finish++;
}

尾删 pop_back()

//尾删
void pop_back()
{//判断是否为空assert(!empty());--_finish;
}

要注意判断是否为空,空时删除无效

在position之前插入val insert(iterator pos,const T& val)

//插入
//指针pos
void insert(iterator pos,const T& val)
{assert(pos >= _start);assert(pos < _finish);//判断扩容if (_finish == _endofstorage){//扩容会导致_start会指向另一段新空间,而pos没有改变到新空间,还是在就空间里//计算pos的位置size_t len = pos - _start;reserve(capacity() == 0 ? 4 : capacity() * 2);//更新pospos = _start + len;}//插入//由于这里是指针,就不用在意和string产生的无符号整数会无限循环iterator it = _finish - 1;while (it >= pos){*(it + 1) = *it;it--;}*pos = val;++_finish;
}

这里的iterator指的是前面T*,typedef T* iterator

使用时

v2.insert(v2.begin(), 11.11);
print_vector(v2);

删除position位置的数据 erase(iterator pos)

//删除
void erase(iterator pos)
{assert(pos >= _start);assert(pos < _finish);iterator it = pos + 1;while (it < _finish){*it = *it + 1;it++;}_finish--;
}

注意要判断pos指向的位置是否在_start和_finish之间,删除时,是将pos之后的位置的数据向前移动覆盖之前的数据

交换两个vector的数据空间 swap(const vector<T>& v)

//交换
void swap(const vector<T>& v)
{std::swap(this->_start, v._start);std::swap(this->_finish, v._finish);std::swap(this->_endofstorage, v._endofstorage);
}

赋值重载 vector<T> operator=(vector<T> v)

//赋值重载
//v1=v3
//v3的拷贝赋值给v1,保证不改变v3的值
vector<T> operator=(vector<T> v)
{//内存不够,扩容reserve(v.capacity());swap(v);
}

赋值重载时,要v3的值给v1,由于v3的改变不能导致v1的改变,所以参数是值传递的拷贝

 像数组一样访问 T operator[](size_t pos)

//运算符重载
T& operator[](size_t pos)
{assert(pos >= 0);return _start[pos];
}const T& operator[](size_t pos) const
{assert(pos >= 0);return _start[pos];
}

将const类型对象和普通对象分开来定义使用

1.2.4 vector 迭代器失效问题

迭代器的主要作用就是让算法能够不用关心底层数据结构,其底层实际就是一个指针,或者是对指针进行了封装,比如:vector的迭代器就是原生态指针T* 。因此迭代器失效,实际就是迭代器底层对应指针所指向的空间被销毁了,而使用一块已经被释放的空间,造成的后果是程序崩溃(即如果继续使用已经失效的迭代器,程序可能会崩溃)。

 对于vector可能会导致其迭代器失效的操作有:
1. 会引起其底层空间改变的操作,都有可能是迭代器失效,比如:resize、reserve、insert、assign、push_back等。

#include <iostream>
using namespace std;
#include <vector>
int main()
{
vector<int> v{1,2,3,4,5,6};
auto it = v.begin();
// 将有效元素个数增加到100个,多出的位置使用8填充,操作期间底层会扩容
// v.resize(100, 8);
// reserve的作用就是改变扩容大小但不改变有效元素个数,操作期间可能会引起底层容量改变
// v.reserve(100);
// 插入元素期间,可能会引起扩容,而导致原空间被释放
// v.insert(v.begin(), 0);
// v.push_back(8);
// 给vector重新赋值,可能会引起底层容量改变
v.assign(100, 8);
/*
出错原因:以上操作,都有可能会导致vector扩容,也就是说vector底层原理旧空间被释放掉,
而在打印时,it还使用的是释放之间的旧空间,在对it迭代器操作时,实际操作的是一块已经被释放的
空间,而引起代码运行时崩溃。
解决方式:在以上操作完成之后,如果想要继续通过迭代器操作vector中的元素,只需给it重新
赋值即可。
*/
while(it != v.end())
{
cout<< *it << " " ;
++it;
}
cout<<endl;
return 0;
}

insert导致的迭代器失效

由于insert扩容时,迭代器it还在指向着原本的空间,所以迭代器失效,而需要更新迭代器

2. 指定位置元素的删除操作--erase

(1)这是正常的

(2)结果出错

由于erase是不断的向前覆盖由于每次it++则会跳过数据删除,导致迭代器错误

3.程序崩溃

这个是由于数据覆盖导致了访问越界了

更新迭代器 

vector<int> v1;
v1.push_back(1);
v1.push_back(2);
v1.push_back(3);
v1.push_back(4);
v1.push_back(4);
v1.push_back(5);
//v1.push_back(4);// 删除偶数 -- 迭代器失效以后,不要直接使用,如果要使用按规则重新更新后使用
//std::vector<int>::iterator it = v1.begin();
vector<int>::iterator it = v1.begin();// 21:15
//cout << typeid(it).name() << endl;
while (it != v1.end())
{if (*it % 2 == 0){it = v1.erase(it);}else{++it;}
}

 3. 注意:Linux下,g++编译器对迭代器失效的检测并不是非常严格,处理也没有vs下极端

// 1. 扩容之后,迭代器已经失效了,程序虽然可以运行,但是运行结果已经不对了
int main()
{
vector<int> v{1,2,3,4,5};
for(size_t i = 0; i < v.size(); ++i)
cout << v[i] << " ";
cout << endl;
auto it = v.begin();
cout << "扩容之前,vector的容量为: " << v.capacity() << endl;
// 通过reserve将底层空间设置为100,目的是为了让vector的迭代器失效
v.reserve(100);
cout << "扩容之后,vector的容量为: " << v.capacity() << endl;
// 经过上述reserve之后,it迭代器肯定会失效,在vs下程序就直接崩溃了,但是linux下不会
// 虽然可能运行,但是输出的结果是不对的
while(it != v.end())
{
cout << *it << " ";
++it;
}
cout << endl;
return 0;
}
程序输出:
1 2 3 4 5
扩容之前,vector的容量为: 5
扩容之后,vector的容量为: 100
0 2 3 4 5 409 1 2 3 4 5
// 2. erase删除任意位置代码后,linux下迭代器并没有失效
// 因为空间还是原来的空间,后序元素往前搬移了,it的位置还是有效的
#include <vector>
#include <algorithm>
int main()
{
vector<int> v{1,2,3,4,5};
vector<int>::iterator it = find(v.begin(), v.end(), 3);
v.erase(it);
cout << *it << endl;
while(it != v.end())
{
cout << *it << " ";
++it;
}
cout << endl;
return 0;
}
程序可以正常运行,并打印:
4 4
5
// 3: erase删除的迭代器如果是最后一个元素,删除之后it已经超过end
// 此时迭代器是无效的,++it导致程序崩溃
int main()
{
vector<int> v{1,2,3,4,5};
// vector<int> v{1,2,3,4,5,6};
auto it = v.begin();
while(it != v.end())
{
if(*it % 2 == 0)
v.erase(it);
++it;
}
for(auto e : v)
cout << e << " ";
cout << endl;
return 0;
}
========================================================
// 使用第一组数据时,程序可以运行
[sly@VM-0-3-centos 20220114]$ g++ testVector.cpp -std=c++11
[sly@VM-0-3-centos 20220114]$ ./a.out
1 3 5
=========================================================
// 使用第二组数据时,程序最终会崩溃
[sly@VM-0-3-centos 20220114]$ vim testVector.cpp
[sly@VM-0-3-centos 20220114]$ g++ testVector.cpp -std=c++11
[sly@VM-0-3-centos 20220114]$ ./a.out
Segmentation fault

从上述三个例子中可以看到:SGI STL中,迭代器失效后,代码并不一定会崩溃,但是运行结果肯定不对,如果it不在begin和end范围内,肯定会崩溃的
 

 4. 与vector类似,string在插入+扩容操作+erase之后,迭代器也会失效

#include <string>
void TestString()
{
string s("hello");
auto it = s.begin();
// 放开之后代码会崩溃,因为resize到20会string会进行扩容
// 扩容之后,it指向之前旧空间已经被释放了,该迭代器就失效了
// 后序打印时,再访问it指向的空间程序就会崩溃
//s.resize(20, '!');
while (it != s.end())
{
cout << *it;
++it;
}
cout << endl;
it = s.begin();
while (it != s.end())
{
it = s.erase(it);
// 按照下面方式写,运行时程序会崩溃,因为erase(it)之后
// it位置的迭代器就失效了
// s.erase(it);
++it;
}
}

迭代器失效解决办法:在使用前,对迭代器重新赋值即可

1.2.5 使用memcpy拷贝问题

reserve中memcpy拷贝问题

//扩容
void reserve(size_t n)
{if (n > capacity()){T *tmp = new T[n];	//开辟n个空间size_t old_size = size();//防止开辟内存时,_start更新以后,_finish与之不在同一段空间,提前拷贝//如果是string类型的vector扩容时,会导致string浅拷贝,两个内容会指向同一份空间memcpy(tmp, _start, sizeof(T)*size());//将_start原有的数据内存空间拷贝到tmp中_start = tmp;_finish = _start + old_size;_endofstorage = _start + n;}
}
int main()
{
bite::vector<bite::string> v;
v.push_back("1111");
v.push_back("2222");
v.push_back("3333");
return 0;
}

问题分析:
1. memcpy是内存的二进制格式拷贝,将一段内存空间中内容原封不动的拷贝到另外一段内存空间中
2. 如果拷贝的是自定义类型的元素,memcpy既高效又不会出错,但如果拷贝的是自定义类型元素,并且自定义类型元素中涉及到资源管理时,就会出错,因为memcpy的拷贝实际是浅拷贝

结论:如果对象中涉及到资源管理时,千万不能使用memcpy进行对象之间的拷贝,因为memcpy是浅拷贝,否则可能会引起内存泄漏甚至程序崩溃

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

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

相关文章

git工具上传文件超过100MB解决方法

Github 上传超过100M的大文件 - 简书 (jianshu.com) 看到一个不错的贴子。 29660DESKTOP-CAB6SQB MINGW64 /d/predict-system $ git init Initialized empty Git repository in D:/predict-system/.git/29660DESKTOP-CAB6SQB MINGW64 /d/predict-system (master) $ git lfs tr…

基于java+springboot+vue实现的居家养老健康管理系统(文末源码+Lw)23-313

摘 要 传统办法管理信息首先需要花费的时间比较多&#xff0c;其次数据出错率比较高&#xff0c;而且对错误的数据进行更改也比较困难&#xff0c;最后&#xff0c;检索数据费事费力。因此&#xff0c;在计算机上安装智慧社区居家养老健康管理系统软件来发挥其高效地信息处理…

设计模式-享元模式(Flyweight)

1. 概念 享元模式是一种结构型设计模式&#xff0c;它通过共享技术有效地支持大量细粒度对象的复用。 2. 原理结构图 图1 图2 2. 1 角色 抽象享元&#xff08;Flyweight&#xff09;&#xff1a;这是所有具体享元类的基类&#xff0c;它定义了享元对象的内部状态和外部状…

java快速幂算法

快速幂算法 参考视频(参考五角七边up大佬&#xff09; 幂运算的介绍 幂运算是指将一个数自身乘以自身多次的运算&#xff0c;其表达式为 a n a^n an&#xff0c;其中 a a a 是底数&#xff0c; n n n 是指数。 快速幂解释 快速幂算法是一种用于快速计算幂运算的算法&…

[当人工智能遇上安全] 13.威胁情报实体识别 (3)利用keras构建CNN-BiLSTM-ATT-CRF实体识别模型

《当人工智能遇上安全》系列将详细介绍人工智能与安全相关的论文、实践&#xff0c;并分享各种案例&#xff0c;涉及恶意代码检测、恶意请求识别、入侵检测、对抗样本等等。只想更好地帮助初学者&#xff0c;更加成体系的分享新知识。该系列文章会更加聚焦&#xff0c;更加学术…

Unity中支持泰语--没有版权限制

在Unity中支持泰语主要涉及以下几个方面&#xff1a; 选择合适的字体&#xff1a;在Unity中&#xff0c;确保使用支持泰文字符的字体是至关重要的。例如&#xff0c;可以选择使用Noto Serif Thai字体&#xff0c;这是一个支持泰语的字体2。 处理Unity版本问题&#xff1a;某些…

CentOS 网卡ifcfg-eth0 ping不通外网(www.baidu.com)

1、如果确认好就直接激活网卡&#xff01; ifup eth0 2、慢慢找&#xff1a; cd /etc/sysconfig/network-scripts/ ls 找到你的网卡是啥&#xff0c;这里网卡是 ifcfg-eth0 执行1就好了&#xff01;

JetBrains PyCharm 2024.1 发布 - 面向专业开发者的 Python IDE

JetBrains PyCharm 2024.1 发布 - 面向专业开发者的 Python IDE 请访问原文链接&#xff1a;JetBrains PyCharm 2024.1 (macOS, Linux, Windows) - 面向专业开发者的 Python IDE&#xff0c;查看最新版。原创作品&#xff0c;转载请保留出处。 作者主页&#xff1a;sysin.org…

acwing2060. 奶牛选美

题目&#xff1a; 代码&#xff1a; //acwing2060. 奶牛选美 #include<iostream> #include<cstring> #include<algorithm> using namespace std; const int N55; const int dx[]{-1,0,1,0},dy[]{0,-1,0,1}; bool st[N][N]; int point[N][N]; char map[N][…

MYBATIS获取参数值

MYBATIS最核心的莫过于动态的获取各种的参数值, 为了将来更好的使用MYBATIS进行开发, 我们必须先打好 "获取参数值" 这一基础 一. MYBATIS获取参数值的两种情况: 1.${} 实质:字符串的拼接 注解:${}使用的字符串拼接的方式拼接SQL语句, 所以, 如果其中出现了字符串…

APP下载页前端自适应HTML源码

源码介绍 APP下载页前端自适应HTML源码&#xff0c;可以作为自己的软件介绍页或者app下载页&#xff0c;喜欢的朋友可以拿去研究 效果预览 HTML源码下载 https://www.qqmu.com/3026.html

Flink WordCount实践

目录 前提条件 基本准备 批处理API实现WordCount 流处理API实现WordCount 数据源是文件 数据源是socket文本流 打包 提交到集群运行 命令行提交作业 Web UI提交作业 上传代码到gitee 前提条件 Windows安装好jdk8、Maven3、IDEA Linux安装好Flink集群&#xff0c;可…

Java并发(1)--线程,进程,以及缓存

线程和进程是什么&#xff1f; 进程 进程是程序的一次执行过程&#xff0c;系统程序的基本单位。有自己的main方法&#xff0c;并且主要由主方法运行起来的基本上就是进程。 线程 线程与进程相似&#xff0c;但线程是一个比进程更小的执行单位。一个进程在其执行的过程中可以…

MVCC(解决MySql中的并发事务的隔离性)

MVCC 如何保证事务的隔离性&#xff1f; 1.排他锁&#xff1a;如一个事务获取了一个数据行的排他锁&#xff0c;其他事务就不能再获取改行的其他锁。 2.MVCC&#xff1a;多版本并发控制。 MVCC&#xff1a; 1.隐藏字段 1.DB_TRX_ID&#xff1a;最近修改事务的id。默认值从0开…

【MYSQL】索引机制概述

由于MySQL是作为存储层部署在业务系统的最后端&#xff0c;所有的业务数据最终都要入库落盘&#xff0c;但随着一个项目在线上运行的时间越来越久&#xff0c;数据库中的数据量自然会越来越多&#xff0c;而数据体积出现增长后&#xff0c;当需要从表查询一些数据时&#xff0c…

javaScript设计模式之简单工厂模式

简单工厂模式(Simple Factory):又叫静态工厂方法&#xff0c;由一个工厂对象决定创建某一种产品对象类的实例。主要用来创建同一类对象。 场景一 假设我们需要计算圆形和矩形的面积 function Circle(radius) {this.radius radius;}Circle.prototype.getArea function() {re…

总结SQL相对常用的几个字符函数

目录 字符的截取 substr() trim()、ltrim()、rtrim() 字符串的拼接 ||、 字符的大小写转换 upper(column_name):大写 lower(column_name):小写 字符替换 replace() 搜索字符 instr(column_name, substring_to_find,start,n_appearence) charindex(substring_to_fi…

【问题解决】ubuntu安装新版vscode报code-insiders相关错误

问题 目前 vscode官网 最新的包为 insiders_1.89.0-1712297812_amd64.deb &#xff0c;双击或者使用sudo dpkg -i code-insiders_1.89.0-1712297812_amd64.deb安装后报错&#xff0c;执行其他命令也报错。 安装环境&#xff1a;ubuntu18.04 dpkg: 处理软件包 code-insiders (…

火绒安全软件:程序员的网络守护天使

目录 前言 系统防护 网络防护 隐私保护 高级设置 软件安全 响应速度 持续更新 总结 前言 在这个充满机遇与挑战的数字时代&#xff0c;程序员们如同探险家&#xff0c;不断探索着代码的新大陆。然而&#xff0c;网络世界也充斥着各种未知的风险和威胁。火绒安全软件&a…

基于java+springboot+vue实现的药品管理系统(文末源码+Lw)23-297

摘 要 传统信息的管理大部分依赖于管理人员的手工登记与管理&#xff0c;然而&#xff0c;随着近些年信息技术的迅猛发展&#xff0c;让许多比较老套的信息管理模式进行了更新迭代&#xff0c;药品信息因为其管理内容繁杂&#xff0c;管理数量繁多导致手工进行处理不能满足广…