【STL学习】(4)vector的模拟

前言

本文将模拟实现vector的常用功能,目的在于更深入理解vector。

一、前置知识

  1. 在模拟之前先对vector的结构和常用接口学习,有一个大致了解。
  2. 看源码,本文参考的源码是SGI版本的stl3.0。
    • 技巧:
      • 看源码不要一行一行的看,要先看框架,了解整体框架
      • 看源码要学会猜,根据单词的意思去猜它想表达什么。规范的代码,每一个名字都有它的含义。
      • 总结:一看框架;二去猜(带着猜想,去验证)。
    • 看框架的步骤:
      • 先看成员变量
      • 再看成员函数
  3. 参考vector的源码:
    • vector的成员变量:是三个原生指针变量(设为原生指针类型有什么好处,在模拟时讲解)在这里插入图片描述
    • vector的成员函数:vector的常用接口讲解链接
  4. STL中的容器因为需要频繁的申请和释放空间,所以STL中提供了内存池(allocator类模板),内存池的本质是先在堆区中申请一定的空间留作备用,当有新的内存需求时,就从内存池中分出一块内存块,若内存块不够再继续申请新的内存,这样可以提高内存分配的效率。现阶段我们只是简单模拟vector,所以我们这没有使用内存池,而是直接在堆区申请空间,后期会讲解内存池的。

二、vector常用接口的模拟

1、vector的成员变量

vector的成员变量是三个原生指针T*:

  1. _start:开始位置,即指向第一个元素的位置
  2. _finish:结束位置,即指向最后一个元素的下一个位置
  3. _end_of_storage:存储结束位置在这里插入图片描述
    虽然vector使用的是三个原生指针,但是可以通过指针运算得到size和capacity。

代码示例:

//为了避免与库中的vector冲突,将其封装在wjs的命名空间中
namespace wjs
{//类模板的实现和定义不分离,后续学到模板进阶会讲解!template<class T>class vector{public:typedef T* iterator;//获取容器中的元素个数size_t size()const//内部不改变成员变量,建议加上const——普通对象和const对象都可以调用{//指针-指针=两者之间的元素个数return _finish - _start;}//获取为当前容器分配的存储空间size_t capacity()const//内部不改变成员变量,建议加上const——普通对象和const对象都可以调用{//指针-指针=两者之间的元素个数return _end_of_storage - _start;}private:iterator _start;//开始位置,指向第一个元素iterator _finish;//结束位置,指向最后一个元素的下一个位置iterator _end_of_storage;//指向存储结束位置};
}

tip:

  1. 使用命名空间将我们模拟实现的vector封装,避免命名冲突。
  2. 类模板的定义与实现不分离,后续在模板进阶讲解。
  3. size和capacity可以通过指针-指针得到,注意指针-指针运算有一个前提是:物理存储空间是连续的。
  4. const成员:
    • const修饰的是*this,即const成员函数的内部不能修改成员变量
    • 建议只要成员函数内部不修改成员变量,都应该加const,这样普通对象和const对象都可以调用

2、vector的默认成员函数

构造函数

  • 构造函数:创建类对象时,编译器自动调用,给成员变量赋初值
  • 类的成员变量建议在初始化列表初始化
  • 成员变量为内置类型需要我们手动初始化,不然为随机值;成员变量为自定义类型不初始化,会去调用它的默认构造(建议每个类,都要有一个默认构造)
  • vector的常用构造函数:
    • 默认构造函数:一般使用最多,构造一个空的vector,即将每个成员初始化为空
    • 构造并初始化n个val:先初始化成员变量,再复用reserve开n的空间,最后再通过尾插将val插入容器
    • 使用迭代器初始化构造:先初始化成员变量,再将迭代器区间的数据尾插入容器

析构函数

  • 析构函数:对象销毁时,编译器自动调用,完成对象中资源的清理
  • 编译器生成的析构函数,对内置类型不做处理,自定义类型会去调用它的析构函数
  • 当类涉及动态资源的申请时,就需要显式实现析构释放资源。

