C++vector的简单模拟实现


文章目录

目录

文章目录

前言

一、vector使用时的注意事项

        1.typedef的类型

        2.vector不是string

        3.vector

        4.算法sort

二、vector的实现

        1.通过源码进行猜测vector的结构

        2.初步vector的构建

2.1 成员变量

2.2成员函数

        2.2.1尾插和扩容

          2.2.2operator[]

        2.2.3  迭代器

        2.2.4尾删、指定位置删除和插入

 3指定位置删除和插入和迭代器失效

        3.1insert和迭代器野指针问题

        3.2erase和迭代器失效

4.拷贝构造

5.operator=赋值

6.迭代器区间初始化

7.n个val初始化和编译器匹配问题

三、{}的使用

 四、vector的问题


前言

        vector的使用方法和string很类似,但是string设计的接口太多,而vector则较少所有我们直接开始模拟实现,如果你对vector的使用不太熟悉可以通过它的文档来了解:vector。我们实现vector的简单模板版本。

        由于模板的小问题,我们在使用模板时最好声明和定义在同一个文件


一、vector使用时的注意事项

        1.typedef的类型

        在看文档时有很多未见过的类型,实际上那是typedef的:

        2.vector<char>不是string

        不能vector<char>和string等价string是专门对待字符串和字符的自定义类型,而vector<char>是char类型的顺序表

        区别在于vector<char>后面要手动加'\0',而string会自动加'\0'.

        3.vector <string>

        vector<string>是string 的顺序表,每个元素都是string,如果使用vector<const char*>则空间还需自己手动调整,使用string则不用。

        4.算法sort

        C++库函数中提供了一个包含算法的头文件<algorithm>,现在我们要学会使用sort来排序:

        默认是升序。

	vector<int> v1 = { 5,6,1,3,4,10 };for (const auto& e : v1){cout << e << ' ';}std::sort(v1.begin(), v1.end());cout << endl;for (const auto& e : v1){cout << e << ' ';}

 

那么该如何降序?

       可以使用反向迭代器:

std::sort(v1.rbegin(), v1.rend());

       使用反函数greater<>:

	greater<int> gt;std::sort(v1.begin(), v1.end(),gt);//std::sort(v1.begin(),v1.end(),greater<int>());//匿名对象

greater是降序,升序是less.在C++中,我们<为升序,> 为降序,所有greater是降序,less是升序。

这里了解一下,后面会详细讲解。

二、vector的实现

        1.通过源码进行猜测vector的结构

        <vctor.h>中,我们先浅浅了解一下,具体实现我们使用我们的思路。

        观察源码typedef:

        观察源码的成员变量:

       start是什么,finish是什么?end_of_storage?从名字上看再对比之前的顺序表结构,或许可以大胆猜测:start到finish是一对和size差不多,end_of_storage应该是capacity

        通过观察成员函数来进行猜测:

        如果finish到了end_of_storage说明满了进行扩容。扩容操作是由insert_aux函数来完成的:

        如果满了就进行扩容,大胆猜测扩容操作时进行三段操作:

1.把position位置之前的数据拷贝到新空间,

2.把x插入到新的空间的尾部

3.再把position位置之后的数据拷贝到新的空间。

这些了解一下。

         关于construc和destroy实际上和内存池有关:

        由于内存池调用new和delete不会进行构造和初始化,所以construc和destroy是定位new的函数,用于对内存池的空间进行构造(这里是拷贝构造)和析构 

        2.初步vector的构建

        使用声明和定义分离来进行模拟实现。在开始我们先不实现构造,使用给缺省值来解决五默认构造的问题。关于为什么给成员变量缺省值就可以进行插入可以查看这一片文章:类和对象(下)初始化列表

2.1 成员变量

