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,一经查实,立即删除!

相关文章

阿里云大数据实战记录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…

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安全防护机制 一、用户认证系统…

【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等。平台既具备传统安…

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

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

Linux学习之RAID

基础概念 RAID&#xff0c;英文全称为Redundant Arrays of Independent Drives&#xff0c;RAID&#xff0c;中文称为独立冗余磁盘阵列&#xff0c;这项技术把多个硬盘设备组合成一个容量更大的、安全性更好的磁盘阵列&#xff0c;把数据切割成许多区段分别放在不同的物理磁盘…

15. 实现业务功能--帖子操作

1. 集成编译器 editor.md 支持 MarkDown 语法编辑&#xff0c;在需要用户输⼊内容的页面按以下代码嵌入编辑器 1.1 编写 HTML <!-- 引⼊编辑器的CSS --> <link rel"stylesheet" href"./dist/editor.md/css/editormd.min.css"> <!-- 引⼊编…

Linux服务器中创建SVN项目详细步骤

一、Linux服务器中的SVN安装和搭建项目环境可以参考一下文章: 1、《阿里云服务器搭建》------搭建SVN服务 2、在一个服务器的svn上&#xff0c;设置一个端口号对应一个项目 3、如何解决Linuxsvn无法显示日志的问题 二、Linux服务器中的SVN项目如何添加项目的忽略文件&#xff1…

Rabbitmq的消息转换器

Spring会把你发送的消息序列化为字节发送给MQ&#xff0c;接收消息的时候&#xff0c;还会把字节反序列化为Java对象 ,只不过&#xff0c;默认情况下Spring采用的序列化方式是JDK序列化。众所周知&#xff0c;JDK序列化存在下列问题&#xff1a; 数据体积过大 有安全漏洞 可读…

TensorFlow-slim包进行图像数据集分类---具体流程

TensorFlow中slim包的具体用法 1、训练脚本文件&#xff08;该文件包含数据下载打包、模型训练&#xff0c;模型评估流程&#xff09;3、模型训练1、数据集相关模块&#xff1a;2、设置网络模型模块3、数据预处理模块4、定义损失loss5、定义优化器模块 本次使用的TensorFlow版本…

open cv快速入门系列---数字图像基础

目录 一、数字图像基础 1.1 数字图像和图像单位 1.2 区分图片分辨率与屏幕分辨率 1.3 图像的灰度与灰度级 1.4 图像的深度 1.5 二值图像、灰度图像与彩色图像 1.6 通道数 二、数字图像处理 2.1 图像噪声及其消除 2.2 数字图像处理技术 2.2.1 图像变换 2.2.2 图像增强…

爬虫逆向实战(二十七)--某某招标投标网站招标公告

一、数据接口分析 主页地址&#xff1a;某网站 1、抓包 通过抓包可以发现数据接口是page 2、判断是否有加密参数 请求参数是否加密&#xff1f; 通过查看“载荷”模块可以发现&#xff0c;请求参数是一整个密文 请求头是否加密&#xff1f; 无响应是否加密&#xff1f; 通…

springboot集成es 插入和查询的简单使用

第一步&#xff1a;引入依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-elasticsearch</artifactId><version>2.2.5.RELEASE</version></dependency>第二步&#xff1a;…

Tomcat安装及基本使用

1. 什么是Web服务器 Web服务器是一种应用程序&#xff08;软件&#xff09;&#xff0c;它封装了对HTTP协议的操作&#xff0c;使得开发人员无需直接操作协议&#xff0c;从而简化了Web开发。其主要功能是提供网上信息浏览服务。 Web服务器安装在服务器端&#xff0c;我们可以…