STL - vector的使用和模拟实现

目录

一:vector的构造函数

二:vector的迭代器

三vector的空间增长问题

[四:vector 增删查改接口](#四:vector 增删查改接口)

五:vector的模拟实现

(一)一些简单接口的实现:

(二)一些复杂接口的实现

迭代器失效问题:

(三)五个构造函数的模拟实现

(1)默认构造

(2)拷贝构造函数

(3)使用n个val去初始化容器的构造

(4)迭代器区间初始化构造:

(5)赋值构造


vector其实就是我们数据结构中的顺序表。

一:vector的构造函数

(1)无参构造:即默认构造。vector构造一个没有元素的空容器。

cpp 复制代码
	// 走默认构造
	vector<char> vc;

(2)填充构造函数:构造一个包含 n 个元素的容器。每个元素都是 val 的副本。

cpp 复制代码
	// 默认初始化为0
	vector<int> v1(10);
    // 初始化为1
	vector<int> v2(10, 1);

(3)拷贝构造函数:构造一个容器,其中包含与范围 [first,last] 一样多的元素,每个元素都按相同的顺序从该区域中的相应元素构造。

cpp 复制代码
	// 拷贝构造
	vector<int> v3(v2);

(4)使用迭代器进行初始化的构造:构造一个容器,其中包含与范围 [first,last] 一样多的元素,每个元素都按相同的顺序从该区域中的相应元素构造。

cpp 复制代码
	vector<int> v1(10);
	// 使用迭代器进行初始化
	vector<int> v3(v1.begin(), v1.end()-3);

二:vector的迭代器

同其他的string容器一样,begin和end分别是容器的第一个位置和最后一个位置,反向迭代器则相反。const迭代器和非const迭代器和string的也是一样的C++_STL_string使用和模拟实现-CSDN博客

这里可以看一下。

cpp 复制代码
// 初始化为一个定值
vector<int> v2(10, 1);
// 迭代器使用
//vector<int>::iterator it;
//vector<int>::const_iterator it;
// 反向迭代器
vector<int>::reverse_iterator it;
//it = v2.begin();
it = v2.rbegin();
//while (it != v2.end())
while (it != v2.rend())
{
	//cout << ++(*it) << " ";
	cout << *it << " ";
	it++;
}

三vector的空间增长问题

(1)size():返回元素的实际个数

(2)capacity():容器容量的大小

(3)resize() :修改实际容器元素个数

(4)reserve():改变capacity的大小

(5)empty():判断容器是否为空

注意:在g++和vs中capacity在扩容的时候可能不一样,有的是1.5倍扩容,有的是2倍扩容

具体我们来看下这几个接口的情况:

cpp 复制代码
// 测试vector 空间增长问题接口
void test02()
{
	vector<int> v2(10, 1);
	cout << v2.size() << endl;
	cout << v2.capacity() << endl;
	cout << v2.empty() << endl;

	// resize如果n大于size的话会将size的元素进行初始化
	v2.resize(40);
    // 这里改变capacity的为13并不会缩容
	v2.reserve(13);
	cout << v2.capacity() << endl;
	for (auto& x : v2)
	{
		cout << x << " ";
	}
}

四:vector 增删查改接口

(1)push_back:在容器末尾插入一个值

cpp 复制代码
void push_back (const value_type& val);

(2)pop_back:删除容器末尾的一个值

cpp 复制代码
void pop_back();

(3)find:在范围内查找值

返回范围中比较等于 的第一个元素的迭代器。如果未找到此类元素,则函数返回 。

cpp 复制代码
template <class InputIterator, class T>   InputIterator find (InputIterator first, InputIterator last, const T& val);

(4)insert:通过在指定位置的 元素之前插入新元素来扩展向量,从而有效地增加容器大小(增加插入的元素数)。

cpp 复制代码
single element (1)	
iterator insert (iterator position, const value_type& val);
fill (2)	
    void insert (iterator position, size_type n, const value_type& val);
range (3)	
template <class InputIterator>    void insert (iterator position, InputIterator first, InputIterator last);

(5)erase:从向量中删除单个元素 (位置) 或一系列元素 ([first,last))

cpp 复制代码
iterator erase (iterator position);iterator erase (iterator first, iterator last);

(6)swap:交换向量的数据空间

cpp 复制代码
void swap (vector& x);

(7)operator[]:返回对向量容器中位置 n 处的元素的引用。

cpp 复制代码
reference operator[] (size_type n);const_reference operator[] (size_type n) const;

1~7接口代码:

cpp 复制代码
void test03()
{

//find
//push_back 
///
	vector<int> v2;
	for (int i = 0; i < 100; i++)
	{
		v2.push_back(i);
	}
	v2.pop_back();
	//for (auto& e : v2)
	//{
	//	cout << e << " ";
	//}
	cout << endl;
	// 在一个范围内查找数据val,并且返回第一个等于val的迭代器,如果没有找到就返回区间的末尾迭代器
	vector<int>::iterator it = find(v2.begin(),v2.end(), 31);
	//while (it != v2.end())
	//{
	//	//cout << ++(*it) << " ";
	//	cout << *it << " ";
	//	it++;
	//}
/
//insert
//在pos迭代器位置插入val,不是下标

	v2.insert(v2.begin() + 10, 10025);
	it = v2.begin();
	//while (it != v2.end())
	//{
	//	//cout << ++(*it) << " ";
	//	cout << *it << " ";
	//	it++;
	//}

//erase
//删除迭代区间的数据或者迭代pos位置的数据

	// iterator erase (iterator first, iterator last);
	v2.erase(v2.begin() + 3, v2.end() - 10);
	it = v2.begin();
	while (it != v2.end())
	{
		//cout << ++(*it) << " ";
		cout << *it << " ";
		it++;
	}
	// iterator erase (iterator position);
	v2.erase(v2.begin() + 20);
	it = v2.begin();
	while (it != v2.end())
	{
		//cout << ++(*it) << " ";
		cout << *it << " ";
		it++;
	}	
}

// 测试swap
void test04()
{
	// template <class T, class Alloc>
	// void swap(vector<T, Alloc>&x, vector<T, Alloc>&y);
	vector<int> v2(10, 1);
	vector<int> v3(10, 3);
	// 交换两个向量的空间
	v2.swap(v3);
	vector<int>::iterator it;
	it = v2.begin();
	//while (it != v2.end())
	//{
	//	//cout << ++(*it) << " ";
	//	cout << *it << " ";
	//	it++;
	//}
//
//reference operator[] (size_type n);const_reference operator[] (size_type n) const;

	for (int i = 0; i < v2.size(); i++)
	{
		cout << ++v2[i]<< ' ';
	}

}

五:vector的模拟实现

在实现vector的时候我们采用模板来实现,方便vector存取任意的类型,可以是自定义类型,也可以是内置类型,例如:vector<vector<int>>(二维数组)。

因为vector的数据结构本质是顺序表,所以我们可以设置一个顺序表来模拟实现它。

然后设置三个标志位:

cpp 复制代码
private:
	iterator _start = nullptr;
	iterator _finsh = nullptr;
	iterator _end_of_storage = nullptr;

_start 表示容器的起始位置,_finsh 表示有效数据个数的最后一个位置,_end_of_storage表示容器容量的大小。

iterator 使我们使用typedef将模板类型重命名的。

cpp 复制代码
namespace cp
{
    template<class T>
    class vector {
        public:
	    typedef T* iterator;
	    typedef const T* const_iterator;
    private:
	    iterator _start = nullptr;
	    iterator _finsh = nullptr;
    	iterator _end_of_storage = nullptr;
    };    
}

我们将我们的vector使用一个单独的命名空间CP包起来,避免跟库里面的vector冲突。

关于模板请看:C++模板初阶-CSDN博客

(一)一些简单接口的实现:

一些比较简单的接口我们就将他们放到类里面

关于容量相关的接口:

cpp 复制代码
//清除数据
void clear()
{
	_finsh = _start;
}
//判断容器是否为空
bool empty()const 
{
	return _finsh == _start;
}
//获取容器容量的大小
size_t size() const 
{
    //指针相减得到元素个数
	return _finsh - _start;
}
//获取容器容量的大小
size_t capacity() const 
{
	return _end_of_storage - _start;
}

迭代器的实现:const迭代器和非const迭代器,反向迭代器就不实现了

cpp 复制代码
几种迭代器
iterator begin()
{
	return _start;
}
iterator end()
{
	return _finsh;
}
const_iterator cbegin() const
{
	return _start;
}
const_iterator begin() const
{
	return _start;
}
const_iterator end() const
{
	return _finsh;
}
const_iterator cend() const
{
	return _finsh;
}

函数后边加const表示成员变量不可以修改。

方括号下标**[ ]**的重载实现:

cpp 复制代码
//返回普通引用,可以修改
T& operator[](size_t pos)
{
	return _start[pos];
}
//返回const引用,不可以修改
const T& operator[](size_t pos)const
{
	return _start[pos];
}

尾删除函数

cpp 复制代码
void pop_back()
{
	assert(!empty());
    --_finsh;
}

(二)一些复杂接口的实现

这些复杂的接口我们将他们放到类外面。

(1)reserve:开辟空间并检查。

cpp 复制代码
template<class T>
void vector<T>::reserve(size_t n)
{
	if (n > capacity())
	{
		size_t old_size = size();

		T* tmp = new T[n];
		for (int i = 0; i < old_size; i++)
		{
			tmp[i] = _start[i];
		}
		if (_start)
		{
			delete[] _start;
		}
		_start = tmp;
		_finsh = _start + old_size;
		_end_of_storage = _start + n;
	}
}

注意:在更新成员变量的时候要先预存一份没交换之前的size,因为在_starttmp交换的时候也会影响到size的改变。另外,记得释放旧空间,当然如果原来的容器是空的就不用释放了。

(2)push_back:在容器的末尾位置尾插一个数据

cpp 复制代码
template<class T>
void vector<T>::push_back(const T& x)
{
	if (_finsh == _end_of_storage)
	{
		reserve(capacity() == 0 ? 4 : 2 * capacity());
	}
	*_finsh = x;
	++_finsh;
}

只要是往容器里面插入数据都要检查容器是否满了,如果满了就需要先扩容再插入数据,这和数据结构的顺序表是一样的。

迭代器失效问题:

通常我们在插入和删除的时候使用的是迭代器,所以在一些情况下会导致迭代器失效,string的迭代器版本也会有这样的情况。

(1)insert:在pos迭代器位置插入数据,并且返回当前插入数据位置的迭代器。

cpp 复制代码
	template<class T>
	typename vector<T>::iterator vector<T>::insert(iterator pos, T val)
	{
		assert(pos >= _start);
		assert(pos < _finsh);
		if (_finsh == _end_of_storage)
		{
			//防止pos位置因为扩容而失效
			size_t len = pos - _start;
			reserve(capacity() == 0 ? 4 : 2 * capacity());
			pos = _start + len;
		}
		iterator end = _finsh;
		while (end >= pos)
		{
			*end = *(end - 1);
			end--;
		}
		*pos = val;
		_finsh++;
		pos++;
		return pos;
	}

**知识点补充:**在这里我们使用typename的原因,因为在实例化的时候编译器不知道迭代器(iterator)到底是类型还是成员变量,所以我们使用typename。(规定使用typename指定的是类型)。

在实现insert的时候我们要先扩容,但是扩容的时候原来_start指向的空间和tmp交换了

但是我们的pos位置迭代器还是原来空间的pos,原来空间交换给tmp之后随着函数结束tmp变量指向的空间销毁,pos所指向的也就是一个随机空间。当我们在挪动数据的时候,因为pos不是当前对象指向的空间位置,所以会发生一些错误,插入数据也就会导致一些问题。

解决方案:

如上述代码,我们先记录pos的相对位置,在更新_start指向的空间的之后我们也更新pos的迭代器,这样就不会发生问题了。

这是第一种迭代器失效的情况:迭代器指向的空间被释放,类似于野指针。

(2)erase:挪动覆盖删除迭代器位置的数据,返回pos位置的迭代器。

我们先实现一下这个成员函数,同样,我们将他们放到类外面。

cpp 复制代码
template<class T>
typename vector<T>::iterator vector<T>::erase(iterator pos)
{
	assert(pos >= _start);
	assert(pos < _finsh);

	iterator start = pos;
	while (start < _finsh)
	{
		*start = *(start + 1);
		start++;
	}
	_finsh--;

	return pos;
}

当我们删除数据之后,pos还是指向原来的位置,但是如果我们以下标的形式看待它。

例如:我们来测试一下,我们写一个vector,往里面放一些数据,删除之后使用它的迭代器来操作。

因为方便测试我们实现一个打印容器的函数模板:

cpp 复制代码
	template<class Container>
	void print_container(const Container& con)
	{
		/*auto it = con.cbegin();
		while (it != con.cend())
		{
			cout << *it << " ";
			++it;
		}
		cout << endl;*/
		for (auto& e : con)
		{
			cout << e << " ";
		}
		cout << endl;
	}
cpp 复制代码
//测试迭代器失效问题
void test_vaid_iterator()
{
	cp::vector<int> v;
	v.push_back(0);
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);
	v.push_back(5);
	//删除5位置的内容
	cp::vector<int>::iterator it = v.erase(v.begin()+3);
	cp::print_container(v);
	*it *= 10;
	cp::print_container(v);
}

