【C++初阶】STL详解(四)vector的模拟实现
本专栏内容为:C++学习专栏,分为初阶和进阶两部分。 通过本专栏的深入学习,你可以了解并掌握C++。
💓博主csdn个人主页:小小unicorn
⏩专栏分类:C++
🚚代码仓库:小小unicorn的代码仓库🚚
🌹🌹🌹关注我带你学习编程知识
STL详解(四)
- vector各函数接口总览
- vector当中的成员变量介绍
- 默认成员函数
- 构造函数1
- 构造函数2
- 构造函数3
- 拷贝构造函数
- 写法一:传统写法
- 写法二:现代写法
- 赋值运算符重载函数
- 写法一:传统写法
- 写法二:现代写法
- 析构函数
- 迭代器相关函数
- begin和end
- 容量和大小相关函数
- size和capacity
- reserve
- resize
- empty
- 修改容器内容相关函数
- push_back
- pop_back
- insert
- erase
- swap
- 访问容器相关函数
- operator[ ]
- 测试相关接口函数:
- 测试1.对元素的访问->迭代器.运算符重载[]
- 测试2.resize
- 测试3.insert
- 测试4.erase
- 测试5.赋值重载与拷贝构造:
- vector模拟实现源码展示:
- vector.h
- test.c
vector各函数接口总览
namespace NIC
{//模拟实现vectortemplate<class T>class vector{public:typedef T* iterator;typedef const T* const_iterator;//默认成员函数vector(); //构造函数vector(size_t n, const T& val); //构造函数template<class InputIterator> vector(InputIterator first, InputIterator last); //构造函数vector(const vector<T>& v); //拷贝构造函数vector<T>& operator=(const vector<T>& v); //赋值运算符重载函数~vector(); //析构函数//迭代器相关函数iterator begin();iterator end();const_iterator begin()const;const_iterator end()const;//容量和大小相关函数size_t size()const;size_t capacity()const;void reserve(size_t n);void resize(size_t n, const T& val = T());bool empty()const;//修改容器内容相关函数void push_back(const T& x);void pop_back();void insert(iterator pos, const T& x);iterator erase(iterator pos);void swap(vector<T>& v);//访问容器相关函数T& operator[](size_t i);const T& operator[](size_t i)const;private:iterator _start=nullptr; //指向容器的头iterator _finish=nullptr; //指向有效数据的尾iterator _endofstorage=nullptr; //指向容器的尾};
}
注:为了防止与标准库当中的vector产生命名冲突,模拟实现时需放在自己的命名空间当中。
vector当中的成员变量介绍
在vector当中有三个成员变量_start、_finish、_endofstorage。

_start指向容器的头,_finish指向容器当中有效数据的尾,_endofstorage指向整个容器的尾。
或许有人好奇,为什么是这样定义的呢?在之前的顺序表实现中,我们采用的是:

这就需要我们看一下在SLT源库中是怎么实现的。

这是vector中所包含的头文件,这里我们打开一下vector.h来看一下:

在vector.h中,我们可以看到,在这个摸版中,迭代器采用的是start,finish与end_of_storage,其实也就是我们按照之前定义的那样,基本是保持一致的。
我们模拟实现时基本按照库里面的实现方式来进行模拟实现。
以下是stl源码展示:

对于我们初学者来说,现在阅读源码会产生困难,但我们现在刚开始学习stl,所以在阅读时不用特别详细的阅读,只要熟悉相关的功能即可。
具体来说:
1.可以一行一行的看
2.不要研究细节,先拉框架,重点关注里面的类:成员变量与成员函数。
3.理解同时可以先猜测一下,然后想办法验证一下我们的猜测。
当然,小编也已经将STL源码上传到了gitee仓库中,可以下载在自己电脑上,方便查阅。
默认成员函数
构造函数1
vector首先支持一个无参的构造函数,对于这个无参的构造函数,我们直接将构造对象的三个成员变量都设置为空指针即可。
//构造函数1
vector()
{}
构造函数2
其次,vector还支持使用一段迭代器区间进行对象的构造。因为该迭代器区间可以是其他容器的迭代器区间,也就是说该函数接收到的迭代器的类型是不确定的,所以我们这里需要将该构造函数设计为一个函数模板,在函数体内将该迭代器区间的数据一个个尾插到容器当中即可。
//构造函数2
template<class InputIterator> //模板函数
vector(InputIterator first, InputIterator last)
{//将迭代器区间在[first,last)的数据一个个尾插到容器当中while (first != last){push_back(*first);first++;}
}
构造函数3
此外,vector还支持构造这样一种容器,该容器当中含有n个值为val的数据。对于该构造函数,我们可以先使用reserve函数将容器容量先设置为n,然后使用push_back函数尾插n个值为val的数据到容器当中即可。
//构造函数3
vector(size_t n, const T& val= T())
{reserve(n); //调用reserve函数将容器容量设置为nfor (size_t i = 0; i < n; i++) //尾插n个值为val的数据到容器当中{push_back(val);}
}
注意:
1)该构造函数知道其需要用于存储n个数据的空间,所以最好用reserve函数一次性开辟好空间,避免调用push_back函数时需要增容多次,导致效率降低。
2)该构造函数还需要实现一个重载函数。
vector(int n, const T& val= T())
{reserve(n); //调用reserve函数将容器容量设置为nfor (int i = 0; i < n; i++) //尾插n个值为val的数据到容器当中{push_back(val);}
}
可以看到,这个重载函数与之不同的就是其参数n的类型不同,但这却是必要的,否则当我们使用以下代码时,编译器会优先与构造函数2相匹配。
拷贝构造函数
vector的构造函数涉及深拷贝问题,这里提供两种深拷贝的写法:
写法一:传统写法
拷贝构造的传统写法的思想是我们最容易想到的:先开辟一块与该容器大小相同的空间,然后将该容器当中的数据一个个拷贝过来即可,最后更新_finish和_endofstorage的值即可。
//传统写法
vector(const vector<T>& v)
{_start = new T[v.capacity()]; //开辟一块和容器v大小相同的空间for (size_t i = 0; i < v.size(); i++) //将容器v当中的数据一个个拷贝过来{_start[i] = v[i];}_finish = _start + v.size(); //容器有效数据的尾_endofstorage = _start + v.capacity(); //整个容器的尾
}
注意: 将容器当中的数据一个个拷贝过来时不能使用memcpy函数,当vector存储的数据是内置类型或无需进行深拷贝的自定义类型时,使用memcpy函数是没什么问题的,但当vector存储的数据是需要进行深拷贝的自定义类型时,使用memcpy函数的弊端就体现出来了。
例如,当vector存储的数据是string类的时候。

并且vector当中存储的每一个string都指向自己所存储的字符串。

如果此时我们使用的是memcpy函数进行拷贝构造的话,那么拷贝构造出来的vector当中存储的每个string的成员变量的值,将与被拷贝的vector当中存储的每个string的成员变量的值相同,即两个vector当中的每个对应的string成员都指向同一个字符串空间。

这显然不是我们得到的结果,那么所给代码是如何解决这个问题的呢?

代码中看似是使用普通的“=”将容器当中的数据一个个拷贝过来,实际上是调用了所存元素的赋值运算符重载函数,而string类的赋值运算符重载函数就是深拷贝,所以拷贝结果是这样的:

总结一下: 如果vector当中存储的元素类型是内置类型(int)或浅拷贝的自定义类型(Date),使用memcpy函数进行进行拷贝构造是没问题的,但如果vector当中存储的元素类型是深拷贝的自定义类型(string),则使用memcpy函数将不能达到我们想要的效果。
写法二:现代写法
拷贝构造函数的现代写法也比较简单,使用范围for(或是其他遍历方式)对容器v进行遍历,在遍历过程中将容器v中存储的数据一个个尾插过来即可。
//现代写法
vector(const vector<T>& v)
{reserve(v.capacity()); //调用reserve函数将容器容量设置为与v相同for (auto e : v) //将容器v当中的数据一个个尾插过来{push_back(e);}
}
注意: 在使用范围for对容器v进行遍历的过程中,变量e就是每一个数据的拷贝,然后将e尾插到构造出来的容器当中。就算容器v当中存储的数据是string类,在e拷贝时也会自动调用string的拷贝构造(深拷贝),所以也能够避免出现与使用memcpy时类似的问题。
赋值运算符重载函数
vector的赋值运算符重载当然也涉及深拷贝问题,我们这里也提供两种深拷贝的写法:
写法一:传统写法
首先判断是否是给自己赋值,若是给自己赋值则无需进行操作。若不是给自己赋值,则先开辟一块和容器v大小相同的空间,然后将容器v当中的数据一个个拷贝过来,最后更新_finish和_endofstorage的值即可。
//传统写法
vector<T>& operator=(const vector<T>& v)
{if (this != &v) //防止自己给自己赋值{delete[] _start; //释放原来的空间_start = new T[v.capacity()]; //开辟一块和容器v大小相同的空间for (size_t i = 0; i < v.size(); i++) //将容器v当中的数据一个个拷贝过来{_start[i] = v[i];}_finish = _start + v.size(); //容器有效数据的尾_endofstorage = _start + v.capacity(); //整个容器的尾}return *this; //支持连续赋值
}
注意: 这里和拷贝构造函数的传统写法类似,也不能使用memcpy函数进行拷贝
写法二:现代写法
赋值运算符重载的现代写法非常精辟,首先在右值传参时并没有使用引用传参,因为这样可以间接调用vector的拷贝构造函数,然后将这个拷贝构造出来的容器v与左值进行交换,此时就相当于完成了赋值操作,而容器v会在该函数调用结束时自动析构。
//现代写法
vector<T>& operator=(vector<T> v) //编译器接收右值的时候自动调用其拷贝构造函数
{swap(v); //交换这两个对象return *this; //支持连续赋值
}
注意: 赋值运算符重载的现代写法也是进行的深拷贝,只不过是调用的vector的拷贝构造函数进行的深拷贝,在赋值运算符重载函数当中仅仅是将深拷贝出来的对象与左值进行了交换而已。
析构函数
对容器进行析构时,首先判断该容器是否为空容器,若为空容器,则无需进行析构操作,若不为空,则先释放容器存储数据的空间,然后将容器的各个成员变量设置为空指针即可。
//析构函数
~vector()
{if (_start) //避免对空指针进行释放{delete[] _start; //释放容器存储数据的空间_start = nullptr; //_start置空_finish = nullptr; //_finish置空_endofstorage = nullptr; //_endofstorage置空}
}
迭代器相关函数
vector当中的迭代器实际上就是容器当中所存储数据类型的指针。
typedef T* iterator;
typedef const T* const_iterator;
begin和end
vector当中的begin函数返回容器的首地址,end函数返回容器当中有效数据的下一个数据的地址。
iterator begin()
{return _start; //返回容器的首地址
}
iterator end()
{return _finish; //返回容器当中有效数据的下一个数据的地址
}
我们还需要重载一对适用于const对象的begin和end函数,使得const对象调用begin和end函数时所得到的迭代器只能对数据进行读操作,而不能进行修改。
const_iterator begin()const
{return _start; //返回容器的首地址
}
const_iterator end()const
{return _finish; //返回容器当中有效数据的下一个数据的地址
}
此时再让我们来看看vector使用迭代器的代码也就一目了然了,实际上就是使用指针遍历容器。
vector<int> v(5, 3);
vector<int>::iterator it = v.begin();
while (it != v.end())
{cout << *it << " ";it++;
}
cout << endl;
现在我们实现了迭代器,实际上也就可以使用范围for遍历容器了,因为编译器在编译时会自动将范围for替换为迭代器的形式。
vector<int> v(5, 3);
//范围for进行遍历
for (auto e : v)
{cout << e << " ";
}
cout << endl;
容量和大小相关函数
size和capacity
对照着vector当中三个成员遍历各自的指向,我们可以很容易得出当前容器中的有效数据个数和最大容量。

