【C++】vector的模拟实现
文章目录
- 前言
- 一. vector的底层
- 二. 关于容量和大小的函数
- 2.1 size和capacity
- 2.2 reserve
- 2.3 resize
- 2.4 empty
- 三. vector的默认成员函数
- 3.1 构造函数
- 3.1.1 无参构造函数
- 3.1.2 构造初始化为n个val值
- 3.1.3 用initializer_list构造初始化
- 3.1.4 使用迭代器区间进行构造初始化
- 3.2 拷贝构造函数
- 3.3 赋值运算符重载
- 3.4 析构函数
- 四. iterator迭代器
- 五. operator[]访问函数
- 六. 修改容器内容的相关函数
- 6.1 push_back
- 6.2 pop_back()
- 6.3 insert
- 6.4 erase
- 6.5 clear
- 七. 源代码
- END
前言
本篇博客讲的是vector的模拟实现,因为上一篇博客已经模拟了string的实现,所以在模拟vector的实现中会比较熟悉,模拟vector的实现是为了我们更好地理解和掌握vector,接下来我们会实现vector一些重要的接口。
一. vector的底层
#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<assert.h>
#include<vector>
using namespace std;
//用命名空间MyVector封装vector,避免与std库的vector冲突
namespace MyVector
{template<class T>class vector{public:typedef T* iterator;typedef const T* const_iterator;private:iterator start; //指向数据块的开始iterator finish; //指向有效数据的末尾iterator end_of_storage; //指向存储容量的末尾};
}
vector的底层实现是由三个迭代器start,finish,end_of_storage来管理内存和数据的,而vector的迭代器就是一个原生态指针T*
- start:指向vector中第一个元素的地址。
- finish:指向vector中最后一个元素的下一个位置(即逻辑上的结束位置)。
- end_of_storage:指向vector分配的内存量的结束位置(即实际分配的内存大小)
注意:end_of_storage是一个指针,它指向分配的内存块的逻辑结束位置,但并不是指向具体的内存单元,而是指向分配的内存块的末尾的下一个位置。它的作用是表示分配的内存的容量上限,而不是实际存储数据的地址。总的来说,end_of_storage是一个虚拟的边界标记。
内存管理
- start到finish:表示vector中实际存储的元素范围。
- finish到end_of_storage:表示已分配但未使用的内存区域。
当 vector 的容量不足时(例如调用 push_back 或 resize),它会重新分配一块更大的内存区域,并将旧数据复制到新内存中,然后释放旧内存。这种机制确保了 vector 的动态扩展能力。
侯捷老师《STL源码剖析》的原图如下:


begin()就是start,end()就是finish
注意:vector是一个类模板,所以在vector的实现中声明和定义不能分离。
类模板的实例化发生在编译阶段,而不是链接阶段。编译器需要知道类模板的完整定义(包括实现)才能生成具体的代码。如果实现部分不在头文件中,编译器无法完成模板的实例化。
模板的编译机制:
模板类的代码(包括定义和实现)必须在编译时完全可见。这是因为模板类的实例化(instantiation)是基于模板参数(如类型参数)动态生成具体代码的。编译器需要在编译时根据模板参数生成具体的代码,因此模板的定义和实现不能分离。
二. 关于容量和大小的函数
2.1 size和capacity
size_t size() const
{return finish - start; //返回容器中有效数据的个数
}
size_t capacity() const
{return end_of_storage - start; //返回容器的最大容量
}
2.2 reserve
reserve规则:
1.当n大于对象当前的容量,就将容量扩容到n。
2.当n小于等于对象当前的容量,就什么都不做。
void reserve(size_t n)
{if (n > capacity()){size_t oldsize = size();T* tmp = new T[n]; //开辟新空间if (start) {memcpy(tmp, start, sizeof(T) * oldsize); //拷贝数据}delete[] start; //释放旧空间start = tmp;finish = start + oldsize;end_of_storage = start + n;}
}
注意:在进行扩容操作前需要记录当前容器的有效数据个数。
因为要改变finish指针,而finish等于start+有效数据的个数,但是start已经指向新空间了,当试图调用size函数获取有效数据个数时就会返回一个随机值,所以在进行操作之前就要先将容器的有效数据个数记录下来。
2.3 resize
resize规则:
1.当n大于容器的有效数据个数size时,将size扩大到n,用val填充未初始化的数据,如果val没有给出,val默认为容器所存储数据类型的默认值。
2.当n小于容器的有效数据个数size时,将size缩小到n。
void resize(size_t n, const T& val = T())
{if (n < size()){finish = start + n;}else{reserve(n); //检查是否需要扩容while (finish < start + n){*finish = val;finish++;}}
}
注意:在C++中,对于自定义类型需要通过默认构造函数:类型()构造出一个临时对象,val再引用这个临时对象,而对于内置类型也可以通过类型()获得该类型的默认值,这是为了兼并内置类型和自定义类型。
2.4 empty
判断容器是否为空
bool empty() const
{return start == finish;
}
三. vector的默认成员函数
3.1 构造函数
3.1.1 无参构造函数
对于vector的无参构造函数,直接将三个成员变量都初始化为nullptr即可。
vector():start(nullptr),finish(nullptr),end_of_shorage(nullptr)
{ }
3.1.2 构造初始化为n个val值
构造一个vector容器,该容器的数据为n个val值。先调用reserve函数将容器的容量设置为n,再将n个val插入容器当中即可。
vector(size_t n, const T& val = T())
{reserve(n);for (size_t i = 0; i < n; i++){start[i] = val;}finish = start + n;
}
3.1.3 用initializer_list构造初始化
在 C++ 中,std::initializer_list 是一个模板类,用于表示初始化列表(即用 {} 包围的值列表)。它通常用于支持统一初始化语法,这是 C++11 引入的一种初始化方式。std::initializer_list 提供了一种类型安全的方式来处理初始化列表,并且可以在函数参数、数组初始化、容器初始化等场景中使用。
基本概念
std::initializer_list 是一个轻量级的容器,它存储了初始化列表的开始和结束指针,但不会复制列表中的元素。它的主要目的是提供一种统一的语法来初始化对象或传递参数。
语法
std::initializer_list<T> list = { value1, value2, ..., valuen };
其中 T 是列表中元素的类型。
vector(initializer_list<T> il)
这个构造函数的作用是将 initializer_list< T > 中的元素复制到 vector 的内部存储中。
实现细节
从 initializer_list 中获取元素的迭代器范围(begin() 和 end()),遍历每个元素,将这些元素逐个复制到vector的内部存储中。
注意事项
- std::initializer_list 的生命周期与初始化列表的生命周期相同。例如,如果初始化列表是在函数调用中传递的,那么 std::initializer_list 的生命周期只持续到函数调用结束。
- std::initializer_list 中的元素是只读的,不能直接修改。如果需要修改,可以先复制到 vector 中,然后对 vector 进行操作。
vector(initializer_list<T> il)
{reserve(il.size());size_t i = 0;for (auto& x : il){start[i++] = x;}finish = start + i; //不要忘记修改finish
}
3.1.4 使用迭代器区间进行构造初始化
为了不止可以用该容器的迭代器区间构造初始化,也可以用其他容器的迭代器区间进行构造初始化,所以我们可以将该构造函数设计为一个函数模板,用模板参数自动推导出迭代器的类型即可。
template<class InputIterator>
vector(InputIterator first, InputIterator last) //左闭右开
{reserve(last - first);size_t i = 0;while (first != last){start[i++] = *first;first++;}finish = start + i;
}
注意:这里有个小bug,如果调用构造函数MyVector::vector< int > vv(5, 1);时,因为构造函数vector(size_t n, const T& val = T())的第一个数n是size_t类型,要调用只能隐式类型转换,而函数模板可以将InputIterator推导为int,两个函数参数都是int类型,无需进行隐式类型转换,那么编译器会选择更加适配的vector(InputIterator first, InputIterator last)构造函数,从而引发报错,那么怎么解决这个问题呢?
可以再写一个第一个函数参数n为int类型的构造函数:vector(int n, const T& val = T())
vector(int n, const T& val = T())
{reserve(n);for (int i = 0; i < n; i++){start[i] = val;}finish = start + n;
}
3.2 拷贝构造函数
思路很简单,先将该容器扩容到和容器v的size一样大小,再将v的数据拷贝过来即可。
vector(const vector<T>& v)
{size_t sz = v.size();reserve(sz);memcpy(start, v.start, sizeof(T) * sz);finish = start + sz;
}
注意:严格来说,将容器v的数据一个一个拷贝过来时不能使用memcpy函数,当vector容器存储的是内置类型或无需进行深拷贝的自定义类型时,可以使用memcpy函数,但是当vector存储的是需要进行深拷贝的自定义类型时,memcpy函数就不适用了,会引发一块空间被多次释放的问题。
例如:当vector存储的是string类型时,情况如下

vector存储的每一个string对象都指向自己所存储的字符串。

如果此时我们使用memcpy函数进行拷贝构造,那么拷贝构造出来的vector存储的string对象的成员变量和被拷贝的vector存储的string对象的成员变量是一模一样的,它们的_str指针都指向同一块内存空间。
当调用vector的析构函数时,从而调用string的析构函数,这样会引起同一块空间被释放多次的问题。
那么如何解决这个问题呢?
当要将vector的string拷贝过来时,应当实现深拷贝,怎样才能实现深拷贝呢?——>string的赋值运算符重载

这样就实现了深拷贝,避免了一块空间被释放多次的问题。
vector(const vector<T>& v)
{size_t sz = v.size();reserve(sz);for (size_t i = 0; i < sz; i++){start[i] = v.start[i];}finish = start + sz;
}
还有一种现代写法,复用了使用迭代器区间进行构造初始化的构造函数:
vector(const vector<T>& v)
{vector<T> tmp(v.begin(), v.end());std::swap(start, tmp.start);std::swap(finish, tmp.finish);std::swap(end_of_storage, tmp.end_of_storage);
}
我们可以实现一个交换函数:
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(const vector<T>& v)
{vector<T> tmp(v.begin(), v.end());swap(tmp);
}
3.3 赋值运算符重载
vector<T>& operator=(vector<T> v) //调用拷贝构造函数构造出v对象
{swap(v);//交换两个对象return *this; //支持连续赋值
}
注意要使用传值传参,编译器会间接调用拷贝构造函数构造出v对象,然后将该对象与v交换即可,为了支持连续赋值,最后返回该对象,编译器在函数调用结束时会自动调用v的析构函数,将v释放掉。
3.4 析构函数
~vector()
{if (start) { //先判断start为不为空,如果不为空则释放空间,否则无需进行操作delete[] start;}start = finish = end_of_storage = nullptr;
}
析构前先判断start是否为空,如果不为空,则释放指向的空间,否则就无需释放,最后将三个指针start,finish,end_of_storage都指向空即可。
四. iterator迭代器
typedef T* iterator;
typedef const T* const_iterator;
vector迭代器就是原生指针T*,begin函数返回容器第一个元素的地址,即start;end函数返回容器最后一个元素的下一个地址,即finish。
注意:const对象的begin和end函数要用const修饰this指针指向的内容,因为const对象不允许被修改。
iterator begin()
{return start;
}
iterator end()
{return finish;
}
const_iterator begin() const
{return start;
}
const_iterator end() const
{return finish;
}
测试:
void Test()
{MyVector::vector<int> v = { 1,2,3,4,5,6 };MyVector::vector<int>::iterator it = v.begin();while (it != v.end()){cout << *it << " ";it++;}cout << endl;const MyVector::vector<int> v1 = { 1,3,5,7,9 };MyVector::vector<int>::const_iterator it1 = v1.begin();while (it1 != v1.end()){cout << *it1 << " ";it1++;}
}
因为实现了迭代器,所以支持使用范围for去遍历vector
void Test()
{MyVector::vector<int> v = { 1,2,3,4,5,6 };for (auto& e : v){cout << e << " ";}cout << endl;const MyVector::vector<int> v1 = { 1,3,5,7,9 };for (auto& e : v1){cout << e << " ";}
}
五. operator[]访问函数
T& operator[](size_t i)
{assert(i < size()); //检查下标是否合法return start[i];
}
const T& operator[](size_t i) const
{assert(i < size()); //检查下标是否合法return start[i];
}
注意:不要忘了重载一个适用于const对象的operator[]函数,因为const对象只允许读,不允许修改。
六. 修改容器内容的相关函数
6.1 push_back
尾插前先判断容器是否已满,如果满了就先扩容再尾插,如果没满直接尾插即可,最后不要忘了将finish向后移一位。
void push_back(const T& x)
{if (finish == end_of_storage) {reserve(capacity() == 0 ? 4 : 2 * capacity());}*finish = x;finish++;
}
测试:
void Test()
{MyVector::vector<string> v;v.push_back("hello world");v.push_back("hello world");v.push_back("hello world");v.push_back("hello world");for (auto& e : v){cout << e << endl;}
}
运行后感觉是没什么问题,如果此时我再尾插一个相同的数据,会发生什么呢?
void Test()
{MyVector::vector<string> v;v.push_back("hello world");v.push_back("hello world");v.push_back("hello world");v.push_back("hello world");v.push_back("hello world"); //再尾插一个相同的数据for (auto& e : v){cout << e << endl;}
}

可以看到,程序崩溃了,为什么会这样呢?
容器里有四个string对象不会报错,再尾插一个就报错了,那么问题应该是出现在了扩容函数上:
void reserve(size_t n)
{if (n > capacity()){size_t oldsize = size();T* tmp = new T[n]; //开辟新空间if (start) {memcpy(tmp, start, sizeof(T) * oldsize); //拷贝数据}delete[] start; //释放旧空间start = tmp;finish = start + oldsize;end_of_storage = start + n;}
}
可以看到,reserve函数里拷贝数据的方式用的是memcpy函数,也就是浅拷贝(一个字节一个字节地拷贝),但是string是自定义类型,之前提到过,string实现的是深拷贝,所以memcpy函数在这里就不适用了,应当使用string的赋值运算符重载函数。
void reserve(size_t n)
{if (n > capacity()){size_t oldsize = size();T* tmp = new T[n]; //开辟新空间if (start) {//memcpy(tmp, start, sizeof(T) * oldsize); //拷贝数据for (size_t i = 0; i < oldsize; i++){tmp[i] = start[i]; //深拷贝}}delete[] start; //释放旧空间start = tmp;finish = start + oldsize;end_of_storage = start + n;}
}
再进行测试就没问题了。
6.2 pop_back()
尾删前先判断容器是否为空,如果为空则做断言处理,不为空则将finish指针向前移动一位即可。
void pop_back()
{assert(!empty()); //判断容器是否为空,为空则断言finish--; //将finish指针向前移一位
}
测试:
void Test()
{MyVector::vector<int> v = { 1,2,3,4 };for (auto& e : v){cout << e << " ";}cout << endl;while (!v.empty()){v.pop_back();for (auto& e : v){cout << e << " ";}cout << endl;}
}
6.3 insert
在pos迭代器的指定位置插入数据x,在插入数据之前先判断是否需要扩容,在pos以及之后的数据向后移动一位,注意是从后往前的顺序,最后在pos位置插入数据x,还有不要忘了将finish向后移动一位,使有效数据个数加一即可。
void insert(iterator pos, const T& x)
{assert(pos >= begin() && pos <= end()); //检查迭代器pos是否合法if (finish == end_of_storage) {//因为要扩容,旧空间会被释放掉,pos迭代器会失效,所以要更新pos迭代器size_t oldsize = pos - start; //记录pos和start之间的长度reserve(capacity() == 0 ? 4 : 2 * capacity()); //扩容pos = start + oldsize; //将pos指向新空间的指定位置}//将pos以及之后的数据向后移动一位iterator it = finish;while (it > pos){*it = *(it - 1);it--;}*pos = x; //更新数据finish++; //finish向后移动一位,有效数据加一
}
测试:
void Test()
{MyVector::vector<int> v = { 1,2,3,4,5,6 };v.insert(v.begin() + 6, 7);v.insert(v.begin() + 2, 8);for (auto& e : v){cout << e << " ";}cout << endl;
}
6.4 erase
将迭代器pos位置的数据删除,首先检查容器是否为空和pos迭代器是否在[start,finish)区间内,如果有一项不符合就做断言处理,删除数据时将pos之后的数据向前移动一位即可(把pos位置的数据覆盖掉),这里注意是从前往后的顺序。
iterator erase(iterator pos)
{assert(!empty()); //检查容器是否为空assert(pos >= start && pos < finish); //检查迭代器pos是否合法//将pos之后的数据向前移动一位iterator it = pos + 1;while (it < finish){*(it - 1) = *it;it++;}finish--; //finish向前移动一位,有效数据减一return pos; //返回被删除数据的下一个数据的迭代器
}
测试:
void Test()
{MyVector::vector<int> v = { 1,2,2,3,4,5,6,7,8 };auto it = v.begin();while (it != v.end()){if (*it % 2 == 0) {//v.erase(it)之后it迭代器就失效了,所以要让it指向被删除数据位置的下一个迭代器it = v.erase(it);}else {it++;}}for (auto& e : v){cout << e << " ";}cout << endl;
}
6.5 clear
将容器内存储的数据清空。
void clear()
{finish = start;
}
测试:
void Test()
{MyVector::vector<int> v = { 1,2,3,4,5,6 };for (auto& e : v){cout << e << " ";}cout << endl;v.clear();for (auto& e : v){cout << e << " ";}cout << endl;
}
七. 源代码
vector.h文件
#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<assert.h>
#include<vector>
using namespace std;
//用命名空间MyVector封装vector,避免与std库的vector冲突
namespace MyVector
{template<class T>class vector{public:typedef T* iterator;typedef const T* const_iterator;size_t size() const{return finish - start; //返回容器中有效数据的个数}size_t capacity() const{return end_of_storage - start; //返回容器的最大容量}void reserve(size_t n){if (n > capacity()){size_t oldsize = size();T* tmp = new T[n]; //开辟新空间if (start) {//memcpy(tmp, start, sizeof(T) * oldsize); //拷贝数据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 resize(size_t n, const T& val = T()){if (n < size()){finish = start + n;}else{reserve(n); //检查是否需要扩容while (finish < start + n){*finish = val;finish++;}}}bool empty() const{return start == finish;}iterator begin(){return start;}iterator end(){return finish;}const_iterator begin() const{return start;}const_iterator end() const{return finish;}vector():start(nullptr),finish(nullptr),end_of_storage(nullptr){}vector(size_t n, const T& val = T()){reserve(n);for (size_t i = 0; i < n; i++){start[i] = val;}finish = start + n;}vector(int n, const T& val = T()){reserve(n);for (int i = 0; i < n; i++){start[i] = val;}finish = start + n;}vector(initializer_list<T> il){reserve(il.size());size_t i = 0;for (auto& x : il){start[i++] = x;}finish = start + i; //不要忘记修改finish}template<class InputIterator>vector(InputIterator first, InputIterator last) //左闭右开{reserve(last - first);size_t i = 0;while (first != last){start[i++] = *first;first++;}finish = start + i;}/*vector(const vector<T>& v){size_t sz = v.size();reserve(sz);memcpy(start, v.start, sizeof(T) * sz);finish = start + sz;}*//*vector(const vector<T>& v){size_t sz = v.size();reserve(sz);for (size_t i = 0; i < sz; i++){start[i] = v.start[i];}finish = start + sz;}*/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(const vector<T>& v){vector<T> tmp(v.begin(), v.end());swap(tmp);}vector<T>& operator=(vector<T> v) //调用拷贝构造函数构造出v对象{swap(v);//交换两个对象return *this; //支持连续赋值}~vector(){if (start) { //先判断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];}void push_back(const T& x){if (finish == end_of_storage) {reserve(capacity() == 0 ? 4 : 2 * capacity());}*finish = x;finish++;}void pop_back(){assert(!empty()); //判断容器是否为空,为空则断言finish--; //将finish指针向前移一位}void insert(iterator pos, const T& x){assert(pos >= begin() && pos <= end()); //检查迭代器pos是否合法if (finish == end_of_storage) {//因为要扩容,旧空间会被释放掉,pos迭代器会失效,所以要更新pos迭代器size_t oldsize = pos - start; //记录pos和start之间的长度reserve(capacity() == 0 ? 4 : 2 * capacity()); //扩容pos = start + oldsize; //将pos指向新空间的指定位置}//将pos以及之后的数据向后移动一位iterator it = finish;while (it > pos){*it = *(it - 1);it--;}*pos = x; //更新数据finish++; //finish向后移动一位,有效数据加一}iterator erase(iterator pos){assert(!empty()); //检查容器是否为空assert(pos >= start && pos < finish); //检查迭代器pos是否合法//将pos之后的数据向前移动一位iterator it = pos + 1;while (it < finish){*(it - 1) = *it;it++;}finish--; //finish向前移动一位,有效数据减一return pos; //返回被删除数据的下一个数据的迭代器}void clear(){finish = start;}private:iterator start; //指向数据块的开始iterator finish; //指向有效数据的末尾iterator end_of_storage; //指向存储容量的末尾};
}
END
对以上内容有异议或者需要补充的,欢迎大家来讨论!
相关文章:
【C++】vector的模拟实现
文章目录 前言一. vector的底层二. 关于容量和大小的函数2.1 size和capacity2.2 reserve2.3 resize2.4 empty 三. vector的默认成员函数3.1 构造函数3.1.1 无参构造函数3.1.2 构造初始化为n个val值3.1.3 用initializer_list构造初始化3.1.4 使用迭代器区间进行构造初始化 3.2 拷…...
C# Winform 入门(9)之如何封装并调用dll
封装dll 首先创建 .Net平台 类库 using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks;namespace _09.Encapsulation_dll {public class Program{/// <summary>/// 求两个double类型的数值的和/// &l…...
【C语言】内存函数
大家好,很高兴又和大家见面了!!! 在C语言标准库中,有一些直接对内存进行操作的函数,我们将其称之为内存函数,这些函数位于头文件<string.h>,在网站https://cplusplus.com/ref…...
SDL视频显示函数
文章目录 1. **`SDL_Init()`**2. **`SDL_CreateWindow()`**3. **`SDL_CreateRenderer()`**4. **`SDL_CreateTexture()`**5. **`SDL_UpdateTexture()`**6. **`SDL_RenderCopy()`**7. **`SDL_RenderPresent()`**8. **`SDL_Delay()`**9. **`SDL_Quit()`**总结示例代码:代码说明:…...
博客文章:深入分析 PyMovie - 基于 Python和 MoviePy 的视频管理工具
这是一个使用 wxPython 构建界面、moviepy 处理视频的自定义 GUI 应用程序。该工具提供了视频播放、元数据提取、格式转换、视频裁剪和截图等功能。通过分析其设计和实现,我们将了解其工作原理、优点和潜在的改进空间。 C:\pythoncode\new\output\pymovieSample.py …...
Redis中AOF的实现方式和AOF重写
一、AOF 的实现方式 核心原理 AOF 通过将写操作命令以追加方式记录到日志文件中,重启时通过重放命令恢复数据。与 RDB 的快照机制不同,AOF 是增量记录,更适用于数据一致性要求较高的场景。写入流程 命令执行:客户端发送写命令&am…...
Supervisor的安装和使用
Supervisor 使用笔记(CentOS 8 环境) 本周,老师让我使用supervisor管理项目服务,当时第一次听说过这个进程管理工具😶🌫️,就上网搜了搜安装和使用,又用ai查了一些细节࿰…...
JVM 内存区域详解
JVM 内存区域详解 Java 虚拟机(JVM)的内存区域划分为多个部分,每个部分有特定的用途和管理机制。以下是 JVM 内存区域的核心组成及其功能: 一、运行时数据区(Runtime Data Areas) 1. 线程共享区域 内存…...
【java】在 Java 中,获取一个类的`Class`对象有多种方式
在 Java 中,获取一个类的Class对象有多种方式。Class对象代表了 Java 中的一个类或接口的运行时类信息,可以用于反射操作。以下是获取Class对象的几种常见方法: 1.使用.class属性 每个类都有一个.class属性,可以直接获取该类的Cl…...
蓝桥杯:对字符串处理常用知识笔记
一、前面四个是计算带有空格字符串的的长度计算 C语言代码 #include<string.h> #include<stdio.h> int main() { char s[105]; gets(s); printf("%d", strlen(s)); return 0; } 算法2 C 代码(常用) #include <iostream> #in…...
c++网络编程,信号透传可能是什么意思
在 C 网络编程中,**信号透传**(Signal Pass-Through)通常 refers to the concept of allowing signals to propagate through a network protocol stack without being interrupted or modified. 具体来说,信号透传可以涉及到几个…...
数据结构与算法学习笔记----贪心·绝对值不等式
数据结构与算法学习笔记----贪心绝对值不等式 author: 明月清了个风 first publish time: 2025.4.5 ps⭐️感觉其实是一个数学的问题, Acwing 104. 货仓选址 [原题链接](104. 货仓选址 - AcWing题库) 在一条数轴上有 N N N家商店,他们的坐标分别为 A…...
CUDA学习--体验GPU性能
学习来源:2 CUDA Python--并行计算基础-卷积计算以及共享内存_哔哩哔哩_bilibili 处理一张图片的处理速度对比 import cv2 from numba import cuda import time import math cuda.jit() def process_gpu(img,channels):tx cuda.blockIdx.x*cuda.blockDim.xcuda…...
博途 TIA Portal之1200做主站与200SMART的S7通讯
有时候,我们与之作S7通讯的西门子系PLC并不是同一个厂商或是同一时期供货的,也有可能不在一个编程软件中。此时进行S7能讯会有所不同。本文将演示博途与200SMART做S7通讯。 1、硬件准备 1200PLC一以,200SMART一台,网线2根,交换机一台。 2、关于编程 1200做主站,因此需…...
a标签download下载图片
a标签的download属性是HTML5中新增的一个属性,用于指定链接点击时直接下载文件,而不是在浏览器中打开文件。 基本用法 指定下载文件名:在a标签中添加download属性,并指定一个文件名。例如: <a href"…...
#SVA语法滴水穿石# (013)关于 disable iff、matched 、expect 的用法
SystemVerilog 断言(SVA)中 disable iff、matched 和 expect 的语法知识。 1. disable iff (condition) 功能与定义 作用:当指定条件(condition)为真时,禁用当前属性的检查。 常用于复位(rese…...
Day2-2:前端项目uniapp壁纸实战
再在wallpaper新建一个目录components 在components下新建组件common-title 记得点击创建同名目录 在index加 <view class"select"><common-title></common-title></view> 图片换了下,原来的有点丑,图片可按自己喜欢…...
pycharm如何通过跳板机连接服务器在本地debug
现在假设你有一个服务器,需要跳板机登陆,但是你从跳板机到服务器,只知道能直接通过ssh连接。 首先你可以现在本地创建一个 SSH 配置文件(~/.ssh/config): Host jumpHostName 跳板机地址Port 端口User 用户…...
Mysql explain中列的解析
EXPLAIN列的解释: table:显示这一行的数据是关于哪张表的 type:这是重要的列,显示连接使用了何种类型。从最好到最差的连接类型为const、eq_reg、ref、range、indexhe和ALL possible_keys:查询可以利用的索引&#…...
场馆预定系统小程序PHP+uniapp
场馆预定系统小程序:基于PHPUniApp的多场景体育场馆智慧化解决方案 随着全民健身意识的提升,体育场馆的数字化管理需求日益增长。场馆预定系统小程序凭借其轻量化、高便捷性,成为体育馆、羽毛球馆、兵乒球馆等场所提升运营效率的核心工具。本…...
05.unity 游戏开发-3D工程的创建及使用方式和区别
05.unity 游戏开发-3D工程的创建及使用方式和区别 提示:帮帮志会陆续更新非常多的IT技术知识,希望分享的内容对您有用。本章分享的是Python基础语法。前后每一小节的内容是存在的有:学习and理解的关联性,希望对您有用~ unity简介…...
php8 命名参数使用教程
简介 PHP 8 引入 命名参数(Named Arguments),允许在调用函数时按参数名传递值,而不是按照参数位置。这增强了代码的可读性、灵活性,并减少参数顺序依赖。 基本用法 传统位置参数(Positional Arguments&a…...
Transformer与注意力机制详解
1 Transformer与注意力机制详解 本文直观上详细介绍了大语言模型中十分重要的结构——Transformer,及其核心:注意力机制的原理。 1. Transformer结构 基础结构如下图所示,左侧由一系列Encoder block(编码器)构成,接收字词句输入;右侧由一系列Decoder block(解码器)…...
xLua环境控制+xLua的Lua调用C#的1
编写自定义加载器加载指定路径的Lua文件: using System.Collections; using System.Collections.Generic; using System.IO; using UnityEngine; using XLua;//Lua是脚本语言,编写代码脚本是实现功能最重要的方式 public class Loader : MonoBehaviour …...
RabbitMQ应用问题
RabbitMQ应用问题 一.幂等性1.简述概念2.MQ的幂等性介绍3.解决幂等性问题(1)全局唯一ID(2)业务逻辑判断 二.顺序性保障1.简单介绍2.无法保证顺序性的场景3.保障方案1)单队列消费者2)分区消费3)消息确认机制4)业务逻辑控制 三.消息积压问题1.原因分析2.解决方案 一.幂等性 1.简…...
轻量化大模型微调工具XTuner指令微调实战(下篇)
接着上篇文章《轻量化大模型微调工具XTuner指令微调实战(上篇)》来接着写教程。 一、模型转换 模型训练后会自动保存成 PTH 模型(例如 iter_500.pth),我们需要利用 xtuner convert pth_to_hf 将其转换为 HuggingFace…...
Redis数据结构之ZSet
目录 1.概述2.常见操作2.1 ZADD2.2 ZRANGE2.3 ZREVRANGE2.4 ZRANGEBYSCORE2.5 ZSCORE2.6 ZCARD2.6 ZREM2.7 ZINCRBY2.8 ZCOUNT2.9 ZMPOP2.10 ZRANK2.11 ZREVRANK 3.总结 1.概述 ZSet和Set一样也是String类型元素的集合,且不允许重复的成员,不同的是ZSet…...
STM32提高篇: CAN通讯
STM32提高篇: CAN通讯 一.CAN通讯介绍1.物理层2.协议层二.STM32CAN外设1.CAN控制器的3种工作模式2.CAN控制器的3种测试模式3.功能框图三.CAN的寄存器介绍1.环回静默模式测试2.双击互发测试四.CAN的HAL代码解读一.CAN通讯介绍 CAN(Controller Area Network 控制器局域网,简称…...
贪心算法之最小生成树问题
1. 贪心算法的基本思想 贪心算法在每一步都选择局部最优的边,希望最终得到整体最优的生成树。常见的两种 MST 算法为 Kruskal 算法 和 Prim 算法。这两者均满足贪心选择性质和最优子结构性质,即: 贪心选择性质:局部最优选择&…...
C++EasyX之五子棋PVP和PVE
以下是该C EasyX五子棋代码的详细解析: 1 代码 1.1 全代码 #include <graphics.h> #include <conio.h> #include <Windows.h> #include <cmath> #include <vector> #include <tuple> #include <algorithm>using na…...
【Tauri2】015——前端的事件、方法和invoke函数
目录 前言 正文 准备 关键url 获取所有命令 切换主题set_theme 设置大小 获得版本version 名字name 监听窗口移动 前言 【Tauri2】005——tauri::command属性与invoke函数-CSDN博客https://blog.csdn.net/qq_63401240/article/details/146581991?spm1001.2014.3001.…...
【C++奇遇记】C++中的进阶知识(继承(二))
🎬 博客主页:博主链接 🎥 本文由 M malloc 原创,首发于 CSDN🙉 🎄 学习专栏推荐:LeetCode刷题集 数据库专栏 初阶数据结构 🏅 欢迎点赞 👍 收藏 ⭐留言 📝 如…...
qt designer 软件主题程序设计
对于使用Qt Designer设计的界面,主题切换的实现需要结合Qt的信号槽机制、样式表动态加载以及资源管理。以下是针对Qt Designer UI的详细解决方案: 一、UI文件与主题系统的整合架构 二、核心实现步骤 1. 动态样式表加载系统 // ThemeManager.h class …...
2025 年 4 月补丁星期二预测:微软将推出更多 AI 安全功能
微软正在继续构建其 AI 网络安全战略,并于本月宣布在 Microsoft Security Copilot 中引入新代理。 他们引入了用于网络钓鱼分类的代理、用于数据丢失预防和内部风险管理的警报分类、条件访问优化、漏洞修复和威胁情报简报。 这些代理的目标是不断从这些不同学科中…...
docker swarm常用命令
1、初始化Swarm集群 用于初始化一个Swarm集群,并将当前节点设置为Manager节点。 用法:docker swarm init --advertise-addr <Manager节点IP> # docker swarm init --advertise-addr 192.168.1.100 这会将当前节点初始化为Swarm集群的管理节点&…...
抖音直播位置与IP属地不同?解析原因与应对策略
在当今短视频直播盛行的时代,抖音作为头部平台吸引了大量主播和观众。然而,许多用户发现一个令人困惑的现象:直播间显示的位置信息与账号IP属地不一致。这种情况不仅让观众产生疑问,也可能给主播带来不必要的麻烦。本文将深入分析…...
「限时开源」全网首发!DeepSeek-R1+AI绘画+音乐生成全栈源码
—企业级AIGC私有化终极方案,3大模态整合,成本直降90% 行业痛点:为什么企业急需这套方案? 1. 多模态AIGC的三大困局 成本失控 API吸血: 使用MidjourneyStable DiffusionGPT-4Suno API生成内容,企业月均支…...
设计模式简述(二)单例模式
单例模式 描述基本使用防破坏单例饿汉式懒汉式有上限多例 描述 一个类全局只有一个实例,自行实例化并提供给使用。 构造函数私有化是前提 基本使用 防破坏单例 防反射:在构造函数中判断类中实例是否已初始化 private InnerClassSingleton (){if(Inn…...
区块链钱包:与主流钱包APP的区别
前言 在前端开发者速入:DApp中的前端要干些什么?文中我简单讲解了在DApp中前端开发者要干的是什么,本来在接下来的内容中我应该继续讲解在DApp中前端开发者的一系列工作和其他所要用到的技术栈,但是为了方便后续的讲解,我们这里不得不提及一下在区块链中让无数人又爱又恨…...
23种设计模式-行为型模式-中介者
文章目录 简介问题解决代码架构优势 总结 简介 中介者是一种行为设计模式, 能让你减少对象之间混乱无序的依赖关系。该模式会限制对象之间的直接交互,强制让它们通过一个中介者对象进行合作。 问题 假如你有一个创建和修改用户资料的对话框࿰…...
mysql数据库中getshell的方式总结
mysql数据库中getshell的方式总结 MySQL版本大于5.0,MySQL 5.0版本以上会创建日志文件,我们通过修改日志文件的全局变量,就可以GetSHELL,下面这篇文章主要给大家介绍了关于mysql数据库中getshell的方式,需要的朋友可以参考下 outfile和dumpfile写shell 利用条件 …...
神经网络基础
神经网络的基本组成元素 一个神经元: 单层神经网络: 多层神经网络:(前向计算) 为什么要使用激活函数 如果不使用激活函数,每层只对上层的输入进行线性变换,实际这些线性变换可以归为一层即可。…...
Redis的公共操作命令
目录 1.Key操作命令1.1 keys *1.2 exists <key]>1.3 type <key>1.4 del <key>1.5 unlink <key>1.6 ttl <key>1.7 expire <key> <秒数>1.8 move <key> <index> 2.库操作命令2.1 select <index>2.2 dbsize2.3 flush…...
Redash:一个开源的数据查询与可视化工具
Redash 是一款免费开源的数据可视化与协作工具,可以帮助用户快速连接数据源、编写查询、生成图表并构建交互式仪表盘。它简化了数据探索和共享的过程,尤其适合需要团队协作的数据分析场景。 数据源 Redash 支持各种 SQL、NoSQL、大数据和 API 数据源&am…...
Transformer+BO-SVM多变量时间序列预测(Matlab)
TransformerBO-SVM多变量时间序列预测(Matlab) 目录 TransformerBO-SVM多变量时间序列预测(Matlab)效果一览基本介绍程序设计参考资料 效果一览 基本介绍 本期推出一期高创新模型,基于Transformer提取时序特征后输入S…...
【Redis】服务端高并发分布式结构
一、认识Redis Redis是开源的,用于存储数据的,在内存中存储数据。Redis被用作数据库,缓存,消息队列等一些作用。 在数据库的学习中,我们学习了MySQL,但是MySQL有一些大问题:其访问速度比较慢。在…...
C和C++(list)的链表初步
链表是构建其他复杂数据结构的基础,如栈、队列、图和哈希表等。通过对链表进行适当的扩展和修改,可以实现这些数据结构的功能。想学算法,数据结构,不会链表是万万不行的。这篇笔记是一名小白在学习时整理的。 C语言 链表部分 …...
第九课:LoRA模型的原理及应用
文章目录 Part.01 3种LoRA的使用方式Part.02 5种LoRA的应用方向Part.01 3种LoRA的使用方式 LoRA能够在家用级设备上训练,实现对Checkpoint在某些方面的微调使用Lora的三种方式:放置Lora模型到目录中,然后作为提示词的一部分输入。点击生成按钮下面的“画”,然后打开Additio…...
make_01_Program_07_$@ $^ 是什么含义
在 Makefile 中,$ 和 $^ 是自动变量,用于表示目标和依赖关系的特殊含义。它们常用于规则中的命令部分,以便简化 Makefile 的书写。 自动变量解析 $: $ 表示当前规则的目标文件名。换句话说,在一个特定的规则中&#x…...
CKPT文件是什么?
检查点(Checkpoint,简称ckpt)是一种用于记录系统状态或数据变化的技术,广泛应用于数据库管理、机器学习模型训练、并行计算以及网络安全等领域。以下将详细介绍不同领域中ckpt检查点的定义、功能和应用场景。 数据库中的ckpt检查点…...