C++ ---- vector的底层原理剖析及其实现

news2024/11/20 19:33:55

vector

  • 一、定义
    • 二、常用接口及模拟实现
      • 三、vector迭代器失效问题
        • 四、使用memcpy拷贝会出现的问题
          • 五、二维数组vector<vector< T >> vv

一、定义

vector 是 C++ 标准模板库(Standard Template Library, STL)中的一个非常有用的容器。它是一个序列容器,可以存储具有相同数据类型的元素集合,这些元素在内存中连续存储。与数组相似,但 vector 提供了更多的灵活性和功能。

主要特点:

1.动态数组:vector 可以动态地增加或减少其大小,这意味着你可以根据需要存储更多的元素,而不需要担心数组越界的问题。
2.随机访问:vector 支持随机访问,即你可以使用下标(索引)直接访问容器中的任何元素,其时间复杂度为 O(1)。
3.内存连续:vector 中的元素在内存中是连续存储的,这使得在需要时(如使用迭代器)可以高效地遍历整个容器。
4.容量和大小:vector 有两个重要的属性:size() 和 capacity()。size() 返回容器中当前元素的数量,而 capacity() 返回容器在不重新分配内存的情况下可以存储的元素的最大数量。当 vector 的大小超过其容量时,它会分配更多的内存空间并可能移动所有元素到新的内存位置。

常用操作:

1.插入元素:可以使用 push_back() 在 vector 的末尾添加新元素,或者使用 insert() 在指定位置插入元素。
2.删除元素:可以使用 pop_back() 删除 vector 的最后一个元素,或者使用 erase() 删除指定位置的元素或元素范围。
3.访问元素:可以直接使用下标操作符 [] 访问元素。
4.遍历元素:可以使用迭代器或者范围基 for 循环遍历 vector 中的所有元素。
获取大小和容量:如前所述,可以使用 size() 和 capacity() 方法分别获取 vector 的大小和容量。

二、常用接口及模拟实现

成员变量:

/这里加缺省值的原因:当对象是通过拷贝构造形成的时,这三个指针
/就不会初始化了(默认是随机值,而不是nullptr(0)),而在拷贝构造函数里,会运用到size(),capacity()之类的需要
/这三个指针相减,而随机值相减可能是很大的数字
/而加了缺省值,进入拷贝构造函数后会先进入这里初始化这三个值

iterator _start=nullptr;   /指向第一个元素
iterator _finish=nullptr;   /指向最后一个元素的下一个位置
iterator _end_of_storage=nullptr;  /指向最大容量的位置

(1)构造函数

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

模拟实现:

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

vector(size_t n, const T& val) {
 _start = new T[n](val);
 _finish = _start + n;
 _end_of_storage = _finish;
}

vector(const vector<T>& v) {
 /写法1:
 /_start = new T[v.capacity()];
 memcpy(_start, v._start, sizeof(T) * v.size());
 _finish = _start + v.size();
 _end_of_storage = _start + v.capacity();/

 /写法2reserve(v.capacity()); /避免push_back多次扩容
 for (auto& num : v) {
	 push_back(num);
 }
}
 template<class Inputiterator> //说明:成员函数里面可以有模板函数
 vector(Inputiterator begin, Inputiterator end) {  //用迭代器区间进行构造和初始化
	    //这里重新定义一个Inputiterator而不用vector里面的iterator,是
	 //因为如果用了iterator,那么调用该函数构造的对象所传的实参只能是vector的迭代器
	 //而不能是诸如list ,map等容器的迭代器,而如果想用list或者其他容器的值来
	 //初始化vector对象的话,只能如此定义一个迭代器模板 。
	 
	 //该构造函数支持任意容器的迭代器来初始化,但前提是这些容器所存的数据与
	 //该vector所存的数据类型一致,如:list<int>l 用该方法来初始化 vector<int> v
	 //它们存的都是int类型的数据

	 while (begin != end) {
		 push_back(*begin);
		 begin++;
	 }
 }

(2)vector的迭代器
vector的迭代器有两种:

  1. iterator:typedef T* iterator
  2. const_iterator:typedef const T* const_iterator
    (T是vector存的数据类型)

迭代器的主要作用就是让算法能够不用关心底层数据结构,其底层实际就是一个指针,或者是对
指针进行了封装(list容器的迭代器),比如:vector的迭代器就是原生态指针T* 。

3)begin()/end(),rbegin()/rend()接口函数

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