可以看到原来位置的意义已经改变了,我们原本的位置在删除数据之后已经改变了。

(3)库里面的迭代器失效问题:Windows平台下。

我们来测试一下库里面的insert和erase。

测试函数:

(1)测试insert:

cpp 复制代码
//测试迭代器失效问题
void test_vaid_iterator()
{
	std::vector<int> v;
	v.push_back(0);
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);
	v.push_back(5);
	//删除5位置的内容
	std::vector<int>::iterator pos = v.begin() + 3;
	v.insert(pos, 3);//删除pos位置的内容
	cp::print_container(v);
	//访问pos位置
	*pos*=10;
	cp::print_container(v);
}

(2)的是erase:

cpp 复制代码
//测试迭代器失效问题
void test_vaid_iterator()
{
	std::vector<int> v;
	v.push_back(0);
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);
	v.push_back(5);
	//删除5位置的内容
	std::vector<int>::iterator pos = v.begin() + 3;
	cp::print_container(v);
	v.erase(pos);//删除pos位置的内容
	*pos *= 10;
	cp::print_container(v);
}

在vs2020中编译器会强制检查报错。在insert和erase之后是不允许访问pos位置的迭代器的。

总结:不管是自己实现的还是标准库内部实现的,在容器中(string,vector..)为了安全考虑,在insert和erase之后就不要访问pos位置的迭代器了。如果要访问就要更新迭代器。