赋值重载函数

  • 赋值重载函数:已经存在的两个对象复制拷贝
  • 当类涉及资源管理时,就需要自己显示实现完成深拷贝,编译器默认生成的赋值重载函数只能完成浅拷贝
  • 赋值重载深拷贝的现代写法:让形参去调用拷贝构造,去帮我们开空间拷贝数据,之后与形参交换(函数结束后形参销毁,也顺便帮我们把旧空间释放了)
  • 现代写法无法避免自己给自己赋值的情况,当现实中也很少会出现

拷贝构造函数

  • 拷贝构造函数:用一个已经存在的对象初始化另一个对象
  • 注意:拷贝构造只有一个参数,并且必须是本类型的引用(使用传值会引发无限递归)
  • 编译器默认生成的拷贝构造也是只能完成浅拷贝,所以当类涉及资源管理时,就需要自己显式实现完成深拷贝
  • 拷贝构造深拷贝的现代写法:自己开空间,自己拷贝数据

总结:当类涉及资源管理时,拷贝构造、赋值重载、析构都需要显式实现。

//为了避免与库中的vector冲突,将其封装在wjs的命名空间中
namespace wjs
{//类模板的实现和定义不分离,后续学到模板进阶会讲解!template<class T>class vector{public://默认构造函数vector()//初始化列表:成员变量定义的地方,建议在初始化列表初始化成员变量//成员变量为内置类型不初始化,为随机值:_start(nullptr),_finish(nullptr),_end_of_storage(nullptr){}//构造并初始化n个valvector(size_t n, const T& val = T())//T()调用构造函数//初始化成员变量:_start(nullptr),_finish(nullptr),_end_of_storage(nullptr){//复用reserve,开空间reserve(n);//复用push_back,尾插n个valfor (size_t i = 0; i < n; ++i){push_back(val);}}//使用迭代器区间初始化//类模板的成员函数也可以是函数模板template<class InputIterator>vector(InputIterator first, InputIterator last)//初始化成员变量:_start(nullptr),_finish(nullptr),_end_of_storage(nullptr){//复用push_back,将迭代器区间[first,last)的数据尾插进容器while (first != last){push_back(*first);++first;}}//当wjs::vector<int> v(10, 1);报错——》 error C2100: 非法的间接寻址//函数重载,调用时会走最匹配的,wjs::vector<int> v(10, 1)两个参数类型都是int,所以他走使用迭代器构造//重载一个vector(int n, const T& val = T()),他就会走构造n个val//构造并初始化n个valvector(int n, const T& val = T())//T()调用构造函数//初始化成员变量:_start(nullptr),_finish(nullptr),_end_of_storage(nullptr){//复用reserve,开空间reserve(n);//复用push_back,尾插n个valfor (int i = 0; i < n; ++i){push_back(val);}}//交换两个vector对象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=(vector<T> v)//形参v直接就去调用拷贝构造,帮我们开空间可拷贝数据了{//与v交换swap(v);return *this;}//拷贝构造函数vector(const vector<T>& v){//传统写法:自己开空间自己拷贝_start = new T[v.capacity()];//注意不能使用memcpy,它只能完成浅拷贝for (size_t i = 0; i < v.size(); ++i){//当T为自定义类型时,会去调用的它的赋值重载,完成深拷贝_start[i] = v._start[i];}_finish = _start + v.size();_end_of_storage = _start + v.capacity();}//析构函数~vector(){if (_start){delete[] _start;_start = _finish = _end_of_storage = nullptr;}}};
}

tip:

  • 重载函数,在调用时,会走匹配的。
  • T():T是一个模板参数,所以T()是一个任意类型的匿名对象。如果T是定义类型会去调用它的默认构造(从这点建议每个类都需要有一个默认构造),如果是内置类型也去调用内置类型的默认构造。
  • 理论上内置类型是没有构造函数的,但是有了模板之后,C++对此做了特殊处理,对内置类型做了升级,也提供了构造。
  • 结论:如果对象中涉及到资源管理时,千万不能使用memcpy进行对象之间的拷贝,因为memcpy是浅拷贝,可能会引起内存泄漏甚至程序崩溃。

3、vector的遍历

迭代器

  • begin():返回指向容器的第一个元素的位置的迭代器
  • end():返回指向容器最后一个元素的下一个位置的迭代器
  • begin和end一般会实现两个版本:普通版本和const版本
  • 有了迭代器,就可以使用范围for,因为范围for的底层就是替换为begin和end

