vector实现遇到的问题

        前言:vector是表示可变大小数组的序列容器。就像数组一样,vector也采用的连续存储空间来存储元素。也就是意味着可以采用下标对vector的元素进行访问,和数组一样高效。但是又不像数组,它的大小是可以动态改变的,而且它的大小会被容器自动处理。 本质讲,vector使用动态分配数组来存储它的元素。当新元素插入时候,这个数组需要被重新分配大小为了增加存储空间。其做法是,分配一个新的数组,然后将全部元素移到这个数组。就时间而言,这是 一个相对代价高的任务,因为每当一个新的元素加入到容器的时候,vector并不会每次都重新分配大小,下面,我们就来介绍一下,实现vector时可能会遇到的一些问题和接决策略,最终奉上模拟实现代码。

还是老规矩,我们给出实现类的初始代码,以方便更好的阅读后续的代码,该部分的代码不再做过多赘述,有兴趣的读者可以评论:

#pragma once
#include<assert.h>namespace my_std
{template<class T>//vector存储的元素需要用模板来表示class vector {public:typedef T* iterator;typedef const T* const_iterator;iterator begin(){return _start;}iterator end(){return _finish;}const_iterator begin() const{return _start;}const_iterator end() const{return _finish;}//默认构造vector()//声明中已给出缺省值,可以省略不写{}~vector(){delete[] _start;//采用new/delete的形式存储_finish = _endofstorage = nullptr;}size_t capacity() const //空间容量{return _endofstorage - _start;}size_t size() const //数组长度{return _finish - _start;}T& operator[](size_t pos)//可读可写{assert(pos < size());return _start[pos];}const T& operator[](size_t pos) const//只读{assert(pos < size());return _start[pos];}private://初始化加上缺省值iterator _start=nullptr;//开头iterator _finish = nullptr;//结尾加1iterator _endofstorage = nullptr;//空间容量};
}

目录

1.尾插元素和扩容函数

异地扩容导致原地址失效

2.匿名对象做缺省值的resize函数

3.memcpy-特殊类型的浅拷贝“隐藏杀手”

string类成员的浅拷贝

4.迭代器失效问题

insert函数带来的迭代器失效

erase函数带来的迭代器失效

5.拷贝构造和赋值问题

6.迭代器区间初始化构造

7.完整代码

vector.h

test.cpp


1.尾插元素和扩容函数

异地扩容导致原地址失效

       我们的vector实际上是一个数组容器,也是一个由头尾指针组成的一定长度的顺序表,我们在实际操作时,只能操作头指针或者尾指针中的一个来控制数组,另一个指针是随着操作的指针变化而变化的,我们只需要知道数组长度即可,但是,在当前空间不够用的情况下,又采取异地扩容,可能会导致头尾指针分离一段时间,如果不能察觉,就会导致数组出现错误:

