😎【博客主页:你最爱的小傻瓜】😎

🤔【本文内容:C++ vector 😍  】🤔

--------------------------------------------------------------------------------------------------------------------------------

在 C++ 的典籍长廊中,vector 恰似一位博闻强识的书吏。

它以动态舒展的卷轴承载万千元素,不必忧虑篇幅局促 —— 新添内容时,只需轻挥 push_back 的笔墨,便有宣纸自然延展开来。那些沉睡的元素,如经卷中的字句般静候翻阅,用下标轻轻一点,便能唤醒某页某行的深意。

当你执迭代器之笔漫溯其间,如同指尖抚过古籍的竹笺,每一个元素都按序铺陈,静待细品。若想为这卷帙重整篇章,sort 函数便是最好的校勘师,转瞬便能将凌乱的字句编排得井然有序。

无论是数字的珠玑、字符的墨韵,还是自定义对象的锦绣文章,它都能妥帖收纳,恰似一座随需而建的藏书楼,让数据在其间各安其位,静待编程者翻阅取用。

---------------------------------------------------------------------------------------------------------------------------------在开始学习vector容器前,我们先要了解vector的概念:

( 一 ) vector 的介绍:

1. vector 是可变大小数组的序列容器。它具备数组采用的连续存储空间来存储元素的特性,又优化了数组无法动态改变大小的缺点。而对于vector容器的动态分配数组,是一个有利也有弊的处理方式当我们插入新的数据时,且vector容器需要增加存储空间。这时他会重新开一个适合的数组来将原先的数组的数据赋值过去而这里就有一个耗时的缺点为了应对它,不同的库会有不同的分配空间策略:(核心)

分配一些额外的空间以适应可能的增长,因为存储空间比实际需要的存储空间更大。但具体是由实现者来进行怎样分配空间的。
无论如何,重新分配都应该是对数增长的间隔大小(比如每次扩容为原来的 1.5 倍或 2 倍),以至于在末尾插入一个元素的时候是在常数时间的复杂度完成的,不会触发重新分配空间。
vector容器与其它动态序列容器相比(deque, list and forward_list),其最大优势:访问元素的时候更加高效,在末尾添加和删除元素相对高效。相对的,vector不在末尾的删除和插入操作效率更低。 
还有:由于存储空间连续​​​​​​​  vector 的迭代器和引用,支持随机访问(像数组一样直接跳着访问元素)、更稳定(只要不扩容,引用和迭代器基本不会失效),而且在遍历、随机取元素时速度更快;而list and forward_list 的迭代器只能逐个挪,还容易失效,效率也低。

(二)vector的使用

1.构造:

接口说明
vector ()(重点)无参构造
vector (size_type n, const value_type& val = value_type())构造并初始化 n 个 val
vector (const vector& x);(重点)拷贝构造
vector (InputIterator first, InputIterator last);使用迭代器进行初始化构造

无参构造:就是构造一个空vector

//vector()
vector<int>v;

构造并初始化 n 个 val:

//vector (size_type n, const value_type& val = value_type())
vector<int>v(10,1); 

拷贝构造:用已存在的 vector 来构造新的 vector

// vector (const vector& x);
vector<int> v1{1, 2, 3};
vector<int> v2(v1);

使用迭代器进行初始化构造:利用其他容器(或 vector 自身部分范围)的迭代器来构造新 vector

// vector (InputIterator first, InputIterator last);
// 示例1:用数组迭代器构造
int arr[] = {1, 2, 3, 4, 5};
vector<int> v(arr, arr + 5);
// 示例2:用vector的部分迭代器构造
vector<int> v1{1, 2, 3, 4, 5};
vector<int> v2(v1.begin() + 1, v1.end() - 1);

2.迭代器访问:

iterator 的使用接口说明
begin + end(重点)获取第一个数据位置的 iterator/const_iterator,获取最后一个数据的下一个位置的 iterator/const_iterator
rbegin + rend获取最后一个数据位置的 reverse_iterator,获取第一个数据前一个位置的 reverse_iterator

begin 用于获取容器中第一个数据位置的 iterator(可修改元素)或 const_iterator(不可修改元素,只读);end 用于获取容器中最后一个数据的下一个位置的 iterator 或 const_iterator

// begin + end(iterator 示例,可修改元素)
vector<int> v{1, 2, 3, 4, 5};
for (vector<int>::iterator it = v.begin(); it != v.end(); ++it) {*it *= 2; // 修改元素值
}
// begin + end(const_iterator 示例,只读)
vector<int> v1{1, 2, 3, 4, 5};
for (vector<int>::const_iterator cit = v1.cbegin(); cit != v1.cend(); ++cit) {cout << *cit << " "; // 只能读取元素值,不可修改
}