operator[]

  • operator []越界是断言处理(断言只在debug下会生效,release下不生效)
  • operator[]一般也会实现两个版本:一个返回普通引用,一个返回常引用
//为了避免与库中的vector冲突,将其封装在wjs的命名空间中
namespace wjs
{//类模板的实现和定义不分离,后续学到模板进阶会讲解!template<class T>class vector{public:typedef T* iterator;typedef const T* const_iterator;//普通版本迭代器——迭代器可读可写iterator begin(){//返回指向第一个元素的位置的迭代器return _start;}iterator end(){//返回指向最后一个元素的下一位置的迭代器return _finish;}//const版本迭代器——迭代器只可读const_iterator begin()const{//返回指向第一个元素的位置的const迭代器return _start;}const_iterator end()const{//返回指向最后一个元素的下一位置的const迭代器return _finish;}//operator[]//普通版本——返回普通引用,可读可写T& operator[](size_t pos){//operator[]越界断言assert(pos >= 0 && pos < size());//返回pos位置元素的引用return _start[pos];}//cosnt版本——返回const引用,只可读const T& operator[](size_t pos)const{//operator[]越界断言assert(pos >= 0 && pos < size());//返回pos位置元素的常引用return _start[pos];}};
}

4、vector的reserve和resize

resize

  • resize将容器大小调整为n
    • 如果n小于当前容器大小,则内容将减少到其前n个元素,删除超出的部分
    • 如果n大于当前容器大小,则通过在末尾插入所需数量的元素来扩展内容,以达到n的大小。如果指定了val,则新元素将初始化为val的副本,否则,它们将进行值初始化
    • 注意:如果n也大于当前容器容量,则会自动重新分配存储空间

reserve

  • reserve请求改变容器的capacity,只有当n>当前容量时,reserve才会重新分配空间,将其容量增加到n(或更大)
namespace wjs
{//类模板的实现和定义不分离,后续学到模板进阶会讲解!template<class T>class vector{public://调整容器的sizevoid resize(size_t n, const T& val = T()){//如果n>sizeif (n > size()){//如果n>capacity,一次扩容,避免多次扩容reserve(n);//尾插val,使size=nwhile (_finish < _start + n){push_back(val);//++_finish;尾插之后finish会+1,所以这里不能再重复+1了}}else{//n<size,使size=n,调整_finish的位置即可_finish = _start + n;}}//调整容器容量void reserve(const size_t n){//只有n>capacity时,才会重新开空间,将其capacity增加到nif (n > capacity()){size_t old_size = size();iterator tmp = new T[n];//判断if (_start){//memcpy是浅拷贝,所以当拷贝的自定义类型且涉及资源管理时,就会报错!//memcpy(tmp, _start, sizeof(T) * old_size);for (size_t i = 0; i < old_size; ++i){//当T为自定义类型时会去调用它的赋值重载,完成深拷贝tmp[i] = _start[i];}delete[] _start;}_start = tmp;//_start的改变会影响size,所以需要在前面将旧size保存_finish = _start + old_size;_end_of_storage = _start + n;}}};
}

tip:

  • memcpy是内存的二进制格式拷贝,将一段内存空间中的内容原封不动的拷贝到另一段内存空间中,即memcpy是浅拷贝
  • 当memcpy拷贝的内容不涉及资源管理时,memcpy即高效又不会出错,但当memcpy拷贝的是自定义类型且涉及资源管理时,就会出错,因为memcpy不能完成深拷贝在这里插入图片描述
  • 结论:如果对象中涉及到资源管理时,千万不能使用memcpy进行对象之间的拷贝,因为memcpy是浅拷贝,可能会引起内存泄漏甚至程序崩溃。

5、vector的插入

push_back

  • 尾插,在vector的末尾插入x
  • 尾插需要注意:①尾插之前需要检查是否扩容;②尾插之后size+1,即++_finish