在这里插入图片描述
在这里插入图片描述
模拟实现:

 iterator begin() const{
	 return _start;
 }
 iterator end()const {
	 return _finish;
 }

(4)空间增长函数

容量空间接口说明
size()获取容器中数据个数
capacity()获取容量大小
empty()判断容器是否为空
resize()改变vector的size
reserve()改变vector的capacity
  1. capacity的代码在vs和g++下分别运行会发现,vs下capacity是按1.5倍增长的,g++是按2
    倍增长的。因此vector的增容都具体增长多少是根据具体的需求定义的。vs是PJ版本STL,g++是SGI版本STL。
  2. reserve只负责开辟空间,如果确定知道需要用多少空间,reserve可以缓解vector增容的代
    价缺陷问题。
  3. resize在开空间的同时还会进行初始化,影响size。

模拟实现:

size_t size()const {
 return _finish - _start; //两指针相减,结果为两指针之间的数据个数
}
size_t capacity() const{
 return _end_of_storage - _start;
}
void resize(size_t n, T val = T()) {
 if (n < size()) {//保留n个数据    
	 _finish = _start + n;
 }
 else {
	 reserve(n);
	 while (_finish < _start + n) {  //扩容并追加n-size()个val值
		 (*_finish) = val;
		 _finish++;
	 }
 }
}
 void reserve(size_t n) {
	 if (n > capacity()) {
		 size_t presize = size(); //要提前记录_start 与_finish 的相对位置
		                       //因为下面的_start先更新了,会导致size()函数
		                       //出错 (除非_finish先更新:_finish=tmp+size(),_start=tmp
		 T* tmp = new T[n];

		 //不严谨的拷贝数据:
		 //memcpy(tmp, _start, sizeof(T) * presize); //拷贝数据

		 //严谨的拷贝数据:
		 for (size_t i = 0; i < presize; i++) {
			 tmp[i] = _start[i];
		 }//这是因为假如类型T是string或者是vector<int>,而memcpy又是单纯的将内存的空间逐个字节拷贝
		 //就导致vector存的string对象的char*指针(或者vector<int>的int*)
		 // 也是单纯的直接拷贝给tmp,
		 //即tmp里的string对象里的指针指向的空间和待释放的vector对象存的
		 //string对象里的指针指向的空间是一样的。
		 //下面delete[] _start 时,会先调用每个string对象的析构函数。
		 //其实就是,vector深拷贝了,但是vector存的string没有深拷贝
		 
		 //而如果将该this对象的内容逐个拷贝给tmp,每次拷贝时都会调用string的深拷贝
		 //从而避免了该情况。
		 // tmp[i]=_start[i]本质上是:string s =string s'(string的拷贝构造) 


		 delete[] _start;
		 //更新三个迭代器所指向的位置:
		 _start = tmp;
		 _finish = tmp + presize;
		 _end_of_storage = tmp + n;
	 }
 }

测试vector在不同平台下的扩容机制:

// 测试vector的默认扩容机制
void TestVectorExpand()
{
   size_t sz;
   vector<int> v;
   sz = v.capacity();
   cout << "making  grow:\n";
   for (int i = 0; i < 100; ++i)
  {
    v.push_back(i);
    if (sz != v.capacity())
    {
      sz = v.capacity();
      cout << "capacity changed: " << sz << '\n';
    }
  }
}
/vs:运行结果:vs下使用的STL基本是按照1.5倍方式扩容
making  grow:
capacity changed: 1
capacity changed: 2
capacity changed: 3
capacity changed: 4
capacity changed: 6
capacity changed: 9
capacity changed: 13
capacity changed: 19
capacity changed: 28
capacity changed: 42
capacity changed: 63
capacity changed: 94
capacity changed: 141

/g++运行结果:linux下使用的STL基本是按照2倍方式扩容
making  grow:
capacity changed: 1
capacity changed: 2
capacity changed: 4
capacity changed: 8
capacity changed: 16
capacity changed: 32
capacity changed: 64
capacity changed: 128
// 如果已经确定vector中要存储元素大概个数,可以提前将空间设置足够
// 就可以避免边插入边扩容导致效率低下的问题了
void TestVectorExpandOP()
{
   vector<int> v;
   size_t sz = v.capacity();
   v.reserve(100); / 提前将容量设置好,可以避免一遍插入一遍扩容
   cout << "making bar grow:\n";
   for (int i = 0; i < 100; ++i)
 {
    v.push_back(i);
    if (sz != v.capacity())
   {
     sz = v.capacity();
     cout << "capacity changed: " << sz << '\n';
   }
 }
}