rbegin 用于获取容器中最后一个数据位置的 reverse_iterator(反向迭代器,从后往前遍历的起始位置);rend 用于获取容器中第一个数据前一个位置的 reverse_iterator(反向遍历的结束位置)。

// rbegin + rend(reverse_iterator 示例)
vector<int> v{1, 2, 3, 4, 5};
for (vector<int>::reverse_iterator rit = v.rbegin(); rit != v.rend(); ++rit) {cout << *rit << " "; // 从后往前遍历并输出元素
}

3.容量的访问:

容量空间接口说明
size获取数据个数
capacity获取容量大小
empty判断是否为空
resize(重点)改变 vector 的 size
reserve(重点)改变 vector 的 capacity

size 用于获取 vector 中数据的个数。

vector<int> v{1, 2, 3, 4, 5};
cout << "数据个数:" << v.size() << endl;

capacity 用于获取 vector 的容量大小(即当前为 vector 分配的存储空间能容纳的元素个数)。

vector<int> v;
v.push_back(1);
cout << "容量大小:" << v.capacity() << endl;

empty 用于判断 vector 是否为空(即是否没有元素)

vector<int> v;
if (v.empty()) {cout << "vector 为空" << endl;
} else {cout << "vector 不为空" << endl;
}

resize 用于改变 vector 的 size(元素个数)。若新 size 大于原 size,会用指定值(若未指定则用默认构造值)填充新增位置;若新 size 小于原 size,则会删除多余元素。

// resize 示例1:新 size 大于原 size,用默认值填充
vector<int> v{1, 2, 3};
v.resize(5);
for (int num : v) {cout << num << " "; // 输出:1 2 3 0 0
}
cout << endl;
// resize 示例2:新 size 大于原 size,用指定值填充
vector<int> v1{1, 2, 3};
v1.resize(5, 10);
for (int num : v1) {cout << num << " "; // 输出:1 2 3 10 10
}
cout << endl;
// resize 示例3:新 size 小于原 size
vector<int> v2{1, 2, 3, 4, 5};
v2.resize(3);
for (int num : v2) {cout << num << " "; // 输出:1 2 3
}

reserve 用于改变 vector 的 capacity(容量),提前为 vector 分配足够的存储空间,避免后续多次扩容带来的性能开销。

vector<int> v;
v.reserve(10); // 提前分配能容纳 10 个元素的空间
cout << "容量大小:" << v.capacity() << endl;

4.增删查改:

vector 增删查改接口说明
push_back(重点)尾插
pop_back(重点)尾删
find查找。(注意这个是算法模块实现,不是 vector 的成员接口)
insert在 position 之前插入 val
erase删除 position 位置的数据
swap交换两个 vector 的数据空间
operator [](重点)像数组一样访问

push_back 用于在 vector 末尾插入元素(尾插)。

vector<int> v;
v.push_back(1);
v.push_back(2);
// 此时v中的元素为[1, 2]

pop_back 用于删除 vector 末尾的元素(尾删)

vector<int> v{1, 2, 3};
v.pop_back();
// 此时v中的元素为[1, 2]

insert 用于在指定位置(position 迭代器指向的位置之前)插入元素

vector<int> v{1, 3, 4};
// 在第二个元素位置(值为3的位置)前插入2
v.insert(v.begin() + 1, 2);
// 此时v中的元素为[1, 2, 3, 4]

erase 用于删除指定位置(position 迭代器指向的位置)的元素。

vector<int> v{1, 2, 3, 4};
// 删除第三个元素(值为3的元素)
v.erase(v.begin() + 2);
// 此时v中的元素为[1, 2, 4]

operator[] 用于像访问数组元素一样,通过下标访问 vector 中的元素。

vector<int> v{10, 20, 30};
cout << v[0] << " " << v[1] << " " << v[2] << endl; // 输出:10 20 30
v[1] = 25;
cout << v[1] << endl; // 输出:25

(三) vector 模拟实现(代码里面的注释很重要):

开始了解vector:对于vector类,由于要储存不同种类的数据,那么就需要模板来实现了。