(三)五个构造函数的模拟实现

接下来我们来实现他们的成员函数。

(1)默认构造

因为构造函数默认走初始化列表,所以我们在实现默认构造函数的时候可以这样写。

cpp 复制代码
vector() = default;

因为我们在成员变量哪里给了缺省值,所以使用default关键字可以强制编译器生成默认构造。

当然,如果们没在成员变量给缺省参数的话可以自己走初始化列表:

cpp 复制代码
		vector()
			:_start(nullptr)
			,_finsh(nullptr)
			,_end_of_storage(nullptr)
		{
			// 啥也不干,因为会自动走舒适化列表用缺省值完成初始化
		}

我们来测试一下:

可以看到v对象的三个成员已经被初始化完成了,都是空指针。

(2)拷贝构造函数

用一个已经存在的对象去初始化另一个刚创建的对象

cpp 复制代码
template<class T>
vector<T>::vector(const vector<T>& v)
{
	//防止自己给自己赋值
	if (this != &v)
	{
		reserve(v.capacity());
		for (auto& e : v)
		{
			push_back(e);
		}
		//_finsh和_endd_of_storage是指针,不是整型,不能直接赋值,而且在reserve和push_back的时候已经更新过了,不用更新
		//_finsh = v._finsh;
		//_end_of_storage = v._end_of_storage;
	}
}