由于两个指针相减的结果,就是这两个指针之间对应类型的数据个数,所以size可以由_finish - _start得到,而capacity可以由_endofstorage - _start得到。
size_t size()const
{return _finish - _start; //返回容器当中有效数据的个数
}
size_t capacity()const
{return _endofstorage - _start; //返回当前容器的最大容量
}
reserve
reserve规则:
1、当n大于对象当前的capacity时,将capacity扩大到n或大于n。
2、当n小于对象当前的capacity时,什么也不做。
reserve函数的实现思路也是很简单的,先判断所给n是否大于当前容器的最大容量(否则无需进行任何操作),操作时直接开辟一块可以容纳n个数据的空间,然后将原容器当中的有效数据拷贝到该空间,之后将原容器存储数据的空间释放,并将新开辟的空间交给该容器维护,最好更新容器当中各个成员变量的值即可。
void reserve(size_t n)
{if (n > capacity()) //判断是否需要进行操作{size_t sz = size(); //记录当前容器当中有效数据的个数T* tmp = new T[n]; //开辟一块可以容纳n个数据的空间if (_start) //判断是否为空容器{for (size_t i = 0; i < sz; i++) //将容器当中的数据一个个拷贝到tmp当中{tmp[i] = _start[i];}delete[] _start; //将容器本身存储数据的空间释放}_start = tmp; //将tmp所维护的数据交给_start进行维护_finish = _start + sz; //容器有效数据的尾_endofstorage = _start + n; //整个容器的尾}
}
在reserve函数的实现当中有两个地方需要注意:
1在进行操作之前需要提前记录当前容器当中有效数据的个数。
因为我们最后需要更新_finish指针的指向,而_finish指针的指向就等于_start指针加容器当中有效数据的个数,当_start指针的指向改变后我们再调用size函数通过_finish - _start计算出的有效数据的个数就是一个随机值了。

2拷贝容器当中的数据时,不能使用memcpy函数进行拷贝。
可能你会想,当vector当中存储的是string的时候,虽然使用memcpy函数reserve出来的容器与原容器当中每个对应的string成员都指向同一个字符串空间,但是原容器存储数据的空间不是已经被释放了,相当于现在只有一个容器维护这这些字符串空间,这还有什么影响。
但是不要忘了,当你释放原容器空间的时候,原容器当中存储的每个string在释放时会调用string的析构函数,将其指向的字符串也进行释放,所以使用memcpy函数reserve出来的容器当中的每一个string所指向的字符串实际上是一块已经被释放的空间,访问该容器时就是对内存空间进行非法访问。

所以说我们还是得用for循环将容器当中的string一个个赋值过来,因为这样能够间接调用string的赋值运算符重载,实现string的深拷贝。