namespace xin
{template<class T >class vector{public:typedef T* iterator;            //常规的迭代器,可以读写typedef const T* const_iterator;//const修饰的迭代器,只能读无法写。//模拟实现    private:iterator _start = nullptr;      //指向vector开头的数据iterator _finish = nullptr;     //指向vector最后一个的数据iterator endofstorage = nullptr;//指向vector存储空间最后的位置};
};

我们先是像实现string类构建一个自己的命名空间域。接着写出模板,然后写迭代器(这里是指针),再在类里面的private里面构建成员变量。(这些就是我们先要了解的)

下一步完善vector:

1.空vector的构造:

vector()
{}

2.析构函数:

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

说到底:析构函数就是将之前申请的空间还给操作系统,然后将之前所用到的指针赋值为空,防止野指针的出现。

3.迭代器:

//迭代器的访问方式:
iterator begin()//可读写,且在函数内部能进行修改所属对象。
{return _start;//返回头指针
}
iterator end()
{return _finish;//返回指向vector最后一个的数据的指针
}
const_iterator begin()const//1.只能读取容器中的元素,不能修改元素的值。2.在该函数内部,不能修改所属对象
{return _start;
}
const_iterator end()const
{return _finish;
}

通过迭代器来去访问vector数据的开头的指针,和指向vector最后一个的数据的指针。

迭代器这个访问方式是每个容器都有的相同访问方式。优势:

1.统一接口

2.抽象底层实现

3.便于算法复用

4.支持范围操作

4.size:

size_t size()const //加这个const是为了在该函数内部,不能修改所属对象。
{return _finish - _start;//这里用指针减指针的方式来计算数据的个数
}

计算vector里面存储的数据大小。

5.capacity:

size_t capacity()const//加这个const是为了在该函数内部,不能修改所属对象
{return _endofstorage - _start;//这里用指针减指针的方式来计算存储空间的大小
}

计算vector 存储空间大小。

6.reserve:

void reserve(size_t n)
{if (n > capacity())//扩容的条件。{size_t sz = size();//计算原数组的数据大小T* tmp = new T[n];//申请新的数组//memcpy(tmp, _start, sizeof(T) * sz);//这里为什么不用 memcpy来将原先的数据赋值给新建的数组,是因为深拷贝的问题 兼容自定义类型的深拷贝需求 for (int i = 0;i < sz;i++)//for循环的更安全可靠。{tmp[i] = _start[i];}delete[] _start;//将这些指针给调到适合的位置。_start = tmp;_finish = _start + sz;_endofstorage = _start + n;}
}

保留:为vector预留空间。

对于不用memcpy是因为:

当拷贝 string 这类包含指针成员的自定义类型时,若使用 memcpy 进行拷贝,会引发浅拷贝问题

自定义类型(如 std::string)一般由栈上的指针(用于管理内部字符数组等)和指针指向的堆内存(存储实际数据)组成。memcpy 只是二进制层面复制栈上的指针值,不会复制指针指向的堆内存,这会导致:

  • 两个对象的指针成员指向同一块堆内存(浅拷贝);
  • 析构时对同一块内存多次释放(double free),造成程序崩溃;
  • 一个对象修改数据会影响另一个对象,破坏拷贝独立性。

对于 vector,本身是深拷贝,但当 vector 中存储的是 string 数组或其他含指针成员的自定义类型数组时,若用 memcpy 处理,也会因浅拷贝而出错。

7.resize(调整容器大小)

void resize(size_t n, const T& val = T())//T()是指确保生成一个符合 “默认初始化” 语义的 T 类型对象。
{if (n < size()){_finish = _start + n;//(比size()小时,我们直接截取到我们想要的n个数据)}else{reserve(n);while (_finish != _start + n)//比size()大时,我们就重新申请空间,在将后面的空间赋值T(){*_finish = val;++finish;}}
}
这里要补充一个概念:

在 C++ 中,T() 是一种值初始化语法,用于创建 T 类型的默认构造对象,这是由 C++ 标准规定的语法规则:

  1. 对于内置类型(如 int、double 等)
    T() 会生成该类型的 “零值”。例如:

    • int() 等价于 0
    • double() 等价于 0.0
    • 指针类型 int*() 等价于 nullptr
  2. 对于自定义类型(类 / 结构体)
    T() 会显式调用该类型的默认构造函数(即无参数的构造函数)。如果类中没有定义任何构造函数,编译器会自动生成一个默认构造函数,此时 T() 会使用这个编译器生成的版本。

8.insert(pos的位置插入):

iterator insert(iterator pos, const T& x)
{assert(pos >= _start && pos <= _finish);//判断pos是否在数组内if (_finish == _endofstorage)//判断两种请况:空 ,两个不同概念的尾部指针重合.以及处理方式。{size_t len = pos - _start;//为处理迭代器失效而留下的坐标。size_t new_capacity = capacity() == 0 ? 4 : capacity() * 2;reserve(new_capacity);pos = _start + len;//解决迭代器失效的问题}iterator end = _finish - 1;//这里减一是为了更好的访问和挪移数据。while (end > pos)//往后挪移{*(end + 1) = *end;--end;}//插入数据*pos = x;++_finish;return pos;
}

pos位置的插入。这里有个迭代器失效的问题我们要注意一下,并去解决。

迭代器失效的本质

迭代器底层多是指针或指针封装(如 vector 迭代器类似原生指针 T*)。迭代器失效,是指其底层对应指针指向的空间被销毁,继续使用会导致程序崩溃。解决扩容导致内部迭代器失效的关键逻辑通过 size_t len = pos - _start; 和 pos = _start + len; 两行代码解决:

  • 记录偏移量len = pos - _start 计算插入位置 pos 相对于容器起始地址 _start 的偏移量(相对位置,不受内存释放 / 移动影响)。
  • 重定位迭代器:扩容时旧内存释放、_start 指向新内存,利用 pos = _start + len 重新确定 pos 在新内存中的位置。

避免失效的原因

  • 绝对地址(如旧内存地址)会因内存释放失效,但偏移量是相对位置,不受内存块移动影响。
  • 即便 pos 原本指向特殊地址,偏移量计算仍能准确定位新内存中的位置。

关于 pos 为 0 的小知识点

有效内存空间的地址不会是 0,0 是操作系统预留的 “无效地址”(对应 nullptr),不会分配给用户程序正常内存空间。所以指向容器有效范围的迭代器,底层指针绝不可能为 0,不存在 “pos 为 0” 的隐患。

返回值 pos 的作用

用于解决外部迭代器失效,防止类似 “先获取迭代器,再执行插入操作后,原迭代器因容器内部变化而失效” 的问题

外部迭代器失效问题

外部代码若保存插入前的迭代器,当插入触发 vector 扩容时,旧内存被释放,该迭代器会指向无效地址(失效)。若继续使用失效迭代器(如 it += 10),会访问已释放内存,引发未定义行为(如程序崩溃),这类操作属于高危行为。

解决方法

insert 操作会返回新的有效迭代器,应改用该返回值来操作,而非使用插入前可能失效的旧迭代器。示例代码:

 
auto it = vec.begin() + 1;
// 插入后,it可能失效,改用返回的新迭代器
auto new_it = vec.insert(it, 200);
*new_it += 10; // 安全,new_it指向新内存中正确位置

insert 返回值的意义

提供扩容后已重定位的有效迭代器,替代可能失效的旧迭代器。

迭代器失效总结

  • 内部防失效:通过偏移量 len 记录相对位置,确保护容后 pos 在新内存中正确定位,保证 insert 内部逻辑正常。
  • 外部防失效insert 返回更新后的有效迭代器,提醒用户放弃使用可能失效的旧迭代器,改用新迭代器。

9.push_back(尾插):

void push_back(const T& x)//尾插
{/*if (_finish == _endofstorage)//判断数据情况,如容器是否为空,为空的处理方式,不为空的处理方式。{size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;reserve(newcapacity);}//尾插。*_finish = x;++_finish;*/insert(end(),x);
}

10.构造函数和拷贝构造函数:

vector(size_t n, const T& val = T())//这里的构造就像当于截取,但因为是初始化,刚开始_start = _finish,所以他是从开头赋值初始化
{resize(n, val);
}
vector(int n, const T& val = T())//这个是因为当我们在传参的时有我们传<int int>时,编译器会更具“精确匹配优先于需要隐式转换的匹配”,会到迭代器哪里但int又不是迭代器,所以会报错。
{resize(n, val);
}
template<class InputIterator>
vector(InputIterator first, InputIterator last)//对于迭代器的初始化,而这里为什么用push_back呢,这是因为当我们不知道你要存储的数据大小时,我们只能一个一个填下去,尽管他是会遇到vector的这一缺点但还是要这样做,因为不知道有大小。
{while (first != last){push_back(*first);//因为刚开始_start = _finish。++first;}
}vector(const vector<T>& v)
{_start = new T[v.capacity()];//拷贝构造,申请空间//memcpy(_start,v._start,sizeof(T)*v.size);//是因为不适合自定义类形,浅拷贝与深拷贝的问题for (size_t i = 0;i < v.size();i++){_start[i] = v._start[i];}//为其赋值。_finish = _start + v.size();_endofstorage = _start + v.capacity();
}
//旧版
/*vector(const vector<T>& v):_start(nullptr), _finish(nullptr), _endofstorage(nullptr){reserve(v.capacity());for (auto e : v){push_back(e);}}*/

这里有一个概念要拿出来讲:

那便是精确匹配优先于需要隐式转换的匹配

对于:

vector<int>v(10,1);

你认为会用哪构造(如果没有int这个显示的),是size_t, 还模板的那个。他会是模板那个因为编译器会把10,1都看成int,满足InputIterator first, InputIterator last的参数类型要求(两个参数类型相同)因此它符合精确匹配优先于需要隐式转换的匹配。而int —》size_t这个隐式转换优先级低。

解决方法:

核心就是不让它隐式转换。

1.外:

vector <int> v(10u, 1);

2.内:

vector(int n, const T& value = T())
{resize(n, value);
}

11.erase(删除pos位置数据):

iterator erase(iterator pos)
{assert(pos >= _start && pos <= _finish);//判断pos是否在数组内iterator it = pos + 1;//这里加一是为了更好的访问和挪移数据while (it != _finish)//往前覆盖{*(it - 1) = *it;++it;}--_finish;return pos;
}

迭代器失效的定义与标准规定

对 vector 执行 erase(pos) 后,被删除元素的迭代器 pos 及其之后的所有迭代器都会失效。此时对失效迭代器进行解引用(如 *it)或递增(如 ++it)操作,属于未定义行为(标准不规定执行结果,编译器可自由处理,可能崩溃、输出错误结果甚至 “看似正常运行”)。

不同平台的表现差异

  • VS:对迭代器有效性做严格额外检查,若检测到使用失效迭代器,会直接报错或崩溃,能提前暴露问题。
  • Linux:默认不做强制检查,失效迭代器可能恰好指向未回收内存,导致程序 “看似能运行”,但这是巧合,本质仍为错误代码。

错误代码示例与问题

 // 错误示例:删除所有偶数for (auto it = v.begin(); it != v.end(); ++it) {if (*it % 2 == 0) {v.erase(it); // 删除后,it已失效// 此处it指向被删除元素的下一个位置,但标准未定义其有效性}}// 输出结果可能异常(如跳过元素、循环提前结束等)for (int num : v) {cout << num << " ";}return 0;

如删除偶数的代码(erase 后直接 ++it),会因迭代器失效出现逻辑错误(如跳过元素),且删除本质是移动数据覆盖待删数据,会导致 finish 前移,后续 it 可能永远不等于 end,循环无法正确结束。

看似 “可行” 代码的隐患

即使在 Linux 下 “通过测试” 的代码,也存在问题:

  • 不符合 C++ 标准,erase(it) 后 it 已失效,后续 it != v.end() 判断也可能出错。
  • 移植性极差,换用 VS 等编译器或调试模式会暴露崩溃 / 逻辑错误。
  • 若触发扩容(内存重分配),失效 it 指向旧内存块,操作会导致严重错误。

正确做法

vector 删除元素时,必须通过 erase 的返回值更新迭代器,这是唯一能保证跨平台一致性和程序正确性的做法。其他 “看似可行” 的写法,本质是依赖未定义行为的侥幸,隐藏着难以排查的风险。判断代码正确性,应看是否符合语言标准,而非 “在某平台能运行”。

12.尾删:

void pop_back()
{erase(--end());
}

直接用erase任意位置删除,如果不想用erase也可以在erase里面的代码里找删除的原理再套用在尾部。

13.重载:

void swap(vector<T>& v)
{std::swap(_start, v._start);std::swap(_finish, v._finish);std::swap(_endofstorage, v._endofstorage);
}// v1 = v2
vector<T>& operator=(vector<T> v)
{swap(v);return *this;
}
T& operator[](size_t pos)
{assert(pos < size());return _start[pos];
}const T& operator[](size_t pos) const
{assert(pos < size());return _start[pos];
}

这里的重载都与strin类里面的类似。主要是swap这个交换,以及对_start指针的运用。

完整代码:

#include<assert.h>
#include<string>
#include<iostream>
using namespace std;
namespace xin
{template<class T >class vector{public:typedef T* iterator;            //常规的迭代器,可以读写  typedef const T* const_iterator;//const修饰的迭代器,只能读无法写。//析构函数:~vector(){if (_start){delete[] _start; //从头指针开始找 释放空间_start = _finish = _endofstorage = nullptr; //将指针赋值为空}}//迭代器的访问方式:iterator begin()//可读写,且在函数内部能进行修改所属对象。{return _start;//返回头指针}iterator end()//可读写,且在函数内部能进行修改所属对象。{return _finish;//返回指向vector最后一个的数据的指针}const_iterator begin()const//1.只能读取容器中的元素,不能修改元素的值。2.在该函数内部,不能修改所属对象{return _start;}const_iterator end()const//1.只能读取容器中的元素,不能修改元素的值。2.在该函数内部,不能修改所属对象{return _finish;}size_t size()const //加这个const是为了在该函数内部,不能修改所属对象。{return _finish - _start;//这里用指针减指针的方式来计算数据的个数}size_t capacity()const//加这个const是为了在该函数内部,不能修改所属对象{return _endofstorage - _start;//这里用指针减指针的方式来计算存储空间的大小}void reserve(size_t n){if (n > capacity())//扩容的条件。{size_t sz = size();//计算原数组的数据大小T* tmp = new T[n];//申请新的数组//memcpy(tmp, _start, sizeof(T) * sz);//这里为什么不用 memcpy来将原先的数据赋值给新建的数组,是因为深拷贝的问题 兼容自定义类型的深拷贝需求 for (int i = 0;i < sz;i++)//for循环的更安全可靠。{tmp[i] = _start[i];}delete[] _start;//将这些指针给调到适合的位置。_start = tmp;_finish = _start + sz;_endofstorage = _start + n;}}void resize(size_t n, const T& val = T())//T()是指确保生成一个符合 “默认初始化” 语义的 T 类型对象。{if (n < size()){_finish = _start + n;//(比size()小时,我们直接截取到我们想要的n个数据)}else{reserve(n);while (_finish != _start + n)//比size()大时,我们就重新申请空间,在将后面的空间赋值T(){*_finish = val;++finish;}}}iterator insert(iterator pos, const T& x){assert(pos >= _start && pos <= _finish);//判断pos是否在数组内if (_finish == _endofstorage)//判断两种请况:空 ,两个不同概念的尾部指针重合.以及处理方式。{size_t len = pos - _start;//为处理迭代器失效而留下的坐标。size_t new_capacity = capacity() == 0 ? 4 : capacity() * 2;reserve(new_capacity);pos = _start + len;//解决迭代器失效的问题}iterator end = _finish - 1;//这里减一是为了更好的访问和挪移数据。while (end > pos)//往后挪移{*(end + 1) = *end;--end;}//插入数据*pos = x;++_finish;return pos;}void push_back(const T& x)//尾插{if (_finish == _endofstorage)//判断数据情况,如容器是否为空,为空的处理方式,不为空的处理方式。{size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;reserve(newcapacity);}//尾插。*_finish = x;++_finish;insert(_finish, x);}vector(size_t n, const T& val = T())//这里的构造就像当于截取,但因为是初始化,刚开始_start = _finish,所以他是从开头赋值初始化{resize(n, val);}vector(int n, const T& val = T())//这个是因为当我们在传参的时有我们传<int int>时,编译器会更具“精确匹配优先于需要隐式转换的匹配”,会到迭代器哪里但int又不是迭代器,所以会报错。{resize(n, val);}template<class InputIterator>vector(InputIterator first, InputIterator last)//对于迭代器的初始化,而这里为什么用push_back呢,这是因为当我们不知道你要存储的数据大小时,我们只能一个一个填下去,尽管他是会遇到vector的这一缺点但还是要这样做,因为不知道有大小。{while (first != last){push_back(*first);//因为刚开始_start = _finish。++first;}}vector(const vector<T>& v){_start = newT[v.capacity];//拷贝构造,申请空间//memcpy(_start,v._start,sizeof(T)*v.size);//是因为不适合自定义类形for (size_t i = 0;i < v.size;i++){_start[i] = v._start[i];}//为其赋值。_finish = _start + v.size;_endofstorage = _start + v.capacity;}iterator erase(iterator pos){assert(pos >= _start && pos <= _finish);//判断pos是否在数组内iterator it = pos + 1;//这里加一是为了更好的访问和挪移数据while (it != _finish)//往前覆盖{*(it - 1) = *it;++it;}--_finish;return pos;}void swap(const vector<T>& v){_start = v._start;_finish = v._finish;_endofstorage = v._endofstorage;}vector<T>& operator=(vector<T>& v){swap(v);return *this;}T& operator [](size_t pos){assert(pos < size());return _start[pos];}const T& operator [](size_t pos)const{assert(pos < size());return _start[pos];}private:iterator _start = nullptr;      //指向vector开头的数据iterator _finish = nullptr;     //指向vector最后一个的数据iterator _endofstorage = nullptr;//指向vector存储空间最后的位置};
};

❤️总结

相信坚持下来的你一定有了满满的收获。那么也请老铁们多多支持一下,为爱博,点点举报,偶布,是点点关注,收藏,点赞。❤️

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

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

相关文章

NAS Docker 安装N8N

NAS Docker 安装N8Ndocker 操作中文版使用 Docker Compose&#xff08;更易于管理&#xff09;创建一个 docker-compose.yml 文件&#xff0c;内容如下&#xff1a;yaml version: 3services:n8n:image: n8nio/n8n:latestcontainer_name: n8nrestart: unless-stoppedports:- &q…

Node.js汉字转拼音指南:pinyin-pro全解析

pinyin-pro 工具库简介核心功能&#xff1a;汉字转拼音、多音字处理、音调控制、格式定制等性能特点&#xff1a;高效、轻量级、支持多种拼音风格应用场景&#xff1a;搜索优化、数据排序、中文输入法等环境准备与安装Node.js npm 或 yarn 安装 pinyin-pronpm install pinyin-p…

UART-TCP双向桥接服务

UART-TCP双向桥接服务是一种将串口&#xff08;UART&#xff09;通信与TCP/IP网络通信相互转换的技术服务&#xff0c;其核心功能是实现两种不同协议之间的数据透明传输。1. 基本概念UART&#xff08;串口&#xff09;&#xff1a;硬件设备的传统通信接口&#xff0c;常见于嵌入…

江协科技STM32学习笔记补充之001。为什么C语言在对STM32编程过程中的二进制要用十六进制来进行读写。而不能直接用二进制来进行读写。

下面给你一个“为什么嵌入式 C&#xff08;如 STM32&#xff09;普遍用十六进制而不是二进制来读写寄存器/地址”的系统性分析。核心观点&#xff1a;十六进制是对底层位模式更高效、更可靠的“人类可读编码”&#xff0c;与硬件资料、编译器和调试器生态形成了标准化协同。1&a…

从 “对话” 到 “共创”:生成式 AI 如何重塑内容创作全流程,普通人也能掌握的高效工具指南

一、引言&#xff1a;内容创作的 “AI 范式转移”—— 从单向输出到双向共创​传统内容创作痛点&#xff1a;灵感枯竭、流程繁琐&#xff08;选题 - 调研 - 初稿 - 修改 - 定稿耗时久&#xff09;、专业门槛高&#xff08;如设计需掌握 PS、写作需深厚文字功底&#xff09;​生…

函数、数组与 grep + 正则表达式的 Linux Shell 编程进阶指南

文章目录1.函数相关2.数组相关3.正则表达式与grep根据你提供的内容&#xff0c;我整理了一份关于Shell脚本中函数、数组和正则表达式的简明参考&#xff1a; 1.函数相关 函数调用&#xff1a; 直接使用函数名调用&#xff1a;函数名 参数传递&#xff1a; 函数内接收参数&…

nginx-realip问题解决方案

nginx-realip问题解决方案一、配置真实ip解析二、日志中记录真实 IP三、在日志中验证一、配置真实ip解析 让backend server知道前端是谁来访问的&#xff0c;知道他们的ip地址 LB在转发数据包的时候&#xff0c;在http请求报文里增加一个字段&#xff0c;携带user的ip地址&am…

Kafka入门指南:从安装到集群部署

一、Kafka 基础与系统要求 1.1 核心概念 Broker&#xff1a;Kafka 服务器节点&#xff0c;负责存储消息和处理客户端请求 Topic&#xff1a;消息分类的逻辑容器&#xff0c;每条消息需指定发送到某个 Topic Partition&#xff1a;Topic 的物理分片&#xff0c;可分布式存储…

20250828在荣品RD-RK3588-MID开发板的Android13系统下适配Bainianxing的GPS模块BU-16M10

20250828在荣品RD-RK3588-MID开发板的Android13系统下适配Bainianxing的GPS模块BU-16M10 2025/8/29 9:50荣品RD-RK3588-MID开发板。适配GPS 38400bps 需要配置波特率吗&#xff1f;一般是 9600这边使用的泰斗 你要适配新的gps模块&#xff1f;规格书&#xff1a;Baud rate 3840…

对部分国家(地区)出口商品类章金额数据库

一、数据库简介【艾思产研数据平台】对部分国家(地区)出口商品类章金额数据库&#xff0c;收录了2015年02月 - 2025年5月的信息&#xff0c;共计49万余条数据&#xff0c;整理出7个常用字段内容。更新频率为月更。字段内容年月、类章、国家、国家id、所属分类、月出口商品类章金…

STM32——中断

总&#xff1a;STM32——学习总纲 一、什么是中断 1.1 作用与意义 1.2 STM32 GPIO 外部中断简图 二、NVIC 2.1 NVIC 基本概念 Nested vectored interrupt controller&#xff0c;嵌套向量中断控制器&#xff0c;属于内核&#xff08;M3、M4、M7&#xff09; 用不到很多的优先…

DVWA靶场通关笔记-Weak Session IDs (Impossible级别)

目录 一、Session ID 二、源码分析 1、index.php 2、impossible.php 三、Weak Session IDs安全级别对比 四、impossible防范方法分析 1、高随机性会话 ID 生成 2、严格的 Cookie 作用域限制 3、安全的传输与存储控制期 本系列为通过《DVWA靶场通关笔记》的Weak Sessio…

SyncBack 备份同步软件: 使用 FTPS、SFTP 和 HTTPS 安全加密传输文件

传输加密是使用安全连接在网络中传输数据&#xff08;例如文件&#xff09;的过程。TLS&#xff08;传输层安全&#xff09;、SSL&#xff08;安全套接字层&#xff09;、SSH&#xff08;安全套接字外壳&#xff09;、HTTPS&#xff08;基于 SSL/TLS 的超文本传输协议&#xff…

保健品跨境电商:如何筑牢产品质量与安全防线?

保健品跨境电商&#xff1a;如何筑牢产品质量与安全防线&#xff1f;在保健品跨境电商领域&#xff0c;“质量与安全”是消费者信任的基石&#xff0c;也是品牌长期发展的生命线。从海外工厂生产到国内消费者手中&#xff0c;产品需经历“跨国运输、清关核验、仓储配送”多环节…

手把手教你搭建 UDP 多人聊天室(附完整源码)

一、项目介绍 本文将分享一个基于 UDP 协议的简易多人聊天室项目&#xff0c;包含服务器端和客户端的完整实现。该聊天室支持多客户端同时连接&#xff0c;能实现消息群发、用户加入 / 退出通知等核心功能&#xff0c;适合作为网络编程入门实践案例。项目采用 C 语言开发…

Vue基础知识-使用监视属性watch和计算属性computed实现列表过滤+排序

一、完整源码<!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Document</title><script src…

自动化运维-ansible中的管理机密

自动化运维-ansible中的管理机密 一、Ansible Vault 在自动化配置管理中&#xff0c;直接以纯文本形式存储密码、API密钥、证书等敏感信息是极大的安全漏洞。Ansible Vault 正是为了解决这一问题而设计的核心功能 Ansible Vault 是 Ansible 的一个核心功能&#xff0c;它允许用…

UFUNCTION C++ 的再次理解

一.UFUNCTION 格式和属性也比较像&#xff0c;两部分 函数说明符&#xff0c;和元数据说明符UFUNCTION不仅能 控制对蓝图公开&#xff0c;还能与 绑定委托&#xff0c;用户输入,网络回调功能相关联&#xff0c;而且还能创建自己控制带命令二.函数说明符控制 &#xff0c;函数在…

《论文阅读》从心到词:通过综合比喻语言和语义上下文信号产生同理心反应 2025 ACL findings

《论文阅读》从心到词:通过综合比喻语言和语义上下文信号产生同理心反应 2025 ACL findings 前言 创新点 形象语言 (Figurative Language) 语义上下文信号(Semantic Context Signals) 模型架构 情绪原因标注 形象语言元数据获取 共情回复生成 实验结果 总结 趋势 前言 亲…

MySQL内置的各种单行函数

精选专栏链接 &#x1f517; MySQL技术笔记专栏Redis技术笔记专栏大模型搭建专栏Python学习笔记专栏深度学习算法专栏 欢迎订阅&#xff0c;点赞&#xff0b;关注&#xff0c;每日精进1%&#xff0c;与百万开发者共攀技术珠峰 更多内容持续更新中&#xff01;希望能给大家带来…