文章目录

  • vector介绍
  • vector和string的区别
  • 补充知识
    • initializer_list
    • emplace_back
    • 结构化绑定
  • vector的使用
    • 构造
    • 析构
    • 遍历+修改
    • insert
    • find
    • 流插入/流提取
    • vector\<vector>(杨辉三角)
  • vector模拟实现
    • 浅品STL源码
    • 构造函数
    • 拷贝构造
    • 多参数构造
    • 迭代器区间构造
    • n个val初始化
    • swap
    • operator=
    • reserve
    • size
    • capacity
    • push_back(补充解释源码里为什么要用定位new)
    • 迭代器
    • pop_back
    • insert(补充迭代器失效)
    • erase
    • clear
    • resize
    • 深层次的深拷贝问题
  • 源码


vector介绍

vector底层和string一样也是数组,类似数据结构我们介绍的顺序表。
它和string的区别是string的元素类型只能是字符类型,vector有模板可以是任何类型,包括自定义类型。

vector和string的区别

既然vector和string底层都是数组,那存char类型数据的vector<char>和string有什么区别呢?
1、虽然vector也有date接口返回底层数组首元素的指针,但是vector<char>末尾没有\0,无法兼容c语言。
2、vector插入数据只有push_back插入一个数据,find也只能找一个数据,但是string有字符串的概念,所以它有append,operator+=插入字符串,find也能找子串。

补充知识

initializer_list

在这里插入图片描述

  • 这是C++11引入的新语法,initializer_list是一种类型,使用它需要包<initializer_list>头文件。
  • 我们把一些数据用大括号括起来一起赋值给i1,这个i1类型就是initializer_list。
    它是底层在栈上开了一块空间,把常量数组存起来,有一个指针指向开始,有一个指针指向结束,所以它支持迭代器,有size()和范围for,后面讲vector多参数构造的底层实现会用到。

用法:

  • typid()可以打印一个对象或者类型的真实类型。
  • initializer_list支持迭代器,所以可以用迭代器遍历。
    在这里插入图片描述

用途:

  • C++11要引入这个新语法是一是为了支持用任意多个值直接构造初始化容器,操作如下:
	vector<int> v1({ 10, 20, 30 });vector<double> v2({ 10.1, 20.4, 30.7, 40.6, 50.5 });

这就是直接调用构造函数。
但是我们实际更喜欢用下面这种操作:

	vector<int> v1 = { 10, 20, 30 };vector<double> v2 = { 10.1, 20.4, 30.7, 40.6, 50.5 };

这里会隐式类型转换,initializer_list会先构造一个vector,再拷贝构造给给对象,编译器会优化为直接构造。

  • 二是可以用于多参数构造函数的隐式类型转换:
	struct A{A(int a1, int a2):_a1(a1),_a2(a2){}int _a1;int _a2;};vector<A> v1;A aa1(1, 1);//有名对象v1.push_back(aa1);//匿名对象v1.push_back(A(2, 3));//多参数隐式类型转换v1.push_back({4,5});

emplace_back

这里小编只能介绍它的用法,理解他还需要后面的知识。
它是模板的可变参数,在上面多参数隐式类型转换的时候像下面这样用,效率会更高。

	A1.emplace_back(4,5);

结构化绑定

	auto [x, y] = aa1;for (auto [X, Y] : v1){cout << X << ':' << Y << endl;}

当对象是一个结构对象比如结构体体时,可以用上面这种方法访问对象的成员。

vector的使用

这里小编不会一一介绍,和string基本一样的接口就跳过了,和string有区别的会挑出来介绍。

构造

在这里插入图片描述

	//默认构造vector<int> v1;v1.push_back(1);v1.push_back(2);v1.push_back(3);v1.push_back(4);//10个1构造vector<int> v2(10, 1);//迭代器区间构造vector<int> v3(v2.begin(), v2.end())

析构

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

_first不为空再delete,delete后记得把指针置空。

遍历+修改

	//使用size()for (int i = 0; i < v1.size(); i++){cout << v1[i] << " ";}cout << endl;//范围forfor (auto e : v2){cout << e << " ";}cout << endl;//迭代器vector<int>::iterator it = v3.begin();while (it != v3.end()){cout << (*it) << " ";it++;}cout << endl;

insert

在这里插入图片描述