(5)vector的增删查改

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

模拟实现:

void push_back(const T& x) { //T可能是自定义类型,所以用const引用较好
 if (_finish == _end_of_storage) {//扩容
	 reserve(capacity() == 0 ? 4 : 2 * capacity());
  }
 *_finish = x; //如果T是string ,这里会调用string的拷贝构造
 _finish++;
}

 void pop_back() {		
	 assert(_start != _finish);
	 --_finish;
 } 

 iterator find(iterator begin, iterator end, const T& x) {
	 vector<T>::iterator it = begin;
	 while (it != end) {
		 if (*it == x) {
			 return it;
		 }
		 it++;
	 }
	 return end;
 }

void insert(iterator pos, const T& x) {
 if (_finish == _end_of_storage) {//扩容
	 size_t pre_distance = pos - _start;
	 reserve(capacity() == 0 ? 4 : 2 * capacity());
	 pos = _start + pre_distance;  //因为_start在扩容时指向的空间变化了,
	                               //因此pos也要相应的变化
 }                                 //否则会出现迭代器失效的情况
 iterator end = _finish-1;
 while (end >=pos) {
    *(end + 1) = *end;
	 end--;
 }
 *pos = x;
 ++_finish;
}

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

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

 T& operator[](int i) {
	 assert(i < size() && i >= 0);
	 return _start[i];
 }

三、vector迭代器失效问题

迭代器失效实际就是迭代器
底层对应指针所指向的空间被销毁了,而仍然使用这一块已经被释放的空间,造成的后果是程序崩溃(即如果继续使用已经失效的迭代器,程序可能会崩溃)。

对于vector可能会导致其迭代器失效的操作有:

  1. 会引起其底层空间改变的操作,都有可能是迭代器失效,比如:resize、reserve、insert、assign、push_back等。即如果已经有了一个迭代器指向一块空间,但是这个空间被上述可以改变空间结构的函数给销毁了,那么这个迭代器也就失效了。
  2. 指定位置元素的删除操作–erase
using namespace std;
#include <vector>
int main()
{
   int a[] = { 1, 2, 3, 4 };
   vector<int> v(a, a + sizeof(a) / sizeof(int));//发生隐式类型转化,调用迭代器区间构造函数
   
   // 使用find查找3所在位置的iterator
   vector<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就认为该位置迭代器失效了。

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;
}

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

四、使用memcpy拷贝会出现的问题

注:reserve接口:

 void reserve(size_t n) {
	 if (n > capacity()) {
		 size_t presize = size(); //要提前记录_start 与_finish 的相对位置
		                       //因为下面的_start先更新了,会导致size()函数
		                       //出错 (除非_finish先更新:_finish=tmp+size(),_start=tmp
		 T* tmp = new T[n];

		 memcpy(tmp, _start, sizeof(T) * presize); //拷贝数据
		 delete[] _start;
		 //更新三个迭代器所指向的位置:
		 _start = tmp;
		 _finish = tmp + presize;
		 _end_of_storage = tmp + n;
	 }
 }
#include<string>
int main()
{
Myvector::vector<string> v;  //vector是自己实现的,string是库实现的

v.push_back("wwww");
v.push_back("eeee");
v.push_back("rrrr");
return 0;
}

上述代码所导致的问题剖析:
在这里插入图片描述
在这里插入图片描述
正确拷贝数据的方法:(逐个数据拷贝)

 void reserve(size_t n) {
	 if (n > capacity()) {
		 size_t presize = size(); //要提前记录_start 与_finish 的相对位置
		                       //因为下面的_start先更新了,会导致size()函数
		                       //出错 (除非_finish先更新:_finish=tmp+size(),_start=tmp
		 T* tmp = new T[n];

		 for (size_t i = 0; i < presize; i++) {
			 tmp[i] = _start[i];
		 }//这是因为假如类型T是string或者是vector<int>,而memcpy又是单纯的将内存的空间逐个字节拷贝
		 //就导致vector存的string对象的char*指针(或者vector<int>的int*)
		 // 也是单纯的直接拷贝给tmp,
		 //即tmp里的string对象里的指针指向的空间和待释放的vector对象存的
		 //string对象里的指针指向的空间是一样的。
		 //下面delete[] _start 时,会先调用每个string对象的析构函数。
		 //其实就是,vector深拷贝了,但是vector存的string没有深拷贝
		 
		 //而如果将该this对象的内容逐个拷贝给tmp,每次拷贝时都会调用string的深拷贝
		 //从而避免了该情况。
		 // tmp[i]=_start[i]本质上是:string s =string s'(string的拷贝构造) 

		 delete[] _start;
		 //更新三个迭代器所指向的位置:
		 _start = tmp;
		 _finish = tmp + presize;
		 _end_of_storage = tmp + n;
	 }
 }

