C++ : STL容器之vector剖析

news/2024/10/16 19:26:46 标签: c++, 开发语言, 笔记, 经验分享

在这里插入图片描述

STL容器之vector剖析

  • 一、构造函数与赋值
    • (一)默认构造
    • (二)拷贝构造
    • (三)几个相同值构造
    • (四)迭代器构造
    • (五)initializer_list 构造
    • (六)赋值重载
  • 二、几个重要接口
    • (一)reserve
    • (二)resize
    • (三)erase
    • (四)insert
  • 三、vector 的实现(完整源码)
  • 四、结束语

一、构造函数与赋值

(一)默认构造

采用初始化列表初始化,将三个迭代器都初始化为 nullptr,用三个指针的关系间接地控制size,capacity以及``data`.

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

(二)拷贝构造

拷贝构造可以用两种方式来实现,下面第一种是直接深拷贝的思路,另外还可以采用复用的方式。

这种方式可以使得拷贝后,size(), capacity()和拷贝的值相等

		vector(const vector<T>& v)
			:_start(nullptr)
			, _finish(nullptr)
			, _endofstorage(nullptr)
		{
			size_t n = v.size(), cap = v.capacity();
			reserve(cap);
			for (size_t i = 0; i < n; i++) {
				_start[i] = v[i];
			}
			_finish = _start + n;
			_endofstorage = _start + cap;
		}

这种方式采用复用,简洁了代码,可以多采用这种方式书写代码。

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

(三)几个相同值构造

拷贝几个相同的值进入一个新的 vector,我们用了两个方式来实现,用 size_t来做参数,扩大n的取值范围,这样的化还需要重载一个函数 int,这是因为下面还有一个迭代器构造函数用函数模板实现,如果不这样的化,我们如果需要3个5的vector,就会自动调用函数模板,达不到预期,还会报错。

vector(size_t n, const T& value = T())
{
	_start = new T[n];
	for (int i = 0; i < n; i++) {
		_start[i] = value;
	}
	_finish = _start + n;
	_endofstorage = _start + n;
}

vector(int n, const T& value = T())
{
	_start = new T[n];
	for (int i = 0; i < n; i++) {
		_start[i] = value;
	}
	_finish = _start + n;
	_endofstorage = _start + n;
}

(四)迭代器构造

成员函数也可以用函数模板来实现,可以用其它容器的迭代器来构造 vector,只要能够进行自动类型转换即可。

		template<class InputIterator>
		vector(InputIterator first, InputIterator second)
		{
			size_t n = second - first;
			_start = new T[n];
			for (int i = 0; i < n; i++) {
				_start[i] = *(first + i);
			}
			_finish = _start + n;
			_endofstorage = _start + n;
		}
void test09()
	{
		vector<int> v1;
		v1.push_back(1);
		v1.push_back(2);
		v1.push_back(3);
		v1.push_back(4);

		vector<int> v2(v1.begin(), v1.end());
		for (auto e : v2)
		{
			cout << e << " ";
		}
		cout << endl;

		string s1("hello");
		vector<int> v3(s1.begin(), s1.end());
		for (auto e : v3)
		{
			cout << e << " ";
		}
		cout << endl;

		vector<int> v4(10, 1);
		vector<double> v5(10, 1.1);
		for (auto e : v4)
		{
			cout << e << " ";
		}
		cout << endl;
	}

在这里插入图片描述

(五)initializer_list 构造

initailzer_list是一个 std 库里面的一个对象,原理大概就是开一个数组用两个首尾指针来维护它

		vector(initializer_list<T> il)
			: _start(nullptr)
			, _finish(nullptr)
			, _endofstorage(nullptr)
		{
			int n = il.size();
			_start = new T[n];
			auto x = il.begin();
			for (int i = 0; i < n; i++) {
				_start[i] = *(x + i);
			}
			_finish = _start + n;
			_endofstorage = _finish;
		}

下面的实例中,v1先构造了一个initializer_list 对象,接着隐式类型转换,但是编译器优化为直接构造。而v2就是直接构造,接着下面演示了如何初始化这种对象。

void test11()
{
	vector<int> v1 = { 1,2,3,4,5,6 };
	vector<int> v2({ 1,2,3,4,5,6 });

	auto il1 = { 1, 2, 3, 4, 5, 6 };
	initializer_list<int> il2 = { 1, 2, 3, 4, 5, 6 };
	for (auto x : il2) {
		cout << x  << " ";
	}
}

(六)赋值重载

现代写法,这种写法简直拉满了,妙哉。
我们知道函数值传参会调用拷贝构造,我们只需要将需要赋值的对象和形参交换,就能达到赋值的效果,而函数结束后会调用析构函数自动释放形参的空间,妙不可言。

		void swap(vector<T>& v1)
		{
			std::swap(_start, v1._start);
			std::swap(_finish, v1._finish);
			std::swap(_endofstorage, v1._endofstorage);
		}

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

二、几个重要接口

(一)reserve

在完成这个函数时,一定要采取for循环来依次赋值,如果使用 memcpy ,会发生一个深层次深浅拷贝的问题,如果_start指向的这片空间设计到动态内存开辟,那么会造成析构时释放两次的现象

		void reserve(size_t n)
		{
			if (n > capacity()) {
				T* temp = new T[n];
				size_t cnt = size();
				if (_start) {
					for (size_t i = 0; i < cnt; i++) {
						temp[i] = _start[i];
					}
					delete[]_start;
				}
				_start = temp;
				_finish = _start + cnt;
				_endofstorage = _start + n;
			}
		}

(二)resize

设置有效元素个数,分情况讨论即可,如果 reserve 里面采用了memcpy 涉及到复用,这里也可能会报错。

void resize(size_t n, T value = T())
{
	if (n <= size()) {
		int num = size() - n;
		_finish -= num;
	}
	else {
		reserve(n);
	    while (size() < capacity()) {
			push_back(value);
		}
	}
}

(三)erase

删除指定元素,并且返回当前删除元素的下一个元素的迭代器,返回值需要注意,否则可能发生迭代器失效的问题。

		iterator erase(iterator pos)
		{
			assert(pos < _finish);
			assert(pos >= _start);
			for (auto x = pos + 1; x != _finish; x++) {
				*(x - 1) = *(x);
			}
			_finish--;
			return pos;
		}

(四)insert

插入一个元素,这里用到了 reserve,因此pos指向的元素可能已经是另外开辟的空间,造成迭代器失效,这种情况我们要记住pos_start的相对位置,这样我们才能在新的数组空间还原pos的位置。`

		iterator insert(iterator pos, const T& value)
		{
			assert(pos <= _finish);
			assert(pos >= _start);
			if (_finish == _endofstorage) {
				int n = pos - _start;
				reserve(capacity() == 0 ? 4 : 2 * capacity());
				pos = _start + n;
			}
			for (auto x = _finish; x > pos; x--) {
				*(x) = *(x - 1);
			}
			*(pos) = value;
			_finish++;
			return pos;
		}

三、vector 的实现(完整源码)

实现vector的时候,我们没有用 capacity size data 来定义,而是用了三个指针来间接地维护这几个值。

#define _CRT_SECURE_NO_WARNINGS 1

#include<iostream>
#include<vector>
#include<assert.h>
using namespace std;

namespace wgm {
	template<class T>
	class vector {
	public:
		typedef T* iterator;
		typedef const T* const_iterator;

		vector()
			: _start(nullptr)
			, _finish(nullptr)
			, _endofstorage(nullptr)
		{
			cout << "vector() 调用" << endl;
		}

		vector(initializer_list<T> il)
			: _start(nullptr)
			, _finish(nullptr)
			, _endofstorage(nullptr)
		{
			cout << "vector(initializer_list<T> il)调用" << endl;
			int n = il.size();
			_start = new T[n];
			auto x = il.begin();
			for (int i = 0; i < n; i++) {
				_start[i] = *(x + i);
			}
			_finish = _start + n;
			_endofstorage = _finish;
		}

		vector(const vector<T>& v)
			:_start(nullptr)
			, _finish(nullptr)
			, _endofstorage(nullptr)
		{
			size_t n = v.size(), cap = v.capacity();
			reserve(cap);
			for (size_t i = 0; i < n; i++) {
				_start[i] = v[i];
			}
			_finish = _start + n;
			_endofstorage = _start + cap;
		}

		template<class InputIterator>
		vector(InputIterator first, InputIterator second)
		{
			size_t n = second - first;
			_start = new T[n];
			for (int i = 0; i < n; i++) {
				_start[i] = *(first + i);
			}
			_finish = _start + n;
			_endofstorage = _start + n;
		}

		vector(size_t n, const T& value = T())
		{
			_start = new T[n];
			for (int i = 0; i < n; i++) {
				_start[i] = value;
			}
			_finish = _start + n;
			_endofstorage = _start + n;
		}

		vector(int n, const T& value = T())
		{
			_start = new T[n];
			for (int i = 0; i < n; i++) {
				_start[i] = value;
			}
			_finish = _start + n;
			_endofstorage = _start + n;
		}

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

		T& operator[](size_t n) 
		{
			assert(n < size());
			assert(n >= 0);
			return _start[n];
		}

		const T& operator[](size_t n) const
		{
			assert(n < size());
			assert(n >= 0);
			return _start[n];
		}

		void swap(vector<T>& v1)
		{
			std::swap(_start, v1._start);
			std::swap(_finish, v1._finish);
			std::swap(_endofstorage, v1._endofstorage);
		}

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

		iterator begin()
		{
			return _start;
		}

		iterator end()
		{
			return _finish;
		}

		const_iterator begin() const
		{
			return _start;
		}

		const_iterator end() const
		{
			return _finish;
		}

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

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

		void reserve(size_t n)
		{
			if (n > capacity()) {
				T* temp = new T[n];
				size_t cnt = size();
				if (_start) {
					for (size_t i = 0; i < cnt; i++) {
						temp[i] = _start[i];
					}
					delete[]_start;
				}
				_start = temp;
				_finish = _start + cnt;
				_endofstorage = _start + n;
			}
		}

		void resize(size_t n, T value = T())
		{
			if (n <= size()) {
				int num = size() - n;
				_finish -= num;
			}
			else {
				reserve(n);
			    while (size() < capacity()) {
					push_back(value);
				}
			}
		}

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

		bool empty() {
			return _start == _finish;
		}

		void pop_back()
		{
			assert(!empty());
			_finish--;
		}

		iterator insert(iterator pos, const T& value)
		{
			assert(pos <= _finish);
			assert(pos >= _start);
			if (_finish == _endofstorage) {
				int n = pos - _start;
				reserve(capacity() == 0 ? 4 : 2 * capacity());
				pos = _start + n;
			}
			for (auto x = _finish; x > pos; x--) {
				*(x) = *(x - 1);
			}
			*(pos) = value;
			_finish++;
			return pos;
		}

		iterator erase(iterator pos)
		{
			assert(pos < _finish);
			assert(pos >= _start);
			for (auto x = pos + 1; x != _finish; x++) {
				*(x - 1) = *(x);
			}
			_finish--;
			return pos;
		}
		

	private:
		iterator _start;
		iterator _finish;
		iterator _endofstorage;
	};
}

四、结束语

相比于stringvector新加入的一些新的元素,但是大同小异,后面的学习中研究不同点即可,vector中的迭代器失效,以及深浅拷贝的问题在学习时需要注意。


http://www.niftyadmin.cn/n/5708428.html

相关文章

在wpf 中 用mvvm 的方式 绑定 鼠标事件

在 wpf中, 如果遇到控件的 MouseEnter MouseLeave 属性时, 往往会因为有参数object sender, System.Windows.Input.MouseEventArgs e 很多人选择直接生成属性在后台, 破坏了MVVM, 这其实是不必要的. 我们完全可以用 xmlns:i“http://schemas.microsoft.com/xaml/behaviors” 完…

【C语言】数据类型

C语言常用数据类型 int 整型 4字节float 浮点型 4字节double 双精度浮点类型 8字节char 字符型 1字节构造类型&#xff1a;数组&#xff08;多个相同类型的变量组合&#xff09;构造类型&#xff1a;结构体&#xff08;多个不同类型的变量组合&#xff09; #include <stdi…

机器学习:opencv--人脸检测以及微笑检测

目录 前言 一、人脸检测的原理 1.特征提取 2.分类器 二、代码实现 1.图片预处理 2.加载分类器 3.进行人脸识别 4.标注人脸及显示 三、微笑检测 前言 人脸检测是计算机视觉中的一个重要任务&#xff0c;旨在自动识别图像或视频中的人脸。它可以用于多种应用&#xff0…

mysql学习教程,从入门到精通,SQL导入数据(44)

1.SQL 导出数据 以下是一个关于如何使用 SQL 导出数据的示例。这个示例将涵盖从一个关系数据库管理系统&#xff08;如 MySQL&#xff09;中导出数据到 CSV 文件的基本步骤。 1.1、前提条件 你已经安装并配置好了 MySQL 数据库。你有访问数据库的权限。你知道要导出的表名。…

微信小程序中的文件查看方法

获得后缀名判断类型,如果是图片用ex.previewImage(),如果是视频,用uni.previewMedia(),如果是word文档这些的,用 uni.downloadFile来下载资源后用 uni.saveFile来保存到本地,uni.openDocument来打开新的网页,如果打不开的话则返回说到PC端去打开 const lookFile (url) > {l…

MySQL中FIND_IN_SET(),IN()和LIKE区别

在 MySQL 中&#xff0c; FIND_IN_SET() 和 LIKE 都可以用于字符串的匹配查找&#xff0c;但它们有以下不同&#xff1a; 一、语法及功能 1. FIND_IN_SET(str,strlist) &#xff1a; 用于在以逗号分隔的字符串列表中查找特定字符串&#xff0c;并返回其位置。如果未找到则返…

DOS时代渐行渐远,而国产新型平台,却在悄然换代

可能就是有点儿念旧吧&#xff0c;没事干的时候&#xff0c;就爱把那些DOS年代的经典软件找出来摆弄摆弄。 虽说它们现在都是老古董了&#xff0c;但在咱们70后、80后的心里头&#xff0c;这些当年闪闪发光的软件宝贝&#xff0c;还是有着独一无二的位置&#xff0c;谁也替代不…

60个java常用的代码(能够帮助您更好地理解Java编程)

以下是60个常用的Java代码片段&#xff0c;涵盖了基础语法、常用类和一些实用功能&#xff1a; 基础语法 Hello World public class HelloWorld { public static void main(String[] args) { System.out.println("Hello, World!"); } } 变量声明 int number…