namespace wjs
{//类模板的实现和定义不分离,后续学到模板进阶会讲解!template<class T>class vector{public://尾插void push_back(const T& x){//插入之前判断是否需要扩容if (_finish == _end_of_storage){//扩容size_t new_capacity = capacity() == 0 ? 4 : 2 * capacity();reserve(new_capacity);}//尾插*_finish = x;++_finish;}};
}

insert

  • 在pos位置的元素之前插入x
  • insert需要注意:①断言pos位置是否合理;②插入之前检查是否需要扩容;③插入之后需要更新size
namespace wjs
{//类模板的实现和定义不分离,后续学到模板进阶会讲解!template<class T>class vector{public://pos位置的元素之前插入void insert(iterator pos, const T& x){//断言pos位置是否合理assert(pos >= _start && pos <= _finish);//插入之前判断是否需要扩容if (_finish == _end_of_storage){//扩容size_t new_capacity = capacity() == 0 ? 4 : 2 * capacity();reserve(new_capacity);}//[pos, _finish - 1]的数据向后挪动,将pos位置空出iterator end = _finish - 1;while (end >= pos){*(end + 1) = *end;--end;}//pos位置插入x*pos = x;++_finish;}};
}

tip:

  • insert在pos位置的元素之前插入x,需要向后挪动数据,在模拟实现string的时候,我们使用的是下标,当是头插的时候,结束条件我们不好控制,因为size_t不会小于0,我们当时使用了npos,现在vector使用iterator就不会出现这种情况了。

测试代码:

//测试insert
void test_vector04()
{wjs::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);v.push_back(8);for (auto& e : v){cout << e << " ";}cout << endl;
}

运行结果:

在这里插入图片描述

分析:

在这里插入图片描述
修改1:

namespace wjs
{//类模板的实现和定义不分离,后续学到模板进阶会讲解!template<class T>class vector{public://pos位置的元素之前插入void insert(iterator pos, const T& x){//断言pos位置是否合理assert(pos >= _start && pos <= _finish);//插入之前判断是否需要扩容//注意:扩容之后需要更新pos,否则pos指向释放的旧空间,会造成迭代器失效if (_finish == _end_of_storage){//计算pos与_start的相对距离size_t len = pos - _start;//扩容size_t new_capacity = capacity() == 0 ? 4 : 2 * capacity();reserve(new_capacity);//更新pospos = _start + len;}//[pos, _finish - 1]的数据向后挪动,将pos位置空出iterator end = _finish - 1;while (end >= pos){*(end + 1) = *end;--end;}//pos位置插入x*pos = x;++_finish;}};
}

测试代码:

//测试insert
void test_vector04()
{wjs::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);v.push_back(8);for (auto& e : v){cout << e << " ";}cout << endl;//头插100auto pos = v.begin();v.insert(pos, 100);//插入之后修改pos位置的元素*pos += 10;cout << *pos << endl;for (auto& e : v){cout << e << " ";}cout << endl;
}

运行结果:

在这里插入图片描述

分析:

  • 扩容之后,在insert中更新pos只解决了内部迭代器失效的问题,外面的pos并没有解决,它仍指向一块已经释放的空间。
  • 思考:那我们可以将pos参数设为引用,内部的改变也影响外面吗?
    答案是:不可以,因为引用的权限可以平移或缩小,但是不可以放大。当外面传的pos迭代器为一个const迭代器时,引用权限被放大,这是错误的,那将参数也设为常引用呢,这也不可以,因为常引用就不可以修改pos了。
  • insert解决外部pos迭代器失效的方法是插入之后,返回修改的pos迭代器(即指向新插入的第一个元素的迭代器)。

修改2:

namespace wjs
{//类模板的实现和定义不分离,后续学到模板进阶会讲解!template<class T>class vector{public://pos位置的元素之前插入iterator insert(iterator pos, const T& x){//断言pos位置是否合理assert(pos >= _start && pos <= _finish);//插入之前判断是否需要扩容//注意:扩容之后需要更新pos,否则pos指向释放的旧空间,会造成迭代器失效if (_finish == _end_of_storage){//计算pos与_start的相对距离size_t len = pos - _start;//扩容size_t new_capacity = capacity() == 0 ? 4 : 2 * capacity();reserve(new_capacity);//扩容之后更新pos,解决内部pos失效问题pos = _start + len;}//[pos, _finish - 1]的数据向后挪动,将pos位置空出iterator end = _finish - 1;while (end >= pos){*(end + 1) = *end;--end;}//pos位置插入x*pos = x;++_finish;//返回形参pos,解决外部pos失效问题return pos;}};
}