总结:不仅vector存stirng类型会出现该问题,只要是存的对象里有指向堆空间的类型(如还有下面的二维数组),都是有memcpy拷贝问题的,而存储自定义类型就不会。

五、二维数组vector<vector< T >> vv

对c语言来说,创建一个二维数组的方法:

test()
{
    //三行四列
    int **two=(int**)malloc(sizeof(int*)*3);
    for(int i=0;i<3;i++){
      two[i]=(int*)malloc(sizeof(int)*4);
    }
}

vector<vecot< T >> vv 的底层空间示意图:
在这里插入图片描述

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

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

相关文章

SD卡受损数据会消失吗 内存卡坏了数据还能恢复吗 SD卡受损里面的数据怎么办 sd卡受损最简单的修复方法

SD卡里的数据有时候也容易出现消失或者遭到破坏等情况&#xff0c;针对此类情况&#xff0c;给大家详细讲解&#xff0c;SD卡受损数据会消失吗&#xff0c;以及SD卡受损了里面的数据怎么办。 一、SD卡受损数据会消失吗 SD卡本身比较小巧&#xff0c;里面的小芯片比较密集&…

04 RabbitMQ:控制界面详解

04 RabbitMQ&#xff1a;控制界面详解 1. 控制台界面2. 控制界面详解2.1. Overview&#xff08;概览&#xff09;2.1.1. Totals&#xff08;总数&#xff09;2.1.1.1. Queued messages2.1.1.2. Message rates2.1.1.3. Global counts 2.1.2. Nodes&#xff08;节点消息&#xff…

SpringBoot多数据源事务处理

多数据源时,一般会配置多个事务管理器 Spring编程式 第二种方式 不可能去同一个方法上写两个事务注解 不允许 SpringBoot 2.6.0之后禁止自己注入自己 本来可以自己注入自己去调用 (为什么要自己注入自己调用,AOP代理,类不是自己写的类) 最简单方式 引入 <dependency&…

DPDK基础入门(一):认识和理解DPDK

Linux的网络瓶颈 以Linux为例&#xff0c;传统网络设备驱动包处理的动作可以概括如下&#xff1a; 数据包到达网卡设备。网卡设备依据配置进行DMA操作。网卡发送中断&#xff0c;唤醒处理器。驱动软件填充读写缓冲区数据结构。数据报文达到内核协议栈&#xff0c;进行高层处理…

Linux中信号的发送及信号的自定义捕捉方法

预备知识&#xff1a; 信号产生时进程早已知道该信号如何处理。 信号产生时进程可能并不能立即处理信号而是等到合适的时候处理。 信号其他相关常见概念 实际执行信号的处理动作称为信号递达(Delivery) 信号从产生到递达之间的状态,称为信号未决(Pending)。 进程可以选择阻…

C语言第14篇

1.以下定义语句中&#xff0c;错误的是________. A) int a[]{1,2}; B) char a{"test"}; C) char s[10]{"test"}; D) int a[]{a,b,c}; 2.以下定义语句中&#xff0c;错误的是________. A) int a[]{1,2}; B) char a[]{…

扬声器、麦克风的等效电路及相关技术参数(灵敏度等)

扬声器、麦克风都是日常我们所需的电子小器件&#xff0c;今天小编来具体讲解一下有关两者的等效电路及相关技术参数。 1、扬声器 等效电路 Re表示扬声器音圈的直流电阻 Le表示音圈的电感,对高频信号产生的阻抗 Mm表示动圈的等效质量,主要影响扬声器的低频响应 Rm 表示动圈…

算力共享:forward_to_next_shard,推断之间的链接