resize
resize规则:
1、当n大于当前的size时,将size扩大到n,扩大的数据为val,若val未给出,则默认为容器所存储类型的默认构造函数所构造出来的值。
2、当n小于当前的size时,将size缩小到n。
根据resize函数的规则,进入函数我们可以先判断所给n是否小于容器当前的size,若小于,则通过改变_finish的指向,直接将容器的size缩小到n即可,否则先判断该容器是否需要增容,然后再将扩大的数据赋值为val即可。
void resize(size_t n, const T& val = T())
{if (n < size()) //当n小于当前的size时{_finish = _start + n; //将size缩小到n}else //当n大于当前的size时{if (n > capacity()) //判断是否需要增容{reserve(n);}while (_finish < _start + n) //将size扩大到n{*_finish = val;_finish++;}}
}
注意: 在C++当中内置类型也可以看作是一个类,它们也有自己的默认构造函数,所以在给resize函数的参数val设置缺省值时,设置为T( )即可。
empty
empty函数可以直接通过比较容器当中的_start和_finish指针的指向来判断容器是否为空,若所指位置相同,则该容器为空。
bool empty()const
{return _start == _finish;
}
修改容器内容相关函数
push_back
要尾插数据首先得判断容器是否已满,若已满则需要先进行增容,然后将数据尾插到_finish指向的位置,再将_finish++即可。
//尾插数据
void push_back(const T& x)
{if (_finish == _endofstorage) //判断是否需要增容{size_t newcapacity = capacity() == 0 ? 4 : 2 * capacity(); //将容量扩大为原来的两倍reserve(newcapacity); //增容}*_finish = x; //尾插数据_finish++; //_finish指针后移
}
pop_back
尾删数据之前也得先判断容器是否为空,若为空则做断言处理,若不为空则将_finish–即可。
//尾删数据
void pop_back()
{assert(!empty()); //容器为空则断言_finish--; //_finish指针前移
}
insert
insert函数可以在所给迭代器pos位置插入数据,在插入数据前先判断是否需要增容,然后将pos位置及其之后的数据统一向后挪动一位,以留出pos位置进行插入,最后将数据插入到pos位置即可。
//在pos位置插入数据
void insert(iterator pos, const T& x)
{assert(pos >= _start);assert(pos <= _finish);if (_finish == _endofstorage) //判断是否需要增容{size_t len = pos - _start; //记录pos与_start之间的间隔size_t newcapacity = capacity() == 0 ? 4 : 2 * capacity(); //将容量扩大为原来的两倍reserve(newcapacity); //增容pos = _start + len; //通过len找到pos在增容后的容器当中的位置}//将pos位置及其之后的数据统一向后挪动一位,以留出pos位置进行插入iterator end = _finish;while (end >= pos + 1){*end = *(end - 1);end--;}*pos = x; //将数据插入到pos位置_finish++; //数据个数增加一个,_finish后移
}
注意: 若需要增容,则需要在增容前记录pos与_start之间的间隔,然后通过该间隔确定在增容后的容器当中pos的指向,否则pos还指向原来被释放的空间。
erase
erase函数可以删除所给迭代器pos位置的数据,在删除数据前需要判断容器释放为空,若为空则需做断言处理,删除数据时直接将pos位置之后的数据统一向前挪动一位,将pos位置的数据覆盖即可。
//删除pos位置的数据
iterator erase(iterator pos)
{assert(!empty()); //容器为空则断言//将pos位置之后的数据统一向前挪动一位,以覆盖pos位置的数据iterator it = pos + 1;while (it != _finish){*(it - 1) = *it;it++;}_finish--; //数据个数减少一个,_finish前移return pos;
}
swap
swap函数用于交换两个容器的数据,我们可以直接调用库当中的swap函数将两个容器当中的各个成员变量进行交换即可。
void swap(vector<T>& v)
{std::swap(_start, v._start);std::swap(_finish, v._finish);std::swap(_endofstorage, v._endofstorage);
}
访问容器相关函数
operator[ ]
vector也支持我们使用“下标+[ ]”的方式对容器当中的数据进行访问,实现时直接返回对应位置的数据即可。
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容器的,因为const容器通过“下标+[ ]”获取到的数据只允许进行读操作,不能对数据进行修改。
测试相关接口函数:
测试1.对元素的访问->迭代器.运算符重载[]
将容器V1按照[]访问,按照迭代器访问,按照范围for访问。
void test_vector1()
{vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);v.push_back(5);for (size_t i = 0; i < v.size(); i++){cout << v[i] << " ";}cout << endl;vector<int>::iterator it = v.begin();while (it != v.end()){*it *= 10;cout << *it << " ";++it;}cout << endl;for (auto e : v){cout << e << " ";}cout << endl;
}
测试结果:

测试2.resize
测试resize
void test_vector2()
{vector<int> v1;v1.push_back(1);v1.push_back(2);v1.push_back(3);v1.push_back(4);v1.push_back(5);v1.resize(10);vector<string> v2;//v2.resize(10, string("xxx"));v2.resize(10, "xxx");for (auto e : v1){cout << e << " ";}cout << endl;for (auto e : v2){cout << e << " ";}cout << endl;
}

