C++之vector类(超详解)
这节我们来学习一下,C++中一个重要的工具——STL,这是C++中自带的一个标准库,我们可以直接调用这个库中的函数或者容器,可以使效率大大提升。这节我们介绍STL中的vector。
文章目录
前言
一、标准库类型vector
二、vector的使用
2.1 vector的初始化和定义
2.2 vector的元素访问
2.2.1 vector iterator的使用
2.3 vector的空间增长问题
2.4 vector增删改查
2.5 vector迭代器失效问题
三、vector的深度剖析与模拟实现
3.1 成员变量及迭代器实现
3.2 使用memcpy的拷贝问题
3.3 动态二维数组的理解
3.4 vector的模拟实现(代码)
前言
我们在上一节中学习了string类,我们通过这个类可以实现各种各样的操作。然而string类是专门设计来用来处理字符类型的,它是用来对字符的高级处理以及内存优化,适用于文本数据。但是在本节中我们将要的vector,是一个通用的容器类,它可以存储任意类型的数据,适用于处理任意类型的集合。
一、标准库类型vector
标准库类型vector表示对象的集合,其中所有对象的类型都是相同的。集合中每一个对象都有对应的索引,索引用于访问对象。因为vector中容纳着其他对象,因此我们也将它称为容器(container)。vector其实与我们C语言学习的数组很像,存放一组相同数据类型的集合,我们可以通过索引值来访问某个值,但是vector通过了类进行了封装,于是我们可以使用vector进行各种操作,可以任意改变存储空间的大小,这是我们前面的数组所没有,大大提高了操作的灵活性。
C++语言既有类模板(class template),也有函数模板,其中vector就是一个类模板。模板我们在之前就已经学习过了,模板并不是一个类或者函数,它们只是一个类或者函数的雏形,我们可以使用这些模板来实例出来一个具体的类或者函数。因此我们对于vector的数据类型要注意了:vector是模板并非类型,由vector生成的类型必须包含vector中的元素类型,例如vector中的元素类型是int,那么它的数据类型就是vector<int>。
vector可以容纳大多数类型的对象作为其元素,但是因为引用并不是一个对象,因此我们不存在包含引用的vector,其他大多数的内置类型(非引用)和类类型都可以构成vector对象。
二、vector的使用
在使用vector之前,我们要先包含一个头文件:#include<vector>和using namespace std这个标准库命名空间。vector的函数都放在这里面,如果我们没写这个头文件,编译器就不认识vector了。
2.1 vector的初始化和定义
同样地,vector有着属于自己的构造函数,vector要存储数据,因此它肯定是要进行资源的申请的。它的构造函数同样很多,主要的就是我们上面所展示的哪几种。我们通过VS2022上的监视调试可以看到,我们可以直接通过vector中的那几个构造函数来进行初始化,它也赋上了相应的值。
除了上面那几种构造方法,在C++11新标准中还提供了另一种为vector对象的元素赋初值的方法,即列表初始化。列表初始化和我们之前对数组初始化的方式很像,都是使用一对{ },然后我们将要初始化的内容放进去。我们可以直接使用{ }初始化,也可以将内容放到{ }中,然后赋值给vector对象。然后编译器就会自动创建相应的大小空间以及赋上我们给定的初始值。
这时可能会有人来问:这个列表初始化,会不会与我们上面那个初始化n个相同的值弄混呢?
这里我们要注意一下:在某些情况下,初始化的真实含义依赖于传递初始值使用的是{ }还是()。
例如,我们使用一个整数来初始vector<Int>时,整数的含义可能是vector的对象容量,也可能是vector的元素的值。类似的,我们如果使用两个整数来初始化vector<int>的话,那么这两个整数就可能是一个是vector的容量大小另一个是要初始化的值,也可能它们是容量为2的vector对象的两个元素的初始值。我们可以通过花括号{ }和圆括号()来区分上面的含义:
vector<int> v1(10) //v1有10个元素,每个元素的初始值都是0
vector<int> v2{10} //v2有1个元素,这个元素的初始值是10vector<int> v3(10,1) //v3有10个元素,每个元素的初始值都是1
vector<int> v4{10,1} //v4有2个元素,分别是10和1
如果我们使用的是圆括号的话,那么可以说提供的值是用来构造(construct)vector对象。如果我们使用的是花括号的话,那么可以表述为我们想列表初始化该vector对象。也就是说,初始化过程会尽可能地把花括号内的值当成元素初始值的列表来进行处理,只有无法执行列表初始化时才会考虑其他初始化方法。另一方面,如果初始化时使用了花括号但是花括号中的内容不适合进行列表初始化,那么这时候就要考虑使用这样的值使用其他构造方法来构造vector对象了。例如,我们如果想要初始化一个含有string类型的元素的vector对象,我们应该赋给能够赋值给string对象的初始值。
vector<string> v5{"hi"} //列表初始化,v5中只有一个元素“hi"
vector<string> v6("hi") //错误,我们不能使用字符串字面值来构造一个vector对象vector<string> v7{10} //v7中有10个默认值(string类型的)的元素
vector<string> v8{10,”hi“} //v8中有10个”hi“的元素
在之前,我们学习了,我们不能使用一个字面值来进行初始化了,因为字面值是一个常量,是一个不可以进行修改的值,如果我们使用这个来进行初始化,相当于将一个只读的元素放到了一个可读可修改的容器中,那样就造成了权限放大的情况,这样是不允许的。因此,我们就不可以使用()来初始化string类了,如果我们想要初始化多个相同的string对象,我们可以使用{ },同样地第一个参数放容量大小,第二个参数放初始化的值。
2.2 vector的元素访问
我们在string类中有三种访问元素的方法:1.使用普通for循环进行遍历访问;2.使用范围for进行遍历循环;3.使用迭代器进行访问。对于vector。同样适用于上面那三种方法。在此之前,我们先来学习一下vector中的迭代器。
2.2.1 vector iterator的使用
vector中的迭代器和string类中的迭代器大致一样,都是指向某个位置的元素。begin()函数是指向vector中首元素的迭代器,end()函数是指向vector中最后一个元素的下一个位置。至于rbegin(),rend()函数则是方向遍历vector。其具体使用方法,我们在string类中已经详细介绍过了。范围for是在迭代器的基础上实现的,这个一般适用于不知道容器中的元素的个数时。
元素访问的三种方式代码如下:
vector<int> v1{ 1,2,3,4,5 };//1.使用普通for循环进行遍历访问v1for (size_t i = 0; i < v1.size(); i++){cout << v1[i] << " ";}cout << endl;//2.使用范围for进行遍历访问for (auto e : v1){cout << e << " ";}cout << endl;//3.使用迭代器进行访问vector<int>::iterator it = v1.begin();while (it!=v1.end()){cout << *it << " ";it++;}cout << endl;
2.3 vector的空间增长问题
string有的那些接口vector同样有,而且它们的功能基本都是一样的,不过是处理的数据类型不同而已,这里我们就不重复介绍这些接口的使用方法了。
我们主要讲讲上面那两个改变size和capacity的接口,这两个的实现原理有所不同:对于resize,它是与原来的那个vector的size值进行比较一下,如果是小于原来的size值,那么它就会删去一些原有的数据,如果大于原来的size值,它会补充我们给定的缺省值,如果大于我们原来的capacity,编译器将会给它重新分配一个内存空间了,这里往往也调用了reserve中的扩容操作。对于reserve,一般都是编译器自己执行这个函数的,我们在插入数据的时候,编译器会调用这个函数来进行扩容,有时候如果我们能够提前知道数据的个数,那么我们也可以使用reserve来提前预留空间大小,这样就能够缓解vector增容的代价缺陷问题,注意reserve一般都是异地创建一个新的内存空间,然后将我们的内容深拷贝过去,并非是在原来的空间中来扩容。
2.4 vector增删改查
上面的那些接口,我们可以实现vector的增删改查,大大提高了其灵活性,我们可以对vector对象中的数据进行修改。其中的find接口,我们需要注意一下,它并不是vector的成员接口,它是在算法库中的,但是我们可以使用vector来进行调用。
这是其在算法库中的函数原型,我们可以看到,它是一个函数模板,因此它并不是一个具体的函数,而是一个可以对于任意类型使用的函数出翔,我们可以传参实例化出来一个具体的函数来进行使用。上面的函数原型中,它的参数是两个迭代器和一个想要查找的值。对于那两个迭代器所表示的区间是:[first,last)。如果我们没有在那段区间中找到我们想要查找的值时,编译器就会返回last迭代器,用于表示未查找出来的情况。
2.5 vector迭代器失效问题
迭代器的主要作用就是让算法能够不用关心底层数据结构,其底层实际就是一个指针,或者是对 指针进行了封装,比如:vector的迭代器就是原生态指针T* (因为,对于vector的底层是一个数组)。因此迭代器失效,实际就是迭代器底层对应指针所指向的空间被销毁了,而使用一块已经被释放的空间,造成的后果是程序崩溃(即 如果继续使用已经失效的迭代器,程序可能会崩溃)。在vector中迭代器失效有好几种,接下来我们一一介绍一下:
1.修改vector的大小(如使用resize)
我们在前面就已经说过了对于那些我们设置的新size值如果比原来的size小的话,那样就会导致超出范围的元素被删除,那么与那些超出范围的元素有关联的迭代器就可能会失效。
std::vector<int> vec = {1, 2, 3};
auto it = vec.begin();
vec.resize(2); // 删除了第三个元素,导致 it 失效,那么我们就不能够使用迭代器,来访问到这第三个元素了
2.vector容量的改变(如使用insert,push_back)
vector在内存空间不够的情况下会自动进行扩容操作,当我们调用insert和push_back时,编译器就会进行扩容,我们在上面已经说过了编译器是异地扩容,在其他地方另外开辟一个新的空间,那么原来的那些迭代器就都会失效,因为它们所指向的位置都已经被改变了。
std::vector<int> vec = {1, 2, 3};
auto it = vec.begin();
vec.push_back(4); // 如果触发扩容,it 会失效,因为我们开辟了一个新的内存块,原来的那些迭代器所指向的位置都已经改变了
3.erase操作
使用erase删除vector中的元素时,会导致被删除位置后的所有元素都会被移动,那样就会导致逻辑错误,原来的指向那些元素的那些迭代器也会失效。
std::vector<int> vec = {1, 2, 3};
auto it = vec.begin();
vec.erase(it); // 删除了第一个元素,it 失效,第一位位置后面的元素都往前移动一位,后面的迭代器所指的元素可能都发生改变了,导致逻辑错误
4.clear操作
使用clear清除ector中的所有元素,会导致vector中有的迭代器全部都失效。
std::vector<int> vec = {1, 2, 3};
auto it = vec.begin();
vec.clear(); // 所有迭代器都失效
5.insert操作
这个与上面的erase差不多,当我们插入某个位置之后,那个位置之后的所有元素都会发生改变,因此那些指向的迭代器就会改变,如果我们插入大量元素或触发内存扩展时,可能导致所有的迭代器都失效。
std::vector<int> vec = {1, 2, 3};
auto it = vec.begin();
vec.insert(it, 0); // 插入元素导致 it 失效
6.swap操作
当我们调用swap函数改变两个vector的内容和容量时,可能会导致两个vetcor中原有的迭代器都失效了。
std::vector<int> vec1 = {1, 2, 3};
std::vector<int> vec2 = {4, 5, 6};
auto it = vec1.begin();
vec1.swap(vec2); // it 失效,因为 vec1 和 vec2 的内存交换了
7.底层分配器的改变
如果你使用自定义的分配器(allocator)来管理vector的内存,某些内存分配或重新分配的策略也可能会导致迭代器失效。因为分配器所作用的对象就是内存,内存都改变了,原来的迭代器就不能指向原来的那些内容了,因此会失效。
上面这么多情况都会可能导致迭代器失效,因此我们在使用那些增删函数时要小心一点。如果需要避免迭代器失效,我们可以考虑使用其他容器,或者在修改vector时重新获取有效的迭代器(对迭代器进行一个更新),这样做之后我们就可以获取我们所需的迭代器了。
//string类中与vector类似,也会遇到迭代器失效的问题,我们可以使用如下方法来规避
#include <string>
void TestString()
{string s("hello");auto it = s.begin();// 放开之后代码会崩溃,因为resize到20会string会进行扩容// 扩容之后,it指向之前旧空间已经被释放了,该迭代器就失效了// 后序打印时,再访问it指向的空间程序就会崩溃//s.resize(20, '!');while (it != s.end()){cout << *it;++it;}cout << endl;it = s.begin();while (it != s.end())
{it = s.erase(it);// 按照下面方式写,运行时程序会崩溃,因为erase(it)之后// it位置的迭代器就失效了// s.erase(it); ++it;}
}
三、vector的深度剖析与模拟实现
3.1 成员变量及迭代器实现
上面是由源文件中截取的源码,我们可以看出来vector的成员函数是三个迭代器,它们分别指向上面那些位置。我们在最开始学习的时候就说了,vector的底层实现是一个数组,为啥我们不像之前模拟实现顺序表那样设置一个数组,一个size值和一个capacity作为成员变量呢?其实这些在源文件中都已经有提及到了,在源文件中我们可以通过上面那三个指针来表示这几个变量。使用这些迭代器能够更加直观地看到vector中的具体位置。
在vetor中的底层中,它直接将那些数据类型的指针重名为迭代器了,因此,我们如果要实现那些begin(),end()函数直接使用我们开始定义的那几个迭代器变量表示就可以了。
3.2 使用memcpy的拷贝问题
我们在string类模拟实现时,我们对于两个字符串的拷贝是使用strcpy这个专门用来字符串拷贝的函数,对于其他类型的变量拷贝,我们就需要使用其他拷贝函数,而memcpy就是我们在C语言阶段学习的一个用来内存拷贝的函数,它可以将内存及内容拷贝过去。memcpy拷贝有如下两个特点:
1. memcpy是内存的二进制格式拷贝,将一段内存空间中内容原封不动的拷贝到另外一段内存 空间中 。
2. 如果拷贝的是自定义类型的元素,memcpy既高效又不会出错,但如果拷贝的是自定义类型 元素,并且自定义类型元素中涉及到资源管理时,就会出错,因为memcpy的拷贝实际是浅拷贝。
在C语言中,我们没有涉及到析构这一说,但是到了C++中,我们学习了析构函数,我们知道我们每次函数在销毁时都会自动调用析构函数来释放内存资源,这里我们使用了memcpy这个浅拷贝函数获取的那个对象,由于我们的一些函数会将拷贝前的那个对象释放掉,那么就会调用析构函数,那么就会将原来的那个空间释放掉,但是我们拷贝后的那个新对象仍然是指向那个旧空间的地址,这样我们新对象的地址就是一个被释放的空间地址,相当于一个野指针,野指针是十分危险的。因此我们不能使用这个浅拷贝的函数,我们需要自己来实现深拷贝(对于那些有资源申请的对象)。
结论:如果对象中涉及到资源管理时,千万不能使用memcpy进行对象之间的拷贝,因为 memcpy是浅拷贝,否则可能会引起内存泄漏甚至程序崩溃。
3.3 动态二维数组的理解
二维数组这一概念,我们是在C语言阶段就已经接触到了。那么二维数组是如何实现的呢?二维数组其实就是一个数组中存放了一组数组的指针,这样就形成了二维数组。而vector的底层实现就是数组,于是我们也可以使用vector来表示一个二维数组,我们可以使用嵌套的方式来进行表示。例如vector<vector<int>>,这个就表示一个元素类型为int的二维数组。如下代码,我们使用vector来表示一个杨辉三角,杨辉三角的实现是一个二维数组。
// 以杨辉三角的前n行为例:假设n为5
void test2vector(size_t n)
{// 使用vector定义二维数组vv,vv中的每个元素都是vector<int>vector<vector<int>> vv(n);// 将二维数组每一行中的vecotr<int>中的元素全部设置为1for (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];}}
}
vector<vector<int>> vv(n); 构造一个vv动态二维数组,vv中总共有n个元素,每个元素 都是vector类型的,每行没有包含任何元素,如果n为5时如下所示:
vv中元素填充完成之后,如下图所示:
3.4 vector的模拟实现(代码)
#pragma once
#include<assert.h>
namespace hjc
{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;}template <class InputIterator> //类模板中也可以定义函数模板vector(InputIterator first, InputIterator last ) //这两个参数的类型是迭代器类型{while (first != last){push_back(*first);first++;}}vector(){}vector(initializer_list<T> il){reserve(il.size());for (auto& e : il){push_back(e);}}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 (int i = 0; i < n; i++){push_back(val);}}//拷贝构造vector(const vector<T>& v){reserve(v.capacity());for (auto& e : v){push_back(e);}}~vector(){if (_start)delete[]_start;_start = _finish = _end_of_storage = nullptr;}T& operator[](size_t i) //我们使用下标运算符,最终返回的是一个元素,因此它的类型是元素的类型{assert(i < size());return _start[i];}const T& operator[](size_t i) const{assert(i < size());return _start[i];}vector<T>& operator=(vector<T> v){swap(v);return *this;}size_t size() const{return _finish - _start;}size_t capacity() const{return _end_of_storage - _start;}void resize(size_t n, T val = T()) //我们第二个参数直接写元素的类型即可, 然后我们初始化为默认初始值{if (n < size())//缩小size,那么size的值就到_start + n位置即可{_finish = _start + n;}else{reserve(n); //扩大size,我们要往里面添加值while (_finish< _start+n) //size=_finish-_start且_start=0于是_finish=size。这里_finish=4{*_finish = val;++_finish;}}}//扩容void reserve(size_t n){if (n >= capacity()){size_t oldSize = size();T* tmp = new T[n];for (size_t i = 0; i < oldSize; i++){tmp[i] = _start[i];}delete[]_start;_start = tmp;_finish = _start + oldSize;_end_of_storage = _start + n;}}void push_back(const T& x){if (_finish == _end_of_storage){reserve(capacity() == 0 ? 4 : capacity() * 2);}*_finish = x;_finish++;}void swap(vector<T>& tmp){std::swap(_start, tmp._start);std::swap(_finish, tmp._finish);std::swap(_end_of_storage, tmp._end_of_storage);}bool empty(){return _start == _finish;}void pop_back(){assert(!empty());_finish--;}iterator insert(iterator pos, const T& x) //这里的位置起始位置是1{assert(_start <= pos && pos <= _finish);if (_finish==_end_of_storage){size_t len = pos - _start;reserve(capacity() == 0 ? 4 : capacity() * 2);pos = _start + len;}iterator i= _finish - 1;while (i>=pos){*(i + 1) = *i;i--;}*pos = x;_finish++;return pos;}iterator erase(iterator pos){assert(_start <= pos && pos < _finish);iterator i = pos + 1;while (i<_finish){*(i - 1) = *i;i++;}_finish--;return pos;}private:iterator _start=nullptr;iterator _finish=nullptr;iterator _end_of_storage=nullptr;};void test_vector1(){vector<int> v1;v1.push_back(2);v1.push_back(3);v1.push_back(4);v1.push_back(5);for (size_t i = 0; i < v1.size(); i++){cout <<v1[i]<< " ";}cout << endl;for (int i : v1){cout << i << " ";}cout << endl;v1.pop_back();v1.pop_back();vector<int>::iterator it = v1.begin();while (it!=v1.end()){cout << *it << " ";it++;}cout << endl;}void test_vector2(){vector<int> v1;v1.push_back(2);v1.push_back(3);v1.push_back(4);v1.push_back(5);cout << v1.size() << endl;cout << v1.capacity() << endl;cout << endl;v1.resize(2);cout << v1.size() << endl;cout << v1.capacity() << endl;for (int i : v1){cout << i << " ";}cout << endl;cout << endl;v1.resize(6,999);cout << v1.size() << endl;cout << v1.capacity() << endl;for (int i : v1){cout << i << " ";}cout << endl;cout << endl;vector<int> v2;v2.push_back(1);v2.push_back(1);v1.swap(v2);for (int i : v1){cout << i << " ";}cout << endl;cout << endl;for (int i : v2){cout << i << " ";}cout << endl;}void test_vector3(){vector<int> v1={ 1,2,3,4 }; //列表初始化//vector<int> v1 { 1,2,3,4 }; //列表初始化vector<int>v2(v1);for (auto i : v1){cout << i << " ";}cout << endl;for (auto i : v2){cout << i << " ";}cout << endl;vector<int>v3(10, 1);for (auto i : v3){cout << i << " ";}cout << endl;v2 = v3;for (auto i : v2){cout << i << " ";}cout << endl;for (auto i : v3){cout << i << " ";}cout << endl;}void test_vector4(){vector<int> v1 = { 1,2,3,4,5,6};vector<int> v2(v1.begin(), v1.end()); //使用函数模板实例化出来的函数进行初始化for (auto e : v1){cout << e << " ";}cout << endl;for (auto e : v2){cout << e << " ";}cout << endl;v1.insert(v1.begin()+2, 30);v1.insert(v1.begin(), 30);v1.insert(v1.begin()+8, 30);for (auto e : v1){cout << e << " ";}cout << endl;v1.erase(v1.begin());for (auto e : v1){cout << e << " ";}cout << endl;int x;cin >> x;auto i = find(v1.begin(), v1.end(), x);if (i != v1.end()){//对于这种改变了原来指定的顺序,可能会导致逻辑错误,因此我们要更新一下,才能够进行访问i = v1.insert(i, 10 * x); //如果不是我们所输入的数,就将它扩大十倍放到指定位置(找到那个数的位置)cout << *i << endl;}for (auto e : v1){cout << e << " ";}cout << endl;//删除v1中的所以偶数auto e = v1.begin(); //确定起始位置while (e!=v1.end()) //然后通过刚刚定义的位置来进行遍历{if (*e % 2 == 0)//如果为偶数,就进行删除,对于指定位置删除,我们要更新它的位置,即到删除元素的位置{e = v1.erase(e);}else //如果不是偶数,我们就直接跳过去{++e;}}for (auto i : v1){cout << i << " ";}cout << endl;}void test_vector5(){vector<string>v1;v1.push_back("111111111111111");v1.push_back("111111111111111");v1.push_back("111111111111111");v1.push_back("111111111111111");v1.push_back("111111111111111");for (auto e : v1){cout << e << " ";}cout << endl;}}
相关文章:
C++之vector类(超详解)
这节我们来学习一下,C中一个重要的工具——STL,这是C中自带的一个标准库,我们可以直接调用这个库中的函数或者容器,可以使效率大大提升。这节我们介绍STL中的vector。 文章目录 前言 一、标准库类型vector 二、vector的使用 2.…...
Go学习笔记
<!-- 注意* --> 初始化工程 go mod init GoDemo 结构体,接口 type i struct{} type i interface{} 条件,选择 循环 键值对 make(map[string]int) 切片,集合 make([]int,10) 函数 通道 Channel make(chan int) ch <- v…...
前端杂的学习笔记
什么是nginx Nginx (engine x) 是一个高性能的HTTP和反向代理web服务器 Nginx是一款轻量级的Web 服务器/反向代理服务器,处理高并发能力是十分强大的,并且支持热部署,启动简单,可以做到7*24不间断运行 正代和反代 学习nginx&a…...
痉挛性斜颈护理:全方位呵护,重燃生活希望
痉挛性斜颈是一种以颈部肌肉不自主收缩导致头部向一侧扭转或倾斜为特征的疾病。对于痉挛性斜颈患者而言,科学有效的护理能够显著提升其生活质量,辅助病情的改善。 生活护理:在生活环境布置上,要充分考虑患者行动的便利性。确保室内…...
MySQL的安装以及数据库的基本配置
MySQL的安装及配置 MySQL的下载 选择想要安装的版本,点击Download下载 Mysql官网下载地址: https://downloads.mysql.com/archives/installer/ MySQL的安装 选择是自定义安装,所以直接选择“Custom”,点击“Next” …...
WangEditor快速实现版
WangEditor快速实现版 效果 案例代码 后端 package com.diy.springboot.controller;import cn.hutool.core.util.IdUtil; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiImplicitParam; import org.sp…...
LeetCode Hot100刷题——反转链表(迭代+递归)
206.反转链表 给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。 示例 1: 输入:head [1,2,3,4,5] 输出:[5,4,3,2,1]示例 2: 输入:head [1,2] 输出:[2,1]示例 3&#…...
10.2 继承与多态
文章目录 继承多态 继承 继承的作用是代码复用。派生类自动获得基类的除私有成员外的一切。基类描述一般特性,派生类提供更丰富的属性和行为。在构造派生类时,其基类构造函数先被调用,然后是派生类构造函数。在析构时顺序刚好相反。 // 基类…...
java项目之基于ssm的智能训练管理平台(源码+文档)
项目简介 智能训练管理平台实现了以下功能: 系统可以提供信息显示和相应服务,其管理员增删改查课程信息和课程信息资料,审核课程信息预订订单,查看订单评价和评分,通过留言功能回复用户提问。 💕…...
29-验证回文串
如果在将所有大写字符转换为小写字符、并移除所有非字母数字字符之后,短语正着读和反着读都一样。则可以认为该短语是一个 回文串 。 字母和数字都属于字母数字字符。 给你一个字符串 s,如果它是 回文串 ,返回 true ;否则…...
(57)[HGAME 2023 week1]easyasm
nss:3477 [HGAME 2023 week1]easyasm 关于这个题吧,我还是和上一个题一样,我观察到了异或0x33 所以我就把result的结果跟0x33异或,然后我就就这样,做出来了...
FY-3D MWRI亮温绘制
1、FY-3D MWRI介绍 风云三号气象卫星(FY-3)是我国自行研制的第二代极轨气象卫星,其有效载荷覆 盖了紫外、可见光、红外、微波等频段,其目标是实现全球全天候、多光谱、三维定量 探测,为中期数值天气预报提供卫星观测数…...
Java集合面试题
引言 Java集合框架是Java编程中不可或缺的一部分,它提供了一系列用于存储和操作对象的接口和类。在Java面试中,集合框架的相关知识往往是必考的内容。本文将汇总一系列关于Java集合的面试题,帮助求职者更好地准备面试。 一、Java集合框架概…...
知识蒸馏综述Knowledge Distillation: A Survey解读
论文链接:Knowledge Distillation: A Survey 摘要:近年来,深度神经网络在工业界和学术界都取得了成功,尤其是在计算机视觉任务方面。深度学习的巨大成功主要归功于它能够扩展以对大规模数据进行编码,并且能够处理数十…...
ES映射知识
映射 映射类似于关系型数据库的Schema(模式)。 映射来定义字段列和存储的类型等基础信息。 {"mappings": {"properties": {"username": {"type": "keyword","ignore_above": 256 // 忽略…...
Spring Boot拦截器(Interceptor)与过滤器(Filter)深度解析:区别、实现与实战指南
Spring Boot拦截器(Interceptor)与过滤器(Filter)深度解析:区别、实现与实战指南 一、核心概念对比 1. 本质区别 维度过滤器(Filter)拦截器(Interceptor)规范层级Serv…...
Debian二次开发一体化工作站:提升科研效率的智能工具
在科研领域,数据处理是实验成功的关键环节之一。随着实验数据的复杂性和规模不断增加,传统的数据处理方法已经难以满足科研人员的需求。这时,一体化工作站应运而生,成为科研实验数据处理的 “智能大脑”。 一体化工作站ÿ…...
swift-5-汇编分析闭包本质
一、枚举、结构体、类都定义方法 方法占用对象的内存么? 不占用 方法的本质就是函数 方法、函数都存放在代码段,因为方法都是公共的,不管 对象一还是对对象二调用都是一样的,所以放在代码段,但是每个对象的成员不一样所…...
Linux安装升级docker
Linux 安装升级docker Linux 安装升级docker背景升级停止docker服务备份原docker数据目录移除旧版本docker安装docker ce恢复数据目录启动docker参考 安装找到docker官网找到docker文档删除旧版本docker配置docker yum源参考官网继续安装docker设置开机自启配置加速测试 Linux …...
小程序事件系统 —— 33 事件传参 - data-*自定义数据
事件传参:在触发事件时,将一些数据作为参数传递给事件处理函数的过程,就是事件传参; 在微信小程序中,我们经常会在组件上添加一些自定义数据,然后在事件处理函数中获取这些自定义数据,从而完成…...
推荐一些免费开源支持Vue3甘特图组件
文章目录 前言一、dhtmlxGantt二、frappe-gantt三、vue-ganttastic四、gantt-elastic五、v-gantt六、vue-gantt-schedule-timeline-calendar七、vue-gantt八、总结 前言 在现代项目管理和任务调度中,甘特图是一种非常实用的工具。它能够直观地展示任务的时间安排、…...
Dify 本地部署教程
目录 一、下载安装包 二、修改配置 三、启动容器 四、访问 Dify 五、总结 本篇文章主要记录 Dify 本地部署过程,有问题欢迎交流~ 一、下载安装包 从 Github 仓库下载最新稳定版软件包,点击下载~,当然也可以克隆仓库或者从仓库里直接下…...
nlp培训重点-5
1. LoRA微调 loader: # -*- coding: utf-8 -*-import json import re import os import torch import numpy as np from torch.utils.data import Dataset, DataLoader from transformers import BertTokenizer """ 数据加载 """cl…...
XWiki使用war部署在tomcat9
xwiki部署 官方文档,比较详细。 https://www.xwiki.org/xwiki/bin/view/Documentation/AdminGuide/Installation/InstallationWAR/ xwiki是基于java的开源知识库,可以替代Confluence。有多种部署方式,本文使用war方式部署在tomca下&#x…...
CTA策略【量化理论】
CTA策略演变史 全称:Commodity Trading Advisor (商品交易顾问) CTA最开始是指通过为客户提供期权、期货方面的交易建议,或者直接通过受管理的期货账户参与实际交易,来获得收益的机构或个人。 随着市场的发展&#…...
旋转编码器原理与应用详解:从结构到实战 | 零基础入门STM32第四十七步
主题内容教学目的/扩展视频旋转编码器电路原理,跳线设置,结构分析。驱动程序与调用。熟悉电路和驱动程序。 师从洋桃电子,杜洋老师 📑文章目录 一、旋转编码器是什么?二、内部结构揭秘2.1 机械组件解剖2.2 核心部件说明…...
计算机视觉cv2入门之图像的读取,显示,与保存
在计算机视觉领域,Python的cv2库是一个不可或缺的工具,它提供了丰富的图像处理功能。作为OpenCV的Python接口,cv2使得图像处理的实现变得简单而高效。 示例图片 目录 opencv获取方式 图像基本知识 颜色空间 RGB HSV 图像格式 BMP格式 …...
基于Canvas和和原生JS实现俄罗斯方块小游戏
这里是一个完整的H5俄罗斯方块游戏,使用了 HTML CSS JavaScript (原生) 实现,支持基本的俄罗斯方块玩法,如: ✅ 方块自动下落 ✅ 方向键控制移动、旋转、加速下落 ✅ 方块堆叠、消行 ✅ 计分系统 在 canvas 上绘制游戏&#x…...
阿里云 QwQ-32B 模型调研文档
阿里云 QwQ-32B 模型调研文档 ——技术解析、部署实践与微调指南 一、模型概述 QwQ-32B 是阿里云开源的轻量化大语言模型,以 320 亿参数 实现与 DeepSeek-R1(6710 亿参数)相当的推理性能。其核心优势包括: 参数效率:1/20 参数量达成竞品性能,显存需求降低 70%部署灵活性…...
【玩转23种Java设计模式】结构型模式篇:组合模式
软件设计模式(Design pattern),又称设计模式,是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性、程序的重用性。 汇总目录链接&…...
Eolink:专为开发者设计的API协作平台
Eolink Apikit 是一款集 API 设计、管理、自动化测试、Mock 和异常监控于一体的全生命周期智能协作平台,旨在提升 API 研发和管理的效率。以下是对其功能和特点的详细介绍: 核心功能: API 设计与文档管理:Apikit 提供了强大的 API…...
【Python】为什么要写__init__.py
文章目录 PackageA(__init__特性)应该往__init__.py里放什么东西?1、包的初始化2、管理包的公共接口3、包的信息 正常我们直接导入就可以执行,但是在package的时候,有一种__init__.py的特殊存在 引入moduleA.py,执行main.py&…...
golang 从零单排 (一) 安装环境
1.下载安装 打开网址The Go Programming Language 直接点击下载go1.24.1.windows-amd64.msi 下载完成 直接双击下一步 下一步 安装完成 环境变量自动设置不必配置 2.验证 win r 输入cmd 打开命令行 输入go version...
30-判断子序列
给定字符串 s 和 t ,判断 s 是否为 t 的子序列。 字符串的一个子序列是原始字符串删除一些(也可以不删除)字符而不改变剩余字符相对位置形成的新字符串。(例如,"ace"是"abcde"的一个子序列&#…...
AI 驱动的软件测试革命:从自动化到智能化的进阶之路
🚀引言:软件测试的智能化转型浪潮 在数字化转型加速的今天,软件产品的迭代速度与复杂度呈指数级增长。传统软件测试依赖人工编写用例、执行测试的模式,已难以应对快速交付与高质量要求的双重挑战。人工智能技术的突破为测试领域注…...
深度相机进行目标物体的空间姿态(位姿)估计
利用深度相机(如Kinect、Intel Realsense、Zed相机等)进行目标物体的空间姿态(位姿)估计,通常结合了3D点云处理、目标识别和位姿优化算法。以下是完整的实现流程、算法选择及注意事项: 一、实现流程 1. 目…...
3月8日实验
拓扑: 需求: 1.学校内部的HTTP客户端可以正常通过域名www.baidu.com访问到白度网络中的HTTP服务器 2.学校网络内部网段基于192.168.1.0/24划分,PC1可以正常访问3.3.3.0/24网段,但是PC2不允许 3.学校内部路由使用静态路由&#…...
GO语言学习笔记
一、viper笔记【七米】 https://liwenzhou.com/posts/Go/viper/ 二、优雅关机和平滑重启 https://liwenzhou.com/posts/Go/graceful-shutdown/ 三、gin使用zap https://liwenzhou.com/posts/Go/zap-in-gin/ 四、flag 用于命令行传参 https://liwenzhou.com/posts/Go/flag/ 五、…...
Autosar技术栈总目录
总目录 Autosar架构理解Autosar Mcal配置开发(TC3xx系列 基于EB)Autosar Mcal配置开发(S32K3xx系列 基于EB)Autosar BSW服务开发(基于Davinci CFG &Dev)Makefile编译自动化脚本 持续更新中… Autosar架…...
开发指南107-谷歌内核浏览器滚动条设置
平台上统一制定了滚动条样式(仅限于webkit内核):/* ------美化谷歌浏览器滚动条 开始-----------*/ ::-webkit-scrollbar{width:12px;height:12px;background-color: #E1E1E1;} ::-webkit-scrollbar-button:single-button { background-color:#E1E1E1; display: …...
25年携程校招社招求职能力北森测评材料计算部分:备考要点与误区解析
在求职过程中,能力测评是筛选候选人的重要环节之一。对于携程这样的知名企业,其能力测评中的材料计算部分尤为关键。许多求职者在备考时容易陷入误区,导致在考试中表现不佳。本文将深入解析材料计算部分的实际考察方向,并提供针对…...
Linux系统编程--线程同步
目录 一、前言 二、线程饥饿 三、线程同步 四、条件变量 1、cond 2、条件变量的使用 五、条件变量与互斥锁 一、前言 上篇文章我们讲解了线程互斥的概念,为了防止多个线程同时访问一份临界资源而出问题,我们引入了线程互斥,线程互斥其实…...
李沐《动手学深度学习》——14.9. 用于预训练BERT的数据集——wiki数据集问题以及存在的其他问题
问题1:出现"file is not a zip file" 原因是链接已经失效。 解决方法:打开下面链接自行下载,需要魔法。下载完解压到特定位置。 下载链接:项目首页 - Wikitext-2-v1数据包下载:Wikitext-2-v1 数据包下载本仓库提供了一…...
【英伟达AI论文】多模态大型语言模型的高效长视频理解
摘要:近年来,基于视频的多模态大型语言模型(Video-LLMs)通过将视频处理为图像帧序列,显著提升了视频理解能力。然而,许多现有方法在视觉主干网络中独立处理各帧,缺乏显式的时序建模,…...
深入理解 DOM 元素
深入理解 DOM 元素:构建动态网页的基石 在网页开发的世界里,DOM(Document Object Model,文档对象模型)元素宛如一座桥梁,连接着静态的 HTML 结构与动态的 JavaScript 交互逻辑。它让原本呆板的网页变得鲜活…...
linux如何判断进程对磁盘是随机写入还是顺序写入?
模拟工具&性能测试工具:fio fio参数说明: filename/dev/sdb1:测试文件名称,通常选择需要测试的盘的data目录。 direct1:是否使用directIO,测试过程绕过OS自带的buffer,使测试磁盘的结果更真…...
实现静态网络爬虫(入门篇)
一、了解基本概念以及信息 1.什么是爬虫 爬虫是一段自动抓取互联网信息的程序,可以从一个URL出发,访问它所关联的URL,提取我们所需要的数据。也就是说爬虫是自动访问互联网并提取数据的程序。 它可以将互联网上的数据为我所用,…...
[Web]get请求和post请求
Get get请求的特点是: 1.所有的参数都通过URL进行传递。其中传输的参数的书写的格式为?key1value1&key2value2。具体示例:https://example.com/search?qapple&limit10。访问的时候,先写/xxx,确定本次请求要访问的资源u…...
【落羽的落羽 C++】C++入门基础:引用,内联,nullptr
文章目录 一、引用1. 引用的概念2. 引用的特点3. 引用的使用4. const引用5. 引用和指针 二、inline内联三、nullptr 一、引用 1. 引用的概念 引用是C中的一个较为重要的概念。它是给已存在变量取的“别名”,编译器不会为引用变量开辟内存空间,它和它引…...
RabbitMQ应用问题大全(精心整理版)
前言 其实这部分知识我是整理在语雀上了,这里是直接复制粘贴过来的。不是很好阅读,可以直接点下方链接去语雀看,那个看的会舒服很多。 https://www.yuque.com/g/ngioig/upbg6b/fkarhyo8fpgrtyq8/collaborator/join?tokenGvlO0di8KaIfO8aF&am…...