C++:Vector的模拟实现

                                                   创作不易,感谢三连 !!

一,前言 

       在学习string类的时候,我们可能会发现遍历的话下标访问特别香,比迭代器用的舒服,但是下标其实只能是支持连续的空间,他的使用是非常具有局限性的,随着STL学习的深入我们会发现其实迭代器才是大佬!!Vector虽然也支持下标访问,但是很多成员函数都是用的迭代器,所以我们要模拟实现的话迭代器十分重要,vs使用的是PJ版的STL版本,比较难懂,所以我们模拟实现统一用SGI版本去实现,所以在模拟实现之前,我们要去看看他的源码到底有哪些成员变量

       SGI下的vector有三个成员变量,通过观察其他源码可以大致推断  _start是指向起始位置,_finish是指向有效数据的下一个位置(迭代器都遵循左闭右开),end_of_storage是指向有效容量的最后一个位置。

     通过这个我们可以观察到SGI版本下的迭代器其实就是一个原生指针,value_type*类型相当于是模板T对应的指针类型,有了这些大致了解,我们就可以去模拟实现啦!!

二,vector的模拟实现

    大致框架需要有模板(类外定义)/迭代器以及迭代器的获取(public定义,要有可读可写的也要有可读不可写的)/成员变量(private定义)  并且为了不和库的vector冲突,我们需要自己搞一个命名空间

namespace cyx
{
//模板
template<class T>
//迭代器(可读可写)
class vector
{
public:
typedef T* iterator;iterator begin()
{return _start;
}iterator end()
{return _finish;
}
//迭代器(可读不可写)
typedef const T* const_iterator;const_iterator begin() const
{return _start;
}const_iterator end() const
{return _finish;
}
private:
//成员变量
iterator _start;
iterator _finish;
iterator _end_of_storage;
}
}

然后我们开始实现!! 

2.1 构造函数和析构函数

2.1.1 无参构造函数

	//无参构造函数vector():_start(nullptr),_finish(nullptr),_end_of_storage(nullptr){}

2.1.2 迭代器区间构造

//传别人的迭代器进行构造
template<class InputIterator>
vector(InputIterator first, InputIterator last):_start(nullptr), _finish(nullptr), _end_of_storage(nullptr)
{//这里传的是别人的迭代器,不知道会传多少数据,不能提前扩容,只能让pushback的时候去判断while (first != last){push_back(*first);++first;}
}

 push_back是尾插数据,具体实现后面会写。

思考:为什么迭代器也要搞个类模板呢?

        答:本质上是为了让这个函数更加灵活,可以传不同类型的迭代器来帮助我们初始化!!

比如这个地方我们传string类的迭代器

 传vector类的迭代器

 2.1.3 有参构造函数(对n个存储的类型去调用他们的构造)

//有参构造函数(对n个存储的类型去调用他们的构造)
vector(size_t n,const T&val=T() ):_start(nullptr), _finish(nullptr), _end_of_storage(nullptr)
{reserve(n);//因为我们知道会进多少数据,所以可以提前开空间for (int i = 0; i < n; ++i)push_back(val);
}

reserve是扩容到n,具体实现后面会写。

思考:

1.缺省值T( )是什么意思

      答:这个地方的缺省值不能给0!!因为vector可能会存储内置类型,也可能会存储自定义类型,比如vector<string>,所以如果我们没给值,缺省值就要给他的默认无参构造函数,这个默认构造函数可以使用匿名对象。

2.const T&val=T()  T()不是用一次就析构吗,为什么可以用引用

     答:T()是一个用一次就析构的匿名对象,其实本质上是因为他没有名字,用T引用val可以充当他的名字,此时用val就相当于用这个匿名对象,所以匿名对象的生命周期被延长到和val一样但是由于匿名对象是一个临时变量,所以具有常性,所以必须用const修饰的val才可以当他的别名,否则会出现权限放大!!

3.非法的间接寻址是为什么?

如下图我传(10,5),会出非法间接寻址

 但是我传(10u,5)就可以正常使用了,为什么会这样??

  答:

根据上图写出一个重载的有参构造

//重载一个防止间接寻址
vector(int n, const T val = T()):_start(nullptr), _finish(nullptr), _end_of_storage(nullptr)
{reserve(n);//因为我们知道会进多少数据,所以可以提前开空间for (int i = 0; i < n; ++i)push_back(val);
}

 2.1.4 拷贝构造+memcpy拷贝问题+赋值重载

 但是真的有这么顺利吗??

思考:

为什么存string类就会崩了??    这就涉及到memcpy的拷贝问题

 我们以上述问题来画图解释一下

总结:

1. memcpy是内存的二进制格式拷贝,将一段内存空间中内容原封不动的拷贝到另外一段内存空间中
2. 如果拷贝的是自定义类型的元素,memcpy既高效又不会出错,但如果拷贝的是自定义类型元素,并且自定义类型元素中涉及到资源管理时,就会出错,因为memcpy的拷贝实际是浅拷贝。
       如果对象中涉及到资源管理时,千万不能使用memcpy进行对象之间的拷贝,因为memcpy是
浅拷贝,否则可能会引起内存泄漏甚至程序崩溃。

 

所以在这个地方我们的拷贝构造不能用memcpy

//拷贝构造(传统)
vector(const vector<T>& v):_start(nullptr),_finish(nullptr),_end_of_storage(nullptr)
{_start = new T[v.capacity()];//memcpy(_start, v._start, sizeof(T)*v.size());  不能用memcpy 是浅拷贝for (int i = 0; i < v.size(); ++i)_start[i] = v._start[i];//实现重载运算符_finish = _start + v.size();_end_of_storage = _start + v.capacity();
}

 但是真的没有问题了吗??看看这个

 但道理来说得打印出9个1  结果呢??

 原因是什么呢,我们先看看resize函数

//重载赋值=(传统)
vector<T>& operator=(const vector<T> &v)
{T* temp = new T [v.capacity()];for(int i=0;i<v.size();++i)temp[i] = v[i];delete[]_start;_start = temp;_finish = _start +v.size();_end_of_storage = _start +v.capacity();return *this;
}

 2.1.5 拷贝构造和赋值重载的现代写法