测试3.insert
测试Insert
void test_vector3()
{vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);v.push_back(5);v.push_back(6);v.push_back(7);for (auto e : v){cout << e << " ";}cout << endl;vector<int>::iterator it = v.begin() + 2;v.insert(it, 30);for (auto e : v){cout << e << " ";}cout << endl;//v.insert(v.begin(), 30);v.insert(v.begin() + 3, 30);for (auto e : v){cout << e << " ";}cout << endl;
}

测试4.erase
测试rease
void test_vector4()
{vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);v.push_back(5);v.push_back(6);v.push_back(7);for (auto e : v){cout << e << " ";}cout << endl;auto pos = v.begin();v.erase(pos);for (auto e : v){cout << e << " ";}cout << endl;v.erase(v.begin() + 3);for (auto e : v){cout << e << " ";}cout << endl;
}

测试5.赋值重载与拷贝构造:
定义v1,然后拷贝给v2,在将v3赋值给v1:
void test_vector7()
{vector<int> v1;v1.push_back(1);v1.push_back(1);v1.push_back(1);v1.push_back(1);v1.push_back(1);//拷贝构造vector<int> v2(v1);cout << "v1为" << endl;for (auto e : v1){cout << e << " ";}cout << endl;cout << "拷贝v2为" << endl;for (auto e : v2){cout << e << " ";}cout << endl;vector<int> v3;v3.push_back(10);v3.push_back(20);v3.push_back(30);v3.push_back(40);v1 = v3;cout << "赋值后v1为" << endl;for (auto e : v1){cout << e << " ";}cout << endl;
}
测试结果:

vector模拟实现源码展示:
模拟实现中,我们没有进行对其定义与声明分离,而是将测试函数写成成员函数包装在命名空间里。
vector.h
#pragma once
#include<assert.h>namespace NIC
{template<class T>class vector{public: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;}vector(){}//构造2:template <class InputIterator>vector(InputIterator first, InputIterator last){while (first != last){push_back(*first);++first;}}//构造3vector(size_t n, const T& val = T()){reserve(n);for (size_t i = 0; i < n; i++){push_back(val);}}vector(int n, const T& val = T()){reserve(n);for (int i = 0; i < n; i++){push_back(val);}}// v2(v1)//拷贝构造:现代写法:vector(const vector<T>& v){reserve(v.capacity());for (auto& e : v){push_back(e);}}void swap(vector<T>& v){std::swap(_start, v._start);std::swap(_finish, v._finish);std::swap(_endofstorage, v._endofstorage);}// v1 = v3vector<T>& operator=(vector<T> tmp){swap(tmp);return *this;}~vector(){delete[] _start;_start = _finish = _endofstorage = nullptr;}void reserve(size_t n){if (n > capacity()){T* tmp = new T[n];size_t sz = size();if (_start){//memcpy(tmp, _start, sizeof(T) * sz);for (size_t i = 0; i < sz; i++){tmp[i] = _start[i];}delete[] _start;}_start = tmp;_finish = _start + sz;_endofstorage = _start + n;}}// T int// T string// T vector<int>//void resize(size_t n, T val = T())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;}}}void push_back(const T& x){/*if (_finish == _endofstorage){reserve(capacity() == 0 ? 4 : capacity() * 2);}*_finish = x;++_finish;*/insert(end(), x);}void insert(iterator pos, const T& x){assert(pos >= _start);assert(pos <= _finish);if (_finish == _endofstorage){size_t len = pos - _start;reserve(capacity() == 0 ? 4 : capacity() * 2);pos = _start + len;}iterator end = _finish - 1;while (end >= pos){*(end + 1) = *end;--end;}*pos = x;++_finish;}iterator erase(iterator pos){assert(pos >= _start);assert(pos < _finish);iterator it = pos + 1;while (it < _finish){*(it - 1) = *it;++it;}--_finish;return 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];}size_t capacity() const{return _endofstorage - _start;}size_t size() const{return _finish - _start;}private:iterator _start = nullptr;iterator _finish = nullptr;iterator _endofstorage = nullptr;};void test_vector1(){vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);v.push_back(5);for (size_t i = 0; i < v.size(); i++){cout << v[i] << " ";}cout << endl;vector<int>::iterator it = v.begin();while (it != v.end()){*it *= 10;cout << *it << " ";++it;}cout << endl;for (auto e : v){cout << e << " ";}cout << endl;}void test_vector2(){vector<int> v1;v1.push_back(1);v1.push_back(2);v1.push_back(3);v1.push_back(4);v1.push_back(5);v1.resize(10);vector<string> v2;//v2.resize(10, string("xxx"));v2.resize(10, "xxx");for (auto e : v1){cout << e << " ";}cout << endl;for (auto e : v2){cout << e << " ";}cout << endl;}void test_vector3(){vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);v.push_back(5);v.push_back(6);v.push_back(7);for (auto e : v){cout << e << " ";}cout << endl;vector<int>::iterator it = v.begin() + 2;v.insert(it, 30);for (auto e : v){cout << e << " ";}cout << endl;//v.insert(v.begin(), 30);v.insert(v.begin() + 3, 30);for (auto e : v){cout << e << " ";}cout << endl;}void test_vector4(){vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);v.push_back(5);v.push_back(6);v.push_back(7);for (auto e : v){cout << e << " ";}cout << endl;auto pos = v.begin();v.erase(pos);for (auto e : v){cout << e << " ";}cout << endl;v.erase(v.begin() + 3);for (auto e : v){cout << e << " ";}cout << endl;}//void test_vector5()//{// // 1 2 3 4 5// // 1 2 3 4 5 6// // 2 2 3 4 5// std::vector<int> v;// v.push_back(1);// v.push_back(2);// v.push_back(3);// v.push_back(4);// v.push_back(5);// //v.push_back(6);// for (auto e : v)// {// cout << e << " ";// }// cout << endl;// auto it = v.begin();// while (it != v.end())// {// // vs2019进行强制检查,erase以后认为it失效了,不能访问,访问就报错// if (*it % 2 == 0)// {// v.erase(it);// }// ++it;// }// for (auto e : v)// {// cout << e << " ";// }// cout << endl;//}void test_vector5(){//std::vector<int> v;vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);v.push_back(4);v.push_back(5);v.push_back(6);for (auto e : v){cout << e << " ";}cout << endl;auto it = v.begin();while (it != v.end()){if (*it % 2 == 0){it = v.erase(it);}else{++it;}}for (auto e : v){cout << e << " ";}cout << endl;}void test_vector6(){vector<string> v;v.push_back("111111111111111111111");v.push_back("111111111111111111111");v.push_back("111111111111111111111");v.push_back("111111111111111111111");v.push_back("111111111111111111111");for (auto e : v){cout << e << " ";}cout << endl;}void test_vector7(){vector<int> v1;v1.push_back(1);v1.push_back(1);v1.push_back(1);v1.push_back(1);v1.push_back(1);//拷贝构造vector<int> v2(v1);cout << "v1为" << endl;for (auto e : v1){cout << e << " ";}cout << endl;cout << "拷贝v2为" << endl;for (auto e : v2){cout << e << " ";}cout << endl;vector<int> v3;v3.push_back(10);v3.push_back(20);v3.push_back(30);v3.push_back(40);v1 = v3;cout << "赋值后v1为" << endl;for (auto e : v1){cout << e << " ";}cout << endl;}void test_vector8(){//vector<int> v0(10, 0);vector<string> v1(10, "xxxx");for (auto e : v1){cout << e << " ";}cout << endl;vector<int> v2;v2.push_back(10);v2.push_back(20);v2.push_back(30);v2.push_back(40);vector<int> v3(v2.begin(), v2.end());string str("hello world");vector<int> v4(str.begin(), str.end());for (auto e : v3){cout << e << " ";}cout << endl;for (auto e : v4){cout << e << " ";}cout << endl;}
}
test.c
test.c用于测试我们实现的相关接口函数:
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<vector>
#include<string>
using namespace std;
#include "vector.h"int main()
{NIC::test_vector7();return 0;
}
相关文章:
【C++初阶】STL详解(四)vector的模拟实现
本专栏内容为:C学习专栏,分为初阶和进阶两部分。 通过本专栏的深入学习,你可以了解并掌握C。 💓博主csdn个人主页:小小unicorn ⏩专栏分类:C 🚚代码仓库:小小unicorn的代码仓库&…...
Zookeeper学习笔记(2)—— Zookeeper API简单操作
前置知识:Zookeeper学习笔记(1)—— 基础知识-CSDN博客 Zookeeper集群搭建部分 前提:保证zookeeper集群处于启动状态 环境搭建 依赖配置 <dependencies><dependency><groupId>junit</groupId><arti…...
YOLOv8-Seg改进:Backbone改进 |Next-ViT堆栈NCB和NTB 构建先进的CNN-Transformer混合架构
🚀🚀🚀本文改进:Next-ViT堆栈NCB和NTB 构建先进的CNN-Transformer混合架构,包括nextvit_small, nextvit_base, nextvit_large,相比较yolov8-seg各个版本如下: layersparametersgradientsGFLOPsnextvit_small61033841075...
DocCMS keyword SQL注入漏洞复现 [附POC]
文章目录 DocCMS keyword SQL注入漏洞复现 [附POC]0x01 前言0x02 漏洞描述0x03 影响版本0x04 漏洞环境0x05 漏洞复现1.访问漏洞环境2.构造POC3.复现 0x06 修复建议 DocCMS keyword SQL注入漏洞复现 [附POC] 0x01 前言 免责声明:请勿利用文章内的相关技术从事非法测…...
利用(Transfer Learning)迁移学习在IMDB数据上训练一个文本分类模型
1. 背景 有些场景下,开始的时候数据量很小,如果我们用一个几千条数据训练一个全新的深度机器学习的文本分类模型,效果不会很好。这个时候你有两种选择,1.用传统的机器学习训练,2.利用迁移学习在一个预训练的模型上训练…...
pom.xml格式化快捷键
在软件开发和编程领域,"格式化"通常指的是将代码按照一定的规范和风格进行排列,以提高代码的可读性和维护性。格式化代码有助于使代码结构清晰、统一,并符合特定的编码规范。 格式化可以包括以下方面: 缩进:…...
【短文】【踩坑】可以在Qt Designer给QTableWidge添加右键菜单吗?
2023年11月18日,周六上午 今天早上在网上找了好久都没找到教怎么在Qt Designer给QTableWidge添加右键菜单的文章 答案是:不可以 在Qt Designer中无法直接为QTableWidget添加右键菜单。 Qt Designer主要用于创建界面布局和设计,无法直接添加…...
Git常用配置
git log 美化输出 全局配置参数 git config --global alias.lm "log --no-merges --color --dateformat:%Y-%m-%d %H:%M:%S --authorghost --prettyformat:%Cred%h%Creset - %Cgreen(%cd)%C(yellow)%d%Cblue %s %C(bold blue)<%an>%Creset --abbrev-commit"…...
力扣每日一题-数位和相等数对的最大和-2023.11.18
力扣每日一题:数位和相等数对的最大和 开篇 这道每日一题还是挺需要思考的,我绕晕了好久,根据题解的提示才写出来。 题目链接:2342.数位和相等数对的最大和 题目描述 代码思路 1.创建一个数组存储每个数位的数的最大值,创建一…...
【win32_001】win32命名规、缩写、窗口
整数类型 bool类型 使用注意: 一般bool 的false0;true1 | 2 | …|n false是为0,true是非零 不建议这样用: if (result TRUE) // Wrong! 因为result不一定只返回1(true),当返回2时,…...
机器学习第8天:SVM分类
文章目录 机器学习专栏 介绍 特征缩放 示例代码 硬间隔与软间隔分类 主要代码 代码解释 非线性SVM分类 结语 机器学习专栏 机器学习_Nowl的博客-CSDN博客 介绍 作用:判别种类 原理:找出一个决策边界,判断数据所处区域来识别种类 简单…...
AI工具合集
网站:未来百科 | 为发现全球优质AI工具产品而生 (6aiq.com) 如今,AI技术涉及到了很多领域,比如去水印、一键抠图、图像处理、AI图像生成等等。站长之家之前也分享过一些,但是在网上要搜索找到它们还是费一些功夫。 今天发现了一…...
代码随想录算法训练营Day 54 || 392.判断子序列、115.不同的子序列
392.判断子序列 力扣题目链接(opens new window) 给定字符串 s 和 t ,判断 s 是否为 t 的子序列。 字符串的一个子序列是原始字符串删除一些(也可以不删除)字符而不改变剩余字符相对位置形成的新字符串。(例如,&quo…...
C 语言 gets()和puts()
C 语言 gets()和puts() gets()和puts()在头文件stdio.h中声明。这两个函数用于字符串的输入/输出操作。 C gets()函数 gets()函数使用户可以输入一些字符,然后按Enter键。 用户输入的所有字符都存储在字符数组中。 空字符将添加到数组以使其成为字符串。 gets()允…...
核—幂零分解
若向量空间 V \mathcal V V存在子空间 X \mathcal X X与 Y \mathcal Y Y,当 X Y V X ∩ Y 0 \mathcal {X\text{}Y\text{}V}\\ \mathcal {X}\cap \mathcal {Y}0 XYVX∩Y0 时称子空间 X \mathcal X X与 Y \mathcal Y Y是完备的,其中记为 X ⊕ Y V \ma…...
轻松掌控财务,分析账户花销,明细记录支出情况
随着科技的发展,我们的生活变得越来越智能化。然而,对于许多忙碌的现代人来说,管理财务可能是一件令人头疼的事情。复杂的账单、花销、收入,这些可能会让你感到无从下手。但现在,我们有一个全新的解决方案——一款全新…...
竞赛 题目:基于机器视觉opencv的手势检测 手势识别 算法 - 深度学习 卷积神经网络 opencv python
文章目录 1 简介2 传统机器视觉的手势检测2.1 轮廓检测法2.2 算法结果2.3 整体代码实现2.3.1 算法流程 3 深度学习方法做手势识别3.1 经典的卷积神经网络3.2 YOLO系列3.3 SSD3.4 实现步骤3.4.1 数据集3.4.2 图像预处理3.4.3 构建卷积神经网络结构3.4.4 实验训练过程及结果 3.5 …...
11. Spring源码篇之实例化前的后置处理器
简介 spring在创建Bean的过程中,提供了很多个生命周期,实例化前就是比较早的一个生命周期,顾名思义就是在Bean被实例化之前的处理,这个时候还没实例化,只能拿到该Bean的Class对象,如果在这个时候直接返回一…...
Python-Python高阶技巧:HTTP协议、静态Web服务器程序开发、循环接收客户端的连接请求
版本说明 当前版本号[20231114]。 版本修改说明20231114初版 目录 文章目录 版本说明目录HTTP协议1、网址1.1 网址的概念1.2 URL的组成1.3 知识要点 2、HTTP协议的介绍2.1 HTTP协议的概念及作用2.2 HTTP协议的概念及作用2.3 浏览器访问Web服务器的过程 3、HTTP请求报文3.1 H…...
P1304 哥德巴赫猜想
题目描述 输入一个偶数 N,验证 4∼N 所有偶数是否符合哥德巴赫猜想:任一大于 22 的偶数都可写成两个质数之和。如果一个数不止一种分法,则输出第一个加数相比其他分法最小的方案。例如 1010,10=3+7=5+510=3+7=5+5,则 10=5+510=5+5 是错误答案。 输入格式 第一行输入一个…...
2026届最火的六大AI辅助写作方案实际效果
Ai论文网站排名(开题报告、文献综述、降aigc率、降重综合对比) TOP1. 千笔AI TOP2. aipasspaper TOP3. 清北论文 TOP4. 豆包 TOP5. kimi TOP6. deepseek 旨在系统阐述大规模语言模型创新架构以及训练方法的DeepSeek系列论文,其核心贡献…...
webpack-blocks生态全景:从官方块到第三方扩展的完整盘点
webpack-blocks生态全景:从官方块到第三方扩展的完整盘点 【免费下载链接】webpack-blocks 📦 Configure webpack using functional feature blocks. 项目地址: https://gitcode.com/gh_mirrors/we/webpack-blocks webpack-blocks是一个革命性的w…...
从选工具到提交论文降AI率全流程避坑指南
把降AI率的整个流程从头到尾捋一遍——从第一次知网检测发现超标,到最终论文成功提交,每一步该干什么,常见问题怎么处理。 这是一篇流程性的指南,适合第一次处理论文AI率的同学从头读,也适合某个步骤卡住了来查的。 …...
让论文润色提速的秘密武器
对于每一位科研人员而言,将心血凝聚成论文初稿仅仅是万里长征的第一步。紧接着,一场更为煎熬的“拉锯战”往往在修改环节悄然打响。你是否也经历过这样的时刻:为了一个地道的表达,对着电脑屏幕逐字逐句地斟酌,耗费数小…...
SAP SD实战:用‘品目阶层’给老板打报表,别再手动筛选了(附OVSV配置步骤)
SAP SD实战:用‘品目阶层’高效生成管理层报表的完整指南 每次月底做销售报表时,你是不是还在手动筛选"男装-夏装"这类产品线数据?作为SAP SD顾问,我经历过无数次熬夜整理Excel表格的痛苦。直到真正掌握了品目阶层的报表…...
Qwen3.5-9B-AWQ-4bit惊艳效果展示:OCR辅助+场景描述真实生成作品集
Qwen3.5-9B-AWQ-4bit惊艳效果展示:OCR辅助场景描述真实生成作品集 1. 开篇:认识这个视觉理解高手 第一次看到Qwen3.5-9B-AWQ-4bit处理图片的效果时,我着实被惊艳到了。这个模型不仅能准确识别图片中的物体和场景,还能把画面内容…...
Windows 10/11下Frida逆向分析环境搭建避坑指南(含ADB驱动安装)
Windows 10/11逆向工程实战:Frida环境搭建全流程与疑难解析 逆向工程的世界就像一场数字考古,而Frida无疑是当前最趁手的工具之一。但很多新手在Windows平台搭建Frida环境时,往往会陷入Python版本地狱、ADB驱动失效、设备连接失败等连环陷阱。…...
SMBIOS字符串逆向解析技巧:从二进制数据到硬件信息全解密(含Type1实例分析)
SMBIOS字符串逆向解析技巧:从二进制数据到硬件信息全解密(含Type1实例分析) 在数字取证和硬件分析领域,SMBIOS数据结构就像一台计算机的"身份证档案库",存储着从主板序列号到电池规格等数百项硬件细节。但当…...
MySQL高可用架构实战:主主复制+Keepalived+HAProxy
技能目标理解 MySQL 高可用的核心概念与企业级架构方案掌握 MySQL 主主复制的双向同步原理与部署流程熟练配置 Keepalived 实现虚拟 IP(VIP)漂移与故障自动切换精通 HAProxy 负载均衡的健康检查、流量分发与读写分离配置完成从环境搭建到故障演练的全流程…...
快速使用 Docker 设置 Nexior AI 平台
Nexior 是一个开源项目,允许用户轻松部署自己的 AI 应用网站,功能包括 AI 问答、Midjourney 绘图、知识库问答、艺术二维码等。用户无需自己开发 AI 系统、购买 AI 账户或担心 API 支持和支付系统配置,提供零启动成本和无风险的方式通过 AI 获…...