测试代码:

void test_vector04()
{wjs::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);v.push_back(8);for (auto& e : v){cout << e << " ";}cout << endl;//头插100auto pos = v.begin();//insert之后,pos迭代器可能会失效(扩容)//记住,insert之后就不要使用这个pos迭代器,因为它可能失效了//使用这个pos迭代器是一个高危行为//如果非要使用这个pos这个位置的迭代器,可以接收insert的返回值//insert的返回值就是指向pos这个位置的迭代器auto ret = v.insert(pos, 100);*ret += 10;cout << *ret << endl;for (auto& e : v){cout << e << " ";}cout << endl;
}

tip:

  • insert之后,pos迭代器可能会失效(扩容)
  • 记住,insert之后就不要使用这个pos迭代器,因为它可能失效了
  • 使用这个pos迭代器是一个高危行为

push_back可以复用insert:

namespace wjs
{//类模板的实现和定义不分离,后续学到模板进阶会讲解!template<class T>class vector{public://尾插void push_back(const T& x){插入之前判断是否需要扩容//if (_finish == _end_of_storage)//{//	//扩容//	size_t new_capacity = capacity() == 0 ? 4 : 2 * capacity();//	reserve(new_capacity);//}尾插//*_finish = x;//++_finish;//复用insertinsert(_finish, x);}};
}

6、vector的删除

erase

  • 删除pos位置的元素
  • erase需要注意:①断言pos位置是否合理(即有没有数据);②删除之后更新size,即_finish。

思考 erase存在迭代器失效吗?

在这里插入图片描述

  • erase删除pos位置元素后,迭代器的意义变了(指向删除的最后一个元素之后的元素的新位置,理论上迭代器并没有失效,因为删除并没有改变底层空间)
  • 注意:如果pos是最后一个元素,删除之后pos刚好是_finish的位置,而_finish位置是没有元素的,所以pos迭代器失效
  • VS系列下检测比较严格,删除vector任意位置上的元素,都认为该位置迭代器失效了
  • Linux下,g++编译器对迭代器失效的检测就相对佛系,处理没有VS下极端,只有删除vector最后一个元素,才认为迭代器失效了(在实际场景中,迭代器的意义变了,也容易出现各种问题)
  • 总结:vector 迭代器对象在erase或insert后,不能再访问这个迭代器,我们都认为它失效了,访问结果是未定义的。
  • erase通过返回一个指向删除的最后一个元素之后的元素的新位置迭代器解决迭代器失效问题。
namespace wjs
{//类模板的实现和定义不分离,后续学到模板进阶会讲解!template<class T>class vector{public://删除pos位置的元素iterator erase(iterator pos){//断言pos是否合理assert(pos >= _start && pos < _finish);//删除pos位置元素,即将[pos+1, _finish - 1]的元素向前挪动iterator begin = pos + 1;while (begin < _finish){*(begin - 1) = *begin;++begin;}--_finish;//erase通过返回一个指向删除的最后一个元素之后的元素的新位置迭代器解决迭代器失效问题,即pos迭代器return pos;}};
}

测试代码:

void test_vector05()
{wjs::vector<int> v2;v2.push_back(1);v2.push_back(2);v2.push_back(2);v2.push_back(3);v2.push_back(4);v2.push_back(5);v2.push_back(6);for (auto& e : v2){cout << e << " ";}cout << endl;//删除所有偶数auto it2 = v2.begin();while (it2 != v2.end()){if (*it2 % 2 == 0){//erase之后迭代器失效//解决方案:it2需要接收erase的返回值it2 = v2.erase(it2);}else{++it2;}}for (auto& e : v2){cout << e << " ";}cout << endl;
}

tip:

  • insert和erase之后的迭代器失效,是通过接收返回值解决的

pop_back

  • 尾删,删除vector中的最后一个元素,尾删之后size-1
  • 这里直接复用erase即可