先写个swap函数

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

        拷贝构造的现代写法思路:创建一个临时对象利用被拷贝对象的迭代器区间构造,然后再swap一下就可以了!

	vector(const vector<T>& v):_start(nullptr), _finish(nullptr), _end_of_storage(nullptr){vector<T> temp(v.begin(), v.end());//让临时对象借助迭代器区间构造出来swap(temp);//窃取革命成果}

       赋值重载的现代写法的思路:反正我自己的空间也不要了,被赋值对象传值过来(这样被赋值对象不会被修改),然后直接和临时对象swap就可以了!!

vector<T>& operator=(vector<T> v)
{swap(v);//反正我原来的空间也要销毁,我跟你传值过来的v直接交换,而且不会改变你return *this;
}

2.1.6 析构函数 

~vector()
{/*if (_start)*///delete 会自动检查空指针  没必要delete[] _start;_start = _finish = _end_of_storage = nullptr;
}

注意:delete空指针是没关系的,delete会自己判断     delete出问题一般都是野指针

2.1.7 构造函数相关的全部代码

 我们发现大部分都设计要到初始化为nullptr,c11后是支持直接在成员变量那边给缺省值的,所以们可以美化一下

全部代码

		//无参构造函数vector(){}//有参构造函数(对n个存储的类型去调用他们的构造)vector(size_t n, const T& val = T()){reserve(n);//因为我们知道会进多少数据,所以可以提前开空间for (int i = 0; i < n; ++i)push_back(val);}//重载一个防止间接寻址vector(int n, const T val = T()){reserve(n);//因为我们知道会进多少数据,所以可以提前开空间for (int i = 0; i < n; ++i)push_back(val);}//传别人的迭代器区间进行构造template<class InputIterator>vector(InputIterator first, InputIterator last){//这里传的是别人的迭代器,不知道会传多少数据,不能提前扩容,只能让pushback的时候去判断while (first != last){push_back(*first);++first;}}//拷贝构造(传统写法)vector(const vector<T>& v){_start = new T[v.capacity()];//memcpy(_start, v._start, sizeof(T)*v.size());  不能用memcpy 是浅拷贝for (size_t i = 0; i < v.size(); ++i)_start[i] = v._start[i];//实现重载运算符_finish = _start + v.size();_end_of_storage = _start + v.capacity();}//拷贝构造(现代写法)//vector(const vector<T>& v)//{//	vector<T> temp(v.begin(), v.end());//让临时对象借助迭代器区间构造出来//	swap(temp);//窃取革命成果//}//交换void swap(vector<T>& v){std::swap(_start, v._start);std::swap(_finish, v._finish);std::swap(_end_of_storage, v._end_of_storage);}重载赋值=(传统)vector<T>& operator=(const vector<T>& v){T* temp = new T[v.capacity()];for (int i = 0; i < v.size(); ++i)temp[i] = v[i];delete[]_start;_start = temp;_finish = _start + v.size();_end_of_storage = _start + v.capacity();return *this;}//赋值重载现代写法//vector<T>& operator=(vector<T> v)//{//	swap(v);//反正我原来的空间也要销毁,我跟你传值过来的v直接交换,而且不会改变你//	return *this;//}//析构函数~vector(){/*if (_start)*///delete 会自动检查空指针  没必要检查delete[] _start;_start = _finish = _end_of_storage = nullptr;}

 2.2 常见接口

2.2.1 获取size和capacity

//获取size
size_t size() const
{return _finish - _start;
}
//获取capacoty
size_t capacity() const
{return _end_of_storage - _start;
}

 2.2.2 判空

		//判空bool empty() const{return _start == _finish;}

2.2.3 重载[ ]

1.可读可写[ ]

	//重载[](可读可写)T& operator[](size_t pos){assert(pos < size());return _start[pos];}

2.可读不可写[]

	//重载[](可读不可写)const T& operator[](size_t pos) const{assert(pos < size());return _start[pos];}

3.三种访问方法

下标

//下标遍历
for (int i = 0; i < v1.size(); ++i)cout << v1[i] << " ";
cout << endl;

迭代器

	//迭代器遍历vector<int>::const_iterator it = v1.begin();while (it != v1.end()){cout << *it << " ";++it;}cout << endl;

范围for 

//范围for遍历
for (auto e : v1)cout << e << " ";
cout << endl;v1.resize(100);
cout << v1.size() << endl;for (auto e : v1)cout << e << " ";
cout << endl;

 2.2.4 提前扩容

void reserve(size_t n)
{size_t sz = size();//防止丢失if (n > capacity()){T* temp = new T[n];if (_start)//如果为空,就不需要拷贝也不需要释放{for (size_t i = 0; i < sz; ++i)temp[i] = _start[i];delete[] _start;}_start = temp;_finish = _start + sz;_end_of_storage = _start + n;}
}

 考虑到之前的memcpy拷贝问题,这里不能用memcpy了!!

还要注意的是要提前记录size(),否则原空间销毁了就找不到了。

2.2.5 提前扩容+初始化

有三种情况,第一种是给的n比原来的size小,第二种是n比size大但是比capacity小,第三种是n比capacity大,这个时候需要扩容

	//提前扩容+初始化void resize(size_t n, T val = T()){//给小if (n < size())_finish = _start + n;//给大else{//容量不够就扩if (n > capacity())reserve(n);while (_finish != _start + n){*_finish = val;++_finish;}}}

2.2.6 尾插和尾删

void push_back(const T& val)
{if (_finish == _end_of_storage)reserve(capacity() == 0 ? 4 : 2 * capacity());*_finish = val;++_finish;
}
//尾删
void pop_back()
{//防止没有元素可删assert(!empty());--_finish;
}

 尾插要注意扩容之前要判断一下,因为如果是0的话怎么扩都是0

我们会发现这次的指定位置插入删除不像string那样用size_t pos 而是iterator pos

2.2.7 指定位置插入

 这样写有什么问题吗??

看似好像没有什么问题,但是如果把pushback(5)去掉

 为什么会这样呢?

原因就是扩容后空间变了,但是pos还是指向原来的空间!!

所以我们解决方案就是pos在扩容的时候要更新一下

iterator insert(iterator pos, const T& val)
{assert(pos >= _start);assert(pos <= _finish);if (_finish == _end_of_storage){size_t len = pos - _start;//记录相对距离,方便更新posreserve(capacity() == 0 ? 4 : 2 * capacity());pos = _start + len;}iterator end = _finish - 1;while (end >= pos){*(end + 1) = *end;--end;}*pos = val;++_finish;return pos;
}

2.2.8 指定位置删除

返回值是pos的下一个位置 

	iterator erase(iterator pos){assert(pos >= _start);assert(pos < _finish);iterator start = pos + 1;while (start != _finish){*(start - 1) = *start;++start;}--_finish;return pos;}

2.3 迭代器失效问题

会引起其底层空间改变的操作,都有可能使得迭代器失效。

 比如:resize、reserve、insert、erase、 push_back等。

2.3.1.insert的失效

就是因为扩容导致pos失效,我们需要去及时更新pos

      但是我们传的pos是值传递,所以我们更新的后pos更新,我们在后面解引用pos就会出现经典的解引用野指针问题。

 那我们怎么传回pos呢??就得用返回值!!这也是为什么insert的返回值用iterator的原因,我们想继续用的话就得去接收一下返回值,就可以了

     虽然有了返回值,我们可以去接收更新后的pos,但是一旦我们使用了任意一个可能扩容的函数,都会到时pos的失效,从而有可能回引发野指针问题,这个问题是不太好避免的,所以我们认为迭代器只能用一次,因为结果不可预测!

2.3.2 erase的失效

        erase 删除 pos 位置元素后,pos 位置之后的元素会往前搬移,没有导致底层空间的改变,理论上讲迭代器不应该会失效,但是:如果 pos 刚好是最后一个元素,删完之后 pos 刚好是 end 的位置,而 end 位置是没有元素的,那么 pos 就失效了。因此删除 vector 中任意位置上元素时,vs 就认为该位置迭代器失效了。

vs和g++对比

 结果是未定义的!!不同编译器场景可能不同,严格来说vs更严谨 

思考:

假设没有强制检查(比如我们自己写的vector),想删除删除 vector 中所有偶数

 但是如果只有4个

为什么会这样呢,我们画图分析

   从这边我们也能看到为什么erase返回值也要用iterator的原因,我们想继续用的话就得去接收一下返回值

2.3.3 扩容导致的失效

可能本来还能用,但是中间扩容过,所以也不能用了

用pos前用一样reserve,也会失效

总而言之:尽量不要复用pos迭代器,因为任何一个可能扩容的操作都会导致失效

2.4 比较不常用的接口

2.4.1 清理元素

	void clear() const{_finish = _start;}

2.4.2 缩容

void shrink_to_fit()
{size_t sz = size();//记录T* temp = new T[sz];for (size_t i = 0; i < sz; ++i)temp[i] = _start[i];delete _start;_start = temp;_finish = _start + sz;_end_of_storage = _start + sz;
}

三,vector实现的全部代码

namespace cyx
{template<class T>class vector{public://迭代器(可读可写)typedef T* iterator;iterator begin(){return _start;}iterator end(){return _finish;}//迭代器(可读不可写)typedef const T* const_iterator;const_iterator begin() const{return _start;}const_iterator end() const{return _finish;}//无参构造函数vector(){}//有参构造函数(对n个存储的类型去调用他们的构造)vector(size_t n, const T& val = T()){reserve(n);//因为我们知道会进多少数据,所以可以提前开空间for (int i = 0; i < n; ++i)push_back(val);}//重载一个防止间接寻址vector(int n, const T val = T()){reserve(n);//因为我们知道会进多少数据,所以可以提前开空间for (int i = 0; i < n; ++i)push_back(val);}//传别人的迭代器进行构造template<class InputIterator>vector(InputIterator first, InputIterator last){//这里传的是别人的迭代器,不知道会传多少数据,不能提前扩容,只能让pushback的时候去判断while (first != last){push_back(*first);++first;}}//拷贝构造(传统写法)vector(const vector<T>& v){_start = new T[v.capacity()];//memcpy(_start, v._start, sizeof(T)*v.size());  不能用memcpy 是浅拷贝for (size_t i = 0; i < v.size(); ++i)_start[i] = v._start[i];//实现重载运算符_finish = _start + v.size();_end_of_storage = _start + v.capacity();}//拷贝构造(现代写法)//vector(const vector<T>& v)//{//	vector<T> temp(v.begin(), v.end());//让临时对象借助迭代器区间构造出来//	swap(temp);//窃取革命成果//}//交换void swap(vector<T>& v){std::swap(_start, v._start);std::swap(_finish, v._finish);std::swap(_end_of_storage, v._end_of_storage);}重载赋值=(传统)vector<T>& operator=(const vector<T>& v){T* temp = new T[v.capacity()];for (int i = 0; i < v.size(); ++i)temp[i] = v[i];delete[]_start;_start = temp;_finish = _start + v.size();_end_of_storage = _start + v.capacity();return *this;}//赋值重载现代写法//vector<T>& operator=(vector<T> v)//{//	swap(v);//反正我原来的空间也要销毁,我跟你传值过来的v直接交换,而且不会改变你//	return *this;//}//析构函数~vector(){/*if (_start)*///delete 会自动检查空指针delete[] _start;_start = _finish = _end_of_storage = nullptr;}//获取sizesize_t size() const{return _finish - _start;}//获取capacotysize_t capacity() const{return _end_of_storage - _start;}//判空bool empty() const{return _start == _finish;}//重载[](可读可写)T& operator[](size_t pos){assert(pos < size());return _start[pos];}//重载[](可读不可写)const T& operator[](size_t pos) const{assert(pos < size());return _start[pos];}//提前扩容+初始化void resize(size_t n, T val = T()){//给小if (n < size())_finish = _start + n;//给大else{//容量不够就扩if (n > capacity())reserve(n);while (_finish != _start + n){*_finish = val;++_finish;}}}//提前扩容void reserve(size_t n){size_t sz = size();//防止丢失if (n > capacity()){T* temp = new T[n];if (_start)//如果为空,就不需要拷贝也不需要释放{for (size_t i = 0; i < sz; ++i)temp[i] = _start[i];delete[] _start;}_start = temp;_finish = _start + sz;_end_of_storage = _start + n;}}//尾插void push_back(const T& val){if (_finish == _end_of_storage)reserve(capacity() == 0 ? 4 : 2 * capacity());*_finish = val;++_finish;}//尾删void pop_back(){//防止没有元素可删assert(!empty());--_finish;}//指定位置插入iterator insert(iterator pos, const T& val){assert(pos >= _start);assert(pos <= _finish);if (_finish == _end_of_storage){size_t len = pos - _start;reserve(capacity() == 0 ? 4 : 2 * capacity());pos = _start + len;}iterator end = _finish - 1;while (end >= pos){*(end + 1) = *end;--end;}*pos = val;++_finish;return pos;}//指定位置删除iterator erase(iterator pos){assert(pos >= _start);assert(pos < _finish);iterator start = pos + 1;while (start != _finish){*(start - 1) = *start;++start;}--_finish;return pos;}//清理元素void clear() const{_finish = _start;}//缩容void shrink_to_fit(){size_t sz = size();//记录T* temp = new T[sz];for (size_t i = 0; i < sz; ++i)temp[i] = _start[i];delete _start;_start = temp;_finish = _start + sz;_end_of_storage = _start + sz;}private:iterator _start= nullptr;iterator _finish= nullptr;iterator _end_of_storage= nullptr;};

有什么不懂得可以问博主哦!

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

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

相关文章

第 125 场 LeetCode 双周赛题解

A 超过阈值的最少操作数 I 排序然后查找第一个大于等于 k 的元素所在的位置 class Solution { public:int minOperations(vector<int> &nums, int k) {sort(nums.begin(), nums.end());return lower_bound(nums.begin(), nums.end(), k) - nums.begin();} };B 超过阈…

C++ 位运算OJ

目录 1、 191. 位1的个数 2、 338. 比特位计数 3、 461. 汉明距离 4、136. 只出现一次的数字 5、 260. 只出现一次的数字 III 6、面试题 01.01. 判定字符是否唯一 7、 268. 丢失的数字 8、 371. 两整数之和 9、 137. 只出现一次的数字 II 10、面试题 17.19. 消失的两个…

【C++】二叉树进阶面试题(上)

目录 1. 二叉树创建字符串 题目 分析 代码 2. 二叉树的分层遍历1 题目 分析 代码 3. 二叉树的分层遍历2 题目 分析 代码 4. 给定一个二叉树, 找到该树中两个指定节点的最近公共祖先 题目 分析 代码 5. 二叉树搜索树转换成排序双向链表 题目 分析 代码 1. …

使用GitHub API 查询开源项目信息

一、GitHub API介绍 GitHub API 是一组 RESTful API 接口&#xff0c;用于与 GitHub 平台进行交互。通过使用 GitHub API&#xff0c;开发人员可以访问和操作 GitHub 平台上的各种资源&#xff0c;如仓库、提交记录、问题等。 GitHub API 提供了多种功能和端点&#xff0c;以…

昇腾芯片解析:华为自主研发的人工智能处理器全面分析

在当今科技发展的浪潮中&#xff0c;昇腾芯片作为一种新兴的处理器&#xff0c;正引起广泛的关注和讨论。升腾芯片究竟是由哪家公司生产的&#xff1f;这个问题一直困扰着许多人。下面小编将全面介绍、分析升腾芯片的生产商及各类参数、应用&#xff0c;以便读者对其有更全面的…

自学高效备考2025年AMC8数学竞赛:2000-2024年AMC8真题解析

今天继续来随机看五道AMC8的真题和解析&#xff0c;根据实践经验&#xff0c;对于想了解或者加AMC8美国数学竞赛的孩子来说&#xff0c;吃透AMC8历年真题是备考最科学、最有效的方法之一。即使不参加AMC8竞赛&#xff0c;吃透了历年真题600道和背后的知识体系&#xff0c;那么小…

Maven终端命令生成Spring-boot项目并输出“helloworld“

1. 生成项目 mvn archetype:generate填写groupId和artifactId&#xff0c;其余默认即可 2. 修改pom.xml文件 将如下内容放入pom.xml文件内 <parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artif…

Conda笔记--移动Conda环境后pip使用异常的解决

1--概述 由于各种原因&#xff0c;需要将Anaconda转变为Minicoda&#xff0c;为了保留之前安装的所有环境&#xff0c;直接将anaconda3/envs的所有环境拷贝到Miniconda/envs中&#xff0c;但在使用移动后环境时会出现pip的错误&#xff1a;bad interpreter: No such file or di…

计算机网络-物理层-传输媒体

传输媒体的分类 导向型-同轴电缆 导向型-双绞线 导向型-光纤 非导向型

面试问答总结之Java进阶

文章目录 &#x1f412;个人主页&#x1f3c5;JavaEE系列专栏&#x1f4d6;前言&#xff1a;&#x1f380;注解Annotaion &#xff08;java标注&#xff09;&#x1f415;内置注解&#x1f415;元注解 &#x1f380;对象克隆&#x1f415;如何实现克隆&#x1f415;如何实现深克…

C#,最小代价多边形三角剖分MCPT(Minimum Cost Polygon Triangulation)算法与源代码

1 最小代价多边形三角剖分算法 凸多边形的三角剖分是通过在非相邻顶点&#xff08;角点&#xff09;之间绘制对角线来形成的&#xff0c;这样对角线就不会相交。问题是如何以最小的代价找到三角剖分的代价。三角剖分的代价是其组成三角形的权重之和。每个三角形的重量是其周长…

LLM春招准备(1)

llm排序 GPT4V GPT-4V可以很好地理解直接绘制在图像上的视觉指示。它可以直接识别叠加在图像上的不同类型的视觉标记作为指针&#xff0c;例如圆形、方框和手绘&#xff08;见下图&#xff09;。虽然GPT-4V能够直接理解坐标&#xff0c;但相比于仅文本坐标&#xff0c;GPT-4V在…

蓝桥杯练习题——dp

五部曲&#xff08;代码随想录&#xff09; 1.确定 dp 数组以及下标含义 2.确定递推公式 3.确定 dp 数组初始化 4.确定遍历顺序 5.debug 入门题 1.斐波那契数 思路 1.f[i]&#xff1a;第 i 个数的值 2.f[i] f[i - 1] f[i - 2] 3.f[0] 0, f[1] 1 4.顺序遍历 5.记得特判 …

Web前端---表格和表单

1.表格概述 表格标记&#xff1a;<table></table> 表格标题标记&#xff1a;<caption></caption> 表头&#xff1a;<th></th>------heading 行标记&#xff1a;<tr></tr>-----r是row 列标记&#xff1a;<td></t…

数据可视化原理-腾讯-散点图

在做数据分析类的产品功能设计时&#xff0c;经常用到可视化方式&#xff0c;挖掘数据价值&#xff0c;表达数据的内在规律与特征展示给客户。 可是作为一个产品经理&#xff0c;&#xff08;1&#xff09;如果不能够掌握各类可视化图形的含义&#xff0c;就不知道哪类数据该用…

快速搭建Vue前端框架

快速搭建Vue前端框架 安装Vue Vue官方安装过程:https://cli.vuejs.org/zh/guide/installation.html 二.创建Vue工程 2.2 安装淘宝镜像 安装淘宝镜像&#xff08;会让你安装Vue的速度加快&#xff09;&#xff1a; npm config set registry https://registry.npm.taobao.or…

CMU 10-414/714: Deep Learning Systems --hw0

hw0 宏观上的步骤: softmax loss: 实现softmax loss代码 概念 softmax就是将结果映射到0~1之间,且所有结果相加为1(概率形式)cross-entropy loss就是计算 p ( x ) log ⁡ q ( x ) p(x)\log {q(x)} p(x)logq(x),此值可用于衡量实际输出与期望输出的距离,进而衡量预测模…

Qt 实现橡皮擦拭显示图片

1.简介 在一些游戏中看见类似解密破案的效果&#xff0c;使用手触摸去擦拭图片上的灰尘&#xff0c;然后显示最终的图片&#xff0c;所以也想试试Qt实现的效果。大家有自己想做的效果&#xff0c;都可以尝试。 以下是效果展示图。 可以控制橡皮擦的大小&#xff0c;进行擦拭…

CRMCHAT修复获取客户ip信息,地区信息

CRMCHAT修复获取客户ip信息&#xff0c;地区信息-TP源码网原因&#xff1a; 因pv.sohu.com/cityjson?ieutf-8接口已无法正确获取ip信息&#xff0c;导致后台站点统计无法正确获取用户ip信息&#xff0c;无法获取地区信息 修改 注释掉无用接口地址 修复ip信息 也可以使用&…

NLP评价指标

一、分类任务常见评估&#xff1a; 准确度(Accuracy) 评估预测正确的比例&#xff0c;精确率(Precision) 评估预测正例的查准率&#xff0c;召回率(Recall) 评估真实正例的查全率。如果是多分类&#xff0c;则每个类别各自求P、R最终求平均值。 TP&#xff08;True Positives…