(3)使用n个val去初始化容器的构造

cpp 复制代码
	template<class T>
	vector<T>::vector(size_t n, vector<T>& val)
	{
		reserve(n);
		for (size_t i = 0; i < n; i++)
		{
			push_back(val);
		}
	}

(4)迭代器区间初始化构造:

cpp 复制代码
template<class input_iterator>
vector(input_iterator first, input_iterator last)
{
	while (first != last)
	{
		push_back(*first);
	    first++;
	}
}

测试:

cpp 复制代码
void Test03()
{
	cp::vector<int> v;
	v.push_back(0);
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);
	v.push_back(5);

	cp::print_container(v);

	cp::vector<int> v4(v.begin()+2, v.begin() + 5);

	cp::print_container(v4);

}

(5)赋值构造

因为都是自定义类型,且需要开辟空间,所以和拷贝构造一样都是深拷贝。

cpp 复制代码
		//this = v
		vector<T>& operator=(const vector<T>& v)
		{
			if (this != &v)
			{
				clear();

				reserve(v.size());

				/*size_t size = v.size();
				T* tmp = new T[v.capacity()];
				for (size_t i = 0; i < size; i++)
				{
					tmp[i] = v._start[i];
				}
				if (_start)
					delete[] _start;

				_start = tmp;
				_finsh = _start + size;
				_end_of_storage = _start + v.capacity();
				*/
				for (auto& e : v)
				{
					push_back(e);
				}
			}
			return *this;
		}

