stl_vector类(使用+实现)(C++)

vector

  • 一、vector-简单介绍
  • 二、vector的常用接口
    • 1.常见构造
      • (1)构造函数
      • (2)初始化
    • 2.iterator的使用
    • 3.容量操作
    • 4.增删查改操作
    • 5.迭代器失效问题
    • 6.动态二维数组
  • 三、vector实现
    • 1.vector类重要的方法实现分析介绍
      • (1)、涉及memcpy深浅拷贝问题
      • (2)、成员变量
    • 2.vector类整体实现代码
  • 四、vector< char > 和 string区别

一、vector-简单介绍

Vector是一个封装了动态大小数组的顺序容器(Sequence Container)。可以简单的认为,vector是一个能够存放任意类型的动态数组。
vector是一个类模板,其中封装了类的多种方法。例如,在使用时要改变大小,它的大小容器会自动处理。字符串的增删查改等操作,通过调用类中的方法便捷的进行操作…。

二、vector的常用接口

1.常见构造

(1)构造函数

(constructor)构造函数声明 接口说明
vector() 无参构造
vector(size_t n, const T& val = T() 构造并初始化n个val
vector(const vector& x) 拷贝构造
vectorvector(InputIterator first, InputIterator last) 使用迭代器进行初始化构造

test:

void test_vector_constructor()
{
	//因为vector是一个模板类,所以string也可以当参数的类型
	//无参构造
	vector<string> v1;
	string name("ren");
	//正常尾插一个string类对象
	v1.push_back(name);
	//匿名对象
	v1.push_back(string("yv"));
	//隐式类型转换
	v1.push_back("qing");

	//构造并初始化n个val
	vector<string> v2(5, "xxx");

	//拷贝构造
	vector<string> v3(v1);

	//使用迭代器初始化构造
	string str("hello world");
	vector<char> v4(str.begin(), str.end());
	vector<int> v5(str.begin(), str.end());//用char初始化int会有整型提升,类型转换。  ASCII

	//使用数组构造
	int a[] = { 16,2,77,29 };
	vector<int> v6(a, a + 4);  //a本质是指针
}

stl_vector类(使用+实现)(C++)_第1张图片

(2)初始化

class A
{
public:
	A( int a = 0){
		cout << "构造函数" << endl;
	}
	A(const A& x){
		cout << "拷贝构造" << endl;
	}
	~A(){
		cout << "析构函数" << endl;
	}
};

int main()
{
	vector<A> va{ A(10),A(20),A(30),A(40) };
	cout << va.size() << endl;
	cout << va.capacity() << endl;
	return 0;
}

代码运行结果:
stl_vector类(使用+实现)(C++)_第2张图片

注意:vector v{ 7, 8, 9 };和vector v = { 7, 8, 9 }; 两者的区别:
这两个表达方式实际上是等价的,它们都是用于创建一个包含整数的vector。两者的不同之处在于初始化的方法不同。

  1. 在第一个例子中,你使用的是初始化列表(initializer list)的方式,通过大括号{}将元素括起来,并直接将它赋值给vector v。这是C++11引入的一种方便的初始化方法。

  2. 而在第二个例子中,你使用的是赋值操作符(assignment operator)来将初始化列表赋值给vector v。这种方式更加传统,在C++11之前就可以使用。

总的来说,这两种方式都可以用于创建和初始化vector,并且具有相同的效果。所以你可以根据个人偏好来选择使用哪种方式。

2.iterator的使用

iterator的使用 接口说明
begin + end 获取第一个数据位置的iterator/const_iterator,获取最后一个数据的下一个位置的iterator/const_iterator
rbegin + rend 获取最后一个数据位置的reverse_iterator,获取第一个数据前一个位置的reverse_iterator

test:
注意: 在string中说过,迭代器实现了之后,范围for可以直接使用。范围for本质就是迭代器

void test_iterator()
{
	int a[] = { 16, 2, 77, 239, 23, 3, 43, 2, 3, 3, };
	int size = sizeof(a) / sizeof(a[0]);
	vector<int> v1(a, a + size);

	vector<int>::iterator it = v1.begin();
	while (it != v1.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;

	vector<int>::reverse_iterator rit = v1.rbegin();
	while (rit != v1.rend())
	{
		cout << *rit << " ";
		++rit;
	}
	cout << endl;
}

stl_vector类(使用+实现)(C++)_第3张图片

3.容量操作

函数名称 接口说明
size 获取数据个数
capacity 获取容量大小
empty 判断是否为空
reserve 改变vector的capacity
resize 改变vector的size

reserve负责开空间,如果预先知道需要使用开多少空间,reserve可以缓解vector增容的代价。

test:

void test_space()
{
	vector<int> v1;
	v1.resize(10);
	for (size_t i = 0; i < 10; i++)
	{
		//使用[]改变
		v1[i] = i;
	}
	
	for (auto e : v1)
	{
		cout << e << " ";
	}
	cout << endl;

	cout << " size:" << v1.size() << endl;
	cout << "capacity:" << v1.capacity() << endl;
	cout << "empty:" << v1.empty() << endl << endl;

	vector<int> v2;
	v2.reserve(10);
	for (size_t i = 0; i < 5; i++)
	{
		//尾插
		v2.push_back(i);
	}

	for (auto e : v2)
	{
		cout << e << " ";
	}
	cout << endl;

	cout << " size:" << v2.size() << endl;
	cout << "capacity:" << v2.capacity() << endl;
	
	v2.clear();
	cout << "empty:" << v2.empty() << endl;
}

test(vector默认扩容机制):vector的增容是1.5倍

void test_vector_expand()
{
	size_t sz;
	vector<int> v;
	sz = v.capacity();
	for (size_t i = 0; i < 100; i++)
	{
		v.push_back(i);
		if (sz != v.capacity())
		{
			sz = v.capacity();
			cout << "New capacity:" << sz << endl;
		}
	}
}

4.增删查改操作

函数名称 接口说明
push_back 尾插
pop_back 尾删
erase 删除pos位置的数据
insert 在pos之前插入val
swap 交换两个vector的数据空间
operator[] 像数组一样访问

test:

void test_modifiers()
{
	vector<int> v;
	//push_back
	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 (auto e : v)
	{
		cout << e << " ";
	}
	cout << endl;

	//删除操作
	v.pop_back();
	v.erase(v.begin());
	v.erase(v.end() - 1);    //vector可以直接减1,list不行
	for (auto e : v)
	{
		cout << e << " ";
	}
	cout << endl;

	//insert
	v.insert(v.begin(), 3);
	v.insert(v.end(), 10);
	for (auto e : v)
	{
		cout << e << " ";
	}
	cout << endl;

	//swap
	vector<int> v1;
	v1.swap(v);    //v现在没有值
	for (auto e : v1)
	{
		cout << e << " ";
	}
	cout << endl;

	for (size_t i = 0; i < v1.size(); i++)
	{
		//operator
		v1[i]++;
		cout << v1[i] << " ";
	}
	cout << endl;
}

5.迭代器失效问题

引起底层空间改变的操作,都有可能是迭代器失效
这里使用insert和erase举例,当然别的方法也存在这个情况,例如:resize,reserve,push_back…。

解决迭代器失效的方法:在使用前,对迭代器重新赋值即可。

test:

void test_iterator()
{
	vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);
	for (auto e : v)
	{
		cout << e << " ";
	}
	cout << endl;

	//vector::iterator it = v.begin();
	//v.insert(it, 100);

	//高危行为
	//*it += 10;

	//删除pos位置的值,导致迭代器失效
	/*vector::iterator pos = find(v.begin(), v.end(), 3);
	v.erase(pos);
	cout << *pos << endl;*/

	//如何解决问题:对迭代器重新赋值
	//eg:删除所有偶数
	vector<int>::iterator it = v.begin();
	while (it != v.end())
	{
		if (*it % 2 == 0)
		{
			//因为erase在调用完成后返回pos的后一个位置,
			//如果pos是最后一个位置则这是容器结束
			it = v.erase(it);
		}
	}
}

stl_vector类(使用+实现)(C++)_第4张图片

6.动态二维数组

stl_vector类(使用+实现)(C++)_第5张图片

三、vector实现

1.vector类重要的方法实现分析介绍

(1)、涉及memcpy深浅拷贝问题

在构造函数里涉及到自定义类型元素中的资源管理。
如果在vector< string >类的单参构造中,使用memcpy。如果仅仅对自定义类型的元素进行拷贝完全没问题,但是拷贝的自定义类型元素中涉及到资源管理就会出现问题。

  1. 内存泄漏
  2. 多次释放一块地址,野指针。程序崩溃。

stl_vector类(使用+实现)(C++)_第6张图片
以拷贝构造举例,这里就不能使用memcpy了,而是一步步遍历赋值

	vector(const vector<T>& v)
	{
		_start = new T[v.capacity()];
		//如果是vector 在拷贝时需要是深拷贝
		//memcpy(_start, v._start, sizeof(T) * v.size());
		for (size_t i = 0; i < v.size(); i++)
		{
			//_start[i] = v._start[i];
			_start[i] = T(v._start[i]);
		}

		_finish = _start + v.size();
		_end_of_storage = _start + v.capacity();
	}

所以在实现单参构造函数时,不能使用memcpy

(2)、成员变量

在vector类实现中,我们采用和库中相同的成员变量,三个迭代器,在前面的动态二维数组知识点提到。

	private:
		//这里最好给默认值,这样在构造函数就可以不用刻意初始化为nullptr
		//使用未定义的值进行构造会出现大问题
		iterator _start = nullptr;
		iterator _finish = nullptr;
		iterator _end_of_storage = nullptr;

_finish - _start = size();
_end_of_storage - _start = capacity();

2.vector类整体实现代码

namespace kpl
{
	template<class T>
	class vector
	{
	public:
		// Vector的迭代器是一个原生指针
		typedef T* iterator;
		typedef const T* const_iterator;

		// 无参构造
		vector()
			//: _start(nullptr)     //这里都可以省略,因为在成员变量声明的时候给了默认值nullptr
			//, _finish(nullptr)
			//, _endOfStorage(nullptr)
		{}

		vector(size_t n, const T& value = T())
		{
			/*reserve(n);
			while (n--)
			{
				push_back(value);
			}*/
			resize(n, value);
		}

		//vector v(10, 1);
		//vector v(10u, 1);
		//vector v(10, "11111");
		//解决例子匹配问题
		vector(int n, const T& value = T())
		{
			resize(n, value);
		}

		// 若使用iterator做迭代器,会导致初始化的迭代器区间[first,last)只能是vector的迭代器
		// 重新声明迭代器,迭代器区间[first,last)可以是任意容器的迭代器
		template<class InputIterator>
		vector(InputIterator first, InputIterator last)
		{
			while (first != last)
			{
				push_back(*first);
				++first;
			}
		}

		//拷贝构造
		vector(const vector<T>& v)
		{
			_start = new T[v.capacity()];
			//如果是vector 在拷贝时需要是深拷贝
			//memcpy(_start, v._start, sizeof(T) * v.size());
			for (size_t i = 0; i < v.size(); i++)
			{
				//_start[i] = v._start[i];
				_start[i] = T(v._start[i]);
			}
	
			_finish = _start + v.size();
			_endOfStorage  = _start + v.capacity();
		}

		//运算符重载,现代写法
		void swap(vector<T>& v)
		{
			std::swap(_start, v._start);
			std::swap(_finish, v._finish);
			std::swap(_endOfStorage , v._endOfStorage );
		}
		vector<T>& operator=(vector<T> v)
		{
			//给成员变量都初始化成nullptr的好处就体现出来了,如果交换之后是一个随机的地址就会出现错误
			swap(v);
			return *this;
		}

		~vector()
		{
			if (_start)
			{
				delete[] _start;
				_start = _finish = _endOfStorage = nullptr;
			}
		}
		
		// 迭代器相关
		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; 
		}

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

		void reserve(size_t n)
		{
			if (n > capacity())
			{
				size_t sz = size();
				// 1. 开辟新空间
				T* tmp = new T[n];

				// 2. 拷贝元素
				if (_start)
				{
					for (size_t i = 0; i < sz; ++i)
						tmp[i] = _start[i];

					// 3. 释放旧空间
					delete[] _start;
				}

				_start = tmp;
				
				//这里不能使用size(),因为size的实现是_finish-_start
				//这时_start改变了,但是finish还没改变
				_finish = _start + sz;
				_endOfStorage = _start + n;
			}
		}

		void resize(size_t n, const T& value = T())
		{
			if (n <= size())
			{
				_finish = _start + n;
				return;
			}
			else
			{
				//增容
				reserve(n);
				
				//赋值
				while (_finish != _start + n)
				{
					*_finish = value;
					++_finish;
				}
			}
		}

		// 元素访问
		T& operator[](size_t pos) 
		{ 
			assert(pos < size());
			return _start[pos]; 
		}

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

		T& front()
		{
			return *_start;
		}

		const T& front()const
		{
			return *_start;
		}

		T& back()
		{
			return *(_finish - 1);
		}

		const T& back()const
		{
			return *(_finish - 1);
		}

		// vector的修改操作
		void push_back(const T& x) 
		{ 
			//if (_finish == _endOfStorage)
			//{
			//	size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;
			//	reserve(newcapacity);
			//}

			//*_finish = x;
			//++_finish;
			insert(end(), x); 
		}

		void pop_back() 
		{ 
			erase(end() - 1); 
		}

		//vector erase和insert迭代器对象使用后,不能在访问这个迭代器
		//我们认为它失效了,访问的结果是未定义的
		iterator insert(iterator pos, const T& x)
		{
			assert(pos <= _finish && pos >= _start);

			// 空间不够先进行增容
			if (_finish == _endOfStorage)
			{
				//保存pos的相对位置
				size_t len = pos - _start;
				
				size_t newCapacity = (0 == capacity()) ? 1 : capacity() * 2;
				reserve(newCapacity);

				//解决pos迭代器失效问题,对pos位置更新
				pos = _start + len;
			}

			iterator end = _finish - 1;
			while (end >= pos)
			{
				*(end + 1) = *end;
				--end;
			}

			*pos = x;
			++_finish;
			return pos;
		}

		// 返回删除数据的下一个数据,如果是最后一个元素,则是容器结束
		// 方便解决:一边遍历一边删除的迭代器失效问题
		iterator erase(iterator pos)
		{
			assert(pos < _finish && pos >= _start);

			iterator it = pos + 1;
			while (it != _finish)
			{
				*(it - 1) = *it;
				++it;
			}

			--_finish;
			return pos;
		}
	private:
		iterator _start = nullptr;		// 指向数据块的开始
		iterator _finish = nullptr;		// 指向有效数据的尾
		iterator _endOfStorage = nullptr;  // 指向存储容量的尾
	};
}

四、vector< char > 和 string区别

  1. 结构不同,string受’\0’的影响,并且要和C语言兼容
  2. string接口丰富,有很多专用接口。eg:+=, find…

你可能感兴趣的:(c++,stl,vector)