目录 forward_to_next_shard 参数 函数逻辑 _process_prompt StandardNode get_current_shard map_partitions_to_shards forward_to_next_shard 这段代码定义了一个名为 forward_to_next_shard 的异步函数,它是设计用于在分布式模型或数据处理系统中的节点(或称为“分…

【秋招笔试】2024-08-03-科大讯飞秋招笔试题(算法岗)-三语言题解(CPP/Python/Java)

🍭 大家好这里是清隆学长 ,一枚热爱算法的程序员 💻 ACM金牌团队🏅️ | 多次AK大厂笔试 | 编程一对一辅导 ✨ 本系列打算持续跟新 秋招笔试题 👏 感谢大家的订阅➕ 和 喜欢💗 ✨ 笔试合集传送们 -> 🧷春秋招笔试合集 🍖 本次题目难度中等偏上,最后一题又是…

dfs深搜

Problem - C - Codeforces 无向图,判断是否是子叶.

Co-Detr

参考&#xff1a;https://www.bilibili.com/video/BV1Sh4y1F7ur/?spm_id_from333.788&vd_source156234c72054035c149dcb072202e6be 之前的detr正样本数量少&#xff0c;匹配不平衡。 主要修改两个地方&#xff1a;encoder和decoder。 1.在encoder之后加入RPN&#xff0c;a…

深入理解接口测试:实用指南与最佳实践(二)API文档解析HTTP协议

​ ​ 您好&#xff0c;我是程序员小羊&#xff01; 前言 这一阶段是接口测试的学习&#xff0c;我们接下来的讲解都是使用Postman这款工具&#xff0c;当然呢Postman是现在一款非常流行的接口调试工具&#xff0c;它使用简单&#xff0c;而且功能也很强大。不仅测试人员会使用…

Leetcode75-3 拥有最多糖果的孩子

题目很简单&#xff0c;逻辑就是找到最大值 然后做个比较看看每个值加上extra能不能超过或者等于最大值。 两个知识点遗忘 数组length 获取数组的长度的格式&#xff1a; 数组名称。length 这将会得到一个int数字&#xff0c;代表数组的长度。 数组一旦创建&#xff0c;程…

Effective-Java-Chapter2

https://github.com/clxering/Effective-Java-3rd-edition-Chinese-English-bilingual/blob/dev/ 准则一 考虑以静态工厂方法代替构造函数 优点 静态工厂方法与构造函数相比的第一个优点&#xff0c;静态工厂方法有确切名称。 知名见意&#xff0c;静态方法我们可以通过命名…

编程新手到大师:大学生学编程的高效学习路径

​​​​​​​ 目录 ​​​​​​​​​​​​​​ 编程新手到大师&#xff1a;大学生学编程的高效学习路径 编程小白如何成为大神&#xff1f;大学新生的最佳入门攻略 一、确定学习目标 1.1、我接受想贯彻互联网思维的洗礼 1.2、我想提升在专业中的竞争力 1.3、我觉得…

(STM32笔记)九、RCC时钟树与时钟 第三部分

我用的是正点的STM32F103来进行学习&#xff0c;板子和教程是野火的指南者。 之后的这个系列笔记开头未标明的话&#xff0c;用的也是这个板子和教程。 九、RCC时钟树与时钟 九、RCC时钟树与时钟3、使用固件库的函数来配置时钟bsp_rccclkconfig.c思路配置HSE时钟把RCC寄存器复位…

OpenCV Python 图像相加与透明色转换

将两幅图添加起来&#xff0c;构成一幅新的图像&#xff0c;并尝试将一个PNG的透明背景转换为特定的颜色。 生成纯背景 以下代码生成一个纯色背景JPG&#xff0c;颜色为照片底板的蓝&#xff08;R:60 G:140 B:220&#xff09;。在用OpenCV创建图像时&#xff0c;颜色按BGR。 …

必须知道的国内linux镜像下载网址,建议收藏

linux镜像下载 一、阿里云开源镜像站下载可用的镜像 二、网易开源镜像下载可用的镜像 三、搜狐开源镜像下载可用的镜像 一、阿里云开源镜像站下载 https://developer.aliyun.com/mirror/ 阿里云开源镜像站是阿里云提供的一个公共服务&#xff0c;旨在为开发者提供快速、稳定…

4. 最长公共前缀

4. 最长公共前缀 题目题目分析 题目 题目分析 首先要对字符串数组进行分析&#xff0c;字符串数组元素的最长公共前缀肯定不会超过最小元素长度&#xff0c;并如存在公共前缀则需遍历整个字符串元素&#xff0c;有点像二维数组&#xff0c;最后加上截取字符串加上判空操作就完…

apache一台服务器如何通过不同端口映射不同网页服务入口

一、背景 由于服务器资源受限&#xff08;IP资源或硬件资源&#xff09;&#xff0c;有时候希望一台服务器IP能有部署多个网页服务入口。 传统都是80端口映射为默认服务入口&#xff0c;当需要部署多个网页服务入口时&#xff0c;可以启用其他端口&#xff0c;不同端口映射到…