C++《vector的模拟实现》
在之前《vector》章节当中我们学习了STL当中的vector基本的使用方法,了解了vector当中各个函数该如何使用,在学习当中我们发现了vector许多函数的使用是和我们之前学习过的string类的,但同时也发现vector当中一些函数以及接口是和string不同的。那么接下来在本篇我们就将试着模拟实现vector,在模拟实现过程当中我们就会了解当vector当中迭代器失效的问题,相信在本篇之后你会对vector有更深的了解,接下来就开始本篇的学习吧!!!
1.实现各个函数之前的工作
在模拟实现我们先要解决在模拟实现vector过程文件该如何创建
在vector由于是要是要使用到类模板来实现,因此在实现就和实现string不同不再将vector成员函数的声明和定义分离到两个文件,因此在此就创建一个头文件vecor.h来存放类vector的实现,在创建一个.cpp文件test.cpp来测试在vector当中实现的各个成员函数是否满足要求以及是否能正常运行。
注:在此为了避免我们实现的vector和std命名空间当中的vector冲突,因此就先新创建一个命名空间,之后将我们模拟实现的vector类放在创建的命名空间内,这样就不会出现冲突了
完成了文件的创建之后接下来就来分析在我们模拟实现的类vector当中成员变量该如何创建
在此你可能就会直接认为由于vector就是顺序表,那么就直接和之前在数据结构当中实现顺序表一样使用三个变量来实现即可,第一个变量就是指向数组的指针;第二个变量就是底层数组的有效元素个数;最后一个变量就是数组的内存空间大小
以上你这种也是可以实现的,但是在通过vector源码就可以发现在实现vector时,vector当中的成员变量是三个迭代器,这时你可能就会疑惑这三个迭代器分别表示的是什么呢?
其实在以上三个迭代器当中start指向的是vector对象内第一个元素,finish指向的是vector对象当中最后一个有效元素之后的位置,end_of_storage指向的是vector对象当中最后内存空间之后的位置。并且在此我们还要了解到由于vector的底层是用数组来实现的那么各个元素在物理逻辑上就是连续存放的,那么在vector当中迭代器底层就可以直接使用对应的指针来实现。那么这时将finish减start就可以得到有效元素,将end_of_storage减finish就可以此时底层数组的内存空间大小
因此通过以上的分析就可以发现使用vector源码当中的成员变量这种形式也是可以满足我们的要求的,那么接下来在模拟实现vector当中也按照源码的这种方式来实现成员变量
#include<iostream>
#include<assert.h>using namespace std;namespace zhz
{template<class T>class vector{public://成员函数. . .private:T* _start = nullptr;T* _finish = nullptr;T* _endofstorage = nullptr;};}
解决了以上的问题接下来就可以开始实现vector当中的各个成员函数了
2.vector模拟实现
在模拟实现vector我们是先实现插入和删除的函数,这时因为在构造函数当中通过调用插入的函数就可以实现各个接口的构造函数,这样就不需要我们自己来实现插入了这就让构造函数的代码写起来简单多了
2.1 size和capacity
在以上我们就分析出了在vector当中该如何来得到size与capacity,因此接下来就直接实现代码
在vector.h内实现函数的代码
size_t size()const
{return _finish - _start;
}size_t capacity()const
{return _endofstorage - _start;
}
2.2 下标+[ ]访问
在vector由于各个元素之间的物理空间是连续的,那么就可以实现[ ]的运算符重载函数,在此要实现const与非const对象的两个版本
在vector.h内实现函数的代码
T& operator[](size_t i)
{assert(i < size());return _start[i];
}
const T& operator[](size_t i)const
{assert(i < size());return _start[i];
}
注:在此在使用[ ]来访问vector对象内的元素不能超出对象内底层数组的大小,因此在此在实现 [ ]的运算符重载函数就需要使用assert断言
2.3 迭代器
在vector当中迭代器的实现直接使用指针来使用就可以满足要求,在此vector的迭代器就是原生态指针T*, 因此在此在实现begin和end函数之前先要将指针重命名为迭代器
typedef T* iterator;//普通迭代器
typedef const T* const_itreator;//const迭代器
完成以上操作就可以来实现begin和end函数的代码了
在vector.h内实现函数的代码
iterator begin()
{return _start;
}iterator end()
{return _finish;
}
//const迭代器
const_itreator begin()const
{return _start;
}
const_itreator end()const
{return _finish;
}
2.4 reserve和resize
通过之前的学习我们了解过了reverse是用于调整内存空间的大小,resize是用于调整有效元素,那么接下来我们就试着来实现这两个函数
先在vector.h内实现reserve函数的代码
在此和之前string实现一样只有当指定的大小比当前的空间大时才进行调整,这时要将原内存空间调整为大小为n的内存空间就需要先申请大小为n个变量的空间之后再将原空间内的数据拷贝到新申请的内存空间内,再将原空间释放。最后将类成员变量的指向改变就完成了
这时reserve实现出的代码就如下所示:
void reserve(int n)
{if (n > capacity()){T* tmp = new T[n];memcpy(tmp, _start, sizeof(T) * size());delete[] _start;_start = tmp;_finish = _start + size();_endofstorage = _start + n;}
}
以上的代码看起来完全可以满足我们的要求,但是其实存在两个严重的问题,你能看出来吗?
首先是在以上代码中我们将_start指向tmp指向的内存空间后,_finish = _start + size()这时在这条语句其实调用size()函数就有问题了,这时在进入size()函数之后由于_start已经指向新的内存空间但是_finish还是指向原空间,再将这两个指针相减就无法得到有效元素个数,并且会导致程序奔溃
那么要解决以上这个问题就先再改变_start之前创建一个变量oldsize将原来的有效元素个数存储在该变量内,之后_finish = _start + size()修改为_finish = _start + oldsize就可以解决该问题
void reserve(int n)
{if (n > capacity()){T* tmp = new T[n];size_t oldsize = size();memcpy(tmp, _start, sizeof(T) * size());delete[] _start;_start = tmp;_finish = _start + oldsize;_endofstorage = _start + n;}
}
以上代码其实还存在一个问题就是memcpy进行的是浅拷贝,这在T是内置类型时确实没什么问题,但我们实现的vector是类模板那么就是为了也能满足vector内的元素是自定义类型这种情况,那么这时再进行浅拷贝就会在使用delete出现问题
就例如当vector对象内的元素是string类型时
在使用完memcpy之后就会使得原内存空间内的string对象内的指针和新空间内string对象对象的指针指向同一块空间,这在调用delete将原空间释放时由于会想调用string的析构函数将string对象内指针指向的空间释放,那么在这之后就会使得在新空间内的string对象内的指针变为空指针
那么要解决以上这个问题就要再拷贝时进行深拷贝而不是浅拷贝
void reserve(int n)
{if (n > capacity()){T* tmp = new T[n];size_t oldsize = size();//memcpy(tmp, _start, sizeof(T) * size());//memcpy完成的是浅拷贝for (int i = 0; i < oldsize; i++){tmp[i] = _start[i];}delete[] _start;_start = tmp;_finish = _start + oldsize;_endofstorage = _start + n;}
}
完成了reserve函数之后接下来在vector.h内实现resize函数的代码
在此在resize函数当中要分为两种情况一种是当要调整的大小比原有效元素个数小时就直接改变_finish指针指向即可,但当要调整的大小比原有效元素个数大时就要从_finish指针开始插入新的元素;并且在最后还要改变_finish指针指向
这时resize实现出的代码就如下所示:
void resize(size_t n,T x=T())
{if (n < size()){_finish = _start + n;}else{ reserve(n);//当n比原内存空间还大时,避免多次扩容while (_finish < _start + n){*_finish = x;_finish++;}}
}
注:在以上代码在插入元素后使用缺少参数T(),在此使用到了匿名对象,这时由于类vector是类模板那么变量x的类型就是不确定的,因此就不能直接将变量的缺省值使用0或者nullptr来实现。因此在此就可以使用到之前在类和对象当中学到的匿名对象来实现这个缺省值
2.5 insert和erase
通过之前的学习我们知道insert和erase是分别实现任意位置的插入与删除,并且在vector当中这两个函数的接口都是迭代器
先在vector.h内实现reserve函数的代码
在insert函数当中要实现的是在pos迭代器位置插入指定的数据,在此在插入数据之前要先检查空间是否已经满了,如果满了就要先进行扩容,并且在此在vector当中我们是使用指针来实现遍历的这就不会出现之前我们在string当中实现insert使用下标时的问题
void insert(iterator pos, const T& x)
{assert(pos >= _start && pos <= _finish);//判断pos指针是否合法if (_finish == _endofstorage){reserve(capacity() == 0 ? 4 : 2 * capacity());//扩容}iterator i = _finish -1;while (pos <= i){*(i + 1) =*i;i--;}*pos = x;_finish++;}
在以上我们就实现了insert的代码,但是以上代码其实存在迭代器失效的问题,首先在解决以上问题之前我们先要来了解什么是迭代器失效
迭代器的主要作用就是让算法能够不用关心底层数据结构,其底层实际就是一个指针,或者是对指针进行了封装,比如:vector的迭代器就是原生态指针T* 。因此迭代器失效,实际就是迭代器底层对应指针所指向的空间被销毁了,而使用一块已经被释放的空间,造成的后果是程序崩溃(即如果继续使用已经失效的迭代器,程序可能会崩溃)。
在了解了什么是迭代器失效之后接下来就可以来分析为什么以上的代码存在迭代器失效的问题了
在插入前当内存空间不足时也就是_finish = _endofstorage时就需要先进行扩容,那么在调用reserve之后_start就指向新的内存空间,但问题是此时指针pos还指向原来的内存空间,但是原内存空间内的数据已经被释放,此时pos指针就变为空指针,所以之后的将要插入位置之后的数据都往后移动一位时就会造成程序奔溃。
所以要解决以上pos迭代器失效的问题就要在扩容之后更新迭代器,并且将该函数的返回值为新插入位置的迭代器
iterator insert(iterator pos, const T& x)
{assert(pos >= _start && pos <= _finish);//判断pos指针是否合法if (_finish == _endofstorage){size_t len = pos - _start;//记录pos迭代器位置reserve(capacity() == 0 ? 4 : 2 * capacity());//扩容pos = _start + len;//更新pos迭代器}iterator i = _finish;while (pos <= i){*(i + 1) =*i;i--;}*pos = x;_finish++;return pos;
}
完成了insert函数之后接下来在vector.h内实现erase函数的代码
在此erase函数就不会出现insert当中pos指针为空的情况,因此你可能就会直接实现出以下代码
void erase(iterator pos)
{assert(pos >= _start && pos < _finish);iterator i = pos+1;while (i < _finish){*(i-1) = *i;i++;}_finish--;
}
但其实迭代器失效除了包括扩容引起的野指针,其实还包括删除数据,导致数据挪动,原本的迭代器已经不是指向之前的位置了,这时该迭代器其实野失效了,这是因为之后再使用原来的迭代器可能会导致逻辑问题
就例如以下示例:
#include <iostream> using namespace std; #include <vector>int main() {int a[] = { 1, 2, 3, 4 };vector<int> v(a, a + sizeof(a) / sizeof(int));// 使用find查找3所在位置的iteratorvector<int>::iterator pos = find(v.begin(), v.end(), 3);// 删除pos位置的数据,导致pos迭代器失效。v.erase(pos);cout << *pos << endl; // 此处会导致非法访问return 0; }
erase删除pos位置元素后,pos位置之后的元素会往前搬移,没有导致底层空间的改变,理
论上讲迭代器不应该会失效,但是:如果pos刚好是最后一个元素,删完之后pos刚好是end
的位置,而end位置是没有元素的,那么pos就失效了。因此删除vector中任意位置上元素
时,vs就认为该位置迭代器失效了。再来看以下代码,以下代码的功能是删除vector中所有的偶数,请问那个代码是正确的,为什么?
#include <iostream> using namespace std; #include <vector> int main() {vector<int> v{ 1, 2, 3, 4 };auto it = v.begin();while (it != v.end()){if (*it % 2 == 0)v.erase(it);++it;}return 0; } int main() {vector<int> v{ 1, 2, 3, 4 };auto it = v.begin();while (it != v.end()){if (*it % 2 == 0)it = v.erase(it);else++it;}return 0; }
以上两段代码再VS下只有第二段代码是能通过的,第一段代码会由于迭代器失效直接报错。而在Linux下,g++编译器对迭代器失效的检测并不是非常严格,处理也没有vs下极端,所以以上两段代码都能实现要求
那么在VS当中为什么在出现使用失效的迭代器就直接报错呢?
这是由于在一些情况下使用失效的迭代器会造成程序奔溃,就例如将以上数组v内数据改为以下时vector<int> v{ 1, 2, 2, 3, 4 , 5, 2 };
这时再将以上第一段代码中在将数组中的第一个2删除之后迭代器it就指向了3,这就会使得数组中的第二个2不会被删除,并且在删除最后的2时这时由于进入erase函数之后只会让_finish--,这时删除完2之后继续it++,在此之后it就一直不会等于_finish,所以while就会死循环,造成越界。这种情况下在Linux下也会出现错误
通过以上的分析就知道为什么VS下一旦出现了使用失效的迭代器就报错,那么为了避免使用失效的迭代器,在我们模拟实现的erase也要作出修改
iterator erase(iterator pos)
{assert(pos >= _start && pos < _finish);iterator i = pos+1;while (i < _finish){*(i-1) = *i;i++;}_finish--;return pos+1;
}
在以上代码将pos迭代器之后位置的迭代器作为erase的返回值,这样在调用了erase之后就可以通过其返回值来更新迭代器
2.6 push_back与pop_back
在以上我们实现insert和erase函数之后,在实现尾插push_back和尾删pop_back函数可以直接通过调用insert和erase来实现
void push_back(const T& x)
{insert(end(), x);
}bool Empty()
{return _start == _finish;
}void pop_back()
{assert(!Empty());erase(end()-1);
}
2.7 swap
在vector当中实现swap就可以避免在调用swap时调用到算法库内的swap从而导致深拷贝,而在我们在vector内实现的swap只需要交换指针即可实现vector对象的交换
void swap(vector<T>& x)
{std::swap(_start, x._start);std::swap(_finish, x._finish);std::swap(_endofstorage, x._endofstorage);
}
2.7 构造函数
在以上实现了插入函数之后接下来实现vector构造函数的各个接口就很简单了
//空构造
vector()
{}//使用迭代器区间构造
template<class InputIterator>
vector(InputIterator fist, InputIterator last)
{while (fist != last){push_back(*fist);fist++;}
}//调用initializer_list构造
vector(initializer_list<T> t1)
{reserve(t1.size());for (auto& x: t1){push_back(x);}}vector(int n, const T& x = T())
{reserve(n);while (n--){push_back(x);}
}//n个x构造
vector(size_t n, const T& x = T())
{reserve(n);while(n--){push_back(x);}
}//拷贝构造
vector(const vector<T>& x)
{reserve(x.capacity());for (auto& e : x){push_back(e);}
}
在以上构造函数中在实现n个变量构造时为什么还要提供以下接口呢?
这是因为当vector对象类型是int时,在使用原本的n个变量构造时由于迭代器区间构造参数类型会比n个变量构造的参数类型更加匹配,这时就构造会调用到迭代器区间构造。因此实现以上函数就是为了避免这个问题
2.8 析构函数
在vector当中析构函数的作用是在对象清除时完成资源清理的工作,又因为在vector当中底层的资源就是数组,那么析构函数内要实现的就是将start指向的内存空间释放
完成了分析接下来就在vector.h内实现函数的代码
~vector()
{if(_start){delete[] _start;_start = _finish = _endofstorage = nullptr;}
}
2.9 赋值运算符重载
在vector赋值运算符重载函数和string中一样可以直接使用以下的现代写法实现
vector<int> operator=(vector<T> x)
{swap(x);return *this;
}
以上就是本篇的所有内容了,希望能得到你的点赞、收藏❤️
相关文章:

C++《vector的模拟实现》
在之前《vector》章节当中我们学习了STL当中的vector基本的使用方法,了解了vector当中各个函数该如何使用,在学习当中我们发现了vector许多函数的使用是和我们之前学习过的string类的,但同时也发现vector当中一些函数以及接口是和string不同的…...

无人机避障——路径规划篇(一) JPS跳点搜索算法A*算法对比
JSP 跳点搜索算法与改进 A*算法对比 一、算法概述: 跳点搜索(Jump Point Search,JPS)算法:一种用于路径规划的启发式搜索算法。它主要用于在网格地图(如游戏地图、机器人运动规划地图等)中快速找到从起点到终点的最短路径。该算法在改进 A*算法的基础上进行了优化,通过跳过一…...

OpenCV ORB角点检测匹配和偏移计算
OpenCV ORB角点检测匹配和偏移计算 1. 简介2. ORB角点检测匹配和偏移计算2.1. 创建平移图片2.2. ORB角点检测2.3. ORB角点匹配2.4. 计算变换矩阵 1. 简介 首先通过 cv2.ORB_create 创建ORB检测器 orb, 然后通过 orb.detectAndCompute 检测两张图片获得ORB角点&…...

图文详解ChatGPT-o1完成论文写作的全流程
学境思源,一键生成论文初稿: AcademicIdeas - 学境思源AI论文写作 本月中旬OpenAI发布了OpenAI o1系列新的AI模型。 据OpenAI介绍,这些模型旨在花更多时间思考后再做出反应,就像人一样。通过训练,它们学会改进思维过…...

在线体验Sketch中文版,免费下载即刻上手!
Sketch是一款轻量而高效的矢量设计工具,助力全球设计师创造了诸多惊艳作品。安装Sketch的优势主要体现在其矢量编辑、控件和样式功能上。而下载安装“Sketch中文版”即时设计同样出色,它作为一站式设计平台,功能更全面。即时设计拥有纯中文的…...

Redis——缓存
目录 前言 一、缓存基本概念 1.概念 2.二八定律 二、使用 Redis 作为缓存 三、缓存的更新策略 1.定期生成 2.实时生成 四、Redis 内存淘汰机制 1.通用淘汰策略 (1)FIFO (2)LRU (3)LFU &#…...

RHCSA笔记三
第二章 linux中执行命令 命令格式 命令分为两类 内置命令:由 shell 程序自带的命令 外部命令:有独立的可执行程序文件,文件名即命令名 格式 主命令 参数 操作对象 # 注意: 下面是对于命令的语法的一些符号的说明࿱…...
【python】sorted() list.sort()
文章目录 sorted()和list.sort()sorted 函数sorted()根据键对字典排序根据字典的键排序根据字典的值排序将排序结果转换回字典 list.sort() 方法总结 keylambda student: student[age] sorted()和list.sort() 在Python中,sorted 函数和 list.sort() 方法都可以用来…...
训练集alpaca、sharegpt格式
LLaMA-Factory微调支持的格式 支持 alpaca 格式和 sharegpt 格式的数据集。 Alpaca格式 格式: [{"instruction": "人类指令(必填)","input": "人类输入(选填)","output": "模型回答(必填)","syst…...

Hive的数据存储格式
目录 一、前言 二、存储格式 2.1、文本格式(TextFile) 2.1.1、定义与特点 2.1.2、存储与压缩 2. 1.3、使用场景 2.2、行列式文件(ORCFile) 2.2.1、ORC的结构 2.2.2、ORC的数据类型 2.2.3、ORC的压缩格式 2.2.3、ORC存储…...
Linux Rsyslog 配置
1、Linux Rsyslog客户端配置 1)安装rsyslog yum install rsyslog 2)启用TCP或UDP传输 vim /etc/rsyslog.conf# Provides UDP syslog reception #若启用UDP进行传输,则取消下面两行的注释 #$ModLoad imudp #$UDPServerRun 514# Provide…...

python实现放烟花效果庆祝元旦
马上就要2025年元旦啦,提前祝大家新年快乐 完整代码下载地址:https://download.csdn.net/download/ture_mydream/89926458...

模型训练识别手写数字(二)
模型训练识别手写数字(一)使用手写数字图像进行模型测试 一、生成手写数字图像 1. 导入所需库 import cv2 import numpy as np import oscv2用于计算机视觉操作。 numpy用于处理数组和图像数据。 os用于文件和目录操作。 2. 初始化画布 canvas np.z…...

深入Vue2
frontend Vue2 学习内容参考 /在线运行 Element 学习内容参考 /视频教学 vue2 1. vue 实例 当一个 Vue 实例被创建时,它将 data 对象中的所有的 property 加入到 Vue 的响应式系统中 但是当使用Object.freeze(),会阻止修改现有的 property&#x…...
opencv-rust 系列3: Create_mask
前言: 这里只是opencv-rust自带示例的中文注解. 略微增加了一些代码也是我在调试时用到的. 调试方法可参见前文. 一. 这个程序还是有点难度的, 关键点在于: 创建了遮罩. 直接调用一个函数, 还是很简单的.窗口事件处理. 注册窗口回调函数, 用以处理鼠标事件进程同步和互斥锁. 为…...
Go语言初识
一、Go语言概述 Go语言是为了取代C和java的地位,既要保留C的简洁,也追求java的规模化开发 并行及分布式的支持,使得开发多核及多机器集群程序如同单机一样简单 Go语言从语言级别支持协程(goroutine, 轻量级线程),Go语言…...

Android Activity SingleTop启动模式使用场景
通知栏 当用户点击通知栏中的通知时,可以使用单顶启动模式来打开对应的活动,并确保只有一个实例存在。 简单集成极光推送 创建应用 获取appkey参数 切换到极光工作台 极光sdk集成 Project 根目录的主 gradle 配置 Module 的 gradle 配置 Jpush依赖配置 配置推送必须…...
PHP 代码执行相关函数
函数 说明 示例代码 ${} 用于复杂的变量解析,通常在字符串内用来解析变量或表达式。可以配合 eval 或其他动态执行代码的功能,用于间接执行代码。 eval(${flag}); eval() 用于执行一个字符串作为 PHP 代码。可以执行任何有效的 PHP 代码片段。没有…...

五周年,继续破浪前行
五周年,TapData 再一次带着自己的“乘风破浪”大队,在一个阳光明媚的日子里,把生日过在了海上。 头顶日升日落,这条属于全体 Tap-pers 的航船,再次校准航向,在船长的带领下,驶向下一个晴好的明…...
【操作系统】Linux之进程管理一
第1关:获取进程常见属性 ret.pidgetpid(); ret.ppidgetppid(); 第2关:进程创建操作-fork pid_t pid fork(); if(pid-1) printf("创建进程失败!"); else if(pid0) printf("Children"); else printf("Parent"); …...

shell脚本--常见案例
1、自动备份文件或目录 2、批量重命名文件 3、查找并删除指定名称的文件: 4、批量删除文件 5、查找并替换文件内容 6、批量创建文件 7、创建文件夹并移动文件 8、在文件夹中查找文件...
DockerHub与私有镜像仓库在容器化中的应用与管理
哈喽,大家好,我是左手python! Docker Hub的应用与管理 Docker Hub的基本概念与使用方法 Docker Hub是Docker官方提供的一个公共镜像仓库,用户可以在其中找到各种操作系统、软件和应用的镜像。开发者可以通过Docker Hub轻松获取所…...

基于uniapp+WebSocket实现聊天对话、消息监听、消息推送、聊天室等功能,多端兼容
基于 UniApp + WebSocket实现多端兼容的实时通讯系统,涵盖WebSocket连接建立、消息收发机制、多端兼容性配置、消息实时监听等功能,适配微信小程序、H5、Android、iOS等终端 目录 技术选型分析WebSocket协议优势UniApp跨平台特性WebSocket 基础实现连接管理消息收发连接…...

解决Ubuntu22.04 VMware失败的问题 ubuntu入门之二十八
现象1 打开VMware失败 Ubuntu升级之后打开VMware上报需要安装vmmon和vmnet,点击确认后如下提示 最终上报fail 解决方法 内核升级导致,需要在新内核下重新下载编译安装 查看版本 $ vmware -v VMware Workstation 17.5.1 build-23298084$ lsb_release…...

Python实现prophet 理论及参数优化
文章目录 Prophet理论及模型参数介绍Python代码完整实现prophet 添加外部数据进行模型优化 之前初步学习prophet的时候,写过一篇简单实现,后期随着对该模型的深入研究,本次记录涉及到prophet 的公式以及参数调优,从公式可以更直观…...

高危文件识别的常用算法:原理、应用与企业场景
高危文件识别的常用算法:原理、应用与企业场景 高危文件识别旨在检测可能导致安全威胁的文件,如包含恶意代码、敏感数据或欺诈内容的文档,在企业协同办公环境中(如Teams、Google Workspace)尤为重要。结合大模型技术&…...

在WSL2的Ubuntu镜像中安装Docker
Docker官网链接: https://docs.docker.com/engine/install/ubuntu/ 1、运行以下命令卸载所有冲突的软件包: for pkg in docker.io docker-doc docker-compose docker-compose-v2 podman-docker containerd runc; do sudo apt-get remove $pkg; done2、设置Docker…...

Java面试专项一-准备篇
一、企业简历筛选规则 一般企业的简历筛选流程:首先由HR先筛选一部分简历后,在将简历给到对应的项目负责人后再进行下一步的操作。 HR如何筛选简历 例如:Boss直聘(招聘方平台) 直接按照条件进行筛选 例如:…...
Swagger和OpenApi的前世今生
Swagger与OpenAPI的关系演进是API标准化进程中的重要篇章,二者共同塑造了现代RESTful API的开发范式。 本期就扒一扒其技术演进的关键节点与核心逻辑: 🔄 一、起源与初创期:Swagger的诞生(2010-2014) 核心…...

Mac下Android Studio扫描根目录卡死问题记录
环境信息 操作系统: macOS 15.5 (Apple M2芯片)Android Studio版本: Meerkat Feature Drop | 2024.3.2 Patch 1 (Build #AI-243.26053.27.2432.13536105, 2025年5月22日构建) 问题现象 在项目开发过程中,提示一个依赖外部头文件的cpp源文件需要同步,点…...