C++奇迹之旅:手写vector模拟实现与你探索vector 容器的核心机制与使用技巧
文章目录
- 📝基本框架
- 🌠 构造和销毁
- 🌉==vector()==
- 🌉==vector(const vector& v)==
- 🌉==vector(size_t n, const T& value = T())==
- 🌉==赋值拷贝构造:vector<T>& operator=(vector<T> v)==
- 🌉==模版函数实现区间初始化 vector(InputIterator first, InputIterator last)==
- 🌉==函数模板的定义 InputIterator==
- 🌉==列表初始化区间vector(initializer_list<T> il)==
- 🌉~vector()
- 🌠begin与end
- 🌉reserve
- 函数实现
- 🌉resize
- 🌉insert与erase
- 🌉push_back与pop_back
- 🌠访问元素
- 🌠全代码
- 🌉test.cpp
- 🌉vector.h
- 🚩总结
📝基本框架
我们先定义自己的命名空间俩封装自定义的vector
类,这样可以避免与标准库中的 vector
发生命名冲突。随即,我们定义模版类vector
,三个成员变量都是迭代器,而vector
迭代器又是原生指针,所以我们将指针取别名为iterator
框架代码:
namespace self
{template<class T>class vector{public:// 定义类型别名 iterator 和 const_iterator,用于表示指向元素的指针类型typedef T* iterator;typedef const T* const_iterator;private:private:iterator _start; // 指向动态数组的起始位置iterator _finish; // 指向当前数组中最后一个元素的下一个位置iterator _end_of_storage; // 指向已分配内存的末尾(容量的终点)};
}
理解代码并添加注释如下:
namespace self
{template<class T>class vector{public:// 定义迭代器类型typedef T* iterator;typedef const T* const_iterator;private:// 数据成员iterator _start; // 指向容器中第一个元素的指针iterator _finish; // 指向容器中最后一个元素之后的位置iterator _end_of_storage; // 指向容器所分配的存储空间的尾部// 在这里添加其他成员函数和成员变量};
}
成员变量的的私有数据成员:
iterator _start;
: 这个成员变量保存了一个指向容器中第一个元素的指针。iterator _finish;
: 这个成员变量保存了一个指向容器中最后一个元素之后的位置的指针。iterator _end_of_storage;
: 这个成员变量保存了一个指向容器所分配的存储空间的尾部的指针。
🌠 构造和销毁
🌉vector()
- 完全空值初始化:
vector(): _start(nullptr), _finish(nullptr), _end_of_storage(nullptr)
{}
- 缺省值初始化
vector() {};private:iterator _start = nullptr; // 指向动态数组的起始位置iterator _finish = nullptr; // 指向当前数组中最后一个元素的下一个位置iterator _end_of_storage = nullptr; // 指向已分配内存的末尾(容量的终点)
3.或者我们也可以偷个懒,让编译器自己生成
//强制编译器生成默认构造函数
vector() = default;
🌉vector(const vector& v)
这个构造函数是 vector
类的拷贝构造函数,用于创建一个新的 vector
对象,它是现有 vector
对象的拷贝。
vector(const vector<T>& v)
{reserve(v.size());for (auto e : v){push_back(e);}
}
这是一个拷贝构造函数,它接受一个 const
引用类型的 vector
对象 v
作为参数。这个函数的目的是创建一个新的 vector
对象,并将传入的 vector
对象 v
的内容拷贝到这个新的 vector
对象中。
这个拷贝构造函数的实现逻辑:
-
预留空间:首先调用
reserve()
函数为目标vector
预留足够的空间来存储源vector
的所有元素。这样可以避免在元素插入过程中多次分配内存,提高性能。 -
拷贝元素:通过范围基于
for
循环遍历源vector
中的每个元素,并使用push_back()
将这些元素逐个添加到目标vector
中。
push_back
和reserve()
下文会有讲解。
🌉vector(size_t n, const T& value = T())
重载构造函数,用于创建一个 vector
对象,并将其初始化为 n
个指定值 value
。
vector(size_t n, const T& value = T())
{for (size_t i = 0; i < n; ++i){push_back(value);}
}
-
这是一个带有两个参数的构造函数:
n
:表示vector
中元素的数量。value
:表示每个元素的初始值,默认值为T()
,即T
类型的默认构造值。
-
这个构造函数用于创建一个
vector
对象,并将其初始化为n
个相同的元素,每个元素的值为value
。 -
const T&
表示一个对T
类型的常量引用。使用常量引用可以避免在函数内部修改传入的值,并且通常比传值的方式更加高效,因为避免了不必要的复制操作。 -
value
是参数的名字,它代表了要初始化vector
中每个元素的值。 -
T()
是T
类型的默认构造函数的调用。它创建了一个T
类型的对象,并使用默认构造函数来初始化它。 -
T()
表示调用T
类型的默认构造函数,生成一个T
类型的临时对象。对于内置类型(如int
,double
),这通常是将其初始化为零;对于用户定义的类型(类或结构体),则会调用该类型的默认构造函数。
默认参数的作用:当构造函数被调用而未提供 value
参数时,value
会被初始化为 T()
,即一个 T
类型的默认值。
- 如果提供了
value
参数,那么构造函数会使用提供的值,而不是默认值。
假设 T
是一个简单的类或结构体:
class Example {
public:int data;Example() : data(0) {} // 默认构造函数,将 data 初始化为 0Example(int val) : data(val) {}
};
在 vector
的构造函数中:
vector(size_t n, const T& value = T())
{for (size_t i = 0; i < n; ++i){push_back(value);}
}
- 当你调用
vector(5)
,value
会被初始化为T()
,即Example()
,因此所有元素将会使用Example
的默认构造值(data
为 0)。 - 当你调用
vector(5, Example(10))
,value
被初始化为Example(10)
,所有元素的data
将会是 10。
总结:
T()
在 const T& value = T()
中的作用是提供一个默认值用于初始化 value
参数。这个默认值是通过调用 T
类型的默认构造函数得到的。这样,构造函数在没有提供具体值的情况下,也能正确地初始化 vector
对象中的元素。
🌉赋值拷贝构造:vector& operator=(vector v)
//v3 = v2
vector<T>& operator=(vector<T> v)
{swap(v);return *this;
}void swap(const vector<T>& v)
{std::swap(_start, v._start);std::swap(_finish, v._finish);std::swap(_end_of_storage, v._end_of_storage);
}
赋值运算符重载 (operator=
)
vector<T>& operator=(vector<T> v)
{swap(v);return *this;
}
这里分为三部:
1.1. 参数 vector<T> v
- 这个赋值运算符重载接受一个
vector<T>
对象v
作为参数。这里的v
是按值传递的,这意味着传递的是v
的一个副本。 - 传递副本而不是引用,有助于实现 强异常安全保证。如果在赋值过程中出现异常,原来的
vector
不会被影响。
1.2. swap(v)
swap(v)
是核心操作,它交换当前对象 (*this
) 与传入的v
对象的数据成员。- 这个操作通过交换指针来交换两个
vector
对象的内部数据(如起始位置_start
、结束位置_finish
、存储容量边界_end_of_storage
),而无需进行逐个元素的复制。 - 交换后,传入的副本
v
原来持有的资源会被销毁,而当前对象将接管这些资源。
1.3. return *this
- 最后返回当前对象的引用
*this
,允许链式赋值操作,如v1 = v2 = v3;
。
总结:
这种实现方式通过参数按值传递的副本,再通过交换数据来实现赋值运算,避免了临时资源分配失败导致的资源泄漏,并且非常高效,因为只交换指针,不进行实际数据复制。
swap
函数
void swap(const vector<T>& v)
{std::swap(_start, v._start);std::swap(_finish, v._finish);std::swap(_end_of_storage, v._end_of_storage);
}
const vector<T>& v
这里是引用:
swap
函数接受一个 const vector<T>& v
作为参数。因为这个函数只是交换内部指针,v
被声明为 const
是为了表明不会修改 v
的逻辑内容(但实际上会修改 v
的内部指针)。 std::swap
是标准库中的模板函数,交换两个变量的值。这里通过交换 _start
、_finish
和 _end_of_storage
三个指针,实现了两个 vector
对象之间的数据交换。
函数效果:交换完成后,原对象和传入的对象交换了内部数据指针,但没有改变实际存储的数据。这种方式避免了内存分配和数据复制操作的开销,提高了效率。
🌉模版函数实现区间初始化 vector(InputIterator first, InputIterator last)
这个构造函数是一个模板函数,目的是让 vector
类支持任意类型的迭代器区间初始化。通过这个构造函数,你可以使用两个迭代器来初始化一个 vector
对象,将迭代器区间 [first, last)
中的所有元素插入到 vector
中。让我们逐步分析这段代码:
//类模版中也可以使用函数模版
//函数模版 --- 目的支持任意容易得迭代器区间初始化
template<class InputIterator>
vector(InputIterator first, InputIterator last)
{while (first != last){push_back(*first);++first;}
}
🌉函数模板的定义 InputIterator
template<class InputIterator>
vector(InputIterator first, InputIterator last)
- 这是一个函数模板,用于在
vector
类中定义一个构造函数。InputIterator
是模板参数,表示输入迭代器的类型。由于这是一个模板函数,所以它可以接受任意类型的迭代器。 first
和last
是两个迭代器,定义了一个半开区间[first, last)
。这个区间的所有元素将被复制到新的vector
中。
循环遍历迭代器区间
while (first != last)
{push_back(*first);++first;
}
while (first != last)
:这个 while
循环用于遍历迭代器区间 [first, last)
。只要 first
不等于 last
,就会继续循环。
push_back(*first)
:
*first
是解引用操作,获取当前first
所指向的元素值。push_back(*first)
将这个值添加到vector
的末尾。push_back()
函数会处理必要的内存扩展,因此即使vector
当前容量不足,它也能动态扩展存储空间,以容纳新元素。
函数的功能和用途:
-
功能:这个模板构造函数允许你用任意的迭代器区间来初始化一个
vector
。这个区间可以是数组、std::list
、std::set
、std::deque
等容器的迭代器区间,甚至是原始指针。 -
用途:这种灵活性使得
vector
可以从几乎任何标准容器或数组中初始化。例如,如果你有一个数组int arr[] = {1, 2, 3, 4};
,可以使用这个构造函数将arr
的内容复制到vector
中:vector<int> vec(arr, arr + 4);
🌉列表初始化区间vector(initializer_list il)
vector(initializer_list<T> il)
{reserve(il.size());for (auto e : il){push_back(e);}}
这段代码是 vector
类的一个构造函数,它接受一个 initializer_list<T>
类型的参数来初始化 vector
。initializer_list
是 C++11 引入的一个特性,允许以列表的形式直接初始化容器。下面详细分析这段代码的每一部分:
vector(initializer_list<T> il)
initializer_list<T>
是 C++11 中引入的标准库类型,表示一个初始化列表,它是一个常量迭代器区间,支持对T
类型的元素进行初始化。il
是构造函数的参数,代表一个初始化列表,其中包含了要插入到vector
中的元素。
reserve(il.size());
reserve()
是vector
类的一个成员函数,用于预先分配一定的内存空间,以避免在添加元素时频繁重新分配内存。il.size()
返回初始化列表中元素的数量。调用reserve(il.size())
可以确保vector
在添加所有元素之前有足够的空间,从而提高性能。
for (auto e : il)
{push_back(e);
}
-
范围基于
for
循环 (for (auto e : il)
) 遍历初始化列表il
中的每一个元素。auto
关键字让编译器自动推断e
的类型,这里e
是T
类型。il
是一个initializer_list<T>
,它提供了迭代器接口,因此可以用这种方式进行遍历。
-
push_back(e)
将当前元素e
插入到vector
的末尾。push_back()
方法会将元素e
添加到vector
中。如果vector
的当前容量不足以容纳新的元素,它会自动扩展容量。
优点和使用场景
- 易用性:支持直接使用花括号初始化列表,例如:
这种方式非常直观,易于理解和使用。self::vector<int> v = {1, 2, 3, 4, 5};
示例
假设我们有一个 vector<int>
,我们可以使用这个构造函数初始化 vector
:
self::vector<int> v = {1, 2, 3, 4, 5};
- 这个语句会创建一个
vector<int>
对象v
,并将初始化列表{1, 2, 3, 4, 5}
中的元素依次插入到vector
中。
🌉~vector()
析构函数,我们只需要释放空间并置指针指向空:
~vector()
{if (_start){delete[] _start;_start = _finish = _end_of_storage = nullptr; }
}
🌠begin与end
迭代器开始与结束:
iterator begin()
{return _start;
}iterator end()
{return _finish;
}iterator begin() const
{return _start;
}iterator end() const
{return _finish;
}const_iterator cbegin() const
{return _start;
}const_iterator cend() const
{return _finish;
}
获取容量大小与有效元素个数,只需要进行对应指针相减
size_t size() const
{return _finish - _start;
}size_t capacity() const
{return _end_of_storage - _start;
}
🌉reserve
void reserve(size_t n)
{if (n > capacity()){size_t old_size = size();//记录原size()的大小T* tmp = new T[n];if (_start){memcpy(tmp, _start, sizeof(T) * size());delete[] _start;}_start = tmp;_finish = _start + old_size;//避免使用原来finish指针与更新后的start指针相减得到错误的size()_end_of_storage = _start + n;}}
这个 reserve
函数用于在 vector
类中预留一定的内存,以便容纳 n
个元素。这个方法的主要目的是扩展 vector
的容量,确保它能够容纳更多的元素,同时管理内部内存的重新分配。下面详细分析这段代码:
函数实现
void reserve(size_t n)
{if (n > capacity()){size_t old_size = size(); // 记录原 size() 的大小T* tmp = new T[n]; // 分配新的内存空间if (_start){memcpy(tmp, _start, sizeof(T) * size()); // 复制原有元素delete[] _start; // 释放原内存}_start = tmp; // 更新 start 指针_finish = _start + old_size; // 更新 finish 指针_end_of_storage = _start + n; // 更新 end_of_storage 指针}
}
记录原大小
size_t old_size = size();
size()
返回vector
中当前元素的数量。old_size
用于记录当前vector
中的元素数量,以便在内存扩展后,能够正确地设置_finish
指针。
因为不记录,等delete了_start
,到这两部
_start = tmp; // 更新 start 指针,_finish = _start + size();
再用旧的减去一个释放的_start,就会有问题。
这里我们执行深拷贝,通过memcpy
将旧数组的元素复制到新数组。需要注意的是,memcpy
是基于字节的浅拷贝,如果vector
实例化为string
类,浅拷贝可能导致二次释放等问题。
虽然我的_start
指向新空间并进行了深拷贝,但string
类却进行了浅拷贝,仍然指向原始空间。为了解决这个问题,我们不能使用memcpy
,而是需要通过赋值操作进行二次深拷贝。
void reserve(size_t n)
{if (n > capacity()){T* tmp = new T[n];size_t old_size = size();//memcpy(tmp, _start, size() * sizeof(T));for (size_t i = 0; i < old_size; i++){tmp[i] = _start[i];}delete[] _start;_start = tmp;_finish = tmp + old_size;_endofstorage = tmp + n;}
}
🌉resize
void resize(size_t n, const T& x = T())
{if (n < size()) // 如果新的大小小于当前大小{_finish = _start + n; // 将_finish指针移动到新大小的位置}else if (n > size()) // 如果新的大小大于当前大小{size_t oldSize = size();reserve(n); // 确保有足够的容量来存储新大小的元素for (size_t i = oldSize; i < n; ++i){push_back(x); // 用默认值x填充新的元素}}// 如果 n 等于当前大小,不需要进行任何操作
}
如果新大小小于当前大小,则截断容器中多余的元素;如果新大小大于当前大小,则使用给定的填充值来填充新的元素。
🌉insert与erase
iterator insert(iterator pos, const T& x)
{assert(pos >= _start);assert(pos <= _finish);if (_finish == _end_of_storage){size_t len = pos - _start;size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;reserve(newcapacity);pos = _start + len;}iterator end = _finish - 1;while (end >= pos){*(end + 1) = *end;--end;}*pos = x;++_finish;return pos;
}
插入位置检查: 确保插入位置 pos
在 vector
当前元素的范围内,即在有效的 [start, finish]
区间内。
内存扩展
if (_finish == _end_of_storage)
{size_t len = pos - _start;size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;reserve(newcapacity);pos = _start + len;
}
- 如果
vector
当前容量已满(_finish == _end_of_storage
),则需要扩展容量。 - 计算插入位置在原始
vector
中的索引len
,并使用reserve(newcapacity)
扩展容量。 - 扩展容量后,更新
pos
的位置,使其指向新的内存区域中插入的目标位置。这里pos = _start + len
是为了确保扩展后的pos
位置正确。
这里的len记录原来位置是防止迭代器失效问题:因为我们使用了reserve,开了新空间,如果未保存,使用新的地址,那将会有风险。
返回值
return pos;
- 返回新插入元素的位置
pos
,使得插入操作可以链式使用,更新迭代器。
在 C++ 中,std::vector
是一个动态数组,它会根据需要扩展其内部存储的容量。由于这种扩展和其他操作,vector
中的迭代器可能会失效。以下是 vector
中迭代器失效的主要条件:
- 内存重新分配
vector
会在需要时扩展其内存容量。当 vector
的容量不足以容纳新元素时,它会重新分配内存。这通常发生在以下操作时:
- 插入元素:当
vector
的容量满时,使用push_back
、insert
等方法插入新元素可能会触发内存重新分配。 - 扩展容量:使用
reserve
增加vector
的容量,如果reserve
请求的容量大于当前容量,vector
可能会重新分配内存。
影响:
- 内存重新分配会导致所有原有的指针和迭代器失效,因为
vector
内部的元素被移动到新的内存位置。 - 在内存重新分配后,原来的迭代器和指针将不再有效,因为它们指向的是旧的内存区域。
- 删除元素
删除元素通常不会导致内存重新分配,但会影响迭代器的有效性:
erase
:调用erase
方法删除元素会使指向被删除元素的迭代器失效。- 范围删除:
erase
删除一段范围内的元素时,范围内的所有迭代器会失效,此外,所有指向被删除元素之后的迭代器也会失效,因为元素的后续位置发生了改变。
影响:
erase
操作可能导致指向被删除元素的迭代器失效。- 被删除元素之后的所有迭代器也会失效,因为删除操作可能会导致元素的重新排列。
在对 vector
进行赋值或移动操作时,虽然这些操作不会直接影响单个迭代器,但会对迭代器的使用产生影响:
- 赋值操作:将一个
vector
赋值给另一个vector
,会涉及到内存重新分配和元素复制,这可能会使原有的迭代器失效。 - 移动操作:使用移动构造函数或移动赋值操作时,也可能导致内部数据的重新分配和元素的重新排列,从而使迭代器失效。
为了避免迭代器失效的影响,在进行可能导致失效的操作后,应当重新获取迭代器或使用容器提供的稳定操作。例如,可以使用 vector
提供的 begin()
和 end()
重新获取迭代器。在设计使用迭代器的代码时,应考虑这些因素,以确保代码的正确性和可靠性。
iterator erase(iterator pos)
{assert(pos >= _start);assert(pos < _finish);iterator end = pos + 1;while (end <= _finish){*(end - 1) = *end;++end;}--_finish;return pos;
}
迭代器失效 :删除操作会使指向被删除元素的迭代器失效。删除一个元素,迭代器还指向原位置,但元素被移动了,也就是原位置的后一个元素来到原位置,因此注意 erase
后,pos
之后的迭代器要更新指向新位置。
🌉push_back与pop_back
push_back函数可以复用,也可以使用insert的方法构造:
void push_back(const T& x)
{//if (_finish == _end_of_storage)//{// size_t newcapacity = capacity() == 0 ? 4 : sizeof(T) * 2;// reserve(newcapacity);//}//*_finish = x;//_finish++;insert(end(), x);
}
void pop_back()
{assert(size() > 0);--_finish;}
🌠访问元素
访问元素operator[
]需要注意的就是,要判断是否pos访问的位置合法:
T& operator[](size_t pos)
{assert(pos < size());return _start[pos];
}const T& operator[](size_t pos) const
{assert(pos < size());return _start[pos];
}
🌠全代码
代码中有丰富的测试用例来验证vector的实现是否有问题:
🌉test.cpp
# define _CRT_SECURE_NO_WARNINGS 1
#include "vector.h"int main()
{//self::test_vector01();//self::test_vector02();//self::test_vector03();//self::test_vector04();//self::test_vector05();//self::test_vector06();//self::test_vector07();self::test_vector08();return 0;
}
🌉vector.h
#pragma once
#include <iostream>
#include <list>
#include <assert.h>
using namespace std;namespace self
{template<class T>class vector{public:typedef T* iterator;typedef const T* const_iterator;iterator begin(){return _start;}iterator end(){return _finish;}iterator begin() const{return _start;}iterator end() const{return _finish;}const_iterator cbegin() const{return _start;}const_iterator cend() const{return _finish;}//强制编译器生成默认构造函数vector() = default;//s2(s1)vector(const vector<T>& v){reserve(v.size());for (auto e : v){push_back(e);}}vector(size_t n, const T& value = T()){for (size_t i = 0; i < n; ++i){push_back(value);}}//v3 = v2vector<T>& operator=(vector<T> v){swap(v);return *this;}void swap(const vector<T>& v){std::swap(_start, v._start);std::swap(_finish, v._finish);std::swap(_end_of_storage, v._end_of_storage);}//类模版中也可以使用函数模版//函数模版 --- 目的支持任意容易得迭代器区间初始化template<class InputIterator>vector(InputIterator first, InputIterator last){while (first != last){push_back(*first);++first;}}vector(initializer_list<T> il){reserve(il.size());for (auto e : il){push_back(e);}}~vector(){if (_start){delete[] _start;_start = _finish = _end_of_storage = nullptr; }}size_t size(){return _finish - _start;}size_t capacity(){return _end_of_storage - _start;}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 old_size = size();//记录原size()的大小T* tmp = new T[n];//if (_start)//{// memcpy(tmp, _start, sizeof(T) * size());// delete[] _start;//}for (size_t i = 0; i < old_size; i++){tmp[i] = _start[i];}delete[] _start;_start = tmp;_finish = _start + old_size;//避免使用原来finish指针与更新后的start指针相减得到错误的size()_end_of_storage = _start + n;}}void resize(size_t n, const T& x = T()){if (n < size()) // 如果新的大小小于当前大小{_finish = _start + n; // 将_finish指针移动到新大小的位置}else if (n > size()) // 如果新的大小大于当前大小{size_t oldSize = size();reserve(n); // 确保有足够的容量来存储新大小的元素for (size_t i = oldSize; i < n; ++i){push_back(x); // 用默认值x填充新的元素}}// 如果 n 等于当前大小,不需要进行任何操作}void push_back(const T& x){//if (_finish == _end_of_storage)//{// size_t newcapacity = capacity() == 0 ? 4 : sizeof(T) * 2;// reserve(newcapacity);//}//*_finish = x;//_finish++;insert(end(), x);}void pop_back(){assert(size() > 0);--_finish;}T& operator[](size_t pos){assert(pos < size());return _start[pos];}const T& operator[](size_t pos) const{assert(pos < size());return _start[pos];}iterator insert(iterator pos, const T& x){assert(pos >= _start);assert(pos <= _finish);if (_finish == _end_of_storage){size_t len = pos - _start;size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;reserve(newcapacity);pos = _start + len;}iterator end = _finish - 1;while (end >= pos){*(end + 1) = *end;--end;}*pos = x;++_finish;return pos;}iterator erase(iterator pos){assert(pos >= _start);assert(pos < _finish);iterator end = pos + 1;while (end <= _finish){*(end - 1) = *end;++end;}--_finish;return pos;}private:iterator _start;iterator _finish;iterator _end_of_storage;};void test_vector01(){self::vector<int> v1;v1.push_back(1);v1.push_back(2);v1.push_back(3);v1.push_back(4);v1.push_back(5);v1.push_back(5);v1.push_back(4);for (size_t i = 0; i < v1.size(); i++){cout << v1[i];}cout << endl;self::vector<int>::iterator it = v1.begin();while (it != v1.end()){cout << *it << " ";it++;}cout << endl;for (auto e : v1){cout << e << " ";}cout << endl;v1.pop_back();for (auto e : v1){cout << e << " ";}cout << endl;}void test_vector02(){self::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;size_t x = 0;cin >> x;self::vector<int>::iterator it = find(v1.begin(), v1.end(), x);if (it != v1.begin()){it = v1.insert(it, 100);cout << *it << endl;}for (auto e : v1){cout << e << " ";}cout << endl;}void test_vector03(){self::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;size_t x = 0;cin >> x;self::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;}for (auto e : v1){cout << e << " ";}cout << endl;cout << typeid(it).name() << endl;}void test_vector04(){vector<int> v3;v3.push_back(1);v3.push_back(2);v3.push_back(3);v3.push_back(4);vector<int>::iterator it = v3.begin();while (it != v3.end()){if (*it % 2 == 0){v3.erase(it);}else{++it;}}for (auto e : v3){cout << e << " ";}cout << endl;}void test_vector05(){vector<int> v6;v6.push_back(1);v6.push_back(1);v6.push_back(1);for (auto e : v6){cout << e << " ";}cout << endl;vector<int> v7(v6);for (auto e : v7){cout << e << " ";}cout << endl;vector<int> v8 = v6;for (auto e : v8){cout << e << " ";}cout << endl;}void test_vector06(){int i = 0;int j(1);int k = int();int x = int(2);vector<string> v8(4, "xxxxx");for (auto e : v8){cout << e << " ";}cout << endl;vector<int> v9(4u, 104);for (auto e : v9){cout << e << " ";}cout << endl;vector<int> v10(v9.begin(), v9.end());for (auto e : v9){cout << e << " ";}cout << endl;}void test_vector07(){vector<string> v8(4, "xxxxx");for (auto e : v8){cout << e << " ";}cout << endl;list<int> It;It.push_back(100);It.push_back(100);It.push_back(100);It.push_back(100);list<int> v3(It.begin(), It.end());for (auto e : v3){cout << e << " ";}cout << endl;}class A{public:A(int a = 0):_a1(a), _a2(0){}A(int a1, int a2):_a1(a1), _a2(a2){}private:int _a1;int _a2;};void test_vector08(){A aa1(1);A aa2(2, 2);const A& aa3 = { 1, 2 };A aa4 = 1;A aa5 = { 1,3 };vector<int> v1({ 1,2,3,4,5,6 });vector<int> v2 = { 1,2,3,4,5,8 };for (auto e : v1){cout << e << " ";}cout << endl;for (auto e : v2){cout << e << " ";}cout << endl;auto il1 = { 1,2,3,4 };initializer_list<int> il2 = { 1,3,4 };cout << typeid(il1).name() << endl;cout << sizeof(il2) << endl;for (auto e : il2){cout << e << " ";}cout << endl;}
}
🚩总结
相关文章:

C++奇迹之旅:手写vector模拟实现与你探索vector 容器的核心机制与使用技巧
文章目录 📝基本框架🌠 构造和销毁🌉vector()🌉vector(const vector& v)🌉vector(size_t n, const T& value T())🌉赋值拷贝构造:vector<T>& operator(vector<T> v)&a…...

018、钩子函数 mounted和beforeDestroy、父组件向子组件传递参数 props 的使用
文章目录 1、mounted 和 beforeDestroy1.1、mounted1.2、beforeDestroy 2、父组件向子组件传递参数 props2.1、子组件定义2.2、父组件调用子组件并传参 3、完整例子3.1、父组件 Tags.vue3.2、子组件 TagsMenu.vue3.3、效果图 1、mounted 和 beforeDestroy 1.1、mounted mount…...

xlnt在Windows中的dll,lib生成
前言 花了半天时间想要把xlnt 集成到VS2022 Cmake项目中,以我目前掌握的能力,Cmake语法对于我来说难懂,对于只是使用过Cmake编译MySQL,或是其他lib,dll库的小白来说,不应该为了显示自己能力多么出众,强行去配置一些程序内容。 生活中没有绝对的事情,有舍有得. https://github…...

【网络】私有IP和公网IP的转换——NAT技术
目录 引言 NAT工作机制编辑 NAT技术的优缺点 优点 缺点 个人主页:东洛的克莱斯韦克-CSDN博客 引言 公网被子网掩码划分为层状结构,一个公网IP的机器又可以用很多私有IP搭建内网。在日常生活场景中用的都是私有IP,例如手机,…...

java 面试 PDF 资料整理
“尊贵的求知者,作者特此献上精心编纂的Java面试宝典PDF,这份资料凝聚了无数面试精华与实战经验,是通往Java技术殿堂的钥匙。若您渴望在Java编程的求职之路上稳健前行,只需轻轻一点,完成这象征支持与认可的一键三联&am…...

初步认识Linux系统
前言 Linux系统具有许多优点,不仅系统性能稳定,而且是开源软件。其核心防火墙组件性能高效、配置简单,保证了系统的安全。在很多企业网络中,为了追求速度和安全,Linux不仅仅是被网络运维人员当作服务器使用,…...

JavaScript AI 编程助手
JavaScript AI 编程助手 引言 随着人工智能技术的飞速发展,编程领域也迎来了前所未有的变革。JavaScript,作为全球最流行的编程语言之一,其与AI的结合为开发者带来了巨大的便利和无限的可能性。本文将探讨JavaScript AI编程助手的定义、功能…...

达梦数据库的系统视图v$datafile
达梦数据库的系统视图v$datafile 达梦数据库的V$DATAFILE 是一个重要的系统视图,提供了有关数据库数据文件的信息。 V$DATAFILE 系统视图 V$DATAFILE 视图用于显示数据库中每一个数据文件的详细信息。通过查询这个视图,数据库管理员可以了解数据文件的…...

Triton/window安装: triton-2.0.0-cp310-cp310-win_amd64.whl文件
下面这个github仓: https://github.com/PrashantSaikia/Triton-for-Windows/tree/main 安装命令也很简单,下载到本地后运行: pip install triton-2.0.0-cp310-cp310-win_amd64.whl...

应急响应-DDOS-典型案例
某单位遭受DDoS攻击事件如下 事件背景 2019年2月17日,某机构门户网站无法访问,网络运维人员称疑似遭受DDoS攻击,请求应急响应工程师协助。 事件处置 应急响应工程师在达到现场后,通过查看流量设备,发现攻击者使用僵…...

JAVA学习之知识补充(下)
六:File类与IO流: 这里给出三种常见的初始化方法: 通过文件路径初始化: File file new File("C:/example/test.txt");这种方法用于创建一个文件对象,该文件对象表示指定路径的文件或目录。例如:File fil…...

qt生成一幅纯马赛克图像
由于项目需要,需生成一幅纯马赛克的图像作为背景,经过多次测试成功,记录下来。 方法一:未优化方法 1、代码: #include <QImage> #include <QDebug> #include <QElapsedTimer>QImage generateMosa…...

python循环——九九乘法表(更加轻松的理解循环结构)
感受 首先,得明确意识到这个问题,就是我的循环结构学的一塌糊涂,完全不能很好的使用这个循环来实现各种九九乘法表达输出,这样的循环结构太差了,还需要我自己找时间来补充一下循环的使用,来拓宽自己的思考方…...

UDS诊断系列之十八故障码的状态掩码
在谈19服务的子功能之前,先说一下故障码(DTC)的状态掩码是什么。 一、状态掩码 状态掩码由八个状态位构成,客户端利用它向服务器请求与其状态相匹配的DTC信息。当服务器接收到来自客户端的请求时,它会通过过滤匹配的…...

【jvm】直接引用
目录 1. 说明2. 形式3. 特点4. 生成过程5. 作用 1. 说明 1.在Java虚拟机(JVM)中,直接引用(Direct Reference)是相对于符号引用(Symbolic Reference)而言的,它是指向内存中实际存在的…...

PythonStudio 控件使用常用方式(二十七)TActionList
PythonStudio是一个极强的开发Python的IDE工具,官网地址是:https://glsite.com/ ,在官网可以下载最新版的PythonStudio,同时,在使用PythonStudio时,它也能及时为用户升到最新版本。它使用的是Delphi的控件&…...

PDF 转Word 开源库
1. Apache PDFBox Apache PDFBox 是一个开源的 Java 库,用于创建和操作 PDF 文档。虽然 PDFBox 本身没有直接支持 PDF 转 Word 的功能,但它可以提取 PDF 内容,你可以结合其他方法将这些内容写入 Word。 添加依赖 <dependency><gr…...

Docker - 深入理解Dockerfile中的 RUN, CMD 和 ENTRYPOINT
RUN docker file 中的 RUN 命令相对来教容易理解 RUN 指令用于在构建镜像时执行命令,这些命令会在 Docker 镜像的构建过程中执行。常用于安装软件包、设置环境变量、创建目录等。RUN 指令会在镜像构建中创建新的镜像层,每个 RUN 指令都会创建一个新的镜…...

Python 函数式编程 内置高阶函数及周边【进阶篇 3】推荐
前面我们已经总结并实践了用python获取到了数据。也介绍了python中http网络请求的几种方式,正在学习python开发语言或者对python3知识点生疏需要回顾的请点这里 ,本章主要总结了函数式编程及特点 和 python中内置的高阶函数及周边知识,方便自…...

【Rust光年纪】探秘Rust GUI库:从安装配置到API概览
Rust语言GUI库全方位比较:选择适合你的工具 前言 在现代软件开发中,图形用户界面(GUI)库扮演着至关重要的角色。随着Rust语言的不断发展,越来越多的优秀的GUI库也相继问世,为Rust开发者提供了更多选择。本…...

Element plus部分组件样式覆盖记录
文章目录 一、el-button 样式二、Popconfirm 气泡确认框三、Popover 气泡卡片四、Checkbox 多选框五、Pagination 分页六、Form 表单七、Table 表格 一、el-button 样式 html: <el-button class"com_btn_style">button</el-button>样式覆盖…...

重塑业务生态,Vatee万腾平台:引领行业变革的新引擎
在数字经济浪潮汹涌的今天,传统行业的边界正被不断模糊与重塑,新兴技术如云计算、大数据、人工智能等正以前所未有的速度改变着商业世界的面貌。在这一背景下,Vatee万腾平台应运而生,以其独特的创新模式和强大的技术实力ÿ…...

标准术语和定义中的【架构】应该如何描述
一、参考国家标准和国际标准中对“架构”的描述 (1)GB/T 8566-2022 国家标准 架构的术语描述:(系统)在其环境中的一些基本概念或性质,体现在其元素关系,以及设计与演进原则中。 (2)ISO/IEC/IEEE 42010 国际标准 架构的…...

华为鸿蒙Core Vision Kit 骨骼检测技术
鸿蒙Core Vision Kit 是华为鸿蒙系统中的一个图像处理框架,旨在提供各种计算机视觉功能,包括物体检测、人脸识别、文本识别等。骨骼检测是其中的一项功能,主要用于检测和识别人类身体的骨骼结构。 骨骼检测的关键点 骨骼点检测:通…...

Table API SQL系统(内置)函数System (Built-in) Function详解
目录 函数类型 引用函数 函数精确引用 函数模糊引用 函数解析顺序 精确的函数引用 模糊的函数引用 系统函数 标量函数(Scalar Functions) 比较函数(Comparison Functions) 逻辑函数(Logical Functions) 算术函数(Arithmetic Functions) 字符串函数(Strin…...

一键运行RocketMQ5.3和Dashboard
一键运行RocketMQ5.3和Dashboard 目录 一键运行RocketMQ5.3和Dashboard通过Docker Compose 来一键启动运行的容器包括docker-compose.yml文件运行命令启动本地效果查看 参考信息 通过Docker Compose 来一键启动 运行的容器包括 NameServerBrokerProxyDashBoard docker-compo…...

HAL STM32 SG90舵机驱动控制
HAL STM32 SG90舵机驱动控制 🔖测试对象:STM32F103SG90舵机 🌼功能实现:通过串口指令,控制SG90舵机转动到指定角度。 ✨在实际硬件舵机驱动过程中,使用SG90普通舵机空载运转情况下,电流在180mA…...

【Kubernetes】k8s集群图形化管理工具之rancher
目录 一.Rancher概述 1.Rancher简介 2.Rancher与k8s的关系及区别 3.Rancher具有的优势 二.Rancher的安装部署 1.实验准备 2.安装 rancher 3.rancher的浏览器使用 一.Rancher概述 1.Rancher简介 Rancher 是一个开源的企业级多集群 Kubernetes 管理平台,实…...

AI编程系列一1小时完成链家房价爬虫程序
背景 AI编程实在太火,写了很多年的Java,现在Python 和Go 简单好用,今天结合智谱清言快速完成一个程序爬虫程序,没有任何Python 编程经验,只需要会提问,熟悉简单HTML结构即可。未来一定是有业务能力者的福…...

【JavaEE初阶】文件内容的读写—数据流
目录 📕 引言 🌴 数据流的概念 🚩 数据流分类 🌳 字节流的读写 🚩 InputStream(从文件中读取字节内容) 🚩 OutputStream(向文件中写内容) 🎄 字符流的…...