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,一经查实,立即删除!

相关文章

云存储与云计算详解

1. 云存储与云计算概述 1.1 云存储 云存储&#xff08;Cloud Storage&#xff09;是指通过互联网将数据存储在远程服务器上&#xff0c;用户可以随时随地访问和管理这些数据。云存储的优点包括高可扩展性、灵活性和成本效益。 1.2 云计算 云计算&#xff08;Cloud Computin…

前端 控制台提示invalid date

如果你遇到了 "Invalid Date" 的错误&#xff0c;这通常意味着传递给 Date 构造函数的字符串或数值无法被解析为一个有效的日期。对于时间戳来说&#xff0c;确保它是一个有效的数字&#xff08;表示自1970年1月1日00:00:00 UTC以来的毫秒数&#xff09;。 以下是一…

Java如何设计一个功能

流程说明:实现一组功能的步骤 1,充分了解需求,包括所有的细节,需要知道要做一个什么样的功能。 2,设计实体/表 正向工程:设计实体、映射文件 --> 建表 反向工程:设计表 --> 映射文件、实体 设计实体类型分析步骤&#xff1a; 1&#xff09;功能模块有几个实体…

【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就是配置文件…

Python 遍历字典的方法,你都掌握了吗

Python中的字典是一种非常灵活的数据结构&#xff0c;它允许通过键来存储和访问值。在处理字典时&#xff0c;经常需要遍历字典中的元素&#xff0c;以下是几种常见的遍历字典的方法。 1. 使用 for 循环直接遍历字典的键 字典的键是唯一的&#xff0c;可以直接通过 for 循环来…

【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…

call、apply和bind

call、apply和bind都是JavaScript中函数对象的方法&#xff0c;用于改变函数的this值。 call&#xff1a;call方法接收一个对象和一系列参数&#xff0c;并立即调用函数&#xff0c;将this值设置为提供的对象。例如&#xff1a; function greet(greeting, punctuation) {cons…

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;东哥在高管会上直言&…

mysql内存结构

一&#xff1a;逻辑存储结构&#xff1a;表空间->段->区->页->行、 表空间&#xff1a;一个mysql实例对应多个表空间&#xff0c;用于存储记录&#xff0c;索引等数据。 段&#xff1a;分为数据段&#xff0c;索引段&#xff0c;回滚段。innoDB是索引组织表&…

215. 数组中的第K个最大元素(快速排序、堆排序)

根据这道题总结一下快速排序和堆排序&#xff0c;再根据这两种方法写这道题。 给定整数数组 nums 和整数 k&#xff0c;请返回数组中第 k 个最大的元素。 请注意&#xff0c;你需要找的是数组排序后的第 k 个最大的元素&#xff0c;而不是第 k 个不同的元素。 你必须设计并实…

qmt量化交易策略小白学习笔记第6期【qmt如何获取股票历史涨跌停价格】

qmt如何获取股票历史涨跌停价格 qmt更加详细的教程方法&#xff0c;会持续慢慢梳理。 也可找寻博主的历史文章&#xff0c;搜索关键词查看解决方案 &#xff01; 感谢关注&#xff0c;需免费开通量化回测与咨询实盘权限&#xff0c;可以和博主联系&#xff01; 获取股票历史…

[数据结构] -- 单链表

&#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…