void reserve(size_t n)//扩容函数{if (n > capacity()){T* tmp = new T[n];size_t sz = size();if (_start){//memcpy(tmp, _start, sizeof(T) * sz);for (size_t i = 0; i < sz; i++){tmp[i] = _start[i];}delete[] _start;}_start = tmp;_finish = _start + sz;_endofstorage = _start + n;}}
void push_back(const T& x){//如果空间满了先扩容size_t n = _finish - _start;//提前算出保存,避免后序因异地扩容而导致原地址失效if (_finish == _endofstorage){size_t len = capacity() == 0 ? 4 : capacity() * 2;T* temp = new T[len];if (_start)//如果原来有内容{memcpy(temp, _start, sizeof(T)*n);delete[] _start;}_start = temp;_finish = _start + n;_endofstorage = _start + len;//reserve(len);如果复用将从T* temp开始到_endofstorge=_start+len;注释即可}*_finish = x;++_finish;//insert(end(), x);}

2.匿名对象做缺省值的resize函数

     如果我们调用resize函数,其实就是重新给数组分配一定的空间,那么,这个空间内部的元素该如何初始化呢,这一般都由输入者自定义的类型来,这样一来,我们就不能将缺省值特别规定为int或者别的类型,而是使用模板,按照代码编辑者需要的类型自动生成对应的缺省值类型即可,并且,在C++中,我们的内置类型(int,double)等也有自己的构造函数,同时,我们可以将参数匿名对象设为const,这样一来可以延长匿名对象的使用周期到其使用结束后销毁。

void resize(size_t n,const T& val=T())//匿名对象表示填入的缺省值,其有可能是整形,字符串,甚至是vector等,所以采用匿名对象来调用默认构造,而const可以延长匿名对象的生命周期到不用之后再销毁{if (n <= size())//不大于尾部不用变{_finish = _start + n;}else{reserve(n);//在reserve函数内部判断是否需要扩容,并返回扩容后的空间,_finish指针不会改变相对于_start的距离while (_finish < _start + n){*_finish = val;++_finish;}}}

3.memcpy-特殊类型的浅拷贝“隐藏杀手”

memcpy函数的简介:

1. memcpy是内存的二进制格式拷贝,将一段内存空间中内容原封不动的拷贝到另外一段内存空间中

2. 如果拷贝的是自定义类型的元素,memcpy既高效又不会出错,但如果拷贝的是自定义类型元素,并且 自定义类型元素中涉及到资源管理时,就会出错,因为memcpy的拷贝实际是浅拷贝。

string类成员的浅拷贝

       我们知道string类对象的字符串并不是存放在对象本身的空间里,而是存放在对象的数据成员所指向的堆中,所以在使用memcpy时,就会导致只是将string对象的数据成员原封不动的复制拷贝下来,相当于其指向的string对象的成员的数据还是保存在原来的堆空间中,并没有发生深拷贝,出现了两个指针指向同一处的情况,这就会在原拷贝对象在析构时产生重复析构的错误。

     解决办法也比较简单,那就是用我们的笨方法,采用循环的方式将原对象一一拷贝到新的空间里来就行了,当然,这种错误的出现只会发生在需要扩容的情况下,所以,我们只需要调整扩容函数即可。

void reserve(size_t n)//扩容函数{if (n > capacity()){T* tmp = new T[n];size_t sz = size();if (_start){//memcpy(tmp, _start, sizeof(T) * sz);//自定义对象造成异地扩容浅拷贝for (size_t i = 0; i < sz; i++){tmp[i] = _start[i];}delete[] _start;}_start = tmp;_finish = _start + sz;_endofstorage = _start + n;}}

4.迭代器失效问题

迭代器失效的特征:

1.如果迭代器失效了,就不能再使用这个迭代器;

2.如果使用了这个迭代器,其结果将会是未定义的。

insert函数带来的迭代器失效

        在vector中,我们使用的一般都是迭代器的位置来表示元素的位置,像在某个元素的位置后面插入一个元素的函数,其定义一般为void insert(iterator pos, const T& x);,那么就可能会发生如下的场景,

       在这样的场景下,插入元素势必就会导致出错,所以,其中一个方法就是,我们记住pos与_start的相对偏移量,在扩容后数组移动到新的空间之后再将pos也更新到新的位置即可。

void insert(iterator pos, const T& x){assert(pos >= _start && pos <= _finish);//等于_finish相当于尾插//首先检查是否空间满了,满了就要先开空间if (_finish == _endofstorage){size_t len1 = pos - _start;cout <<"扩容前待插入位置相对于begin的偏移量为"<< len1 << endl;reserve(capacity() == 0 ? 4 : capacity() * 2);//注意,扩容之后会导致迭代器发生变化,也将会导致原来传入的参数失效size_t len2 = pos - _start;cout <<"扩容后待插入位置相对于begin的偏移量为"<< len2 << endl;pos = _start + len1;}iterator end = _finish-1;_finish++;while (end >= pos){*(end + 1) = *end;--end;}*pos = x;}

上述的做法还有些许不妥之处,如果我们有如下的需求:

这样的话,返回的迭代器还能使用吗

       首先,我们知道insert函数在内部修改了传入的迭代器,但是这和实参并没有什么关系,因为我们的insert函数是传值传参,众所周知,传值传参不影响实参,所以,一旦调用了insert函数,迭代器就会出现诸多的可能性,也就是迭代器失效了。

为何不建议传引用传参

      针对上面的问题,有的人可能会想到传引用传参,但是,我们得形参是实参的一份拷贝,而实参又是一个常量,形参作为临时变量而具有了常性,所以不能被修改,逻辑上也就不对了,如果再将引用加上const,那我们在insert函数内部就不能对参数迭代器再进行修改了,所以,对于insert函数来说,正常的使用我们一般选择传值传参,但是其使用会导致迭代器失效的问题,这一点,我们需要注意。


erase函数带来的迭代器失效

     vector的删除函数比较好写,我们传入迭代器参数,因为不涉及扩容问题,自然也就不存在上面的insert函数造成的迭代器失效的类型的问题,我们直接在原来的空间上进行移动覆盖即可,这里详细的也不再赘述:

void erase(iterator pos){assert(pos >= _start && pos < _finish);//注意不能等于_finish,因为_finish指向的是尾部元素的下一个元素,该处本来没有元素所以也不能删除iterator it = pos + 1;while (it != end()){*(it - 1) = *it;it++;}--_finish;}

那删除完之后的迭代器失效了吗?

       我们不妨来实测一下,我们取三组样例,分别对样例中的偶数元素进行删除,分析并查看其结果:

       为什么会有这样的结果呢,其实重点就在迭代器的位置,我们知道,当迭代器判断到一个元素符合条件时,会执行删除指令,这个指令也就是我们上面的erase函数,其本质上是向前挪动数据覆盖达到删除效果,而此时的迭代器在删除完元素后其实是指向了被删除元素的下一个元素,下一步我们就需要判断该元素,如果此时仍然让迭代器++的话,就会导致上一个被删除元素后面紧挨着的下一个数据被跳过了判断,也就出现了错误问题。

      解决办法其实也简单,就是我们在判断符合条件时不执行迭代器++操作,只有当该元素不符合删除条件时才执行迭代器++操作,这样就可以保证每个元素都能够被判断到,也就解决了错误。

      那,如果我想访问这个迭代器,就真没办法了吗?别急,还真有,我们来看官方文档对于迭代器失效问题的解决方案:

      官方的意思是,让erase函数返回一个迭代器,这个迭代器指向上一个已删除元素的下一个元素,其实还是原来的那个迭代器的位置。  

iterator erase(iterator pos){assert(pos >= _start && pos < _finish);//注意不能等于_finish,因为_finish指向的是尾部元素的下一个元素,该处本来没有元素所以也不能删除iterator it = pos + 1;while (it != end()){*(it - 1) = *it;it++;}--_finish;return pos;//删除元素的下一个位置,其实还是该迭代器的位置,因为向前挪动覆盖,原来迭代器的位置上删除后存储的就是下一个元素}
void test_vector5(){// 1 2 3 4 5// 1 2 3 4 5 6// 2 2 3 4 5std::vector<int> v;//调用库里的vector,默认原来的erase迭代器失效v.push_back(2);v.push_back(2);v.push_back(3);v.push_back(4);v.push_back(5);//v.push_back(6);for (auto e : v){cout << e << " ";}cout << endl;auto it = v.begin();while (it != v.end()){if (*it % 2 == 0){it=v.erase(it);//让迭代器重新赋值,再次有效}else++it;}

5.拷贝构造和赋值问题

      对于自己写的vector,如果不单独写拷贝构造函数,那么将会使用默认的拷贝函数,也就会造成浅拷贝的情况,和赋值操作同理,所以,我们也是需要编写拷贝构造函数和赋值函数,这部分在string的部分已经详细展开,这里也不再赘述,

//拷贝构造函数vector(const vector<T> & x):_start(nullptr), _finish(nullptr), _endofstorage(nullptr){reserve(x.capacity());//修改空间为x的空间for (auto& e : x){push_back(e);//赋值即可}}
//赋值操作// v1 = v3void swap(vector<T>& v)//写成vector &x也可以{std::swap(_start, v._start);std::swap(_finish, v._finish);std::swap(_endofstorage, v._endofstorage);}vector<T>& operator=(vector<T> tmp){swap(tmp);//交换后tmp为临时变量后续自动销毁return *this;}

6.迭代器区间初始化构造

在官方的vector文档中,我们还可以看到vector构造函数的另外l两种初始化方案:

 在上面实现的功能的基础上,想实现这两个构造函数并不能,这里给出一种实现方案:

template <class InputIterator>vector(InputIterator first, InputIterator last){while (first != last){push_back(*first);++first;}}vector(size_t n, const T& val = T()){reserve(n);for (size_t i = 0; i < n; i++)push_back(val);}

但是如果我们来整上一个测试样例:vector<int> v0(10, 0);显然我们的目的是想让编译器给我们初始化10个空间,并且全都初始化为0,但是,编译器真的会乖乖的听我们的吗?

         很显然出错了,编译器不听我们的,因为,编译器不知道该匹配哪个了?因为这两个数据参数都可以看做同一个类型,所以,它也符合模板的迭代器初始化的函数的参数列表,所以,编译器就会调用最匹配的那一个函数来构造:

       这种情况下,官方采用的是将 vector(size_t n, const T& val = T())再次重载,使其更加符合我们类型,比如重载为vector(int n, const T& val = T()),就可以解决我们上面的错误了,但不过说实话,这种方式的代码复用性比较差,有点面向样例编程的滋味,但是目前也没有好的解决办法了。

7.完整代码

vector.h

#pragma once
#include<assert.h>
#include <vector>namespace my_std
{template<class T>class vector {public:typedef T* iterator;typedef const T* const_iterator;iterator begin(){return _start;}iterator end(){return _finish;}const_iterator begin() const{return _start;}const_iterator end() const{return _finish;}//默认构造vector()//声明中已给出缺省值,可以省略不写{}//拷贝构造函数vector(const vector<T> & x):_start(nullptr), _finish(nullptr), _endofstorage(nullptr){reserve(x.capacity());//修改空间为x的空间for (auto& e : x){push_back(e);//赋值即可}}template <class InputIterator>vector(InputIterator first, InputIterator last){while (first != last){push_back(*first);++first;}}vector(size_t n, const T& val = T()){reserve(n);for (size_t i = 0; i < n; i++)push_back(val);}vector(int n, const T& val = T()){reserve(n);for (size_t i = 0; i < n; i++)push_back(val);}//赋值操作// v1 = v3void swap(vector<T>& v){std::swap(_start, v._start);std::swap(_finish, v._finish);std::swap(_endofstorage, v._endofstorage);}vector<T>& operator=(vector<T> tmp){swap(tmp);//交换后tmp为临时变量后续自动销毁return *this;}~vector(){delete[] _start;//采用new/delete的形式存储_finish = _endofstorage = nullptr;}size_t capacity() const //空间容量{return _endofstorage - _start;}size_t size() const //数组长度{return _finish - _start;}void reserve(size_t n)//扩容函数{if (n > capacity()){T* tmp = new T[n];size_t sz = size();if (_start){//memcpy(tmp, _start, sizeof(T) * sz);//自定义对象造成异地扩容浅拷贝for (size_t i = 0; i < sz; i++){tmp[i] = _start[i];}delete[] _start;}_start = tmp;_finish = _start + sz;_endofstorage = _start + n;}}void resize(size_t n,const T& val=T())//匿名对象表示填入的缺省值,其有可能是整形,字符串,甚至是vector等,所以采用匿名对象来调用默认构造,而const可以延长匿名对象的生命周期到不用之后再销毁{if (n <= size())//不大于尾部不用变{_finish = _start + n;}else{reserve(n);//在reserve函数内部判断是否需要扩容,并返回扩容后的空间,_finish指针不会改变相对于_start的距离while (_finish < _start + n){*_finish = val;++_finish;}}}void push_back(const T& x){//如果空间满了先扩容size_t n = _finish - _start;//提前算出保存,避免后序因异地扩容而导致原地址失效if (_finish == _endofstorage){size_t len = capacity() == 0 ? 4 : capacity() * 2;//T* temp = new T[len];//if (_start)//如果原来有内容//{//	memcpy(temp, _start, sizeof(T)*n);//	delete[] _start;//}//_start = temp;//_finish = _start + n;//_endofstorage = _start + len;reserve(len);}*_finish = x;++_finish;//insert(end(), x);}void insert(iterator pos, const T& x)//而且此处不能加引用{assert(pos >= _start && pos <= _finish);//等于_finish相当于尾插//首先检查是否空间满了,满了就要先开空间if (_finish == _endofstorage){size_t len1 = pos - _start;cout <<"扩容前待插入位置相对于begin的偏移量为"<< len1 << endl;reserve(capacity() == 0 ? 4 : capacity() * 2);//注意,扩容之后会导致迭代器发生变化,也将会导致原来传入的参数失效size_t len2 = pos - _start;cout <<"扩容后待插入位置相对于begin的偏移量为"<< len2 << endl;pos = _start + len1;}iterator end = _finish-1;_finish++;while (end >= pos){*(end + 1) = *end;--end;}*pos = x;}iterator erase(iterator pos){assert(pos >= _start && pos < _finish);//注意不能等于_finish,因为_finish指向的是尾部元素的下一个元素,该处本来没有元素所以也不能删除iterator it = pos + 1;while (it != end()){*(it - 1) = *it;it++;}--_finish;return pos;//删除元素的下一个位置,其实还是该迭代器的位置,因为向前挪动覆盖,原来迭代器的位置上删除后存储的就是下一个元素}T& operator[](size_t pos)//可读可写{assert(pos < size());return _start[pos];}const T& operator[](size_t pos) const//只读{assert(pos < size());return _start[pos];}private://初始化加上缺省值iterator _start=nullptr;//开头iterator _finish = nullptr;//结尾加1iterator _endofstorage = nullptr;//空间容量};void test1(){vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);v.push_back(5);for (size_t i = 0; i < v.size(); i++)cout << v[i] << " ";cout << endl;for (vector<int>::iterator it = v.begin(); it != v.end(); it++){*it *= 10;cout << *it << " ";}cout << endl;for (auto e : v)cout << e << " ";cout << endl;}void test_vector2(){int i = 0;int j(1);//可见内置类型在模板中是存在默认构造函数的int k = int(2);vector<int*> v1;v1.resize(10);vector<string> v2;//v2.resize(10, string("xxx"));v2.resize(10, "xxx");//单参数的构造函数支持隐式类型的转换for (auto e : v1){cout << e << " ";}cout << endl;for (auto e : v2){cout << e << " ";}cout << endl;}void test_vector3(){vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);v.push_back(5);v.push_back(6);v.push_back(7);for (auto e : v){cout << e << " ";}cout << endl;vector<int>::iterator it = v.begin() + 2;v.insert(it, 30);for (auto e : v){cout << e << " ";}cout << endl;//v.insert(v.begin(), 30);v.insert(v.begin() + 3, 30);for (auto e : v){cout << e << " ";}cout << endl;}void test_vector4(){vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);v.push_back(5);v.push_back(6);v.push_back(7);for (auto e : v){cout << e << " ";}cout << endl;auto pos = v.begin();v.erase(pos);for (auto e : v){cout << e << " ";}cout << endl;v.erase(v.begin() + 3);for (auto e : v){cout << e << " ";}cout << endl;}void test_vector5(){// 1 2 3 4 5// 1 2 3 4 5 6// 2 2 3 4 5std::vector<int> v;//调用库里的vector,默认原来的erase迭代器失效v.push_back(2);v.push_back(2);v.push_back(3);v.push_back(4);v.push_back(5);//v.push_back(6);for (auto e : v){cout << e << " ";}cout << endl;auto it = v.begin();while (it != v.end()){if (*it % 2 == 0){it=v.erase(it);//让迭代器重新赋值,再次有效}else++it;}for (auto e : v){cout << e << " ";}cout << endl;}void test_vector6(){vector<string> v;v.push_back("111111111111111111111");v.push_back("111111111111111111111");v.push_back("111111111111111111111");v.push_back("111111111111111111111");v.push_back("111111111111111111111");for (auto e : v){cout << e << " ";}cout << endl;}void test_vector7(){vector<int> v1;v1.push_back(1);v1.push_back(1);v1.push_back(1);v1.push_back(1);v1.push_back(1);vector<int> v2(v1);for (auto e : v1){cout << e << " ";}cout << endl;for (auto e : v2){cout << e << " ";}cout << endl;vector<int> v3;v3.push_back(10);v3.push_back(20);v3.push_back(30);v3.push_back(40);v1 = v3;for (auto e : v1){cout << e << " ";}cout << endl;}void test_vector8(){vector<int> v0(10, 0);vector<string> v1(10, "xxxx");for (auto e : v1){cout << e << " ";}cout << endl;vector<int> v2;v2.push_back(10);v2.push_back(20);v2.push_back(30);v2.push_back(40);vector<int> v3(v2.begin(), v2.end());string str("hello world");vector<int> v4(str.begin(), str.end());for (auto e : v3){cout << e << " ";}cout << endl;for (auto e : v4){cout << e << " ";}cout << endl;}
}

test.cpp

#include<iostream>
using namespace std;
#include "vector.h"
int main()
{//my_std::test1();my_std::test_vector8();return 0;
}

       

        每个人都有一段异常艰难的时光,生活的压力,工作的失意,学业的压力,爱的惶惶不可终日,如果此刻不太顺利的话,一定是有十倍百倍的运气在前方等着你!不要怀疑自我,大步往前走,以后会很好很好的。

    

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

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

相关文章

学习笔记:用ROS接收rosbag发布的topic

用ROS接收 bag.open发布的topic python语言 要使用ROS接收保存在rosbag文件中的话题消息&#xff0c;可以按照以下步骤进行操作&#xff1a; 1.首先&#xff0c;请确保你已经安装了ROS和相关的依赖。 2.创建一个ROS功能包&#xff08;或使用现有的功能包&#xff09;来处理…

阿里云大数据实战记录8:拆开 json 的每一个元素,一行一个

目录 一、前言二、目标介绍三、使用 pgsql 实现3.1 拆分 content 字段3.2 拆分 level 字段3.3 拼接两个拆分结果 四、使用 ODPS SQL 实现4.1 拆分 content 字段4.2 拆分 level 字段4.3 合并拆分 五、使用 MySQL 实现六、总结 一、前言 商业场景中&#xff0c;经常会出现新的业…

docker network

docker network create <network>docker network connect <network> <container>docker network inspect <network>使用这个地址作为host即可 TODO&#xff1a;添加docker-compose

【CI/CD技术专题】「Docker实战系列」本地进行生成镜像以及标签Tag推送到DockerHub

背景介绍 Docker镜像构建成功后&#xff0c;只要有docker环境就可以使用&#xff0c;但必须将镜像推送到Docker Hub上去。创建的镜像最好要符合Docker Hub的tag要求&#xff0c;因为在Docker Hub注册的用户名是liboware&#xff0c;最后利用docker push命令推送镜像到公共仓库…

Redis发布订阅

Redis发布订阅 Redis 发布订阅(pub/sub)是一种 消息通信模式&#xff1a;发送者(pub)发送消息&#xff0c;订阅者(sub)接收消息。 Redis 客户端可以订阅任意数量的频道。 订阅/发布消息图&#xff1a; 下图展示了频道 channel1 &#xff0c; 以及订阅这个频道的三个客户端 —…

Linux中的工具:yum,vim,gcc/g++,make/makefile,gdb

目录 1、yum 1.1 查看软件包&#xff1a; 1.2 安装软件包 1.3 卸载软件 2、vim 2.1 vim的三种模式 2.2 vim的基本操作 2.3. vim正常模式命令集 2.3.1 插入模式 2.3.2 移动光标 2.3.3 删除文字 2.3.4 复制 2.3.5 替换 2.3.6撤销上一次操作 2.3.7 更改 2.3.8 跳至…

h5分享页适配手机电脑

实现思路 通过media媒体查询结合rem继承html文字大小来实现。 快捷插件配置 这里使用了VSCode的px to rem插件。 先在插件市场搜索cssrem下载插件&#xff1b; 配置插件 页面编写流程及适配详情 配置meta h5常用配置信息:<meta name"viewport" content&quo…

代码随想录训练营二刷第七天 | 454.四数相加II 383. 赎金信 15. 三数之和 18. 四数之和

代码随想录训练营二刷第七天 | 454.四数相加II 383. 赎金信 15. 三数之和 18. 四数之和 一、454.四数相加II 题目链接&#xff1a;https://leetcode.cn/problems/4sum-ii/ 思路&#xff1a;求索引的组合&#xff0c;转化为两组&#xff0c;ab -(cd)&#xff0c;map中键为ab&…

uniapp 开发之仿抖音,上下滑动切换视频、点击小爱心效果

效果图&#xff1a; 功能描述&#xff1a; 上下滑动视频&#xff0c;双击暂停&#xff0c;然后第一个视频再往上滑显示”已经滑到顶了“ 开始代码&#xff1a; 首先视频接口使用的公开的视频测试接口 开放API-2.0 官网展示 Swagger UI 接口文档 一…

Django基础7——用户认证系统、Session管理、CSRF安全防护机制

文章目录 一、用户认证系统二、案例&#xff1a;登陆认证2.1 平台登入2.2 平台登出2.3 login_required装饰器 三、Django Session管理3.1 Django使用Session3.1.1 Cookie用法3.1.2 Session用法 3.2 案例&#xff1a;用户登录认证 四、Django CSRF安全防护机制 一、用户认证系统…

4399面试总结C/C++游戏开发

主要流程 首先询问了C/C知识点 然后询问操作系统&#xff0c;计算机组成&#xff0c;数据结构&#xff0c;计算机网络哪两门熟悉 涉及的相关问题 多态的概念 tcp,udp&#xff1f; tcp,udp区别 tcp可靠&#xff0c;udp不可靠 tcp这个链接的过程? 一个TCP连接必须要经过三次“…

提升代码逻辑的感觉——python循环语句

提升代码逻辑的感觉——python循环语句 简介 循环是编程中的一个非常重要的概念&#xff0c;它用于处理重复性任何和迭代草错&#xff0c;通过循环我们能优化并简化代码&#xff0c;提高代码的可维护性&#xff0c;在Python中循环是一种控制结构&#xff0c;允许我们重复执行…

【100天精通python】Day47:python网络编程_Web编程基础

目录 1 网络编程与web编程 1.1 网络编程 1.2 web编程 2 Web开发概述 3 Web开发基础 3.1 HTTP协议 3.2 Web服务器 3.3 前端基础 3.4 静态服务器 3.5 前后端交互的基本原理 4 WSGI接口 4.1 CGI 简介 4.2 WSGI 简介 4.3 定义 WSGI 接口 4.4 运行 WSGI 服务 4.5…

视频汇聚/视频云存储/视频监控管理平台EasyCVR视频平台添加萤火云设备的具体操作步骤

安防视频监控/视频集中存储/云存储/磁盘阵列EasyCVR平台可拓展性强、视频能力灵活、部署轻快&#xff0c;可支持的主流标准协议有国标GB28181、RTSP/Onvif、RTMP等&#xff0c;以及支持厂家私有协议与SDK接入&#xff0c;包括海康Ehome、海大宇等设备的SDK等。平台既具备传统安…

【ARM Coresight 系列文章 20 -- linux perf 与 ARM coresight】

文章目录 1.1 Perf Introduction1.1.1 Perf 架构图1.1.2 Perf Tools 介绍1.1.3 Perf 命令介绍1.2 Events1.2.1 Perf 与 PMU 的关系1.2.2 Hardware events1.2.1.1 linux perf 事件分类1.2.2 Software Events1.2.3 Tracepoint Events1.3 Perf 工具使用1.4 用户态开发1.4.1 用户态…

Springboot 整合 Redis配置

RedisService接口 import java.util.List; import java.util.Map; import java.util.Set;/*** Redis操作Service* Created by macro on 2020/3/3.*/ public interface RedisService {/*** 保存属性*/void set(String key, Object value, long time);/*** 保存属性*/void set(St…

Java项目-苍穹外卖-Day07-redis缓存应用-SpringCache/购物车功能

文章目录 前言缓存菜品问题分析和实现思路缓存菜品数据清理缓存数据功能测试 SpringCache介绍入门案例 缓存套餐购物车功能添加购物车需求分析和产品原型测试 前言 本章节主要是进行用户端的购物车功能开发 和redis作为mysql缓存的应用以及SpringCache的介绍 因为很多人查询数…

VSCode-C++环境配置+Cmake

文章目录 一、环境配置二、Win10 Cmake 一、环境配置 转载链接 二、Win10 Cmake 创建CMakeLists.txt cmake_minimum_required(VERSION 3.26) project(graph_algorithm)set(CMAKE_CXX_STANDARD 17)add_executable(main main.cppshared_variable.cpp )cmake . -G "MinGW…

【Java List与Map】List<T> Map与Map List<T>的区别(126)

List&#xff1c;T&#xff1e; Map&#xff1a;List里面的数据类型包含Map&#xff1b; Map List&#xff1c;T&#xff1e;&#xff1a;Map里面value的数据类型包含List&#xff1b; 测试案例&#xff1a; import java.util.ArrayList; import java.util.HashMap; import j…

安全区域边界技术测评要求项

1.边界防护-非授权设备接入、非授权连接外部网络、无线网络使用和设备可信接入 &#xff08;网络边界就是采用不同安全策略的两个网络的连接处&#xff09; 1-1/2-1/3-4/4-6 a&#xff09;保证跨越边界的访问和数据流通过边界设备提供的受控接口进行通信 b&#xff09;应能够对…