namespace wjs
{//类模板的实现和定义不分离,后续学到模板进阶会讲解!template<class T>class vector{public:void pop_back(){//复用eraseerase(_finish - 1);}};
}

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

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

相关文章

【全套源码教程】基于SpringBoot+MyBatis+Vue的流浪动物救助网站的设计与实现

目录 前言 需求分析 可行性分析 技术实现 后端框架&#xff1a;Spring Boot 持久层框架&#xff1a;MyBatis 前端框架&#xff1a;Vue.js 数据库&#xff1a;MySQL 功能介绍 前台界面功能介绍 动物领养及捐赠 宠物论坛 公告信息 商品页面 寻宠服务 个人中心 购…

全面探究 LangChain Text Splitters

全面探究 LangChain Text Splitters 0. 引言1. 文本拆分器的类型2. 探究各个文本拆分器2-1. Split by HTML header2-2. Split by HTML section2-3. Split by character2-4. Split code2-5. MarkdownHeaderTextSplitter2-6. Recursively split JSON2-7. Recursively split by ch…

力扣---分隔链表

给你一个链表的头节点 head 和一个特定值 x &#xff0c;请你对链表进行分隔&#xff0c;使得所有 小于 x 的节点都出现在 大于或等于 x 的节点之前。 你应当 保留 两个分区中每个节点的初始相对位置。 示例 1&#xff1a; 输入&#xff1a;head [1,4,3,2,5,2], x 3 输出&a…

C语言调用Python

目录 1.直接调用python语句 头文件引用 2.调用无参有参函数 1、调用无参函数 1.建立nopara.py文件 2.使用c语言根据上面流程进行调用 2、调用有参函数 1.建立nopara.py文件 2.使用c语言根据上面流程进行调用 C语言调用python需要我们已经安装好了libpython3的 dev依赖…

DFS序列

什么是DFS序 DFS序是指对一棵树进行DFS时&#xff0c;每个节点被访问到的顺序。DFS序分成两个部分&#xff1a;进入该节点的顺序和退出该节点的顺序。 如何求DFS序 对于DFS中当前节点 1&#xff1a;计数 2&#xff1a;进入当前节点的顺序等于当前计数 3&#xff1a;想所有…

Shell脚本从0到会基础学习(个人学习)

一、前言 本人在找工作的时候&#xff0c;发现有的职位要会shell或者是python其中一种脚本语言。由于博主学过python。所以专门开栏学习&#xff0c;只做个人学习。一天学会那种~ 二、练习 2.1 我的第一个脚本--HelloWorld 代码 运行结果 2.2 输出系统常量和 自定义变量 #!…

5. 4 二重循环将二维数组的某列、某矩形转大写

5. 4 二重循环将二维数组的某列、某矩形转大写 1. 把每一行的b都变成大写 assume cs:codesg,ds:data,ss:stack data segmeNTstr db aaaaabbbbbcccccdb aaaaabbbbbcccccdb aaaaabbbbbcccccdb aaaaabbbbbccccc,$ data endsstack segmentdb 10 dup(0) stack endscodesg SEgments…

一种遥感影像多类变化检测方法

多任务学习孪生网络的遥感影像多类变化检测 马惠1, 刘波2, 杜世宏2 1.河南省国土空间调查规划院,郑州 450016 2.北京大学遥感与地理信息系统研究所,北京 100871 摘要: 精确掌握土地覆盖/利用的变化及变化类型对国土空间规划、生态环境监测、灾害评估等有着重要意义,然而现有…

渲染一帧特效需要多少钱?云渲染特效每帧成本

特效渲染的成本受到诸多因素的影响&#xff0c;每帧的渲染费用是评估整个项目预算的重要依据。随着云渲染技术的发展&#xff0c;其高效率和可伸缩性赢得了业界的广泛关注。对于影视制作公司和独立创作者而言&#xff0c;掌握云渲染特效的单帧成本是管理和优化预算分配的关键。…

PCL 点到三角形的距离(3D)

文章目录 一、简介二、实现代码三、实现效果参考资料一、简介 给定三角形ABC和点P,设Q为描述ABC上离P最近的点。求Q的一个方法:如果P在ABC内,那么P的正交投影点就是离P最近的点Q。如果P投影在ABC之外,最近的点则必须位于它的一条边上。在这种情况下,Q可以通过计算线段AB、…