#pragma once
#include <iostream>
using namespace std;
#include <vector>namespace vet
{template<class T>class vector{public:typedef T* iterator;private://T* _a;//size_t _size;//size_t _capacity;//俩者本质上是一样的iterator _start;iterator _finish;iterator _end_of_storage;};
}

2.2成员函数

        2.2.1尾插和扩容

        尾插涉及扩容,这我们直接实现capacity和size函数。

 vector.h中  void reserve(size_t n){if (n > capacity()){T* tmp = new T[n];if (_start)//如果为空不进行拷贝{memcpy(tmp, _start, sizeof(T) * size());delete _start;_start = tmp;	}_finish = _start + size();_end_of_storage = _start + n;}    }size_t capacity(){return _end_of_storage - start;}size_t size(){return _finish - _start;}void push_back(const T& x){if (_finish == _end_of_storage){//扩容size_t newcapacity = capacity() == 0 ? 4 : 2 * capacity();reserve(newcapacity);}*finish = x;++finish;}
          2.2.2operator[]
	T& operator[](size_t pos){assert(pos < size());return _start[pos];}

        测试代码:

        运行会发现,程序奔溃

test.cpp中
void test_myvector1()
{vet::vector<int> v1;v1.push_back(1);v1.push_back(2);v1.push_back(3);v1.push_back(4);v1.push_back(5);for (int i = 0; i < v1.size(); i++){cout << v1[i] << ' ';}
}

        通过调试发现_finish = x是对空指针操作,实际上错误是在size()计算阶段:

而size()是通过_finish - _start来计算的:

_start指向新的空间,而_finish是nullptr,使用空指针减去_start操作错误。size()不是我们想要的size().

俩种解决办法:

void reserve(size_t n)
{if (n > capacity()){T* tmp = new T[n];if (_start){memcpy(tmp, _start, sizeof(T) * size());delete _start;}_finish = tmp + size();_start = tmp;	_end_of_storage = _start + n;}
}

这样顺序不好,用户可能看不懂,所以可以记录一下size()然后进行更新:

void reserve(size_t n)
{size_t oldsize = size();if (n > capacity()){T* tmp = new T[n];if (_start){memcpy(tmp, _start, sizeof(T) * size());delete _start;}_start = tmp;_finish = _start + oldsize;_end_of_storage = _start + n;}
}

结果:

        我们这里并未写析构和构造,系统是抽查的机制,动态开辟的一般在delete的时候进行检测,所以我们有时候可能在越界暴露不出来,推荐写上析构:
        再测试有无问题。

	~vector(){if (_start){delete[] _start;_finish = _end_of_storage = nullptr;}}

        2.2.3  迭代器

        有了迭代器,那么就可以使用范围for了;

iterator begin()
{return _start;
}
iterator end()
{return _finish;
}

测试代码:

	vet::vector<int> v1;v1.push_back(1);v1.push_back(2);v1.push_back(3);v1.push_back(4);v1.push_back(5);vet::vector<int>::iterator it = v1.begin();while (it != v1.end()){cout << *it << ' ';it++;}cout << endl;for (const auto& e : v1){cout << e << ' ';}

结果:

        2.2.4尾删、指定位置删除和插入
	//尾删void pop_back(){assert(size() > 0);--_finish;}

 3指定位置删除和插入和迭代器失效

  指定位置删除插入:

        要实现指定位置删除或插入就要找到要删除或插入的值

        观察vector 的文档发现,vector没有find,是因为在算法<algorithm>中已经存在find的模板:体现了复用,vector,list都可以用

        3.1insert和迭代器野指针问题

        空间不够扩容,够了挪动数据。

	void insert(iterator pos, const T& x){assert(pos >= _start && pos <= _finish);if (_finish == _end_of_storage){//扩容size_t newcapacity = capacity() == 0 ? 4 : 2 * capacity();reserve(newcapacity);}//挪动数据,从后往前挪iterator end = _finish - 1;while (end >= pos){*(end + 1) = *end;--end;}*pos = x;++_finish;}

        运行代码:

void test_myvector2()
{vet::vector<int> v1;v1.push_back(1);v1.push_back(2);v1.push_back(3);v1.push_back(4);for (auto e : v1){cout << e << ' ';}cout << endl;v1.insert(v1.begin(), 0);for (auto e : v1){cout << e << ' ';}
}

结果:

通过运行结果发现,程序崩溃了,这是为什么?

实际上这里是迭代器失效了,空间满了需要扩容操作,扩容造成了迭代器失效。如果空间足够则可以正常插入

迭代器失效是指在使用迭代器遍历集合(如数组、列表、字典等)的过程中,对集合进行了修改(添加、删除操作)导致迭代器指向的元素位置发生变化,从而影响迭代器的正确性和结果不可预测的现象

我们的代码实在扩容的时候发生了迭代器失效。

扩容操作改变的是_start和_finish以及_end_of_stroage:

所以这里的迭代器失效本质是野指针问题

既然pos迭代器失效,那我们就更新pos迭代器。pos要指向新空间的pos位置:
记录与_start的距离:size_t len = pos - _start;

void insert(iterator pos, const T& x)
{assert(pos >= _start && pos <= _finish);if (_finish == _end_of_storage){size_t len = pos - _start;//扩容size_t newcapacity = capacity() == 0 ? 4 : 2 * capacity();reserve(newcapacity);pos = _start + len;}//挪动数据,从后往前挪iterator end = _finish - 1;while (end >= pos){*(end + 1) = *end;--end;}	*pos = x;++_finish;
}

此时运行上面的测试代码:
结果运行正常。

一般使用insert往往伴随着find,所以我们使用find再进行测试

	vet::vector<int> v1;v1.push_back(1);v1.push_back(2);v1.push_back(3);v1.push_back(4);for (auto e : v1){cout << e << ' ';}cout << endl;vet::vector<int>::iterator vi = find(v1.begin(),v1.end(),3);v1.insert(vi, 10);for (auto e : v1){cout << e << ' ';}

这样又有一个问题:发生扩容时insert以后vi个迭代器实参会不会失效

        仔细想想,空间不足时,需要扩容进行空间转移,而vi指向的是否是原来的空间函数中的pos是否会改变vi?

        答案是会失效,因为,vi是实参,而pos是形参,形参的改变不会影响实参的改变。

vet::vector<int>::iterator vi = find(v1.begin(),v1.end(),3);
if(vi != v1.end())
{ v1.insert(vi, 10);cout << *vi << endl;
}

由于不知道什么时候扩容,所以一般认为这种情况是迭代器失效。这时候我们建议不要访问和修改vi指向的空间了(即不使用失效的迭代器)。

        如果非要访问插入的位置呢?该怎么办?文档中insert提供了一种方法就是函数的返回值:

iterator insert(iterator pos, const T& x)
{//代码..return pos;
}

一般也不会这么做,所以一般认为失效了。string也有迭代器失效,其他也不例外。也是通过返回值。

        3.2erase和迭代器失效

        erase要将pos位置之后的数据前移:

	void erase(iterator pos){assert(pos >= _start && pos < _finish);iterator it = pos + 1;//将后面的数据前移while (it != _finish){*(it - 1) = *it;++it;}--_finish;}

测试代码:

void test_myvector03()
{vet::vector<int> v1;v1.push_back(1);v1.push_back(2);v1.push_back(3);v1.push_back(4);for (auto e : v1){cout << e << ' ';}cout << endl;v1.erase(v1.begin());for (auto e : v1){cout << e << ' ';}
}

测试结果:

erase也有迭代器失效,我们使用vector库里的删除进行测试看看:

同样也不能访问,而且是断言错误。而使用返回值时则可以使用:

既然删除只涉及数据移动,那为什么删除也会是迭代器失效呢?

由于C++并未规定删除是否会缩容,所以删除时不同的平台可能不同:

是有可能野指针的。

就算不缩容,那么在删5的时候呢?删除5的时候后面没有数据,如果要访问迭代器会造成越界访问。这里迭代器失效并不是野指针

所以我们认为erase后,迭代器失效。

vs下要访问迭代器的话,同样是使用返回值,那我们实现也使用,但是在删除最后应一个有效元素时,不能进行访问:

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

测试代码:

	std::vector<int> v1;v1.push_back(1);v1.push_back(2);v1.push_back(3);v1.push_back(4);v1.push_back(5);int x;cin >> x;std::vector<int>::iterator it = find(v1.begin(), v1.end(), x);if (it != v1.end()){it = v1.erase(it);if(it != v1.end())cout << *it << endl;}

如下题,要删除所有偶数,使用失效的迭代器则会保存,所有应该使用返回值:

void test_myvector05()
{std::vector<int> v1;v1.push_back(1);v1.push_back(2);v1.push_back(3);v1.push_back(4);v1.push_back(5);for (auto e : v1){cout << e << ' ';}//删除所有偶数std::vector<int>::iterator it = v1.begin();while (it != v1.end()){if (*it % 2 == 0)it = v1.erase(it);else++it;}for (auto e : v1){cout << e << ' ';}
}

而在linux下不使用返回值则可以。但是不同平台不一样,使用最好使用函数返回值更新

4.拷贝构造

        在不写拷贝构造函数时,编译器会默认生成,该拷贝是浅拷贝。

void test_myvector06()
{vet::vector<int> v1;v1.push_back(1);v1.push_back(2);v1.push_back(3);v1.push_back(4);v1.push_back(5);//没有写拷贝构造//浅拷贝vet::vector<int> v2(v1);for (auto e : v2){cout << e << ' ';}
}

结果肯定是报错,因为浅拷贝是值拷贝成员变量值是一样的,此时v1和v2指向同一块空间

,在出函数作用域时会调用析构函数,此时v1、v2都会析构,但是指向同一块空间,所以析构了俩次

所有我们要写拷贝构造:
        像这样写就可以开辟空防止指向同一块空间。

vector(const vector<T>& v)
{for (auto e : v){push_back(e);}
}

但是由于我们没有写默认构造函数:

由于我们写了一个拷贝构造,编译器不会自动生成构造函数(类和对象中上)了。

我们可以通过写默认构造函数来解决也可以通过下面这种方法:

vector() = default;
vector(const vector<T>& v)
{for (auto e : v){push_back(e);}
}

第一行代码使用于编译器强制生成默认构造函数

我们在后面再添加上默认构造函数,这里我们先完成拷贝构造函数,由于拷贝构造我们使用的是尾插的方式,所以每次插入可能涉及很多扩容,所以我们可以提前开好空间:

	vector(const vector<T>& v){reserve(v.capacity());for (auto e : v){push_back(e);}}

        运行时发现不行:

是因为我们的capacity和size,迭代器不是const成员函数,所以我们加上const:

实现const迭代器:

	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;}

结果:

5.operator=赋值

        我们使用现代写法:

	//v1 = v2void swap(vector<T> v){std::swap(_start, v._start);std::swap(_finish,v._finish);std::swap(_end_of_storage,v._end_of_storage);}//现代写法:传值传参,v出函数作用域会调析构,//但是我们交换了this和v的成员变量,所以析构的是原来的空间而不是v的而是this的vector<T>& operator=(vector<T> v){swap(v);return this;}

6.迭代器区间初始化

        allocator是内存池,内存池是自己写的空间配置,我们使用new来开空间就好。

        迭代器区间需要俩个迭代器first和last,我们写成函数模板,可以支持任意类型:

//支持任意容器的迭代器初始化
template <class InputIterator>
vector(InputIterator first, InputIterator last)
{while (first != last){push_back(*first);++first;}
}

测试代码:

void test_myvector07()
{vet::vector<int> v1;v1.push_back(1);v1.push_back(2);v1.push_back(3);v1.push_back(4);v1.push_back(5);for (auto e : v1){cout << e << ' ';}cout << endl;vet::vector<int> v2(v1.begin()+1,v1.end()-1);for (auto e : v2){cout << e << ' ';}
}

结果:

7.n个val初始化和编译器匹配问题

        需要考虑缺省值。

缺省值能否是0?很明显不能,因为T的类型未知,如果是string、vector、list类型,给0肯定会有问题。所以该怎么办?

缺省参数一般给常量,但自定义类型怎么办,C++中自定义类型可以传T(),即匿名对象(调用默认构造)。内置类型是否可以这样?是可以的,C++对内置类型进行升级,可以进行构造:
值分别为0、1、0、2。

测试代码:

void test_myvector08()
{vet::vector<string> v1(10);vet::vector<string> v2(10, "xx");for (auto e : v1){cout << e << ' ';}cout << endl;for (auto e : v2){cout << e << ' ';}
}

结果:

在使用实例化类模板时,如果对它构造n个1会有意想不到的错误

void test_myvector09() 
{vet::vector<string> v1(10, 1);for (auto e : v1){cout << e << ' ';}
}

         报错到了这里?

这里提一下调试技巧:目前我们知道时我们的测试代码上下都没有其他代码,

1.如果有其他代码,先通过屏蔽其他代码锁定时哪一段代码出来问题。

2.通过一步步调试进行观察。

这里实际上时参数的匹配问题,编译器的选择问题

由于我们传参的都是int,所以模板实例化的也是int int。

编译器会匹配更好的,参数更匹配的。

实际上vector<int>(10,1)也会出错:

解决办法:要使得匹配到正确的函数我们就要给出一个重载的函数:

这样就可以匹配到合适的函数。vector库内也面临这样的问题。

三、{}的使用

        在类和对象(下)中我们提到了多参数和单参数的隐式类型转换。使用到了{},这时C++11的特性:一切皆可用{}进行初始化

class A
{
public:A(int a):_a1(a),_a2(a){}A(int a1, int a2):_a1(a1), _a2(a2){}
private:int _a1;int _a2;
};
int main()
{//单参数隐式类型转换  1-> 构造临时对象A(1) -> 拷贝构造给 a1 => 优化为直接构造A a1(1);A a2 = 1;A a3{ 1 };A a4 = { 1 };//多参数隐式类型转换  1,2-> 构造临时对象A(1,2) -> 拷贝构造给 aa1 => 优化为直接构造A aa1(1, 2);A aa2 = { 1,2 };A aa3{ 1,2 };return 0;
}

所以在平常使用中可以使用{}.

再来看下面代码:

我们使用库中的vector。

void test_vector1()
{std::vector<int> v1 = { 1,2,3,4,5,6 };for (auto e : v1){cout << e << ' ';}
}

这里是隐式类型转换,但不是转化为vector<int>而是initializer list,然后再进隐式类型转化,是C++11的一种构造:

initializer list是C++11新增的类型

它是一个自定义类型:

il1和il2是等价的。由于{}内是常量数组。内部实际上是俩个指针,分别指向常量数组的开始和末尾

它也有迭代器和size,所以支持范围for:

所以在我们自己实现的vector中要使用{1,2,3,4,5}这样的形式我们要支持initializer_list的构造

	vector(initializer_list<T>il){reserve(il.size());//initializer_list支持迭代器for (auto e : il){push_back(e);}}

结果:

有了这个特性我们就可以像下面这样:

class A
{
public:A(int a = 0):_a1(a),_a2(a){}A(int a1, int a2):_a1(a1), _a2(a2){}
private:int _a1;int _a2;
};
int main()
{vet::vector<A> v1 = { 1,{1},A(1),A(1,2),A{1},A{1,2},{1,2} };return 0;
}

{}的用法很杂,使用再使用{}进行初始化时,尽量不要写的太奇怪。

 四、vector<string>的问题

        观察下面的程序:

void test_myvector11()
{vet::vector<string> v1;v1.push_back("1111111111111111");v1.push_back("1111111111111111");v1.push_back("1111111111111111");v1.push_back("1111111111111111");for (auto e : v1){cout << e << endl;}cout << endl;
}

        插入4个string,结果没有问题:

        而再插入一个string会发生意料之外的结果:

程序崩溃了。这是为什么?关键就在我们多插入的一次,通过调试观察:

到这没有什么问题,然后进memcpy,释放就释放旧空间

释放旧空间:

走早这里已经知晓了为什么会改变了,释放的空间是我们拷贝的空间,这样的情况就是浅拷贝。

画图进行分析:

memcpy对任意类型数据拷贝是浅拷贝。memcpy对数据一个字节一个字节拷贝。在对_start进行释放时,string会调用析构函数,对其中的_str进行释放。

解决方案:进行深拷贝:

		void reserve(size_t n){size_t oldsize = size();if (n > capacity()){T* tmp = new T[n];if (_start){//memcpy(tmp, _start, sizeof(T) * size());for (size_t i = 0; i < oldsize; i++){//进行赋值//内置类型进行赋值,自定义类型调用它的赋值操作tmp[i] = _start[i];}delete[] _start;}_start = tmp;_finish = _start + oldsize;_end_of_storage = _start + n;}}

进行赋值,内置类型进行赋值自定义类型调用它的赋值操作。在这里tmp[i]和_start[i]相当于是

string对象进行赋值

如果你有所收获,可以留下你的点赞和关注,谢谢你的观看!


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

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

相关文章

【Apache Doris】BE宕机问题排查指南

【Apache Doris】BE宕机问题排查指南 背景BE宕机分类如何判断是BE进程是Crash还是OOMBE Crash 后如何排查BE OOM 后如何分析Cache 没及时释放导致BE OOM&#xff08;2.0.3-rc04&#xff09; 关于社区 作者&#xff5c;李渊渊 背景 在实际线上生产环境中&#xff0c;大家可能遇…

校园网拨号上网环境下多开虚拟机,实现宿主机与虚拟机互通,并访问外部网络

校园网某些登录客户端只允许同一时间一台设备登录&#xff0c;因此必须使用NAT模式共享宿主机的真实IP&#xff0c;相当于访问外网时只使用宿主机IP&#xff0c;此方式通过虚拟网卡与物理网卡之间的数据转发实现访问外网及互通 经验证&#xff0c;将centos的物理地址与主机物理…

有什么好用的语音翻译软件推荐?亲测实用的语音翻译工具来了

嘿&#xff0c;大家好&#xff01;你们有没有想过&#xff0c;现在世界这么“小”&#xff0c;我们跟不同国家的人打交道的机会越来越多了。 但是呢&#xff0c;语言不通真是个大问题。别担心&#xff0c;现在有个超棒的解决方案——语音翻译技术&#xff01;这玩意儿能实时把…

Spring Cloud学习笔记(Nacos):配置中心基础和代码样例

这是本人学习的总结&#xff0c;主要学习资料如下 - 马士兵教育 1、Overview2、样例2.1、Dependency2.2、配置文件的定位2.3、bootstrap.yml2.4、配置中心新增配置2.5、验证 1、Overview 配置中心用于管理配置项和配置文件&#xff0c;比如平时写的application.yml就是配置文件…

【Spring Security + OAuth2】OAuth2

Spring Security OAuth2 第一章 Spring Security 快速入门 第二章 Spring Security 自定义配置 第三章 Spring Security 前后端分离配置 第四章 Spring Security 身份认证 第五章 Spring Security 授权 第六章 OAuth2 文章目录 Spring Security OAuth21、OAuth2简介1.1、OAu…

Linux驱动开发笔记(二) 基于字符设备驱动的I/O操作

文章目录 前言一、设备驱动的作用与本质1. 驱动的作用2. 有无操作系统的区别 二、内存管理单元MMU三、相关函数1. ioremap( )2. iounmap( )3. class_create( )4. class_destroy( ) 四、GPIO的基本知识1. GPIO的寄存器进行读写操作流程2. 引脚复用2. 定义GPIO寄存器物理地址 五、…

【2024最新华为OD-C卷试题汇总】传递悄悄话的最长时间(100分) - 三语言AC题解(Python/Java/Cpp)

&#x1f36d; 大家好这里是清隆学长 &#xff0c;一枚热爱算法的程序员 ✨ 本系列打算持续跟新华为OD-C卷的三语言AC题解 &#x1f4bb; ACM银牌&#x1f948;| 多次AK大厂笔试 &#xff5c; 编程一对一辅导 &#x1f44f; 感谢大家的订阅➕ 和 喜欢&#x1f497; 文章目录 前…

东哥一句兄弟,你还当真了?

关注卢松松&#xff0c;会经常给你分享一些我的经验和观点。 你还真把自己当刘强东兄弟了?谁跟你是兄弟了?你在国外的房子又不给我住&#xff0c;你出去旅游也不带上我!都成人年了&#xff0c;东哥一句客套话&#xff0c;别当真! 今天&#xff0c;东哥在高管会上直言&…

[数据结构] -- 单链表

&#x1f308; 个人主页&#xff1a;白子寰 &#x1f525; 分类专栏&#xff1a;C打怪之路&#xff0c;python从入门到精通&#xff0c;数据结构&#xff0c;C语言&#xff0c;C语言题集&#x1f448; 希望得到您的订阅和支持~ &#x1f4a1; 坚持创作博文(平均质量分82)&#…

c++编程14——STL(3)list

欢迎来到博主的专栏&#xff1a;c编程 博主ID&#xff1a;代码小豪 文章目录 list成员类型构造、析构、与赋值iterator元素访问修改元素list的操作 list list的数据结构是一个链表&#xff0c;准确的说应该是一个双向链表。这是一个双向链表的节点结构&#xff1a; list的使用…

Vue学习笔记3——事件处理

事件处理 1、事件处理器&#xff08;1&#xff09;内联事件处理器&#xff08;2&#xff09;方法事件处理器 2、事件参数3、事件修饰符 1、事件处理器 我们可以使用v-on 指令(简写为)来监听DOM事件&#xff0c;并在事件触发时执行对应的JavaScript。 用法: v-on:click"me…

JVM学习-执行引擎

执行引擎 执行引擎是Java虚拟机核心组成部分之一虚拟机是一个相对于物理机的概念&#xff0c;这两种机器都有代码执行能力&#xff0c;其区别是物理机的执行引擎是直接建立在处理器、缓存、指令集和操作系统层面上的&#xff0c;而虚拟机的执行引擎是由软件自行实现的&#xf…

【算法】递归、搜索与回溯——简介

简介&#xff1a;递归、搜索与回溯&#xff0c;本节博客主要是简单记录一下关于“递归、搜索与回溯”的相关简单概念&#xff0c;为后续算法做铺垫。 目录 1.递归1.1递归概念2.2递归意义2.3学习递归2.4写递归代码步骤 2.搜索3.回溯与剪枝 递归、搜索、回溯的关系&#xff1a; …

ICML2024 定义新隐私保护升级:DP-BITFIT新型微调技术让AI模型学习更安全

DeepVisionary 每日深度学习前沿科技推送&顶会论文分享&#xff0c;与你一起了解前沿深度学习信息&#xff01; 引言&#xff1a;差分隐私在大模型微调中的重要性和挑战 在当今的深度学习领域&#xff0c;大型预训练模型的微调已成为提高各种任务性能的关键技术。然而&am…

推特热帖:大语言模型自荐能够替代的20种人类工作!快来看你是否需要转行!

最近推特上有一个例子引起了广泛的讨论&#xff0c;事情的起因是这样的&#xff1a;网友让 GPT-4o 预测一下自己未来将会替代人类哪些工作&#xff1f; 这听起来很有趣&#xff01;GPT-4o会给出什么样的预测呢&#xff1f; 3.5研究测试&#xff1a;hujiaoai.cn 4研究测试&…

02-Linux【基础篇】

一、Linux的目录结构 1.基本介绍 Linux的文件系统采用层级式的树状目录结构&#xff0c;在此结构中的最上层是根目录"/"&#xff0c;然后在此目录下再创建其他的目录 深刻理解Linux树状文件目录是非常重要的 记住一句经典的话&#xff1a;在Linux世界里&#xff…

如何在 DigitalOcean Droplet 云主机上创建 Ubuntu 服务器

在本文中&#xff0c;你将通过 DigitalOcean 的管理面板创建一个 Ubuntu 服务器&#xff0c;并将其配置为使用你的 SSH 密钥。设置好服务器后&#xff0c;你可以在其上部署应用程序和网站。 本教程是DigitalOcean云课程简介的一部分&#xff0c;它指导用户完成将应用程序安全地…

win10右键没有默认打开方式的选项的处理方法

问题描述 搞了几个PDF书籍学习一下&#xff0c;不过我不想用默认的WPS打开&#xff0c;因为WPS太恶心人了&#xff0c;占用资源又高。我下载了个Sumatra PDF&#xff0c;这时候我像更改pdf文件默认的打开程序&#xff0c;发现右击没有这个选项。 问题解决 右击文件–属性–…

汽车以太网发展现状及挑战

一、汽车以太网技术联盟 目前推动汽车以太网技术应用与发展的组织包括&#xff1a;OPEN Alliance&#xff08;One-Pair Ether-Net Alliance SIG&#xff09;联盟&#xff0c;主要致力于汽车以太网推广与使用&#xff0c;该联盟通过推进 BroadR- Reach 单对非屏蔽双绞线以太网传…