赋值构造的现代写法:

cpp 复制代码
void swap(vector<T>& v)
{
	std::swap(_start, v._start);
	std::swap(_finsh, v._finsh);
	std::swap(_end_of_storage, v._end_of_storage);
}
//现代写法
vector<T>& operator=(vector<T> v)
{
	swap(v);
	
	return *this;
}

(6)析构函数

cpp 复制代码
~vector()
{
	if (_start)//如果_start是空(为假),则说明已经析构了,不需要析构
	{
		delete[] _start;
		_start = _finsh = _end_of_storage = nullptr;
	}
}
相关推荐
蜀黍@猿12 分钟前
【C++ 基础】从C到C++有哪些变化
c++
Am心若依旧40913 分钟前
[c++11(二)]Lambda表达式和Function包装器及bind函数
开发语言·c++
明月看潮生15 分钟前
青少年编程与数学 02-004 Go语言Web编程 20课题、单元测试
开发语言·青少年编程·单元测试·编程与数学·goweb
zh路西法23 分钟前
【C++决策和状态管理】从状态模式,有限状态机,行为树到决策树(一):从电梯出发的状态模式State Pattern
c++·决策树·状态模式
大G哥25 分钟前
java提高正则处理效率
java·开发语言
VBA633735 分钟前
VBA技术资料MF243:利用第三方软件复制PDF数据到EXCEL
开发语言
轩辰~37 分钟前
网络协议入门
linux·服务器·开发语言·网络·arm开发·c++·网络协议
小_太_阳1 小时前
Scala_【1】概述
开发语言·后端·scala·intellij-idea
向宇it1 小时前
【从零开始入门unity游戏开发之——unity篇02】unity6基础入门——软件下载安装、Unity Hub配置、安装unity编辑器、许可证管理
开发语言·unity·c#·编辑器·游戏引擎
lxyzcm1 小时前
C++23新特性解析:[[assume]]属性
java·c++·spring boot·c++23