算法:计数类dp

文章目录 一、举个栗子例子1&#xff1a;爬楼梯问题例子2&#xff1a;不同路径例子3&#xff1a;计数子序列 二、基本思路三、典型例题一、ACWing&#xff1a;900. 整数划分1、解法一1.1、状态转移方程1.2、参考代码 O(n) 超时 2、解法二&#xff1a;类似完全背包问题1.1、状态…

博客部署002-centos安装nginx

1、centos 如何安装nginx? 在CentOS系统上安装Nginx的过程相对直接&#xff0c;通常可以通过系统自带的Yum包管理器来安装。以下是安装Nginx的最新稳定版的步骤&#xff1a; 1.1 更新系统软件包 在安装Nginx之前&#xff0c;首先确保系统软件包是最新的&#xff0c;运行…

如何使用GraphQL和Apollo构建一个宝可梦应用

宝可梦是一个由视频游戏、动画系列与电影、交换卡牌游戏以及其他相关媒体组成的日本媒体特许经营权。 在本文中&#xff0c;我们将使用一个宝可梦GraphQL API&#xff0c;该API提供有关不同宝可梦的数据。 我们将使用Apollo和GraphQL来处理数据获取&#xff0c;以及React来构…

谨慎使用通过光纤传输的HDMI光纤线,存严重缺陷

严重缺陷&#xff1a; 1.只能单向传输 只能单向传输&#xff0c;从一端到另一端&#xff0c;和二极管一样&#xff0c;只能单向传输信号。某些情况你需要变更传输方向时&#xff0c;你将欲哭无泪.传统的HDMI线&#xff0c;不带放大器的&#xff0c;都可以双向传输.网上搜索布…

Vue3【进阶】

简介 https://cn.vuejs.org/guide/introduction.html 创建vue3工程 【基于 vue-cli创建】 基本和vue-cli的过程类似&#xff0c;只是选择的时候用vue3创建 【基于vite创建】【推荐】 【官网】https://vitejs.cn/ 【可以先去学一下webpack】 步骤 【https://cn.vitejs.…

#QT项目实战(天气预报)

1.IDE&#xff1a;QTCreator 2.实验&#xff1a; 3.记录&#xff1a; &#xff08;1&#xff09;调用API的Url a.调用API获取IP whois.pconline.com.cn/ipJson.jsp?iphttp://whois.pconline.com.cn/ipJson.jsp?ip if(window.IPCallBack) {IPCallBack({"ip":&quo…

华为海思2024春招数字芯片岗机试题(共9套)

huawei海思2024春招数字芯片岗机试题(共9套&#xff09;&#xff08;WX:didadidadidida313&#xff0c;加我备注&#xff1a;CSDN huawei数字题目&#xff0c;谢绝白嫖哈&#xff09; 题目包含数字集成电路、System Verilog、Verilog2001、半导体制造技术、高级ASIC芯片综合、…

Java实现二叉树(上)

1.树型结构 1.1树型结构的概念 树是一种 非线性 的数据结构&#xff0c;它是由 n &#xff08; n>0 &#xff09;个有限结点组成一个具有层次关系的集合。 把它叫做树是因为它看 起来像一棵倒挂的树&#xff0c;也就是说它是根朝上&#xff0c;而叶朝下的 1.2树型结构的特点…

二 maven构建项目

一 Maven的GAVP Maven工程相对之前的工程&#xff0c;多出一组gavp属性&#xff0c;gav需要我们在创建项目的时指定&#xff0c;p有默认值&#xff0c;后期通过配置文件修改。 GAVP是指 GroupId、ArtifactId、Version、Packaging 等四个属性的缩写&#xff0c;其中前三个是必…

c++的学习之路:14、list(1)

本章讲一下如何使用list&#xff0c;代码在文章末 目录 一、list介绍 二、增 三、删 四、查和改 五、交换 六、代码 一、list介绍 首先还是看一看官方文档的介绍如下图&#xff0c;如下方五点&#xff1a; 1. list是可以在常数范围内在任意位置进行插入和删除的序列式…