C++打怪升级(十)- STL之vector

~~~~

  • 前言
  • 1. vector 是什么
  • 2. 见见vector的常用接口函数吧
    • 构造函数
      • 无参构造函数
      • 使用n个val构造
      • 拷贝构造
      • 使用迭代器范围构造
      • 初始化形参列表构造
    • 析构函数
    • 赋值运算符重载函数
    • 元素访问
      • []运算符重载函数访问
      • at函数访问
      • front函数
      • back函数
    • 迭代器相关
      • 正向迭代器
      • 反向迭代器
    • 容量相关
      • size函数
      • capacity函数
      • empty函数
      • reserve函数
      • resize函数
    • 增删查改相关
      • push_back函数
      • pop_back
      • assign函数
      • insert函数
      • erase函数
      • clear函数
      • swap函数
      • emplace函数
    • 非成员函数 - 关系运算符重载函数
      • ==
      • !=
      • <
      • <=
      • >
      • >=
    • 非成员函数 - 其他函数
      • swap函数
  • 3. 揭开vector的真面目-模拟实现吧
    • 底层成员变量
    • 构造函数
      • 默认无参构造
      • 使用n个val构造
      • 使用迭代器范围构造
    • 拷贝构造函数
    • 赋值运算符重载
    • 析构函数
    • 迭代器相关
      • 迭代器的实现 - 原生指针
      • 正向迭代器
    • 元素访问
      • operator[]
    • 容量相关
      • size函数
      • capacity函数
      • reserve函数
      • resize函数
      • empty函数
    • 增删查改相关
      • push_back函数
      • insert函数
        • 插入一个元素
        • insert的迭代器失效问题
        • 插入多个元素
      • erase函数
        • erase的迭代器失效问题
      • assign函数
    • 非成员函数 - swap函数
  • 4. vector的一些深拷贝问题
  • 结语

在这里插入图片描述

前言

本节将要介绍STL容器中的vector的常用接口函数,简单模拟实现一下vector。


1. vector 是什么

vector可变大小的顺序容器,类似于数组,存放元素的空间是连续的,所以可以实现类似于数组的随机访问;
相对于其他容器deque、list、forward_list,vector的优势:

  • 尾插和尾删操作相对高效,访问元素时也是相当高效;

劣势:

  • 头插和中间插入元素、头删和中间删除元素的操作由于涉及到大量元素的移动,效率很低;
  • vector的空间可以动态的开辟,开辟空间是存在系统资源的消耗的,所以vector不是每新增一个元素就重新开辟空间,而是再空间满时开辟适当的空间,具体开辟多少空间取决于不同版本实现者的考虑。虽然每次开辟的空间没有具体要求,但是需要保证开辟空间是以对数间隔进行的,以便于每次在结束位置插入数据时vector能提供均摊的常数时间复杂度;
  • 与string的元素类型是char不同,vector是一个类模板,这意味着它可以接受任意类型的参数,存放任意类型的元素;

2. 见见vector的常用接口函数吧

构造函数

无参构造函数

函数声明

vector();

例子

vector<int> v;

注意不能写成vector<int> v();,定义初始化时,如果是无参构造或是全缺省的构造不加括号(),否则编译器会把这句代码识别成无参、返回类型是vector<int>的函数声明;只有这句代码并不会报错,但当你把v当做vector<int>类型的变量去使用时就会出现问题,因为编译器认为v是一个函数指针;

使用n个val构造

声明

vector(size_t n, const value_type& val = value_type());

eg

vector<char> v1(3, 'x'); // 结果为 xxx,没有'\0'
vector<int> v2(3, 7) 	 // 结果为 777

拷贝构造

声明

vector (const vector& x);

例子

// 5个9构造vector<int>类型对象 v1
vector<int> v1(5, 9);vector<int> v2(v1);

使用迭代器范围构造

声明
两个参数first和last都是迭代器类型,且该函数时函数模板,迭代器类型可以指定,即可以使用其他顺序容器的迭代器构造vector;

template <class InputIterator>vector (InputIterator first, InputIterator last);

eg

vector<int> v1(5, 6); // 66666vector<int>v2(v1.begin(), v1,begin() + 3); // 666

初始化形参列表构造

声明
initializer_list是一个类模板,使用由大括号括起来的以逗号分隔的元素构造initializer_list对象;

vector (initializer_list<value_type> il);

eg

vector<int> v1{1,2,3,4,5}; // 1 2 3 4 5
vector<int> v2 = {3,4,5,6,7); // 3 4 5 6 7

析构函数

声明
在vector对象销毁之前调用,释放vector对象申请的资源;

~vector();

赋值运算符重载函数

声明

vector& operator= (const vector& x);

eg
直接vector v2 = v1; 使用会被编译器当做定义初始化而调用拷贝构造函数,而不是赋值运算符重载函数;

vector<int> v1(5, 6); // 66666
vector<int> v2;v2 = v1; // 66666

元素访问

[]运算符重载函数访问

声明
[]运算符重载函数会在内部对传入的坐标参数进行严格检查,超出有效坐标范围将会报错,这与系统的抽查不同;
[]运算符重载函数既需要满足读也要满足写,所以实现了非const版本和const版本;

reference operator[] (size_type n);
const_reference operator[] (size_type n) const;

eg

非const

vector<int> v{ 1,2,3,4,5 };
for (int i = 0; i < v.size(); ++i) {v[i]++;cout << v[i] << " "; // 结果为2 3 4 5 6
}
cout << endl;

const

const vector<int> v{ 1,2,3,4,5 };
for (int i = 0; i < v.size(); ++i) {//v[i]++; 不能修改const对象成员cout << v[i] << " "; // 结果为1 2 3 4 5
}
cout << endl;

at函数访问

声明
at也会对传入的坐标参数进行判断,超出坐标范围后会抛异常,而不是报错;

reference at (size_type n);
const_reference at (size_type n) const;

eg

非const

vector<int> v{ 1,2,3,4,5 };
for (int i = 0; i < v.size(); ++i) {v.at(i)++;cout << v.at(i) << " ";
}
cout << endl;

const

const vector<int> v{ 1,2,3,4,5 };
for (int i = 0; i < v.size(); ++i) {cout << v.at(i) << " ";
}
cout << endl;

front函数

声明
返回第一个元素的引用

reference front();
const_reference front() const;

eg

vector<int> v{1,2,3,4};
v.first(); // 结果为 1

back函数

声明
返回最后一个元素的引用

reference back();
const_reference back() const;

eg

vector<int> v{1,2,3};
v.back(); // 结果为 3

迭代器相关

迭代器是像指针一样的类型,使用时需要解引用*

正向迭代器

定义
包括非const和const迭代器;

返回vector对象内元素起始位置的迭代器

iterator begin() noexcept;
const_iterator begin() const noexcept;

返回vector对象内元素结束位置的下一个位置的迭代器

iterator end() noexcept;
const_iterator end() const noexcept;

eg

vector<int> v1{ 1,2,3,4,5 };
vector<int>::iterator it = v1.begin();
while (it != v1.end()) {cout << *it << " "; // 结果为 1 2 3 4 5it++;
}
cout << endl;
const vector<int> v2{ 6,7,8,9,0 };
vector<int>::const_iterator cit = v2.begin();
while (cit != v2.end()) {cout << *cit << " "; // 结果为6 7 8 9 0cit++;
}
cout << endl;

反向迭代器

定义

返回vector对象内最后一个元素的所在位置的迭代器

reverse_iterator rbegin() noexcept;
const_reverse_iterator rbegin() const noexcept;

返回vector对象内第一个位置的元素的前一个理论位置的迭代器

reverse_iterator rend() noexcept;
const_reverse_iterator rend() const noexcept;

eg

容量相关

size函数

声明
返回vector对象的元素个数

size_type size() const noexcept;

eg

vector<int> v{1,2,3,4,5};
v.size(); // 结果为 5

capacity函数

声明
返回vector对象的引用

size_type capacity() const noexcept;

eg

vector<int> v{1,2,3,4,5};
v.capacity(); // 结果为 5

empty函数

声明
判断vector对象是否为空,是则返回true;否则返回false

bool empty() const noexcept;

eg

vector<int> v1; // 空
vector<int> v2(2, 3); // 33v1.empty(); // true
v2.empty(); // false

reserve函数

声明
参数size_type n,表示要申请开辟的容量大小;
改变vector对象的容量capacity,当n小于等于当前vector对象的容量capacity时,reserve函数什么也不做;
只有当n大于vector对象的容量时,reserve函数才会向系统申请开辟空间;即reserve函数一般只进行扩容处理,不缩容;

void reserve (size_type n);

eg

vector<int> v{ 1,2,3,4,5 }; // capacity为 5v.reserve(3); // capacity为 5v.reserve(5); // capacity为 5v.reserve(10); // capacity为 10

resize函数

声明
参数size_t n表示vector对象有效元素的个数;
如果n小于vector原有的size,相当于删除元素直到size为n;
如果n等于vector原有size就什么也不做;
如果n大于vector原有size且小于等于vector的原有capacity,相当于尾插元素直到size为n;
如果n大于vector原有的capacity,相当于先扩容使容量增大,再尾插元素直到size为n;

void resize (size_type n);
void resize (size_type n, const value_type& val);

eg

vector<int> v2{1,2,3,4,5};v2.reserve(10);cout << v2.size() << endl;cout << v2.capacity() << endl;print(v2);v2.resize(3);cout << v2.size() << endl;cout << v2.capacity() << endl;print(v2);v2.resize(8, 1);cout << v2.size() << endl;cout << v2.capacity() << endl;print(v2);v2.resize(15, 2);cout << v2.size() << endl;cout << v2.capacity() << endl;print(v2);

image.png

增删查改相关

push_back函数

声明
在vector对象内最后一个元素的末尾增加一个元素,增加的元素参数val的拷贝;

void push_back (const value_type& val);
void push_back (value_type&& val);

eg

vector<int> v;
for (int i = 0; i < 5; ++i) {v.push_back(i);
}
// v的内容为 0 1 2 3 4 

pop_back

声明
删除vecrot的最后一个元素,但是debug模式下,当vector为空时调用pop_back尾删元素时程序会出错;

void push_back (const value_type& val);
void push_back (value_type&& val);

eg

vector<int> v;for (int i = 0; i < 5; ++i) {v.push_back(i);
}
// v的内容为 0 1 2 3 4 
for (int i = 0; i < 5; ++i) {v.pop_back();
}
// v的内容为 空

assign函数

声明
使用参数迭代器first和last表示元素的起始位置和结束位置;
使用迭代器范围内的元素为vector赋值,同时会把vector原先的内容去除;
这个范围是左闭右开的区间[first, last]

// range (1)
template <class InputIterator>
void assign (InputIterator first, InputIterator last);

eg

vector<int> v1{ 1,2,3,4 };
vector<int> v2;
v2.assign(v1.begin(), v1.end()); // 结果为 1 2 3 4

声明
使用size_t类型的nvalue_type类型的val为vector赋值;

// fill (2)	
void assign (size_type n, const value_type& val);

eg

vector<int> v1{ 1,2,3,4 };
vector<int> v2;
v2.assign(3, 5); // 结果为 555

声明
使用初始化列表对vector对象进行赋值

// initializer list (3)	
void assign (initializer_list<value_type> il);

eg

vector<int> v2;
v2.assign({ 1,2,3,4,5 }); // 结果为 1 2 3 4 5

insert函数

声明
参数const_iterator pos是vector内的元素对应位置的迭代器;
参数const value_type是待插入的值;
在迭代器位置pos之前插入一个元素,该元素是val的拷贝;

// single element (1)
iterator insert (const_iterator pos, const value_type& val);

eg

vector<int> v3{ 1,2,3,4,5 }; 
v3.insert(v3.begin() + 3, 9); // 结果为 1 2 3 9 4 5

声明
参数const_iterator pos是vector内的元素对应位置的迭代器;
参数size_type n是插入元素的个数;
参数const value_type是待插入的值;
在迭代器位置pos之前插入n个元素;

// fill (2)	
iterator insert (const_iterator pos, size_type n, const value_type& val);

eg

vector<int> v3{ 1,2,3,4,5 }; 
v3.insert(v3.begin() + 3, 59); // 结果为 1 2 3 9 9 9 9 9 4 5

声明
参数const_iterator pos是vector内的元素对应位置的迭代器;
参数first、last是迭代器范围,first是起始位置,last是结束位置;
在迭代器pos位置之前依次插入[first, last)范围内的所有元素;

// range (3)	
template <class InputIterator>
iterator insert (const_iterator pos, InputIterator first, InputIterator last);

eg

vector<int> v1{ 1,2,3,4,5 };vector<int> v2{ 1,2,3,4,5 };
v4.insert(v2.begin()+ 4, v1.begin(), v1.begin() + 3); // 结果为 1 2 3 4 1 2 3 5

声明

// initializer list (5)	
iterator insert (const_iterator pos, initializer_list<value_type> il);

eg

vector<int> v{ 1, 2, 3, 4 };
v.insert(v.begin(), { 2, 3 ,3 }); // 结果是 1 2 3 2 3 3 4

erase函数

声明
参数const_iterator pos表示vector内元素所在位置的迭代器;
删除pos位置的元素;

iterator erase (const_iterator pos);

eg

vector<int> v{ 1,2,3,4,5 };
v.erase(v.begin() + 2);

声明
参数first是待删除范围的起始元素的位置;
参数last是待删除范围的最后一个元素的下一个位置;
删除迭代器范围内的所有元素;

iterator erase (const_iterator first, const_iterator last);

eg

vector<int> v{ 1,2,3,4,5 };
v.erase(v1.begin() + 1, v.begin() + 4);

clear函数

声明
清空vector对象,删除所有元素;

void clear() noexcept;

eg

vector<int> v2{ 9, 9, 9, 9, 9 }; // size = 5, capacity = 5v2.clear(); // size = 0, capacity = 5

swap函数

声明
与另一个vector对象进行内容的交换,也包括size和capacity;

void swap (vector& x);

eg

vector<int> vv1{ 1,2,3,4,5 };   // size=5, capacity=5;
vector<int> vv2{ 6, 6, 6 }; 	// size=3, capacity=3;vv1.swap(vv2); 
// vv1 -> 内容:6 6 6       size=3, capacity=3; 
// vv2 -> 内容:1 2 3 4 5   size=5, capacity=5

emplace函数

声明
参数pos是vector的元素对应位置的迭代器
参数args是待插入的元素
pos位置之前插入元素,类似于insert

template <class... Args>
iterator emplace (const_iterator pos, Args&&... args);

eg

vector<int> vvv1{ 1, 2, 3, 4, 5 };
vvv1.emplace(vvv1.begin() + 3, 233); // 1 2 3 233 4 5
vvv1.emplace(vvv1.end(), 1145); 	 // 1 2 3 233 4 5 1145 

非成员函数 - 关系运算符重载函数

==

声明
比较两个vector对象v1, v2的内容是否相同;
比较过程:先比较v1和v2的size是否相等,不相等直接不相等;相等就继续顺序比较二者的元素,如果存在一对不相等的元素就是不相等;元素都相等时才是相等;
不比较二者的cappacity;

template <class T>
bool operator==(const vector<T>& v1, const vector<T>& v2);

eg

vector<int> v1{ 1,2,3,4,5 };
vector<int> v2{ 1,2,3,4,5 };
v1 == v2;		// true
v1.reserve(10);
v1 == v2;       // true
v1.resize(3);
v1 == v2;       // false

image.png

!=

声明

template <class T>
bool operator!=(const vector<T>& v1, const vector<T>& v2);

eg

vector<int> v1{ 1,2,3,4,5 };
vector<int> v2{ 1,2,3,4,5 };
v1 != v2;		// false

<

声明

template <class T>
bool operator<(const vector<T>& v1, const vector<T>& v2);

eg

vector<int> v1{ 1,2,3,4,5 };
vector<int> v2{ 1,2,3,4,5 };
v1 < v2; // false

<=

声明

template <class T>
bool operator<=(const vector<T>& v1, const vector<T>& v2);

eg

vector<int> v1{ 1,2,3,4,5 };
vector<int> v2{ 1,2,3,4,5 };
v1 <= v2; // true

>

声明

template <class T>
bool operator>(const vector<T>& v1, const vector<T>& v2);

eg

vector<int> v1{ 1,2,3,4,5 };
vector<int> v2{ 1,2,3,4,5 };
v1 > v2; // false

>=

声明

template <class T>
bool operator>=(const vector<T>& v1, const vector<T>& v2);

eg

vector<int> v1{ 1,2,3,4,5 };
vector<int> v2{ 1,2,3,4,5 };
v1 >= v2; // true

非成员函数 - 其他函数

swap函数

声明
与另一个vector对象进行内容的交换,也包括size和capacity;
与成员函数swap相比,第一个参数不再是隐式的this了;

template <class T>void swap (vector<T>& x, vector<T>& y);

eg

vector<int> v1{6, 6, 6, 6};   // size=4, capacity=4
vector<int> v2{1, 2, 3, 4, 5} // size=5, capacity=5
swap(v1, v2);
// v1 -> 1,2,3,4,5  size=5, capacity=5
// v2 -> 6,6,6,6    size=4, capacity=4

3. 揭开vector的真面目-模拟实现吧

底层成员变量

vector类模板的底层设计中包含三个T类型的指针:
_start指向空间的起始地址,也是第一个元素的地址;
_finish指向最后一个元素的下一个位置;
_end_of_storage指向空间的结尾位置的下一个位置;

template<class T>
class vector {
public:
private:T* _start;T* _finish;T* _end_of_storage;
};

image.png

构造函数

默认无参构造

不传参数时把三个指针都初始化为nullptr

vector():_start(nullptr),_finish(nullptr),_end_of_storage(nullptr){ }

使用n个val构造

new申请的空间大小是n*sizeof(T),别忘记类型T的大小;

vector(size_t n, const T& val = T()) {_start = new T[n * sizeof(T)];_end_of_storage = _finish = _start + n;for (int i = 0; i < _finish - _start; ++i) {_start[i] = val;}
}

与上面的函数都是使用n个val构造,只是上面函数n的类型是size_t,而本函数n的类型是int
对于vector<int> v(5, 1)这句代码,编译器将会调用vector(InputIterator first, InputIterator last)函数,而不是vector(size_t n, const T& val = T())
首先模板参数T实例化为int,而5和1默认作为int类型参数处理,两个参数类型一致,编译器会把vector(InputIterator first, InputIterator last)的InputIterator模板参数实例化为int,即vector(int first,int last)从而更符合参数5和1的类型,但是5和1不是迭代器范围,编译时报错;
所以需要额外实现函数vector(int n, const T& val = T())可以直接匹配5和1参数;
对于vector<int> v(5, 'a')等情况则仍将会正常调用vector(size_t n, const T& val)函数的;

vector(int n, const T& val = T()) {_start = new T[n * sizeof(T)];_end_of_storage = _finish = _start + n;for (int i = 0; i < _finish - _start; ++i) {_start[i] = val;}
}

使用迭代器范围构造

template<class InputIterator>
vector(InputIterator first, InputIterator last) {size_t n = last - first;reserve(n);iterator it = begin();while (first != last) {(*it) = (*first);it++;first++;}_finish = _start + n;
}

拷贝构造函数

vector(const vector<T>& v):_start(nullptr), _finish(nullptr), _end_of_storage(nullptr) {reserve(v.capacity());iterator it = begin();const_iterator cit = v.begin();while (cit != v.end()) {*it = *cit;++it;++cit;}_finish = it;
}

image.png

vector(const vector<T>& v) :_start(nullptr),_finish(nullptr),_end_of_storage(nullptr){T* tmp = new T[v.capacity()];for (int i = 0; i < v.size(); ++i) {tmp[i] = v[i];}_start = tmp;_finish = _start + v.size();_end_of_storage = _start + v.capacity();
}

赋值运算符重载

现代写法,提高了代码书写效率
开空间交给了拷贝构造函数,而不是我们再次实现;

vector<T>& operator=(vector<T> v) {swap(v);return *this;
}

传统写法

vector<T>& operator=(const vector<T>& v) {// 不能自己给自己赋值if (*this != v) {T* tmp = new T[v.capacity()];for (int i = 0; i < v.size(); ++i) {tmp[i] = v[i];}//delete[] _start;_start = tmp;_finish = _start + v.size();_end_of_storage = _start + v.capacity();}return *this;
}

析构函数

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

迭代器相关

迭代器的实现 - 原生指针

typedef T* iterator;
typedef const T* const_iterator;

迭代器的返回类型,传值返回还是传引用返回?
对于非const版本,都可以,一般传值返回即可;
对于const版本,只能传值返回;因为引用返回会导致权限的放大,this指针类型是vector<T>*const,是指针常量,const修饰时变为vector<T> const * const,常量指针常量,函数返回类型const_iterator&const T* &,返回指针变量_start是this指针指向的内容,故类型是T* const,所以由T*constconst T *将会出错;

正向迭代器

非const版本

begin返回指向开始元素的迭代器,即_start
end返回指向最后一个元素的下一个位置的迭代器,即_finish

image.png

iterator begin() {return _start;
}
iterator end() {return _finish;
}

const版本

const_iterator begin() const{return _start;
}
const_iterator end() const {return _finish;
}

元素访问

operator[]

访问pos位置的元素,注意需要对pos进行检查,防止越界访问;

非const版本

T& operator[](size_t pos) {assert(pos < size());return _start[pos];
}

const版本

const T& operator[](size_t pos) const {assert(pos < size());return _start[pos];
}

容量相关

size函数

vector没有直接记录元素个数的变量,通过_finish - _start指针相减计算;

size_t size() const {return _finish - _start;
}

capacity函数

vector没有直接记录空间容量的变量,通过_finish - _start指针相减计算;

size_t capacity() const {return _end_of_storage - _start;
}

reserve函数

改变vector对象的容量capacity,
n小于等于当前vector对象的容量capacity时,reserve函数什么也不做;
只有当n大于vector对象的容量时,reserve函数才会向系统申请开辟空间;即reserve函数一般只进行扩容处理,不缩容;

void reserve(size_t n) {if(n <= capacity()) return;T* tmp = new T[n];for (int i = 0; i < size(); ++i) {tmp[i] = _start[i];}// 保存旧空间,防止_start更新后直接调用size()函数导致计算错误,// 此时_start是新的,而_finish还是旧的;int oldsize = size();delete[] _start;_start = tmp;_finish = _start + oldsize;_end_of_storage = _start + n;
}

resize函数

如果n小于vector原有的size,相当于删除元素直到size为n,_finish更新为_start + n即可;
如果n等于vector原有size就什么也不做;
如果n大于vector原有size且小于等于vector的原有capacity,相当于尾插元素直到size为n,即每次把val拷贝给_finish指向位置的元素,再++_finish直到_finish等于_start+n
如果n大于vector原有的capacity,相当于先扩容使容量增大,再尾插元素直到size为n;

void resize(size_t n, const T& val = T()) {// 小于 if (n < size()) {_finish = _start + n;}else if (n < capacity()) {while (_finish < _start + n) {*_finish = val;++_finish;}}else {reserve(n);while (_finish < _start + n) {*_finish = val;++_finish;}}
}

empty函数

bool empty() const {return size() == 0;
}

增删查改相关

push_back函数

dd

void push_back(const T& val) {if (_finish == _end_of_storage) {int newcapacity = _end_of_storage - _start == 0 ? 4 : 2 * (_end_of_storage - _start);reserve(newcapacity);}*_finish = val;_finish++;
}

insert函数

再pos迭代器位置之前插入元素

插入一个元素

image.png

iterator insert(iterator pos, const T& val) {assert(pos >= _start && pos < _finish);int len = pos - _start;if (_finish == _end_of_storage) {int newcapacity = capacity() == 0 ? 4 : 2 * capacity();reserve(newcapacity);}// update pospos = _start + len;// moveiterator end = _finish;while (end > pos) {*end = *(end - 1);end--;}*pos = val;++_finish;return pos;
}
insert的迭代器失效问题

例子引入

vector<int> v(4, 3);
vector<int>::iterator pos = v.begin() + 1;
v.insert(pos, 9);
(*pos)++; // 野指针
vector<int> v(4, 3);
vector<int>::iterator pos = v.begin() + 1;
pos = v.insert(pos, 9);
(*pos)++; // 正确访问

传值传入插入位置的迭代器变量pos,在insert内部vector对象会先进行容量检查:

  • 如果容量满了就会进行扩容,这将会导致_start指向的空间变为扩容后新申请的空间,而pos还是指向旧空间的位置,此时pos是野指针,我们不能使用旧的pos了,需要pos指向新空间对应元素的位置后,再使用pos才能正确访问到元素;而外部调用insert函数后,作为参数的外部pos也失效了,其也指向了旧空间的位置,直接使用就是访问已经释放的空间,是非法访问内存,解决方法是外部pos接受insert的返回值新pos
  • 如果容量没满就无需扩容,内部pos不会失效,外部pos也不会失效;

为什么insert函数的参数iterator pos不使用引用类型iterator& pos

  • 诚然,使用引用后,外部的pos可以随着内部pos的更新而更新,不会失效了;但是此时形参pos只能是iterator的引用,遇到匿名对象等具有常性的迭代器时就会报错,导致权限放大问题即iterator 引用了 const_iterator对象,于是乎前人考虑的种种情况下,选择了insert更新后的pos作为返回值为外部提供,这样当外部想继续使用pos之前需要接收insert的参数即可。
插入多个元素
iterator insert(iterator pos, size_t n, const T& val) {assert(pos >= _start && pos < _finish);int len = pos - _start;if (size() + n > 2 * capacity()) {// 2}else if (size() + n > capacity()) {}pos = _start + len;// moveiterator end = _finish;while (end > pos) {*(end + n - 1) = *(end - 1);end--;}iterator begin = pos;while (begin < pos + n) {*begin = val;++begin;}_finish += n;return pos;
}

erase函数

删除pos位置的元素

iterator erase(iterator pos) {assert(pos >= _start && pos < _finish);iterator begin = pos + 1;while (begin < _finish) {*(begin - 1) = *begin;begin++;}_finish--;return pos;
}
erase的迭代器失效问题

例子引入:

vector<int> v = {1, 2, 3, 4}; //
vector<int>::iterator pos1 = v.end() - 1;
v.erase(pos1);
(*pos1)++; // vector<int>::iterator pos2 = v.begin();
v.erase(pos2);
(*pos2)++; //

这个例子的容器v分别执行了erase操作之后,迭代器pos1pos2你是否认为失效了呢?
这在g++和vs2019编译器下有着不同的结果:

  • g++编译器认为没有失效,erase操作之后pos2仍然指向第二个位置,此时该位置的元素是3,g++版本的STL的vector底层实现上迭代器是一个原生的指针即typedef T* iterator,即使我们使用了失效的迭代器也无法检查出来;
  • vs编译器会对迭代器进行强制检查,认为pos2是失效的,程序会直接报错。这是因为vs下STL的实现方式更加复杂,对于vector容器的迭代器来说,不是一个原生的指针,而是被进一步封装为一个类,在使用迭代器时会先进行检查是否失效,如果失效了程序就会报错,反之就正常执行;
    当前我们实现的版本更加接近g++的vector版本,是一个原生的指针,也就是说我们实现的版本在erase操作之后也无法对失效的迭代器进行有效检查;

那么到底该不该认为erase操作之后的迭代器失效呢?

  • 我的答案是不管编译器是否认为erase之后的迭代器是否失效,都认为迭代器是失效的,即每次erase之后及时更新迭代器,这样不管在g++编译器还是vs编译器下我们写的程序都是统一能跑的。

assign函数

为vector对象分配一个新值

一个迭代器范围[first, last)作为参数

template <class InputIterator>
void assign(InputIterator first, InputIterator last) {assert(first);assert(last);assert(first < last);if(_start) delete[] _start;_start = _finish = _end_of_storage = nullptr;while (first != last) {push_back(*first);first++;}
}

nval作为参数;
有两个版本,功能完全相同,只有参数n的类型不同,目的是为了防止特殊情况下导致的问题:
传入(5, 1)时期望是用5个int类型的1赋值给vector对象,但是在没有assign(int,const T&)函数时将会优先匹配assign(InputIterator first, InputIterator last)函数导致其内部解引用产生非法访问内存错误。

void assign(size_t n, const T& val) {vector<T> tmp(n, val);swap(tmp);
}
void assign(int n, const T& val) {vector<T> tmp(n, val);swap(tmp);
}

非成员函数 - swap函数

交换两个vector对象的内容

template <class T>
void swap(vector<T>& x, vector<T>& y) {x.swap(y);
}

4. vector的一些深拷贝问题

reserve函数是进行扩容的函数,其中涉及到开辟新空间,再把旧空间内容拷贝到新空间,最后释放旧空间的过程。
对于把内容从旧空间拷贝到新空间的方式,对于内置类型进行值拷贝就行;对于自定义类型特别是涉及资源管理的自定义类型来说,不能简单的进行值拷贝如使用memcpy函数,而是需要进行深拷贝,调用赋值运算符重载函数,前提是该自定义类型内部已经显式实现了深拷贝功能。
image.png
使用memcpy函数拷贝包含资源管理的自定义类型时发生的问题
以vector类为例,string类内部包含的指针指向了堆上的可变的字符数组,是需要管理的空间资源。其作为vector的元素在扩容时将会涉及到vector本身的深拷贝和string内部的深拷贝,也就是两层深拷贝。
image.png
image.png
image.png
image.png
解决方法:赋值操作,当是自定义类型时将会调用该自定义类型的赋值运算符重载函数完成深拷贝操作
image.png


结语

本节主要介绍了STL容器部分的vector,对于接口函数的学习先使我们整体了解vector如何使用。之后深入探究vector底层的实现方式,对比的是SGI版本,其中需要着重注意的是使用inserterase导致的迭代器失效问题,实现vectorresere函数时考虑到涉及到资源管理的自定义类型需要深拷贝的情况。


T h e The The E n d End End

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

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

相关文章

C# OpenCvSharp 玉米粒计数

效果 项目 代码 using OpenCvSharp; using System; using System.Drawing; using System.Text; using System.Windows.Forms;namespace OpenCvSharp_Demo {public partial class frmMain : Form{public frmMain(){InitializeComponent();}string fileFilter "*.*|*.bmp;…

【NLP】特征提取: 广泛指南和 3 个操作教程 [Python、CNN、BERT]

什么是机器学习中的特征提取&#xff1f; 特征提取是数据分析和机器学习中的基本概念&#xff0c;是将原始数据转换为更适合分析或建模的格式过程中的关键步骤。特征&#xff0c;也称为变量或属性&#xff0c;是我们用来进行预测、对对象进行分类或从数据中获取见解的数据点的…

JAVA微信端医院3D智能导诊系统源码

医院智能导诊系统利用高科技的信息化手段&#xff0c;优化就医流程。让广大患者有序、轻松就医&#xff0c;提升医疗服务水平。 随着人工智能技术的快速发展&#xff0c;语音识别与自然语言理解技术的成熟应用&#xff0c;基于人工智能的智能导诊导医逐渐出现在患者的生活视角中…

java--String

1.String创建对象封装字符串数据的方式 ①方式一&#xff1a;java程序中的所有字符串文字(例如"abc")都为此类的对象 ②方式二&#xff1a;调用String类的构造器初始化字符串对象。 2.String提供的操作字符串数据的常用方法

docker部署mongodb

1&#xff1a;拉去momgodb镜像 2&#xff1a;拉去成功后&#xff0c;通过docker-compose.yml配置文件启动mongodb&#xff0c;docker-compose.yml配置如下 version: 3.8 services:mongodb-1:container_name: mongodbimage: mongo ports:- "27017:27017"volumes:- G:…

计网----累积应答,TCP的流量控制--滑动窗口,粘包问题,心跳机制,Nagle算法,拥塞控制,TCP协议总结,UDP和TCP对比,中介者模式

计网----累积应答&#xff0c;TCP的流量控制–滑动窗口&#xff0c;粘包问题&#xff0c;心跳机制&#xff0c;Nagle算法&#xff0c;拥塞控制&#xff0c;TCP协议总结&#xff0c;UDP和TCP对比&#xff0c;中介者模式 一.累积应答 1.什么是累计应答 每次发一些包&#xff0…

前端构建工具vite与webpack详解

文章目录 前言什么是构建工具先说说企业级项目里都需要具备哪些功能&#xff1f;这是代码改动后需要做的事情样例总结 一、构建工具他到底承担了哪些脏活累活&#xff1f;二、vite相较于webpack的优势三、 vite会不会取代webpack四、 你必须要理解的vite脚手架和vitecreate-vit…

虚幻引擎:如何在工程里面添加插件

1.在自己的项目中安装插件 在content目录下创建一个Plugins的文件,将插件文件放进去即可 2.在软件上安装,这样所有创建的项目都会带有此插件 将插件放在自己软件的这个目录下就好了

Linux 多线程编程详解

目录 为什么要使用多线程 线程概念 线程的标识 pthread_t 线程的创建 向线程传入参数 线程的退出与回收 线程主动退出 线程被动退出 线程资源回收(阻塞方式) 线程资源回收(非阻塞方式) 为什么要使用多线程 在编写代码时&#xff0c;是否会遇到以下的场景会感觉到难以…

后期混音效果全套插件Waves 14 Complete mac中文版新增功能

Waves 14 Complete for Mac是一款后期混音效果全套插件&#xff0c;Waves音频插件,内置混响&#xff0c;压缩&#xff0c;降噪和EQ等要素到建模的模拟硬件&#xff0c;环绕声和后期制作工具&#xff0c;包含全套音频效果器&#xff0c;是可以让你使用所有功能。Waves 14 Comple…

Vue3全局共享数据

目录 1&#xff0c;Vuex2&#xff0c;provide & inject2&#xff0c;global state4&#xff0c;Pinia5&#xff0c;对比 1&#xff0c;Vuex vue2 的官方状态管理器&#xff0c;vue3 也是可以用的&#xff0c;需要使用 4.x 版本。 相对于 vuex3.x&#xff0c;有两个重要变…

微信小程序电商审核模版

《电商平台服务协议》与交易规则 下载链接 电商平台对入驻经营者的审核要求或规范文件 下载链接 电商平台对用户交易纠纷处理的机制或方案 下载链接 打包下载&#xff1a;https://download.csdn.net/download/nicepainkiller/88519604https://download.csdn.net/download/nicep…

拓世科技集团打造数字人营销解决方案,为车企提供新的“增长担当”

汽车&#xff0c;已经渐渐融入了现代人的日常生活&#xff0c;从高端的身份标志转变为普罗大众的出行选择&#xff0c;它驶入了千家万户&#xff0c;成为了我们日常出行的可靠伙伴&#xff0c;见证着人们的生活故事和时代的变迁。 中国汽车市场的蓬勃发展引起了业内外的广泛关…

怎样使用ovsyunlive在web网页上直接播放rtsp/rtmp视频

业务中需要在网页中直接播放rtsp和rtmp视频&#xff0c;多方比较测试发现ovsyunlive的播放器能直接播放rtsp/rtmp视频&#xff0c;还是非常方便简洁&#xff0c;使用过程如下&#xff1a; 1&#xff0c;Windows系统在github上面下载ovsyunlive绿色包下载解压。 github地址&am…

Android Studio代码无法自动补全

Android Studio代码自动无法补全问题解决 在写layout布局文件时&#xff0c;代码不提示&#xff0c;不自动补全&#xff0c;可以采用如下方法&#xff1a; 点击File—>Project Structure&#xff0c;之后如图所示&#xff0c;找到左侧Modules&#xff0c;修改SDK版本号&…

『 MySQL数据库 』数据库基础之表的基本操作

文章目录 创建表&#x1f5e1;查看表&#x1f5e1;✒ 查看表内所有信息(描述\表结构等)✒ 根据条件查看表内数据✒ 查看表的具体详细信息: 修改表&#x1f5e1;✒ 修改表名:✒ 修改表的存储引擎、编码集(字符集和校验集):✒ 表内插入数据:insert into✒ 在表中新添一个字段(列)…

pytorch中常用的损失函数

1 损失函数的作用 损失函数是模型训练的基础&#xff0c;并且在大多数机器学习项目中&#xff0c;如果没有损失函数&#xff0c;就无法驱动模型做出正确的预测。 通俗地说&#xff0c;损失函数是一种数学函数或表达式&#xff0c;用于衡量模型在某些数据集上的表现。损失函数在…

解决Jenkins执行git脚本时报错:No such device or address问题

问题现象&#xff1a; Jenkins执行BeanShell脚本时&#xff0c;报错&#xff1a;jenkins fatal: could not read Username for http://112.11.120.1: No such device or address 解决方案&#xff1a; 解决服务器拉取git仓库的代码权限&#xff0c;使用高级子模块克隆功能。…

mysql千万数据快速插入-实战

文章目录 前言环境一、配置二、效果总结 前言 数据量太大了&#xff0c;每天半夜要同步很大数据到 mysql 数据库&#xff0c;其中一张表就上2千万&#xff0c;总计上亿条数据。同步任务每天0点之后开始任务&#xff08;因为到0之后才能统计前一天数据&#xff09;&#xff0c;…

Tcl语言:基础入门(一)

Tcl语言https://blog.csdn.net/weixin_45791458/category_12488978.html?spm1001.2014.3001.5482 Tcl语言是一种脚本语言&#xff0c;类似于Bourne shell(sh)、C shell&#xff08;csh&#xff09;、Bourne-Again Shell(bash)等UNIX shell语言。Shell程序主要作为胶水缝合其他…