vector的插入重载要比string少很多,并且vector是以迭代器为标准插入的,string是size_t pos为标准。

	//头插v1.insert(v1.begin(), 1000);//下标为3的位置前面插入v1.insert(v1.begin() + 3, 1000);

find

在这里插入图片描述

  • vector并没有自己的find,它用的是算法库的find,它是在一段迭代器区间查找。
  • string要自己实现find是因为string需要从某一位置开始向后查找还要查找子串。

在这里插入图片描述

算法库的find是通过一段迭代器区间找,因为迭代器区间是左闭右开的,所以找到了就返回第一个找到的元素的迭代器,没找到就返回last。

	auto it = find(v1.begin(), v1.end(), 10);if (it != v1.end()){v1.insert(it, 100);}

流插入/流提取

  • vector以及后面的容器都不再提过输入输出,要输入输出自己通过范围for或者迭代器实现。
  • string提供因为会经常输入输出字符串。

vector<vector>(杨辉三角)

在介绍vector<vector>之前我们先看一道题:
题目链接:杨辉三角
在这里插入图片描述

这道题就需要用到vector<vector>来实现C语言类似二维数组的功能。
要理解vector我们先理解它是如何实例化的,我们以vector<vector<int>>为例:

在这里插入图片描述

这里和C语言单纯的二维数组不同,vector<vector>是对象数组。

当我们要读写vector<vector<int>>需要采用和C语言一样的方法,但是底层原理已经完全不同了,C语言的指针的偏移和解引用,这里是两个函数的调用,下面代码两种写法是等价的,第一个是vector<vector>里的operator[],返回值的vector<int>&,返回值是可以修改的,第二个是vector<int>里的operator[],返回值是int&。有些读者可能会有疑问,两个函数调用不会拉低效率吗?内联函数的作用就体现出来了,operator[]很短小,所以会直接内联展开替换成指令,不会调用函数。

vv[i][j];
vv.operator[](i).operator[](j);

完整代码:

class Solution {
public:vector<vector<int>> generate(int numRows) {//定义vector<vector>+开空间vector<vector<int>> vv;//开numRows行个空间,resize第二个参数要传对象,这里是匿名对象vv.resize(numRows, vector<int>());for(int i = 0; i < numRows; i++){//每一列数据个数是列数加1vv[i].resize(i + 1, 1);}//控制行//前两行不用动,i从2开始for(int i = 2; i < vv.size(); i++){//控制列//每列的第一个和最后一个不用动for(int j = 1; j < vv[i].size() - 1; j++){vv[i][j] = vv[i - 1][j - 1] + vv[i - 1][j];}}return vv;}
};

vector模拟实现

首先我们要明确这里我们只用两个文件来实现,因为vector要严格用模板来实现,模板的声明和定义不能分离到两个文件。

浅品STL源码

在自己模拟实现之前,先看一下它的源码吧,但是小编目前功力不足,只能带大家看一部分。 首先看它的成员函数:

iterator start;
iterator finish;
iterator end_of_storage;

嗯?三个迭代器?这和string完全不一样啊,快去看看这些迭代器是什么:

typedef T value_type;
typedef value_type* iterator;

哦,原来就是原始指针啊,那就猜一下start就是指向数组首元素的指针,finish是指向有效数据结尾下一个位置的指针,end_of_storage是指向空间末尾的指针,那事实是不是这样呢,接下来应该去找begin()和end()和capacity():

iterator begin() { return start; }
iterator end() { return finish; }
size_type capacity() const { return size_type(end_of_storage - begin()); }

看来猜的大差不差,接下来就开始模拟实现vector吧。

构造函数

vector()
{ }

拷贝构造

		vector(const vector<T>& v){reserve(v.capacity());for (auto e : v){push_back(e);}}

拷贝构造本质就是一个对象想要另一个对象一样大的空间一样的值,所以可以复用reserve来开空间,再用范围for遍历把数据全部拷贝过来。
但是这里要注意一个细节,在拷贝构造之前需要把成员变量初始化,如果不初始化的话在有些编译器里就会默认是随机值,然后capacity()来调用这些随机值就会出错,这里我们初始化方法推荐在成员变量处给随机值。
那既然我们已经给了缺省值了,为什么前面还要实现一个默认构造函数呢,我们不写它直接用缺省值不就行了吗?其实就算它没有实质用处,也还是要写它,构造函数规定只要显示写了构造函数那么编译器就不会生成默认构造了,我们写了的拷贝构造就是构造函数,所以如果不写前面那个默认构造函数的话我们就无法调用默认构造函数了。
除了自己写之外我们还可以强制编译器生成默认构造:

		vector() = default;

多参数构造

		vector(initializer_list<T> i1){reserve(i1.size());for (auto e : i1){push_back(e);}}

这里就可以用前面介绍的initializer_list来实现多参数构造。

迭代器区间构造

这里我们可以用函数模板,如果我们写死为iterator的吗那只有vector能调用这里的迭代器区间构造,如果我们写成模板那其他类对象比如string的迭代器也能构造初始化,但是要数据类型匹配,比如字符就可以转化成这里vector实例化出的整型。
也就是说类模板的成员函数还可以继续写成函数模板。

		template <class InputIterator>vector(InputIterator first, InputIterator last){reserve(last - first);while (first != last){push_back(*first);first++;}}

n个val初始化

这个构造用的也挺多,比如前面讲的杨辉三角。
这里还要注意参数匹配,不匹配的话会调到迭代器区间初始化去。

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

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=(vector<T>& v){swap(v);return *this;}

这里就是我们在string部分介绍的现代写法。

reserve

		void reserve(size_t n){//若外部调用该函数需要判断if (n > capacity()){size_t old_size = size();//开新空间T* tmp = new T[n];//拷贝旧空间数据到新空间,并释放旧空间//若_start为0(nullptr),无需拷贝if (_start){//memcpy会引发深层次的深拷贝问题//memcpy(tmp, _start, sizeof(T) * old_size);for (int i = 0; i < old_size; i++){tmp[i] = _first[i];}delete[] _start;}_start = tmp;_finish = _start + old_size;_endofstorage = _start + n;}}

这里要提前把旧对象size()值保存下来,避免后面调用size() {return _finish - _first;}其中一个是旧的数据,一个是更新后的数据,结果不是我们想要的。
注意拷贝数据时不能用nencpy,详细解释见深层次的深拷贝问题。

size

//不改变调用对象的函数加const
size_t size() const
{return _finish - _first;
}

capacity

size_t capacity() const
{return _endofstorage - _first;
}

push_back(补充解释源码里为什么要用定位new)

void push_back(const T& x)
{if (_finish == _endofstorage){reserve(capacity() == 0 ? 4 : 2 * capacity());}*_finish = x;finish++;
}

这里我们直接就把x的值赋给finish所在位置了,因为我申请的空间是直接new出来的,不仅开辟了空间,还调用了构造函数初始化这块空间,所以不管x是内置类型还是自定义类型,都可以直接赋值。
但是源码里的vector空间不是直接在堆上new的,而是在内存池里malloc的空间,所以空间是没有初始化的,若空间里是自定义类型的变量,直接赋值就会出问题:
我们以string为例,先看我们实现的string的赋值运算符重载:

	string& string::operator=(const string& s) {if (*this != s){char* tmp = new char[s._size + 1];memcpy(tmp, s._str, s._size + 1);delete[] _str;_str = tmp;_size = s._size;_capacity = s._capacity;}return *this;}

这里我们用没初始化的s直接调用赋值运算符重载就会出问题,因为没初始化的s的成员变量都是随机值,比方说new char[s._size +
1]这里的_size就是随机值,那么它最终new了多少数据就是未知的,和我们的想法违背。
所以源码里还使用了定位new用来显示调用构造函数,避免出现上面的情况。

迭代器

		typedef T* iterator;typedef const T* const_iterator;iterator begin(){return _first;}iterator end(){return _finish;}const_iterator begin() const{return _first;}const_iterator end() const{return _finish;}

普通迭代器和const迭代器都要实现,以便const对象调用。

pop_back

		bool empty(){return _finish == _first;}void pop_back(){assert(!empty());_finish--;}

pop_back不用删除数据,只用把_finish–就行了,但是要注意判断数组是否为空。

insert(补充迭代器失效)

		iterator insert(iterator pos, const T& x){assert(pos >= _first && pos <= _finish);//扩容if (_finish == _endofstorage){size_t len = pos - _first;reserve(capacity() == 0 ? 4 : 2 * capacity());//更新pospos = _first + len;}//挪动数据iterator end = _finish;while (end >= pos){*(end + 1) = *end;end--;}//插入数据*pos = x;_finish++;return pos;}

这里返回值为什么是迭代器,在后面erase部分再展开讲解。
乍一看这段代码非常完美,其实潜藏了一个坑点,当数组空间不够要扩容时,因为是堆上的空间,所以大概率会异地扩,那么扩完之后_first和_finish都指向新空间了,但是pos还指向旧空间,pos不就成野指针了吗,其实这里说是野指针并不准确,官方来讲应该是迭代器失效,因为这里迭代器恰好就是原生指针,其他容器并不一定。
解决方法:只需要每次扩容的时候更新pos就行了,运用指针的相对位置,先用len记录pos到_first的距离,扩容后_first+len就得到更新后的pos了。

优化后:

		void pop_back(){assert(!empty());_finish--;}void insert(iterator pos, const T& x){assert(pos >= _first && pos <= _finish);//扩容if (_finish == _endofstorage){size_t len = pos - _first;reserve(capacity() == 0 ? 4 : 2 * capacity());//更新pospos = _first + len;}//挪动数据iterator end = _finish;while (end >= pos){*(end + 1) = *end;end--;}//插入数据*pos = x;_finish++;}

我们再来看下面这段代码:

		int x;cin >> x;auto it = find(v.begin(), v.end(), 4);if (it != v.end()){v.insert(it, x * 10);//it是否还能用?}

意思是我们随便输入一个x,然后再v里找是否有x,有的话在它前面插入10x。 这里我们想想插入过后迭代器it是否还能用?(因为标准没有规定何时扩容,所以这里一致认为insert后扩容了并且导致迭代器失效)
有的读者可能会认为当然可以啊,it的数据都更新了,但是其实是不行的,因为更新的只是形参pos,这里只是传值所以形参的改变不会影响实参。
那直接把insert的第一个参数类型改为iterator&不就行了吗,其实这也是不对的,比如下面:

v.insert(v.begin(), 30);
v.insert(it + 2, 30);

用begin函数调用insert,我们前面实现begin函数是传值返回的_first,所以返回的并不是_first本身,而是它的一份临时拷贝,那如果这里insert引用了这个返回值,就会发生权限的放大,第二钟情况也是同理,it + 2表达式的的结果也是临时对象,具有常性的。所以小编在这里提醒大家,纵使引用有万般好,也要视情况用。

erase

		void erase(iterator pos){assert(pos >= _first && pos < _finish);auto it = pos + 1;//挪动数据while (it != _finish){*(it - 1) = *it;it++;}_finish--;}//优化后iterator erase(iterator pos){assert(pos >= _first && pos < _finish);auto it = pos + 1;//挪动数据while (it != _finish){*(it - 1) = *it;it++;}_finish--;return pos;}

实现思路就是把目标位置以后的数据往前挪,开头的assert也能检查数组是否为空。

那erase后迭代器是否会失效呢?我们来看下面这段程序:

	std::vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);v.push_back(5);//删除偶数auto it = v.begin();while (it != v.end()){if ((*it) % 2 == 0){v.erase(it);//it是否失效?}it++;}

这段代码会删除v里面的的偶数数据。我们分析一下,第一次会删除2,然后后面数据依次往前挪,以前指向2的迭代器就被动指向3了,这里就出现迭起器失效的问题了,因为指向的内容被改变了,因为vs对迭代器失效进行了严格检验,所以迭代器失效后再对这个迭代器访问会直接报错,但是在g++下就能通过,因为不同编译器对迭代器失效的处理是不同的,总的来说,这里是有问题的。
并且这里还有一个隐藏问题,就是删除2了之后,所有数据往前挪,那此时迭代器就指向3了,it++后就指向4了,相当于3没判断直接跳过了。
解决方法标准已经给出了,我们来看标准库的erase:

在这里插入图片描述

它给了一个返回值iterator,它返回的是删除元素位置下一个位置的迭代器,这就是方便我们来赋值更新迭代器的,也就是用it接受这个返回值就达到了更新迭代器的目的,这样就。完美解决了迭代器失效的问题。

优化后:

	auto it = v.begin();while (it != v.end()){if ((*it) % 2 == 0){it = v.erase(it);}else{it++;}}

总结:无论insert还是erase都会造成迭代器失效,因为它们有中间迭代器参数pos,解决办法就是迭代器失效后及时更新迭代器。

clear

		void clear(){_finish = _start;}

这里只能把_start赋值给_finish,因为_start必须指向空间的开始,clear不用释放空间,交给析构函数来释放。

resize

在介绍resize之前,小编想先讲一下C++标准因为支持模板,所以它还支持了内置类型的构造函数,所以下面这些代码都是支持的:

	int i = 10;int j(10);int k = int();int m = int(10);

接下的resize的缺省参数就会用到。

		void resize(size_t n, T val = T()){if (n > size()){reserve(n);while (_finish != _start + n){(*_finish) = val;_finish++;}}else{_finish = _start + n;}}

这里resize第二个参数是模板,所以就不能像以前一样用0充当缺省值,这里最好就是用匿名对象T()当缺省值,(其实匿名对象就是调用了默认构造,因为默认构造就是不传实参也可以调用的构造)不管是内置类型还是自定义类型的默认构造的值都接近与0,int就是0,char就是ASCII码为0的’\0’,这样不管参数是内置类型还是自定义类型都可以调用。

深层次的深拷贝问题

	wusaqi::vector<string> v1;v1.push_back("1111111111111111111111");v1.push_back("1111111111111111111111");v1.push_back("1111111111111111111111");v1.push_back("1111111111111111111111");print(v1);

这段代码是用string来实例化vector,我们分别来看vector扩容前和扩容后打印出来的值:

在这里插入图片描述
在这里插入图片描述

我们看到在扩容后数组的前四个vector被置成随机值了,敏锐一点的读者应该会感觉是浅拷贝之类的问题。事实确实是这样。我们既然知道是扩容后发生的问题,那我们来看扩容接口:

		void reserve(size_t n){//若外部调用该函数需要判断if (n > capacity()){size_t old_size = size();//开新空间T* tmp = new T[n];//拷贝旧空间数据到新空间,并释放旧空间//若_start为0(nullptr),无需拷贝if (_start){memcpy(tmp, _start, sizeof(T) * old_size);delete[] _start;}_start = tmp;_finish = _start + old_size;_endofstorage = _start + n;}}

这里拷贝数据是memcpy,我们知道memcpy只会一个字节一个字节拷贝,相当于它会原封不动的把string的成员变量:_str _size _caoacity拷贝到tmp,也就是说tmp数组里的string成员变量_str和原数组里的string成员变量_str的值是一样的,指向同一块空间,那么调用delete释放原数组后tmp数组里的值也被释放了,所以就解释了为什么扩容后前四个string为随机值。
我们借用监视窗口可以看到底层数组确实是指向同一块空间:

在这里插入图片描述

这里问题就出在reserve的拷贝上,解决思路很简单,我们不用memcpy,而是for循环遍历把每一个数组里的元素然后用赋值运算符重载把元素依次赋给tmp数组,这样内置类型正常浅拷贝赋值,自定义类型就会去调用自己的赋值运算符重载完成深拷贝。

		void reserve(size_t n){//若外部调用该函数需要判断if (n > capacity()){size_t old_size = size();//开新空间T* tmp = new T[n];//拷贝旧空间数据到新空间,并释放旧空间//若_start为0(nullptr),无需拷贝if (_start){//memcpy会引发深层次的深拷贝问题//memcpy(tmp, _start, sizeof(T) * old_size);for (int i = 0; i < old_size; i++){tmp[i] = _first[i];}delete[] _start;}_start = tmp;_finish = _start + old_size;_endofstorage = _start + n;}}

源码

vector.h

#pragma once
#include <assert.h>
#include <string.h>
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;namespace wusaqi
{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(){}//vector() = default;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);}vector<T>& operator=(vector<T>& v){swap(v);return *this;}vector(initializer_list<T> i1){reserve(i1.size());for (auto e : i1){push_back(e);}}template <class InputIterator>vector(InputIterator first, InputIterator last){reserve(last - first);while (first != last){push_back(*first);first++;}}vector(int n, const T& val = T()){reserve(n);resize(n, val);}vector(size_t n, const T& val = T()){reserve(n);resize(n, val);}~vector(){if (_start){delete[] _start;_start = _finish = _endofstorage = nullptr;}}void reserve(size_t n){//若外部调用该函数需要判断if (n > capacity()){size_t old_size = size();//开新空间T* tmp = new T[n];//拷贝旧空间数据到新空间,并释放旧空间//若_start为0(nullptr),无需拷贝if (_start){//memcpy会引发深层次的深拷贝问题//memcpy(tmp, _start, sizeof(T) * old_size);for (int i = 0; i < old_size; i++){tmp[i] = _start[i];}delete[] _start;}_start = tmp;_finish = _start + old_size;_endofstorage = _start + n;}}T& operator[](size_t i){//断言防止越界访问assert(i < size());return _start[i];}//不改变调用对象的函数加constsize_t size() const{return _finish - _start;}size_t capacity() const{return _endofstorage - _start;}void push_back(const T& x){//size_t storage = capacity();//if (_finish == _endofstorage)//{//	size_t newstorage = storage == 0 ? 4 : 2 * storage;//	reserve(newstorge);//}//简化:if (_finish == _endofstorage){reserve(capacity() == 0 ? 4 : 2 * capacity());}*_finish = x;_finish++;}bool empty(){return _finish == _start;}void pop_back(){assert(!empty());_finish--;}iterator insert(iterator pos, const T& x){assert(pos >= _start && pos <= _finish);//扩容if (_finish == _endofstorage){size_t len = pos - _start;reserve(capacity() == 0 ? 4 : 2 * capacity());//更新pospos = _start + len;}//挪动数据iterator end = _finish;while (end >= pos){*(end + 1) = *end;end--;}//插入数据*pos = x;_finish++;return pos;}iterator erase(iterator pos){assert(pos >= _start && pos < _finish);auto it = pos + 1;//挪动数据while (it != _finish){*(it - 1) = *it;it++;}_finish--;return pos;}void clear(){_start = _finish;}void resize(size_t n, const T& val = T()){if (n > size()){reserve(n);while (_finish != _start + n){(*_finish) = val;_finish++;}}else{_finish = _start + n;}//size_t capacity = _endofstorage - _start;//size_t size = _finish - _start;//if (n < size)//{//	_finish = _start + n;//}//else//{//	if (n > capacity)//	{//		reserve(n);//		_endofstorage = _start + n;//	}//	iterator pos = _finish;//	while (pos != _start + n)//	{//		(*pos) = val;//		pos++;//	}//	_finish = _start + n;//}}private:iterator _start = nullptr;iterator _finish = nullptr;iterator _endofstorage = nullptr;};
}

test.cpp

#define _CRT_SECURE_NO_WARNINGS 1
#include "vector.h"template<class T>
void print(const T& v)
{for (auto ch : v){cout << ch << " ";}cout << endl;
}void test01()
{wusaqi::vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);v.push_back(5);v.pop_back();for (int i = 0; i < v.size(); i++){cout << v[i] << " ";}cout << endl;for (auto ch : v){cout << ch << " ";}cout << endl;print(v);
}void test02()
{wusaqi::vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);//v.push_back(5);v.insert(v.begin(), 30);print(v);//v.erase(v.begin() + 2);//print(v);int x;cin >> x;wusaqi::vector<int>::iterator it = find(v.begin(), v.end(), x);if (it != v.end()){v.erase(it);}print(v);}void test03()
{wusaqi::vector<int> v;v.push_back(1);v.push_back(2);v.push_back(5);v.push_back(2);v.push_back(3);v.push_back(4);v.push_back(5);v.push_back(4);print(v);//删除偶数auto it = v.begin();while (it != v.end()){if ((*it) % 2 == 0){it = v.erase(it);}else{it++;}}print(v);v.clear();print(v);
}void test04()
{wusaqi::vector<int> v;v.push_back(1);v.push_back(2);v.push_back(5);v.push_back(2);v.push_back(3);v.push_back(4);v.push_back(5);v.push_back(4);print(v);v.resize(10, 100);print(v);//int i = 10;//int j(10);//int k = int();//int m = int(10);
}void test05()
{wusaqi::vector<int> v1;v1.push_back(1);v1.push_back(2);v1.push_back(3);v1.push_back(4);v1.push_back(5);print(v1);wusaqi::vector<int> v2(v1);print(v2);wusaqi::vector<int> v3 = { 1, 2, 3, 4, 5, 1, 1, 1 };print(v3);wusaqi::vector<int> v4(v3.begin() + 2, v3.end() - 2);print(v4);string s1("good morning");wusaqi::vector<int> v5(s1.begin() + 2, s1.end() - 2);print(v5);std::vector<int> v6(10, 1);print(v6);std::vector<size_t> v7(10, 1);print(v7);
}void test06()
{wusaqi::vector<int> v1;v1.push_back(1);v1.push_back(2);v1.push_back(3);v1.push_back(4);v1.push_back(5);print(v1);wusaqi::vector<int> v3;v3 = v1;print(v3);
}void test07()
{wusaqi::vector<string> v1;v1.push_back("1111111111111111111111");v1.push_back("1111111111111111111111");v1.push_back("1111111111111111111111");v1.push_back("1111111111111111111111");v1.push_back("1111111111111111111111");print(v1);
}int main()
{//test01();//cout << typeid(std::vector<int>::iterator).name() << endl;//cout << typeid(wusaqi::vector<int>::iterator).name() << endl;//test02();//test03();//test04();test05();//test06();//test07();return 0;
}

以上就是小编分享的全部内容了,如果觉得不错还请留下免费的关注和收藏
如果有建议欢迎通过评论区或私信留言,感谢您的大力支持。
一键三连好运连连哦~~

在这里插入图片描述

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

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

相关文章

MySql升级安装、socket 及密码重置

升级 项目需要使用Mysql8.0, 查看自己的ubuntu22.04上mysql版本为5.7&#xff0c; 使用以下命令自动升级到8.0版本。 sudo apt install Mysqlsock错误&#xff1a; Can’t connect to local MySQL server through socket 运行mysql -u -p 报以下错误&#xff1a; ERROR 200…

Python网络爬虫技术:从入门到实战

在当今数字化时代&#xff0c;网络爬虫技术已经成为数据挖掘和信息收集的重要工具。通过网络爬虫&#xff0c;我们可以高效地从互联网上获取大量有价值的数据&#xff0c;用于数据分析、市场研究、学术研究等多种场景。本文将带你从零开始&#xff0c;了解Python网络爬虫的基本…

偏微分方程初值问题求解

题目 问题 2. (a) u t + 3 u x − 2 u y = x ; u t + x u x + y u y = x ; u_t + 3u_x - 2u_y = x; \quad u_t + xu_x + yu_y = x; ut​+3ux​−2uy​=x;ut​+xux​+yuy​=x; u t + x u x − y u y = x ; u t + y u x + x u y = x ; u_t + xu_x - yu_y = x; \quad u_t + yu_…

【专业梳理】PMP知识体系,以SIPOC流程图为核心的质量工具扩展

​​1. SIPOC流程图:质量管理的起点​​ SIPOC(Supplier-Input-Process-Output-Customer)是六西格玛和流程管理中的核心工具,用于定义和优化跨职能流程。在PMBOK中,它与质量管理知识领域(尤其是质量规划、质量保证)紧密关联: ​​质量规划​​:通过SIPOC明确流程边界…

OpenCV指定pid和vid通过MSMF打开摄像头

在基于OpenCV的项目中&#xff0c;实际开发过程会面临设备上存在多个摄像头&#xff0c;需要指定摄像头的pid和vid打开摄像头。在OpenCV通过MSMF打开摄像头时&#xff0c;需要传入摄像头的index&#xff0c;因此需要在打开该摄像头前需要找出摄像头的index&#xff0c;下面给出…

STM32F103ZET6系统启动过程

STM32F103ZET6系统启动过程 一、概述 STM32F103ZET6启动过程指硬件选择启动模式后,执行固件程序之前的一系列动作。对于系统存储器模式,系统执行Bootloader程序升级状态,检测数据进行串口升级;对于内部Flash模式,系统执行启动文件,设置堆栈大小,配置系统时钟,最终调用…

[Data Pipeline] Kafka消息 | Redis缓存 | Docker部署(Lambda架构)

第七章&#xff1a;Kafka消息系统&#xff08;实时流处理&#xff09; 欢迎回到数据探索之旅&#xff01; 在前六章中&#xff0c;我们构建了强大的**批量处理流水线**。 通过Airflow DAG&#xff08;批量任务编排&#xff09;协调Spark作业&#xff08;数据处理&#xff09;…

jquery 赋值时不触发change事件解决——仙盟创梦IDE

一、传统方法jquey change $(#village_id).trigger(change);$("#village_id").val(99);$("#village_id").change(); 不生效 二、传统方法jquey $(#village_id).trigger(change); 四、传统方法jquey <input type"text" /> <button…

Android | 签名安全

检验和签名 校验开发者在数据传送时采用的一种校正数据的一种方式&#xff0c; 常见的校验有:签名校验(最常见)、dexcrc校验、apk完整性校验、路径文件校验等。 通过对 Apk 进行签名&#xff0c;开发者可以证明对 Apk 的所有权和控制权&#xff0c;可用于安装和更新其应用。…

Android14 耳机按键拍照

在相机拍照预览界面 通过耳机按键实现拍照功能 耳机按键定义 frameworks/base/core/java/android/view/KeyEvent.java public static final int KEYCODE_HEADSETHOOK 79;相机界面 拍照逻辑 DreamCamera2\src\com\android\camera\PhotoModule.java Override public bool…

【AI作画】第2章comfy ui的一般输入节点,文本框的类型和输入形式

目录 CLIP文本编码器 条件输出和文本输出 转换某一变量为输入 展示作品集 在默认的工作流之外&#xff0c;我们如何自己添加节点呢&#xff1f; 一般我们用到的sampler采样器在“鼠标右键——添加节点——采样——K采样器” 我们用的clip文本编码器在“鼠标右键——添加节…

vue3仿高德地图官网路况预测时间选择器

<template><div class"time-axis-container"><div class"time-axis" ref"axisRef"><!-- 刻度线 - 共25个刻度(0-24) --><divv-for"hour in 25":key"hour - 1"class"tick-mark":class&…

ZArchiver:高效解压缩,轻松管理文件

在数字时代&#xff0c;文件的压缩与解压已成为我们日常操作中不可或缺的一部分。无论是接收朋友分享的大文件&#xff0c;还是下载网络资源&#xff0c;压缩包的处理都极为常见。ZArchiver正是一款为安卓用户精心打造的解压缩软件&#xff0c;它以强大的功能、简洁的界面和高效…

1432.改变一个整数能得到的最大差值

贪心思想&#xff0c;为了得到最大差&#xff0c;想办法变成一个最大的数和一个最小的数。 这里有规则&#xff0c;从最高位开始&#xff0c; 变成最大&#xff0c;如果<9&#xff0c;则将该数位代表的数都变成9&#xff0c;如果该数位已经是9了&#xff0c;则将下一个数位…

前端跨域解决方案(4):postMessage

1 postMessage 核心 postMessage 是现代浏览器提供的跨域通信标准 API&#xff0c;允许不同源的窗口&#xff08;如主页面与 iframe、弹出窗口、Web Worker&#xff09;安全交换数据。相比其他跨域方案&#xff0c;它的核心优势在于&#xff1a; 双向通信能力&#xff1a;支持…

大语言模型指令集全解析

在大语言模型的训练与优化流程中&#xff0c;指令集扮演着关键角色&#xff0c;它直接影响模型对任务的理解与执行能力。以下对常见指令集展开详细介绍&#xff0c;涵盖构建方式、规模及适用场景&#xff0c;助力开发者精准选用 为降低指令数据构建成本&#xff0c;学术界和工…

OpenCV CUDA模块设备层-----用于封装CUDA纹理对象+ROI偏移量的一个轻量级指针类TextureOffPtr()

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 TextureOffPtr<T, R> 是 OpenCV 的 CUDA 模块&#xff08;opencv_cudev&#xff09;中用于封装 CUDA 纹理对象 ROI 偏移量 的一个轻量级指…

Python 数据分析10

2.3.3其他 除了前面所介绍的常用语数据挖掘建模的库之外&#xff0c;还有许多库也运用于数据挖掘建模&#xff0c;如jieba、SciPy、OpenCV、Pillow等。 1.jieba jieba是一个被广泛使用的Python第三方中文分词库。jieba使用简单&#xff0c;并且支持Python、R、C等多种编程语言的…

css 制作一个可以旋转的水泵效果

如图&#xff0c;项目里面有一个小图片可以旋转&#xff0c;达到看起来像是一个在工作的水泵。我使用css旋转动画实现。 一、HTML结构部分 <div className"ceshixuanzhuan"><img src{lunkuo} className"lunkuo"/><img src{yepian} classN…

数据结构期末程序题型

一、 队列 1、简单模拟队列排列 #include<bits/stdc.h> using namespace std; int main(){int n;cin>>n;queue<int>q;string str;while(true){cin>>str;if(str"#")break;if(str"In"){int t;cin>>t;if(q.size()<n){q.pu…