详解C++中vector的理解以及模拟实现

目录
  • vector介绍
  • vector常见函数介绍
  • vector模拟实现及迭代器失效讲解

vector介绍

vector文档

1.vector是表示可变大小数组的序列容器。

2.就像数组一样,vector也采用的连续存储空间来存储元素。也就是意味着可以采用下标对vector的元素进行访问,和数组一样高效。但是又不像数组,它的大小是可以动态改变的,而且它的大小会被容器自动处理。

3.本质讲,vector使用动态分配数组来存储它的元素。当新元素插入时候,这个数组需要被重新分配大小为了增加存储空间。其做法是,分配一个新的数组,然后将全部元素移到这个数组。就时间而言,这是一个相对代价高的任务,因为每当一个新的元素加入到容器的时候,vector并不会每次都重新分配大小。

4.vector分配空间策略:vector会分配一些额外的空间以适应可能的增长,因为存储空间比实际需要的存储空间更大。不同的库采用不同的策略权衡空间的使用和重新分配。但是无论如何,重新分配都应该是对数增长的间隔大小,以至于在末尾插入一个元素的时候是在常数时间的复杂度完成的。

5.因此,vector占用了更多的存储空间,为了获得管理存储空间的能力,并且以一种有效的方式动态增长。

6.与其它动态序列容器相比(deque, list and forward_list), vector在访问元素的时候更加高效,在末尾添加和删除元素相对高效。对于其它不在末尾的删除和插入操作,效率更低。比起list和forward_list统一的迭代器和引用更好

vector常见函数介绍

构造函数:

vector();//无参构造
vector(size_t n,const val_type & val=val_type());//利用v个val初始化vector
vector(const val_type&val);//拷贝构造
template <class InputIterator>
vector (InputIterator first, InputIterator last)//可以用其他类的迭代器来初始化vector

当然,vector还可以直接用数组来填充:

迭代器:

begin();

end();非const对象正向迭代器;

begin() const;

end() const ;const对象正向迭代器;

在VS平台下,vector的迭代器并不是用原生指针来实现的,而是用了一种比较复杂的方式;

VS下vector的迭代器类型:

在g++版本下,迭代器主要采用原生指针的方式,我们下面模拟的时候也是借鉴这种方式!

容量空间:

size();//获取vector有效元素个数;
capacity();//获取vector当前容量;
empty();//判断vector是否为空;
resize(size_t n,const val_type &val=val_type());//设置vector的size大小,使用resize可能会引发扩容;
reserve(size_t n);//设置容量,只有当n>大于当前容量是才会增容;

capacity的代码在vs和g++下分别运行会发现,vs下capacity是按1.5倍增长的,g++是按2倍增长的。

这个问题经常会考察,不要固化的认为,vector增容都是2倍,具体增长多少是根据具体的需求定义的。vs是PJ版本STL,g++是SGI版本STL。

reserve只负责开辟空间,如果确定知道需要用多少空间,reserve可以缓解vector增容的代价缺陷问题。

resize在开空间的同时还会进行初始化,影响size。

增删查改:

push_back(const vay_type &val);//插入一个元素;
pop_back();//从尾部删除一个元素;
InputIterator find (InputIterator first, InputIterator last, const T& val)//这不是vector的成员函数,而是函数模板,用于在某段特定区间[first,last)查找val;找到了返回val的迭代器;找不到返回last;
iterator insert (iterator position, const value_type& val);//在pos位置之前插入数据,insert插入有代价尽量少用;
iterator erase (iterator position);//删除指定位置的数据;
swap()//成员swap比模板swap快;
operator[]( size_type n);//[ ]重在运算符;

vector模拟实现及迭代器失效讲解

#pragma once
#include<iostream>
#include<vector>
#include<assert.h>
#include<string.h>
#include<algorithm>
namespace MySpace
{
	template <typename 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;
		}

		//构造函数和析构函数
		explicit vector() {}
		//由于泛型编程的出现,内置类型也必须支持默认构造函数
		//int a=int();//是编译器允许的
		//int*pa=int*();//直接使用编译器是不允许的,但是写出泛型编程时,编译器又允许了:T pa=T();//这是编译器允许的;
		//很奇怪对吧!
		explicit vector(size_t n,const T&val=T()) {
			reserve(n);
			for (size_t i = 0; i < n; i++)
				push_back(val);
			_finish = _start + n;
		}
	 explicit vector(int n, const T& val = T()) {
			reserve(n);
			for (size_t i = 0; i < (size_t)n; i++)
				push_back(val);
			_finish = _start + n;
		}
		vector(const vector<T>& v)
		{
			reserve(v.capacity());
			//memcpy(_start,v._start,v.size()*sizeof(T));//memcpy是字节拷贝,也就是浅拷贝!当T的类型是string一类的时候,就会出现!
			//v1、v2虽然_start、_finish、_end_of_storage 实现了深拷贝!但是他们里面的元素string,是用
			//memcpy拷贝过来的,也就是浅拷贝!也就造成了不同的string元素管理着同一块字符串;
			//当vector析构的时候,就会调用string的析构,就会造成对同一块空间释放两次!
			//为此,我们的vector元素之间也应该实现深拷贝!利用赋值运算符!
			for (const auto& x : v)
				push_back(x);
			_finish = _start + v.size();
		}

		//可以将任和迭代区间转换成vector元素!(前提是隐式类型转换支持)
		template<typename InputIterator>
		vector(InputIterator first, InputIterator last)
		{
			while (first != last)
				push_back(*(first++));
		}

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

		void reserve(size_t n)
		{
			if (n > capacity())
			{
				iterator tmp = new T[n];
				//memcpy(tmp, _start, (_finish - _start) * sizeof(T));//这里不要用memcpy,memcpy是字节拷贝,也就是浅拷贝
				//对于T=string这样,里利用memcpy就会出现错误!
				for (size_t i = 0; i < size(); i++)
					tmp[i] = _start[i];
				size_t sz = _finish - _start;//提前记录一下元素个数
				delete[] _start;
				_start = tmp;
				//_finish = tmp + (_finish - _start);//注意此时的_start已经不在指向原来的那块快空间,此时_start==tmp
				//因此_finish还是等于原来的_finish没有改变,为了解决这个问题,我们可以提前记录一下sz;
				_finish = _start + sz;
				_end_of_storage = _start + n;
			}
		}
		void resize(size_t n, const T& val = T())//const+引用对于匿名对象来说可以延长匿名对象的生命周期
		{
			if (n <= size())
			{
				_finish = _start + n;
			}
			else
			{
				if (n > capacity())//需要扩容
				{
					reserve(n);
				}
				for (size_t i = 0; i < n - size(); i++)
					_finish[i] = val;
				_finish += n - size();
			}
		}
		//capacity
		size_t size()const
		{
			return _finish - _start;
		}
		size_t capacity()const
		{
			return _end_of_storage - _start;
		}
		bool empty()const
		{
			return size() == 0;
		}
		void clear()
		{
			_finish = _start;
		}
		//访问
		T& operator[](size_t pos)
		{
			assert(pos >= 0 && pos < size());
			return _start[pos];
		}
		const T& operator[](size_t pos)const
		{
			assert(pos >= 0 && pos < size());
			return _start[pos];
		}
		//修改数据
		void push_back(const T& val)
		{
			if (_finish == _end_of_storage)//容量满了
			{
				//扩容
				reserve(_finish==nullptr?4:(_finish-_start)*2);
			}
			*(_finish) = val;
			_finish++;
		}
		void pop_back()
		{
			assert(empty() == false);
			_finish--;
		}
		iterator insert(iterator pos, const T& val)
		{
			if (_finish == _end_of_storage)//发生扩容过后,pos还是指向的原来的空间,pos是个野指针,需要修正pos
			{//在insert里面也就是这里会发生,迭代器失效!
				size_t gap = pos - _start;
				reserve(capacity() ? capacity() * 2 : 4);
				pos = _start + gap;//修正pos,使pos变成合法指针
			}
			iterator end = _finish - 1;
			while (end >= pos)
			{
				end[1] = end[0];
				end--;
			}
			pos[0] = val;
			_finish++;
			return pos;//指向插入位置
		}

		iterator erase(iterator pos)//删除pos位置,返回删除位置的地址
		{
			assert(empty() == false);
			iterator end = pos + 1;
			while (end != _finish)
			{
				end[-1] = end[0];
				end++;
			}
			_finish--;
			return pos;
		}

		//交换
		void swap(const vector<T>& v)
		{
			std::swap(_start, v._start);
			std::swap(_finish, v._finish);
			std::swap(_end_of_storage,v._end_of_storage);
		}

		//重载赋值运算符
		vector<T>& operator=(const vector<T>& v)
	 	 {
       if(this!=&v)
       {
          clear();
          reserve(v.capacity());
          for(const auto&x:v)
            push_back(x);
          _finish=_start+v.size();
       }
       return *this;
     }

	private:
		iterator _start = nullptr;//开始位置
		iterator _finish=nullptr;//最后一个元素的下一个位置
		iterator _end_of_storage = nullptr;//表示当前容量的指针

	};
	void test8()
	{
		std::string str = "Hello World";
		vector<int> v(str.begin(), str.end());
		for (const auto& x : v)
			std::cout << x << " ";
			std::cout << std::endl;

	}
	void test7()
	{
		/*vector<int> v(10,6);
		vector<int> v2 = v;*/

		//vector<std::string> v(3, "112222222255522111");
		vector<std::string> v1 = v;//当T类型为自定义类型时会出现程序崩溃!//主要是因为出现了浅拷贝问题!!

		//v.push_back("2111111111111111");//要避免memcpy带来的浅拷贝危害!
		//v.push_back("333332111111111111111");
		vector<int> v1(3,10);
		vector<int> v2(4,19);
		vector<int> v3(5,6);

		vector<vector<int>> vv;
		vv.push_back(v1);//这里会出现报错,因为我们还没有实现vector<T>的赋值运算符
		vv.push_back(v2);
		vv.push_back(v3);
		//vector<vector<int>> vv2=vv;

	}
	void test6()
	{
		vector<int> v1;
		v1.push_back(1);
		v1.push_back(2);
		v1.push_back(3);
		v1.push_back(4);
		for (size_t i = 0; i < v1.size(); i++)
			std::cout << (v1[i]) << " ";
		std::cout << std::endl;
	vector<int>::iterator pos = v1.begin() + 2;
		pos=v1.erase(pos);
		//*pos += 10;//这里也存在着迭代器失效的问题?从语法上我们认为pos指向的是一块有效空间,这没问题!
		//但是从逻辑上来说,pos却不一定是一块“合法”的空间:假如pos刚好是最后一个元素,erase过后pos与_finish相等
		//如果我们后续再访问pos位置的话就有点不合适了,在逻辑上我们认为pos是个不“合法”的位置,pos不应该访问!
		//为此,为了解决这个问题,vs版本下的vector给出了比较严格的限制,当我们使用erase过后的pos时直接报错!
		//g++下的erase过后的pos是允许使用的,但是这是不合理的!
		//因此我们一般认为erase过后的pos也是迭代器失效,解决办法就是接收一下erase的返回值!
		for (size_t i = 0; i < v1.size(); i++)
			std::cout << (v1[i]) << " ";
	}
	void test1()
	{
		vector<int> v1;
		v1.push_back(1);
		v1.push_back(2);
		v1.push_back(3);
		v1.push_back(4);

	}
	void test2()
	{
		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.resize(3);
	}
	void test3()
	{
		 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 (size_t i = 0; i < v.size(); i++)
			std::cout << (v[i] *=10)<< std::endl;
	}
	void test5()
	{
		vector<int> v;
		v.push_back(1);
		v.push_back(2);
		v.push_back(3);
		v.push_back(4);
		for (size_t i = 0; i < v.size(); i++)
			std::cout << (v[i] ) << " ";
		std::cout << std::endl;
		vector<int>::iterator pos = std::find(v.begin(),v.end(),3);
		if (pos != v.end())
		{
			pos=v.insert(pos,30);
			//(*pos)++;//这里也是个迭代器失效,虽然我们修正了,insert内部pos,
			//但是外部的pos还是指向的原来的空间,外部的pos还是个野指针,我们的pos是值传递,形参的改变不影响实参!
			//那么我们pos参数用引用可不可以?
			//答:语法上可以!但是实际用起来却不行!eg:insert(v.begin(),10);//如果pos是引用的话,程序直接报错
			//因为begin是值返回,insert相当于引用的一个临时对象,临时对象具有常性!(权限放大了)
			//也不要想着给pos加const解决(加了const,pos都修正不了了,迭代器失效的问题无法解决了)
			//因此我们在insert过后,最好不要再用pos值,要用也应该从新接收一下insert返回值!

			//为此vs为了我们使用失效的迭代器!当我们使用insert过后的pos时,就会直接报错!
		}
		(*pos) += 10;
		for (size_t i = 0; i < v.size(); i++)
			std::cout << (v[i]) << " ";
		std::cout << std::endl;
	}
	void test4()
	{

		std::vector<int> v;
		v.push_back(1);
		v.push_back(2);
		v.push_back(3);
		v.push_back(4);
		for (size_t i = 0; i < v.size(); i++)
			std::cout << (v[i]) << " ";
			std::cout << std::endl;
		std::vector<int>::iterator pos = std::find(v.begin(),v.end(),3);
		if (pos != v.end())
		{
			pos=v.insert(pos,30);
		}
		(*pos) += 10;
		for (size_t i = 0; i < v.size(); i++)
			std::cout << (v[i]) << " ";
		std::cout << std::endl;
		std::cout << typeid(std::vector<int>::iterator).name()<<std::endl;//vs版本下的vector迭代器不是使用的原生指针!
	}
}

到此这篇关于详解C++中vector的理解以及模拟实现的文章就介绍到这了,更多相关C++ vector内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • C++模拟实现STL容器vector的示例代码

    目录 一.vector迭代器失效问题 二.模拟实现构造函数调用不明确 1.问题描述 2.解决调用不明确的方法 三.reserve中的深浅拷贝问题 1.reserve中浅拷贝发生原因 2.浅拷贝发生的图解 3.解决方法 四.模拟实现vector整体代码 一.vector迭代器失效问题 1.Visual Studio和g++对迭代器失效问题的表现 int main(){ std::vector<int>v{1,2,3,4}; std::vector<int>::iterator it

  • C++模拟实现vector的示例代码

    目录 一.迭代器 定义 普通迭代器 const类型迭代器 二.构造类 构造函数 拷贝构造函数 赋值运算符重载 析构函数 三.容量相关操作 size.capacity empty resize reserve 三.元素访问 [ ]重载 front back 四.修改类接口 push_back pop_back insert erase clear swap 五.成员变量 一.迭代器 定义 vector类型的迭代器就是原生态的指针,对T*进行重命名即可 typedef T* iterator; ty

  • C++模拟如何实现vector

    目录 一.迭代器 定义 普通迭代器 const类型迭代器 二.构造类 构造函数 拷贝构造函数 赋值运算符重载 析构函数 三.容量相关操作 size.capacity empty resize reserve 三.元素访问 [ ]重载 front back 四.修改类接口 push_back pop_back insert erase clear swap 五.成员变量 总结 一.迭代器 定义 vector类型的迭代器就是原生态的指针,对T*进行重命名即可 typedef T* iterator;

  • C++模拟实现vector示例代码图文讲解

    目录 vector的模拟实现 使用memcpy拷贝问题 vector的模拟实现 #include <iostream> using namespace std; #include <assert.h> namespace myVector { template<class T> class vector { public: // Vector的迭代器是一个原生指针 typedef T* iterator; typedef const T* const_iterator;

  • C++示例讲解vector容器

    目录 vector基本概念 创建 vector 的各种方法 vector容器的构造 vector赋值操作 vector容量和大小 vector容器插入和删除 vector数据存取 vector互换容器 vector基本概念 功能: vector数据结构和数组非常相似,也称为单端数组 vector与普通数组区别: 不同之处在于数组是静态空间,而vector可以动态扩展 动态扩展: 并不是在原空间之后继续接新空间,而是找更大的内存空间,然后将元数据拷贝新空间,释放原空间 创建 vector 的各种方

  • 详解C++中vector的理解以及模拟实现

    目录 vector介绍 vector常见函数介绍 vector模拟实现及迭代器失效讲解 vector介绍 vector文档 1.vector是表示可变大小数组的序列容器. 2.就像数组一样,vector也采用的连续存储空间来存储元素.也就是意味着可以采用下标对vector的元素进行访问,和数组一样高效.但是又不像数组,它的大小是可以动态改变的,而且它的大小会被容器自动处理. 3.本质讲,vector使用动态分配数组来存储它的元素.当新元素插入时候,这个数组需要被重新分配大小为了增加存储空间.其做

  • 详解Java中Vector和ArrayList的区别

    首先看这两类都实现List接口,而List接口一共有三个实现类,分别是ArrayList.Vector和LinkedList.List用于存放多个元素,能够维护元素的次序,并且允许元素的重复. 3个具体实现类的相关区别如下: 1.ArrayList是最常用的List实现类,内部是通过数组实现的,它允许对元素进行快速随机访问.数组的缺点是每个元素之间不能有间隔,当数组大小不满足时需要增加存储能力,就要讲已经有数组的数据复制到新的存储空间中.当从ArrayList的中间位置插入或者删除元素时,需要对

  • 详解Python中最难理解的点-装饰器

    本文将带领大家由浅入深的去窥探一下,这个装饰器到底是何方神圣,看完本篇,装饰器就再也不是难点了. 一.什么是装饰器 网上有人是这么评价装饰器的,我觉得写的很有趣,比喻的很形象 每个人都有的内裤主要是用来遮羞,但是到了冬天它没法为我们防风御寒,肿木办? 我们想到的一个办法就是把内裤改造一下,让它变得更厚更长,这样一来,它不仅有遮羞功能,还能提供保暖,不过有个问题,这个内裤被我们改造成了长裤后,虽然还有遮羞功能,但本质上它不再是一条真正的内裤了.于是聪明的人们发明长裤 在不影响内裤的前提下,直接把长

  • 详解c++中的异常

    一.什么是异常处理 一句话:异常处理就是处理程序中的错误. 二.为什么需要异常处理,异常处理的基本思想 C++之父Bjarne Stroustrup在<The C++ Programming Language>中讲到:一个库的作者可以检测出发生了运行时错误,但一般不知道怎样去处理它们(因为和用户具体的应用有关):另一方面,库的用户知道怎样处理这些错误,但却无法检查它们何时发生(如果能检测,就可以再用户的代码里处理了,不用留给库去发现). Bjarne Stroustrup说:提供异常的基本目的

  • Java详解Swing中的几种常用按钮的使用

    目录 Swing中的常用按钮 AbstractButton的常用方法 JRadionButton(单选按钮) 单选按钮的构造方法 复选框(JCheckBox) 复选框的构造方法 组合框(JComboBox) 组合框的构造方法 下拉列表框的常用方法 小结 Swing中的常用按钮 在Swing中,常见的按钮组件有JButton,JCheckBox,JRadioButton等,它们都是抽象类AbstractButton类的直接或间接子类.在AbstractButton类中提供了按钮组件通用的一些方法.

  • 详解Java中多线程异常捕获Runnable的实现

    详解Java中多线程异常捕获Runnable的实现 1.背景: Java 多线程异常不向主线程抛,自己处理,外部捕获不了异常.所以要实现主线程对子线程异常的捕获. 2.工具: 实现Runnable接口的LayerInitTask类,ThreadException类,线程安全的Vector 3.思路: 向LayerInitTask中传入Vector,记录异常情况,外部遍历,判断,抛出异常. 4.代码: package step5.exception; import java.util.Vector

  • 详解Android中Intent对象与Intent Filter过滤匹配过程

    如果对Intent不是特别了解,可以参见博文<详解Android中Intent的使用方法>,该文对本文要使用的action.category以及data都进行了详细介绍.如果想了解在开发中常见Intent的使用,可以参见<Android中Intent习惯用法>. 本文内容有点长,希望大家可以耐心读完. 本文在描述组件在manifest中注册的Intent Filter过滤器时,统一用intent-filter表示. 一.概述 我们知道,Intent是分两种的:显式Intent和隐式

  • 详解Javascript中prototype属性(推荐)

    在典型的面向对象的语言中,如java,都存在类(class)的概念,类就是对象的模板,对象就是类的实例.但是在Javascript语言体系中,是不存在类(Class)的概念的,javascript中不是基于'类的',而是通过构造函数(constructor)和原型链(prototype chains)实现的.但是在ES6中提供了更接近传统语言的写法,引入了Class(类)这个概念,作为对象的模板.通过class关键字,可以定义类.基本上,ES6的class可以看作只是一个语法糖,它的绝大部分功能

  • 详解IIS中URL重写工具的匹配URL-规则模式(rule patterns)

    rule patterns规则模式在IIS的URL重写模块中,是较为关键的设置.只有规则模式的URL匹配成功时,其他的规则才能起到作用.规则模式的匹配URL设置主要是匹配URL中的路径部分,一般使用正则表达式和通配符对URL路径进行匹配,下面会仔细的说明. 首先要了解规则模式是匹配URL中的哪一部分,假设当前有网站的URL地址为http://shiyousan.com/Home/Index?page=1,那么规则模式匹配的部分就是Home/Index,域名的主机部分和参数部分如果要匹配,则需要在

  • 详解IIS中的重写工具下关于操作重定向URL中的{R:N}与{C:N}使用介绍

    URL Rewrite(URL重写工具)作为IIS下较为常用的模块组件, 提供了重写.重定向.自定义响应.中止请求等功能.但是其相关的中文资料比较缺少,官方倒是有完整和详细的英文文档,之前我在项目中遇到需要设置重写/重定向URL操作规则时,对于范例中的{R:N}和{C:N}规则就理解的十分辛苦,因此写下本文分享下经验. 这里先附上官网的文档,其实文档链接在IIS的URL重写模块的右边菜单就有:URL Rewrite Module Configuration Reference(URL重写模块配置

随机推荐