【STL深入浅出】之从零到精通:vector使用与模拟

在这里插入图片描述

📃博客主页: 小镇敲码人
💚代码仓库,欢迎访问
🚀 欢迎关注:👍点赞 👂🏽留言 😍收藏
🌏 任尔江湖满血骨,我自踏雪寻梅香。 万千浮云遮碧月,独傲天下百坚强。 男儿应有龙腾志,盖世一意转洪荒。 莫使此生无痕度,终归人间一捧黄。🍎🍎🍎
❤️ 什么?你问我答案,少年你看,下一个十年又来了 💞 💞 💞

【STL深入浅出】之从零到精通:vector使用与模拟

  • 🌞 vector的使用介绍
    • 🐕 vector容器的介绍
    • 🐕 vector容器的一些重要接口
      • 🌿 构造函数
      • 🌿 迭代器的使用
      • 🌿 push_back和pop_back函数
      • 🌿 insert和erase
      • 🌿 迭代器失效问题及其解决办法
        • 🐞 扩容导致迭代器失效
        • 🐞 删除数据导致迭代器失效
  • 🌞 vector的模拟实现
    • 🐕 vector的成员变量
      • 🌿 基本框架
    • 🐕 vector成员函数的模拟实现
      • 🌿 迭代器部分
      • 构造函数部分
      • 🌿 和空间相关的函数
      • 🌿 []访问函数
      • 🌿 交换函数
      • 赋值运算符重载函数
      • 🌿 尾插和尾删函数
      • 🌿 插入和删除函数
    • 🐕 vector的测试
      • 🌿 测试访问
        • 🐞 像数组一样使用[]访问
        • 🐞 迭代器访问
      • 🌿 尾插和尾删
      • 🌿 插入函数
      • 🌿 erase函数测试
      • 🌿 缩容和扩容函数测试
      • 🌿 其它函数(区间构造、clear、empty)测试
      • 🌿 测试动态二维vector

前言:上篇博客我们介绍了string容器的使用及其模拟实现,今天我们继续来看C++STL中的另外一个重要的容器vector的使用及其模拟实现。代码仓库自取

🌞 vector的使用介绍

由于STL中各个容器的使用和接口都比较相似,我们主要介绍vectorstring这个容器不太一样的点。

🐕 vector容器的介绍

vector类似我们学习过的数据结构动态顺序表,只不过这里是使用c++语言描述的,它的空间随元素的增加可以动态的增长,不像普通的数组一样,空间是一个定值。
在这里插入图片描述
c++中的vector类是一个类模板,也就是我们在创建vector这个对象的时候,需要显式的传一个类型作为vector容器中存储的数据的类型。

🐕 vector容器的一些重要接口

🌿 构造函数


我们了解vector的构造函数后,就知道了如何来创建vector对象。c++98版本给出了4种构造方法。我们在string类的介绍中也谈到过,分别是无参数的默认构造、有参数的构造、使用迭代器来进行范围构造、拷贝构造。我们重点来看一下第二个构造函数。
在这里插入图片描述

代码演示:

#include<iostream>
#include<vector>using namespace std;
void Test1()
{vector<int> arr1(10);//构造一个大小为10的vector对象for (int i = 0; i < 10; ++i)cout << arr1[i] << endl;int arr2[10];for (int i = 0; i < 10; ++i)cout << arr2[i] << endl;
}
int main()
{Test1();return 0;
}

运行结果:

在这里插入图片描述

这是为什么呢?我们在创建vector对象的时候也没有给初值,这里为什么打印vector的10个元素全都初始化为了0呢,这是由于第二个构造函数的第二个参数有缺省值。

在这里插入图片描述

可以看到这里这个构造函数的第二个参数是代表n个元素初始化的值,这里如果不给就会给缺省值value_type(),这个value_type就是类的第一个模板参数。类型后面加括号,我们在学习类和对象时就知道,这是该类型的一个匿名对象,但是自定义类型可以理解,内置类型也有它的匿名对象吗?这里C++为了给缺省值方便,给内置类型也设置了匿名对象:
看下面这段代码:

#include<iostream>
#include<vector>using namespace std;
typedef int* pf;
void Test2()//内置类型的匿名对象
{cout << int() << endl;cout << float() << endl;cout << pf() << endl;cout << char() << endl;
}int main()
{Test2();return 0;
}

运行结果:

在这里插入图片描述

注意,如果是指针类型编译器会打印这个指针的地址,32为机器是4个字节(32bit位),以16进制形式打印也就是0x00000000.
可以看到,intdouble是0,指针是空,char\0

🌿 迭代器的使用

像数组那样访问数据我们上面的代码已经演示过了,接下来来介绍一下vector使用迭代器访问数据:

迭代器也有两种访问,一种是范围for,另外一种是普通的按照迭代器(像指针一样的行为)去走。

c++11之后支持范围for,它的底层还是去调用迭代器的函数,begin()end,利用迭代器实现的:
我们可以通过反汇编来验证我们的结论:

在这里插入图片描述
代码演示:

#include<iostream>
#include<vector>using namespace std;void Test3()//迭代器访问
{vector<int> arr(10, 1);//创建vector对象vector<int>::iterator it = arr.begin();while(it != arr.end())//老老实实使用迭代器{cout << *it << endl;it++;}for (int val: arr)//范围for,交给编译器来做{cout << val << endl;}
}
int main()
{Test3();return 0;
}

运行结果:

在这里插入图片描述

  • 注意:如果在使用范围for访问时,想要改变元素中的值,范围for里面的那个变量要带引用。

🌿 push_back和pop_back函数

这是vector中使用的比较多的函数,尾插和尾删:
在这里插入图片描述
在这里插入图片描述

使用起来也很简单:

#include<iostream>
#include<vector>using namespace std;void Test4()//尾插和尾删
{vector<int> arr;//创建arr对象for (int i = 0; i < 10; ++i)//尾插10个元素arr.push_back(i);for (auto& val : arr)cout << val << " ";cout << endl;arr.pop_back();//尾删arr.pop_back();//尾删for (auto& val : arr)cout << val << " ";
}
int main()
{Test3();return 0;
}

运行结果:

在这里插入图片描述

🌿 insert和erase

我们想在vector对象中进行头插和头删,或者在中间某个位置进行插入或者删除,还得借助这两个函数:

  1. insert插入函数:

在这里插入图片描述
string类中的insert函数类似,都是在某个迭代器前插入一个值或者一些值,也可以用迭代器实现范围插入。这个函数一般和find函数配合使用,find函数的作用是返回某个值的迭代器的值。

算法库<algorithm>中有通用的查找函数,我们的vector类好像没有额外提供。

在这里插入图片描述

我们用下面的代码来演示一下:

#include<iostream>
#include<vector>using namespace std;void Test5()//insert
{vector<int> arr(5, 1);//创建vector对象for (int val : arr)//范围for,交给编译器来做{cout << val << " ";}cout << endl;arr.insert(arr.begin() + 3, 12);//在arr.begin()+3位置前插入12for (int val : arr)//范围for,交给编译器来做{cout << val << " ";}cout << endl;arr.insert(arr.begin(),3,-1);//头插3个-1for (int val : arr)//范围for,交给编译器来做{cout << val << " ";}cout << endl;arr.insert(arr.end(), arr.begin(), arr.begin() + 2);//把arr这两个迭代器范围的值,尾插到arr中for (int val : arr)//范围for,交给编译器来做{cout << val << " ";}cout << endl;
}
int main()
{Test5();return 0;
}

运行结果:

在这里插入图片描述
2.erase函数

在这里插入图片描述

实现了两个版本一个是直接删除pos位置的值,还有一个是删除某个范围的值。

代码演示:

#include<iostream>
#include<vector>using namespace std;void Test6()//erase
{vector<int> arr;for (int i = 0; i < 10; ++i)arr.push_back(i);for (int val : arr)//范围for,交给编译器来做{cout << val << " ";}cout << endl;arr.erase(arr.end()-1);//尾删for (int val : arr)//范围for,交给编译器来做{cout << val << " ";}cout << endl;arr.erase(arr.begin(),arr.end()-1);//[begin,end-1)for (int val : arr)//范围for,交给编译器来做{cout << val << " ";}cout << endl;}int main()
{Test6();return 0;
}

运行结果:

在这里插入图片描述

区间删除,都是左闭右开。

🌿 迭代器失效问题及其解决办法

所谓迭代器失效问题,说白了就是由于插入或者删除某个值后,导致原先迭代器位置的地址(vector的迭代器也是原生指针)已经失效(不属于我们了或者不代表我们想要的那个含义),文字比较抽象,我们来看具体的例子:

🐞 扩容导致迭代器失效

这个很好理解,扩容之后,原先的地址空间不属于我们了,再去访问肯定会出问题:

#include<iostream>
#include<vector>using namespace std;void Test7()//扩容导致迭代器失效
{vector<int> arr(10,-1);auto it = arr.begin();arr.resize(100, 0);while(it != arr.end()){cout << *it << " ";it++;}
}
int main()
{Test7();return 0;
}

运行结果:

在这里插入图片描述

解决办法,重新给迭代器赋值一下即可:

在这里插入图片描述

🐞 删除数据导致迭代器失效
#include<iostream>
#include<vector>using namespace std;void Test8()//删除元素导致迭代器失效
{vector<int> arr(10, -1);auto it = arr.begin();arr.erase(arr.begin());//头删while (it != arr.end()){cout << *it << endl;it++;}
}
int main()
{Test8();return 0;
}

运行结果:

在这里插入图片描述

按理来说,删除一个元素后,后面的元素为挪动到相应的位置,迭代器的地址并没有失效,但是此时如果这个元素是最后一个元素,删除之后变成了end,那就出问题了,所以VS(LIUNX下不一定)为了防止这样的事情出现,删除任意一个元素后,原先的迭代器都会失效,要重新更新。

在这里插入图片描述

string也会有迭代器失效的问题,和vector类似。

🌞 vector的模拟实现

🐕 vector的成员变量

我们可以借助stl30的版本,来借鉴一下它是如何设计成员变量的:

在这里插入图片描述
在这里插入图片描述

我们不难知道,start就是我们vector类存储元素的起始迭代器,finish就类似endend_of_storage是我们实际开的空间的结束地址。这里我们看源码还可以发现反向迭代器需要重新搞一个类模板出来,我们这里就不实现反向迭代器了。

🌿 基本框架

namespace my_vector
{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;}iterator end() const{return finish;}size_t size() const{return finish - start;}size_t capacity() const{return end_of_storage - start;}bool empty() const{vector():start(nullptr),finish(nullptr),end_of_storage(nullptr)//无参构造函数{};vector(size_t n, const T& val = T());vector(int n, const T& val = T());vector(const vector<T>& x);template <class InputIterator>vector(InputIterator first, InputIterator last);//函数模板,区间初始化vector& operator= (const vector<T>& x);//赋值运算符重载void resize(size_t n, const T& val = T());T& operator[] (size_t n);const T& operator[] (size_t n) const;	void push_back(const T& val);void pop_back();iterator insert(iterator position, const T& val);iterator erase(iterator position);void shrink_to_fit();void swap(vector& x);void clear();~vector();private:iterator start;iterator finish;iterator end_of_storage;};template<class T>void swap(vector<T>& x, vector<T>& y);
}

🐕 vector成员函数的模拟实现

这里vector涉及到模板的分文件编译,我们会在模板进阶里面谈到,所以这里我们就不再分文件写了,都放在.h文件中实现。

🌿 迭代器部分

这部分还是一如既往的很短,我们只实现了普通的迭代器和const的迭代器:

		typedef T* iterator;typedef const T* const_iterator;iterator begin(){return start;}iterator end(){return finish;}const_iterator begin() const{return start;}iterator end() const{return finish;}

构造函数部分

这里我们构造函数实现了5个版本,其中第3个构造函数是为了防止编译器去调用第5个构造函数,因为编译器再找不到现成的函数的时候会去使用第5个函数模板自己生成一份,这样就不是我们本意了。这个问题待会我们在测试的时候还会细说。

     vector():start(nullptr),finish(nullptr),end_of_storage(nullptr)//无参构造函数{};vector(size_t n, const T& val = T())//第一个参数是元素的个数,第二个是每个元素的值{resize(n, val);}vector(int n, const T& val = T())//第一个参数是元素的个数,第二个是每个元素的值{resize(n, val);}vector(const vector<T>& x)//拷贝构造{reserve(x.capacity());//扩容resize(x.size());//改变sizefor (int i = 0; i < x.size(); ++i)*(start + i) = *(x.start + i);}template <class InputIterator>vector(InputIterator first, InputIterator last)//函数模板,区间初始化{while (first != last){push_back(*first);first++;}}

这里其实还有一点就是在拷贝构造的时候为什么没有直接去使用内存拷贝函数memcpy,这是因为元素T的类型可以是自定义类型,如果直接使用值拷贝,就是浅拷贝,可能会引发多次析构的问题,如果使用赋值,就会去调用T的赋值运算符重载函数(假设T为自定义类型)。

🌿 和空间相关的函数

	   size_t size() const{return finish - start;}size_t capacity() const{return end_of_storage - start;}bool empty() const//判断vector是否为空,我们只需要看size是否为0即可{return start == finish;//return size() == 0;}void resize(size_t n, const T& val = T()){if (n > size())//如果n比当前的size大{reserve(n);//不管需不需要扩容,我们先调用这个函数再说,因为vector的这个reserve是不会缩容的while (finish < start + n)//把多出来的赋值为val{*(finish++) = val;}}else//否则,直接改finish{finish = start+n;}}void reserve(size_t n)//扩容函数{if (n > capacity())//如果需要扩容{T* tmp = new T[n];//重新开空间size_t old_size = size();//先保存之前的size()大小if (start)//如果start不为空{for (int i = 0; i < size(); ++i)//把之前的值赋值给新开的动态数组tmp[i] = *(start + i);delete[] start;//释放原来的空间}//更新相应的值start = tmp;finish = tmp + old_size;end_of_storage = tmp + n;}}void clear()//清理内容,但是不清理空间{finish = start;}void shrink_to_fit(){if (capacity() > size()){int n = size();T* tmp = new T[n];for (int i = 0; i < size(); ++i)tmp[i] = (*this)[i];if(start)delete[] start;start = tmp;finish = end_of_storage = tmp + n;}}~vector(){delete[] start;start = finish = end_of_storage = nullptr;}	

这里vector类中的reserve函数和string的有所不同,通过我们的测试,发现在VS的环境下,reserve函数不会缩容(无论size()为多少),所以我们自己实现的时候也就没有缩容。

缩容函数shrink_to_fit我们实现的逻辑就是让空间和元素的个数保持一致,stl 30版本也是类似的逻辑。

在这里插入图片描述

🌿 []访问函数

运算符重载中函数中的一种,vector的空间是连续存储的所以支持了operator []函数。实现逻辑很简单,将对应的指针解引用即可,注意指针应该在数组范围内,不能越界。

  	T& operator[] (size_t n){assert(n < size());return *(start + n);}const T& operator[] (size_t n) const{assert(n < size());return *(start + n);}

🌿 交换函数

类里面有一个交换函数,在类外面我们也实现了一个交换函数,和string类似,第二个swap函数放在类外面:

     void swap(vector& x)//复用库里面的swap函数,交换两者的变量的值{std::swap(x.finish, finish);std::swap(x.start, start);std::swap(x.end_of_storage,end_of_storage);}template<class T>void swap(vector<T>& x, vector<T>& y)//复用刚刚实现的类里面的swap函数{x.swap(y);}

赋值运算符重载函数

这个函数我们还是复用拷贝构造函数,然后调用类中的swap函数交换一下tmp*this的内容。

	vector& operator= (const vector<T>& x)//赋值运算符重载{vector<T> tmp(x);swap(tmp);return *this;}

🌿 尾插和尾删函数

这两个函数在vector中使用的还算比较多。实现起来注意尾插的扩容逻辑即可,因为有时候会出现capacity为0的情况。

		void push_back(const T& val){if (finish < end_of_storage)//如果不需要扩容{*(finish++) = val;}else//如果需要扩容,就去复用reserve{size_t newcapacity = capacity() == 0 ? 4 : capacity() * 1.5;reserve(newcapacity);*(finish++) = val;}}void pop_back(){assert(start != finish);//数组不能没有元素finish--;}

🌿 插入和删除函数

尾插和尾删函数不能满足我们的所有需求,我们还实现了插入和删除函数:
插入的逻辑是把相应的值后挪,然后插入。删除的逻辑则是一直往前覆盖,直到pos位置停下,然后最后finish--

       iterator insert(iterator position, const T& val){//删除的位置必须不能越界assert(position >= start);assert(position <= finish);size_t pos = position - start;//先保存position指针离start的相对位置,防止后面会迭代器失效reserve(size() + 1);//不管需不需要扩容,先去调用一下扩容函数,如果需要扩容就会扩容,不需要就什么也不干for (int i = size(); i > pos; --i)//把所有的值后挪动一位(*this)[i] = (*this)[i - 1];(*this)[pos] = val;//插入finish++;return start + pos;}iterator erase(iterator position){//删除的位置必须不能越界assert(position >= start);assert(position < finish);for (int i = position - start; i < size() - 1; ++i)//把pos位置后面的值往前覆盖(*this)[i] = (*this)[i+1];finish--;return position;//返回最后一个被删除位置的下一个新元素的迭代器}

🐕 vector的测试

构造函数的测试贯穿了整个测试函数,我们就不单独测试了。

🌿 测试访问

🐞 像数组一样使用[]访问
#include "vector.h"void Test1()
{my_vector::vector<int> arr1(10);//构造一个大小为10的vector对象for (int i = 0; i < 10; ++i)cout << arr1[i] << " ";cout << endl;int arr2[10];for (int i = 0; i < 10; ++i)cout << arr2[i] << " ";cout << endl;
}int main()
{Test1();
}

vector类型会去调用operator []函数:

在这里插入图片描述
运行结果:

在这里插入图片描述

🐞 迭代器访问
#include "vector.h"void Test3()//迭代器访问
{my_vector::vector<int> arr(10, 1);//创建vector对象my_vector::vector<int>::iterator it = arr.begin();while(it != arr.end())//老老实实使用迭代器{cout << *it << " ";it++;}cout << endl;for (int val: arr)//范围for,交给编译器来做{cout << val << " ";}
}int main()
{Test3();
}

运行结果:

在这里插入图片描述

这个地方我们之前埋了一个坑,在创建vector对象的时候,我们调用的是下面画框的构造函数:

在这里插入图片描述

如果没有这个函数存在,也不会去调用第一个而是会去调用下面这个函数模板生成的int函数:

在这里插入图片描述

因为编译器在只存在这个模板函数和size_t类型的构造函数的时候,不会去考虑那个size_t,因为涉及到隐式类型转换,而size_t没有负数,这一行为可能是危险的,所以编译器宁可使用下面的函数模板去自己生成一个int型的函数,但是这样一来问题就大了,因为我们的本意这个模板函数是迭代器区间构造,对一个int型进行解引用会报错的,我们把int型的那个函数屏蔽,发现编译器去调用那个函数模板的构造函数,然而这并非我们的本意:

在这里插入图片描述

如果我们把这个函数模板的区间构造函数注释掉,编译器才会不得不调用刚刚的size_t类型的函数,这也是模板的一个规则,有合适的现成的吃现成的,没现成看有没有合适的模板。

🌿 尾插和尾删

#include "vector.h"void Test4()//尾插和尾删
{my_vector::vector<int> arr;//创建arr对象for (int i = 0; i < 10; ++i)//尾插10个元素arr.push_back(i);for (auto& val : arr)cout << val << " ";cout << endl;arr.pop_back();//尾删arr.pop_back();//尾删for (auto& val : arr)cout << val << " ";
}int main()
{Test4();
}

运行结果:

在这里插入图片描述

🌿 插入函数

#include "vector.h"void Test5()//insert
{my_vector::vector<int> arr(5, 1);//创建vector对象for (int val : arr)//范围for,交给编译器来做{cout << val << " ";}cout << endl;arr.insert(arr.begin() + 3, 12);//在arr.begin()+3位置前插入12for (int val : arr)//范围for,交给编译器来做{cout << val << " ";}cout << endl;arr.insert(arr.begin(),-1);//头插1个-1for (int val : arr)//范围for,交给编译器来做{cout << val << " ";}cout << endl;
}int main()
{Test5();
}

运行结果:

在这里插入图片描述

🌿 erase函数测试

#include "vector.h"void Test6()//erase
{my_vector::vector<int> arr;my_vector::vector<int> arr1;arr1 = arr;for (int i = 0; i < 10; ++i)arr.push_back(i);for (int val : arr)//范围for,交给编译器来做{cout << val << " ";}cout << endl;arr.insert(arr.begin(),100);arr.erase(arr.end()-1);//尾删for (int val : arr)//范围for,交给编译器来做{cout << val << " ";}cout << endl;
}int main()
{Test6();
}

运行结果:

在这里插入图片描述

🌿 缩容和扩容函数测试

#include"vector.h"void Test9()//reserve
{my_vector::vector<int> arr;arr.reserve(10000);//扩容cout << arr.capacity() << endl;arr.reserve(10);//缩容,不会成功cout << arr.capacity() << endl;
}void Test10()//shrink_to_fit
{my_vector::vector<int> arr;arr.reserve(1000);cout << arr.capacity() << endl;arr.resize(99);arr.shrink_to_fit();cout << arr.capacity() << endl;
}int main()
{Test9();Test10();return 0;
}

运行结果:

在这里插入图片描述

🌿 其它函数(区间构造、clear、empty)测试

#include"vector.h"void Test11()//迭代器范围构造
{my_vector::vector<int> arr1(10, 1);//普通构造my_vector::vector<int> arr2(arr1.begin(), arr1.end());//区间构造for (auto& i : arr2)cout << i << " ";cout << endl;my_vector::vector<int> arr3(11,0);//创建arr3for (auto& i : arr3)cout << i << " ";cout << endl;arr3 = arr2;//赋值运算符重载for (auto& i : arr3)cout << i << " ";cout << endl;arr2[1] = 100;//更改第2个元素的值swap(arr1, arr2);for (auto& i : arr1)cout << i << " ";cout << endl;arr1.clear();//清除arr1的内容cout << arr1.capacity() << " " << arr1.size() << endl;cout << arr1.empty() << endl;
}
int main()
{Test11();return 0;
}

运行结果:

在这里插入图片描述

🌿 测试动态二维vector

这里二维的vector我们将其和二维数组联系起来理解就简单多了,外面的vector的数组存储的类型也是vector<元素类型>。二维数组是存储一维数组的数组,对应二维vector是存储一维vector的vector
测试函数:

#include"vector.h"// 以杨慧三角的前n行为例:void Test12(size_t n)
{// 使用vector定义二维数组vv,vv中的每个元素都是vector<int>my_vector::vector<my_vector::vector<int>> vv(n);// 将二维数组每一行中的vecotr<int>中的元素全部设置为1
for (size_t i = 0; i < n; ++i)vv[i].resize(i + 1, 1);
// 给杨慧三角出第一列和对角线的所有元素赋值for (int i = 2; i < n; ++i){for (int j = 1; j < i; ++j){vv[i][j] = vv[i - 1][j] + vv[i - 1][j - 1];}}for (int i = 0; i < n; ++i){for (auto i : vv[i])cout << i << " ";cout << endl;}
}
int main()
{Test12(10);return 0;
}

运行结果:

在这里插入图片描述

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

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

相关文章

thinkphp6 queue队列的maxTries自定义

前景需求&#xff1a;在我们用队列的时候发现maxtries的个数时255次&#xff0c;这个太影响其他队列任务 我目前使用的thinkphp版本是6.1 第一部定义一个新的类 CustomDataBase&#xff08;我用的mysql数据库存放的队列&#xff09; 重写__make 和createPlainPayload方法 …

每日两题 / 34. 在排序数组中查找元素的第一个和最后一个位置 33. 搜索旋转排序数组(LeetCode热题100)

34. 在排序数组中查找元素的第一个和最后一个位置 - 力扣&#xff08;LeetCode&#xff09; 根据二分函数&#xff0c;得到>target和<target的两个&#xff0c;分别是答案的l和r class Solution { public:vector<int> searchRange(vector<int>& nums,…

【Uniapp小程序】自定义导航栏uni-nav-bar滚动渐变色

效果图 新建activityScrollTop.js作为mixins export default {data() {return {navBgColor: "rgba(0,0,0,0)", // 初始背景颜色为完全透明navTextColor: "rgba(0,0,0,1)", // 初始文字颜色};},onPageScroll(e) {// 设置背景const newAlpha Math.min((e.s…

小学数学出题器-Word插件-大珩助手

Word大珩助手是一款功能丰富的Office Word插件&#xff0c;旨在提高用户在处理文档时的效率。它具有多种实用的功能&#xff0c;能够帮助用户轻松修改、优化和管理Word文件&#xff0c;从而打造出专业而精美的文档。 【新功能】小学数学出题器 1、实现了难度设定&#xff1b;…

HCIP-Datacom-ARST自选题库__MAC【14道题】

一、单选题 1.缺省情况下&#xff0c;以下哪种安全MAC地址类型在设备重启后表项会丢失? 黑洞MAC地址 Sticky MAC地址 安全动态MAC地址 安全静态MAC地址 2.华为交换机MAC地址表中的动态sticky MAC地址的默认老化时间是多少秒? 300 不会老化 400 500 3.华为交换机MA…

Golang | Leetcode Golang题解之第129题求根节点到叶节点数字之和

题目&#xff1a; 题解&#xff1a; type pair struct {node *TreeNodenum int }func sumNumbers(root *TreeNode) (sum int) {if root nil {return}queue : []pair{{root, root.Val}}for len(queue) > 0 {p : queue[0]queue queue[1:]left, right, num : p.node.Left, …

大规模 Transformer 模型 8 比特矩阵乘

本文基于 Hugging Face Transformers、Accelerate 以及 bitsandbytes库。 Transformers&#xff1a;Hugging Face 提供的一个开源库&#xff0c;包含了多种预训练的 Transformer 模型&#xff0c;方便用户进行各种 NLP 任务。Accelerate&#xff1a;Hugging Face 开发的一个库…

大型语言模型的工作原理(LLM:从零学起)

目录 一、说明 二、LLM如何运作 三、预训练&#xff1a;基本模型 四、微调&#xff1a;培训助手 五、RLHF&#xff1a;从人类反馈中强化学习 六、提示工程 七、总结 一、说明 这是我们谈论LLM系列的第二篇文章。在本文中&#xff0c;我们旨在为大型语言模型 &#xff08;LLM&am…

Java开发:Spring Boot 实战教程

序言 随着技术的快速发展和数字化转型的深入推进&#xff0c;软件开发领域迎来了前所未有的变革。在众多开发框架中&#xff0c;Spring Boot凭借其“约定大于配置”的核心理念和快速开发的能力&#xff0c;迅速崭露头角&#xff0c;成为当今企业级应用开发的首选框架之一。 《…

Linux运维应知必会的LVS高可用负载均衡方案

背景 在业务量达到一定量的时候&#xff0c;往往单机的服务是会出现瓶颈的。此时最常见的方式就是通过负载均衡来进行横向扩展。其中我们最常用的软件就是 Nginx。通过其反向代理的能力能够轻松实现负载均衡&#xff0c;当有服务出现异常&#xff0c;也能够自动剔除。但是负载…

PromptIR论文阅读笔记

MZUAI和IIAI在NIPS2023上的一篇论文&#xff0c;用prompt来编码degradation&#xff0c;然后用来guide restoration network&#xff0c;使得模型能够泛化到不同degradation types and levels&#xff0c;也就是说是一个模型一次训练能够应对多种degradation的unified model。文…

生成式AI,在云端的绽放与盛开

编辑&#xff1a;阿冒 设计&#xff1a;沐由 毫无疑问&#xff0c;生成式AI已然成为当今技术发展和应用创新的重要引擎之一。 过去的一年多时间里&#xff0c;我们每个人都在目睹和见证着生成式AI是如何以移山倒海的力量&#xff0c;为诸多行业带来革命性乃至颠覆性的变革&…

计算机网络7——网络安全4 防火墙和入侵检测

文章目录 一、系统安全:防火墙与入侵检测1、防火墙1&#xff09;分组过滤路由器2&#xff09;应用网关也称为代理服务器(proxy server)&#xff0c; 二、一些未来的发展方向 一、系统安全:防火墙与入侵检测 恶意用户或软件通过网络对计算机系统的入侵或攻击已成为当今计算机安…

Vue——监听器简单使用与注意事项

文章目录 前言编写简单demo注意事项 前言 监听器&#xff0c;在官网中称为侦听器&#xff0c;个人还是喜欢称之为监听器。官方文档如下&#xff1a; vue 官网 侦听器 编写简单demo 侦听器在项目中通常用于监听某个属性变量值的变化&#xff0c;并根据该变化做出一些处理操作。…

【python科学文献计量】关于中国知网检索策略的验证,以事故伤害严重程度检索为例

关于中国知网检索策略的验证,以事故伤害严重程度检索为例 1 背景2 文献下载3 数据处理1 背景 由于要进行相关研究内容的综述,需要了解当前我国对于事故伤害严重程度的研究现状,采用国内较为知名的检索网站(中国知网)进行文献数据集检索 由于最近知网出bug,检索的结果在…

HTML+CSS+JS 选项卡导航栏

效果演示 实现了一个导航栏切换内容的效果。页面上方有一个导航栏,每个导航项都有一个圆形背景,点击导航项时,圆形背景会放大并显示对应的内容。每个内容区域都包含一个大号字母,数字会在内容区域显示时淡入。点击其他导航项时,当前内容区域会淡出并隐藏,同时新的内容区域…

家宽动态公网IP,使用docker+ddns 实现动态域名解析

官方地址&#xff1a;https://github.com/jeessy2/ddns-go 安装docker docker pull jeessy/ddns-godocker run -d --name ddns-go --restartalways --nethost -v /opt/ddns-go:/root jeessy/ddns-go然后访问ip端口 配置时注意如下

蓝图collapseNodes很有用

学到了&#xff0c;选中N个节点后&#xff0c;再右键collapseNode&#xff0c;可以使代码很清晰&#xff0c;双击后可以看到相应的代码&#xff0c;具有层次感。

Qt图像处理技术十二:QImage实现边缘检测(sobel算法)

效果图 原理 Sobel算法是一种常用的边缘检测算法&#xff0c;它利用图像的灰度变化来检测图像中物体的边缘。Sobel算法主要包括以下几个步骤&#xff1a; 灰度化&#xff1a; 首先将彩色图像转换为灰度图像&#xff0c;因为灰度图像只包含单通道的灰度信息&#xff0c;有利于…

实战16:基于apriori关联挖掘FP-growth算法挖掘关联规则的手机销售分析-代码+数据

直接看视频演示: 基于apriori关联挖掘关联规则的手机销售分析与优化策略 直接看结果: 这是数据展示: 挖掘结果展示: 数据分析展示: