C++容器源码分析-----Vector

基本数据结构:

基本上, STL ⾥⾯所有的容器的源码都包含⾄少三个部分:
  • 迭代器,遍历容器的元素,控制容器空间的边界和元素的移动;
  • 构造函数,满⾜容器的多种初始化;
  • 属性的获取,⽐如 begin()end()等;

迭代器:

template 
class vector {
public:
	// 定义 vector ⾃身的嵌套型别
    typedef T value_type;
    typedef value_type* pointer;
	typedef const value_type* const_pointer;
	// 定义迭代器, 这⾥就只是⼀个普通的指针
	typedef value_type* iterator;
	typedef const value_type* const_iterator;
	typedef value_type& reference;
	typedef const value_type& const_reference;
	typedef size_t size_type;
	typedef ptrdiff_t difference_type;
	...
protected:
	typedef simple_alloc data_allocator; // 设置其空间配置器
	iterator start; // 当前使⽤空间的头
	iterator finish; // 当前使⽤空间的尾
	iterator end_of_storage; // 当前可⽤空间的尾
	...
};
因为 vector 需要表示⽤户操作的当前数据的起始地址,结束地址,还需要其真正的最⼤地址。所以总共需要 3
迭代器分别指向:数据的头 (start) ,数据的尾 (finish) ,数组的尾 (end_of_storage)

构造函数:

vector 有多个构造函数 , 为了满⾜多种初始化。
C++容器源码分析-----Vector_第1张图片

 

因为 vector 是⼀种 class template , 所以呢,我们并不需要⼿动的释放内存, ⽣命周期结束后就⾃动调⽤析构从⽽释放调⽤空间,当然我们也可以直接调⽤析构函数释放内存。
void deallocate() {
 if (start)
 data_allocator::deallocate(start, end_of_storage - start);
}
// 调⽤析构函数并释放内存
~vector() {
 destroy(start, finish);
 deallocate();
}

属性获取:

下⾯的部分就涉及到了位置参数的获取, ⽐如返回 vector 的开始和结尾,返回最后⼀个元素,返回当前元素个数,元素容量,是否为空等。
这⾥需要注意的是因为 end() 返回的是 finish ,⽽ finish 是指向 最后⼀个元素的后⼀个位置的指针 ,所以使⽤end() 的时候要注意。
public:
 // 获取数据的开始以及结束位置的指针. 记住这⾥返回的是迭代器, 也就是 vector 迭代器就是该类型的指针.
 iterator begin() { return start; }
 iterator end() { return finish; }
 reference front() { return *begin(); } // 获取值
 reference back() { return *(end() - 1); }
 const_iterator begin() const { return start; }// 获取右值
 const_iterator end() const { return finish; }
 
 const_reference front() const { return *begin(); }
 const_reference back() const { return *(end() - 1); }
 
 size_type size() const { return size_type(end() - begin()); } // 数组元素的个数
 size_type max_size() const { return size_type(-1) / sizeof(T); } // 最⼤能存储的元素
个数
 size_type capacity() const { return size_type(end_of_storage - begin()); } // 数组的
实际⼤⼩
 bool empty() const { return begin() == end(); }
 //判断 vector 是否为空, 并不是⽐较元素为 0,是直接⽐较头尾指针

push_back():

C++容器源码分析-----Vector_第2张图片

 C++容器源码分析-----Vector_第3张图片

 pop_back():

public:
 //将尾端元素拿掉 并调整⼤⼩
 void pop_back() {
 --finish;//将尾端标记往前移动⼀个位置 放弃尾端元素
 destroy(finish);
 }

erase():

erase 函数清除指定位置的元素, 其重载函数⽤于清除⼀个范围内的所有元素。实际实现就是将删除元素后⾯所有元素往前移动 ,对于 vector 来说删除元素的操作开销还是很⼤的

 

//清楚[first, last)中的所有元素
iterator erase(iterator first, iterator last) {
	iterator i = copy(last, finish, first);
	destroy(i, finish);
	finish = finish - (last - first);
	return first;
}
//清除指定位置的元素
iterator erase(iterator position) {
	if (position + 1 != end()) {
		copy(position + 1, finish, position);//copy 全局函数
	}
	--finish;
	destroy(finish);
	return position;
}
 void clear() {
	 erase(begin(), end());
 }

C++容器源码分析-----Vector_第4张图片

insert(): 

vector 的插⼊元素具体来说呢,⼜分三种情况:
  • 如果备⽤空间⾜够且插⼊点的现有元素多于新增元素;
  • 如果备⽤空间⾜够且插⼊点的现有元素⼩于新增元素;
  • 如果备⽤空间不够;
插⼊点之后的现有元素个数 > 新增元素个数:
C++容器源码分析-----Vector_第5张图片

插⼊点之后的现有元素个数 <= 新增元素个数:

C++容器源码分析-----Vector_第6张图片 

如果备⽤空间不⾜ :

C++容器源码分析-----Vector_第7张图片

这⾥呢,要注意⼀个坑,就是所谓的 迭代器失效问题。
通过图解我们就明⽩了,所谓的迭代器失效问题是由于元素空间重新配置导致之前的迭代器访问的元素不在了,总结来说有两种:
  • 由于插⼊元素,使得容器元素整体迁移导致存放原容器元素的空间不再有效,从⽽使得指向原空间的迭代器失效;
  • 由于删除元素,使得某些元素次序发⽣变化导致原本指向某元素的迭代器不再指向期望指向的元素。



vector总结:

优点:
  • 在内存中分配⼀块连续的内存空间进⾏存,可以像数组⼀样操作,动态扩容。
  • 随机访问⽅便,⽀持下标访问和vector.at()操作。
  • 节省空间。
缺点:
  • 由于其顺序存储的特性,vector 插⼊删除操作的时间复杂度是 O(n)
  • 只能在末端进⾏poppush
  • 当动态⻓度超过默认分配⼤⼩后,要整体重新分配、拷⻉和释放空间。
  • vector的缺点也很明显, 在频率较⾼的插⼊和删除时效率就太低了
  • vector 的成员函数都不做边界检查 (at ⽅法会抛异常) ,使⽤者要⾃⼰确保迭代器和索引值的合法性

你可能感兴趣的:(c++,c++,开发语言,c语言,蓝桥杯)