C++中的vector

一丶vector的介绍和使用

1.vector的简单介绍

关于vector:
1.vector是表示可变大小数组的容器。
2.就像数组一样,vector采用连续存储空间来存储元素。这意味着可以采用下标对vector的元素进行访问,和数组一样高效。但是又不像数组,它的大小是可以动态改变的,而且它的大小会被容器自动处理
3.本质上讲,vector使用动态分配数组来存储它的元素。当新元素插入时,如果空间不够,这个数组需要被重新分配大小,也就是需要增加存储空间。其做法是,分配一个新的数组,然后将全部元素移动到这个数组。就时间而言,这是一个相对代价高的任务,因为每当一个新的元素加入到容器的时候,vector并不会每一都重新分配大小,这取决于vector是否需要扩容。
4.vector分配空间的策略:vector会分配一些额外的空间以适应可能新的增长,因为存储空间比实际需要的存储空间更大。不同的库采用不同的策略权衡空间的使用和重新分配。但是无论如何,重新分配都应该是对数增长的间隔大小,以至于在末尾插入一个元素的时候是在常数时间的复杂度完成的。
5.因此,vector占用了更多的存储空间,为了获得管理存储空间的能力,并且以一种有效的方式动态增长。
6.与其他动态序列容器相比,比方说deque丶list丶forward_list等等,vector在访问元素时要更加高效,在末尾添加和删除元素相对高效对于其他不在末尾的删除和插入操作,效率更低。比起list和forward_list统一的迭代器和引用更好。

cplusplus–vector的文档介绍

2.vector的使用

I.构造函数

(constructor)构造函数声明
1. vector()
无参构造
2. vector(size_type n, const value_type& val = value_type())
构造并初始化n个val
3. vector (const vector& x)
拷贝构造
4. vector (InputIterator first, InputIterator last)
使用迭代器进行初始化构造

II.相关的迭代器

iterator的使用
1. begin+end
获取第一个数据位置的iterator/const_iterator, 获取最后一个数据的下一个位置的iterator/const_iterator
2. rbegin+rend
rbegin()获取最后一个数据位置的reverse_iterator,rend()获取第一个数据前一个位置的
reverse_iterator

III.空间容量以及大小

容量空间
1. size
获取数据个数
2. capacity
获取容量大小
3. empty
判断是否为空
4. resize
改变vector的size
5. reverse
改变vector的capacity

  • capacity的代码在vs和g++下分别运行会发现,vs下capacity是按1.5倍,g++是按2倍增长的。注意:不要固化地认为,vector增容都是2倍,具体增长多少是根据具体的需求定义的

  • reserve只负责开辟空间,它不会修改vector的成员_size, 如果确定需要多少空间,reverse可以缓解vector增容的代价缺陷问题,这个会在后面的代码体现。

  • resize在开空间的同时还会进行初始化,会修改vector的成员_size。

// 如果已经确定vector中要存储元素大概个数,可以提前将空间设置足够
// 就可以避免边插入边扩容导致效率低下的问题了
void TestVectorExpandOP()
{
	vector<int> v;
	size_t sz = v.capacity();
	v.reserve(100); // 提前将容量设置好,可以避免一遍插入一遍扩容
	cout << "making bar grow:\n";
	for (int i = 0; i < 100; ++i)
	{
		v.push_back(i);
		if (sz != v.capacity())
		{
			sz = v.capacity();
			cout << "capacity changed: " << sz << '\n';
		}
	}
}

IV. vector的增删查改

vector的增删查改
1. push_back
尾插
2. pop_back
尾删
3. find
查找.(注意:这个是算法模块实现,不是vector的成员接口)
4. insert
在position位置之前插入val
5. erase
删除position位置的数据
6.swap
交换两个vector的数据空间
7. operator[]
像数组一样访问

3.迭代器失效的问题

迭代器的主要作用就是让算法能够不用关心底层数据结构,其底层实际就是一个指针,或者是对指针进行了封装.比如:vector的迭代器就是原生态指针T*.
因此迭代器失效,实际就是迭代器底层对应指针所指向的空间被销毁了,而使用一块已经被释放的空间,造成的后果是程序崩溃(即如果继续使用已经失效的迭代器,程序可能崩溃.)。

对于vector可能会导致其迭代器失效的操作有:

  1. 会引起其底层空间改变的操作,都有可能是迭代器失效。比如:resizereserveinsertassignpush_back等等。
include <iostream>
using namespace std;
#include <vector>
int main()
{
	vector<int> v{ 1,2,3,4,5,6 };
	auto it = v.begin();
	// 将有效元素个数增加到100个,多出的位置使用8填充,操作期间底层会扩容
	// v.resize(100, 8);
	// reserve的作用就是改变扩容大小但不改变有效元素个数,操作期间可能会引起底层容量改变
	// v.reserve(100);
	// 插入元素期间,可能会引起扩容,而导致原空间被释放
	// v.insert(v.begin(), 0);
	// v.push_back(8);
	// 给vector重新赋值,可能会引起底层容量改变
	v.assign(100, 8);
	/*
	出错原因:以上操作,都有可能会导致vector扩容,也就是说vector底层原理旧空间被释放掉,
	而在打印时,it还使用的是释放之间的旧空间,在对it迭代器操作时,实际操作的是一块已经被释放的
	空间,而引起代码运行时崩溃。
	解决方式:在以上操作完成之后,如果想要继续通过迭代器操作vector中的元素,只需给it重新
	赋值即可。
	*/
	//鉴于assign操作有可能会修改底层空间,导致it指向的可能不是assign后的v.begin
	//所以要重新获取一下
	it = v.begin()
	while (it != v.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;

	return 0;
}

  1. 指定位置元素的删除操作erase
void test_vector4()
{
	using namespace std;
	std::vector<int> v1;
	v1.push_back(1);
	v1.push_back(2);
	v1.push_back(3);
	v1.push_back(4);
	v1.push_back(5);

	std::vector<int>::iterator it = v1.begin();

	while (it != v1.end())
	{
		if (*it % 2 == 0)
		{
			//v1.erase(it);
			//注意:这里的写法是错误的  因为it如果进行erase后 
			//		不允许在进行访问  VS编译器进行了强制的检查
			//		但是在while处又进行it的访问 这很明显会报错
			//正确的写法:
			it = v1.erase(it);
			//要更新it 在erase后 it指向的是删除元素的下一个位置

			//同理 Linux下不会强制让迭代器失效
		}
		else
		{
			++it;
		}
	}

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

}
  1. Linux下,g++编译器对迭代器失效的检测并不是非常严格,处理也没有vs下极端。

具体是这样的,比方说我们进行erase操作后,迭代器在未更新之前在VS环境下是不能访问的,否则会报错。而在Linux环境下,即使迭代器未更新,仍让可以允许访问,不会报错。但是,实际上我们知道这个指向已经不对了,它实际指向的内容不是我们想要的。Linux只是允许我们访问这个空间了,但实际上这个空间的指向不是我们想要的。

// 1. 扩容之后,迭代器已经失效了,程序虽然可以运行,但是运行结果已经不对了
int main()
{
	vector<int> v{ 1,2,3,4,5 };
	for (size_t i = 0; i < v.size(); ++i)
		cout << v[i] << " ";
	cout << endl;
	auto it = v.begin();
	cout << "扩容之前,vector的容量为: " << v.capacity() << endl;
	// 通过reserve将底层空间设置为100,目的是为了让vector的迭代器失效
	v.reserve(100);
	cout << "扩容之后,vector的容量为: " << v.capacity() << endl;
	// 经过上述reserve之后,it迭代器肯定会失效,在vs下程序就直接崩溃了,但是linux下不会
	// 虽然可能运行,但是输出的结果是不对的
	while (it != v.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;
	return 0;
}
//程序输出:
//1 2 3 4 5
//扩容之前,vector的容量为: 5
//扩容之后,vector的容量为 : 100
//0 2 3 4 5 409 1 2 3 4 5
// 2. erase删除任意位置代码后,linux下迭代器并没有失效
// 因为空间还是原来的空间,后序元素往前搬移了,it的位置还是有效的
#include <vector>
#include <algorithm>
int main()
{
	vector<int> v{ 1,2,3,4,5 };
	vector<int>::iterator it = find(v.begin(), v.end(), 3);
	v.erase(it);
	cout << *it << endl;
	while (it != v.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;
	return 0;
}
// 3: erase删除的迭代器如果是最后一个元素,删除之后it已经超过end
// 此时迭代器是无效的,++it导致程序崩溃
int main()
{
	vector<int> v{ 1,2,3,4,5 };
	// vector<int> v{1,2,3,4,5,6};
	auto it = v.begin();
	while (it != v.end())
	{
		if (*it % 2 == 0)
			v.erase(it);
		++it;
	}
	for (auto e : v)
		cout << e << " ";
	cout << endl;
	return 0;
}

//Linux下去执行相同的代码:
========================================================
// 使用第一组数据时,程序可以运行
[sly@VM-0-3-centos 20220114]$ g++ testVector.cpp -std=c++11
[sly@VM-0-3-centos 20220114]$ ./a.out
1 3 5
=========================================================
// 使用第二组数据时,程序最终会崩溃
[sly@VM-0-3-centos 20220114]$ vim testVector.cpp
[sly@VM-0-3-centos 20220114]$ g++ testVector.cpp -std=c++11
[sly@VM-0-3-centos 20220114]$ ./a.out
Segmentation fault

从上述三个例子中可以看到:SGI STL中,迭代器失效后,代码并不一定会崩溃,但是运行结果肯定不对,如果it不在begin和end范围内,肯定会崩溃的。

  1. 与vector类似,string在插入+扩容操作+erase之后,迭代器也会失效
#include <string>
void TestString()
{
	string s("hello");
	auto it = s.begin();
	// 放开之后代码会崩溃,因为resize到20会string会进行扩容
	// 扩容之后,it指向之前旧空间已经被释放了,该迭代器就失效了
	// 后序打印时,再访问it指向的空间程序就会崩溃
	//s.resize(20, '!');
	while (it != s.end())
	{
		cout << *it;
		++it;
	}
	cout << endl;
	it = s.begin();
	while (it != s.end())
	{
		it = s.erase(it);
		// 按照下面方式写,运行时程序会崩溃,因为erase(it)之后
		// it位置的迭代器就失效了
		// s.erase(it);
		++it;
	}
}

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

二丶vector的深度剖析和模拟实现

1. 使用memcpy拷贝出现的问题

假设模拟实现的vector中的reserve接口中,使用memcpy进行拷贝,会发生什么呢?

int main()
{
	bite::vector<bite::string> v;
	v.push_back("1111");
	v.push_back("2222");
	v.push_back("3333");
	return 0;
}

阐述一下memcpy的特性:
1. memcpy是内存的二进制格式拷贝,将一段内存空间中的内容原封不动的拷贝到另外一段空间中。
2. 如果拷贝的是内置类型的元素,memcpy即高效又不会出错,但如果拷贝的是自定义类型元素,并且自定义类型元素中涉及到资源管理时,那么就会出错,这是因为memcpy的拷贝实际上是浅拷贝

既然是浅拷贝,那么涉及资源管理,就不能使用memcpy了,否则会造成内存泄漏甚至程序崩溃。

2. vector的模拟实现

鉴于模板声明和定义分离时,会出现链接问题,因此我们将声明和定义都放在vector.h文件中,并在vector.cpp中直接测试我们实现的功能。

vector.h

#pragma once
//模板的声明和定义要放到一个文件里
#include <assert.h>
#include <iostream>
#include <vector>
#include <list>

namespace bit
{
	/*template<class T>
	class vector
	{
	public:
		T* _a;
		size_t _size;
		size_t _capcacity;
	};*/

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

		const_iterator cbegin() const
		{
			return _start;
		}

		const_iterator cend() const
		{
			return _finish;
		}

		iterator begin()
		{
			return _start;
		}

		iterator end()
		{
			return _finish;
		}

		//迭代器区间初始化(也是构造函数)
		//为类模板的成员函数 同时该函数为函数模板
		//其目的是支持任一容器的迭代器区间初始化
		template<class InputIterator>
		vector(InputIterator first, InputIterator last)
		{
			while (first != last)
			{
				push_back(*first);
				++first;
			}
		}

		//n个value的构造参数
		vector(size_t n, const T& val = T())
		{	//const T& val = T()这种写法是提供一个缺省值
			//该缺省值为匿名对象 (无论是内置类型还是自定义类型都可以)
			reserve(n);
			for (size_t i = 0; i < n; i++)
			{
				push_back(val);
			}
		}
		 
		vector(int n, const T& val = T())
		{	//const T& val = T()这种写法是提供一个缺省值
			//该缺省值为匿名对象 (无论是内置类型还是自定义类型都可以)
			reserve(n);
			for (size_t i = 0; i < n; i++)
			{
				push_back(val);
			}
		}

		vector(initializer_list<T> il)
		{	
			reserve(il.size());
			for (auto e: il)
			{
				push_back(e);
			}
		}


		//表示强制编译器生成一个默认构造函数
		vector() = default;

		//未自行实现任何的构造 那么编译器将提供
		//一旦实现一种构造 那么编译器将不提供其他构造
		vector(const vector<T>& v)
		{
			//直接开辟好内存空间  不必每几次的时候就开辟
			//大大减少了扩容次数 减少了能耗
			reserve(v.capacity());
			for (auto e : v)
			{
				push_back(e);
			}
		}

		void swap(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=(vector<T>& v)
		{
			this->swap(v);
			return *this;
		}

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

		}

		void reserve(size_t n)
		{
			//if (n > capacity())
			//{
			//	T* tmp = new T[n];

			//	if (_start)
			//	{
			//		memcpy(tmp, _start, sizeof(T) * size());
			//		delete[] _start;
			//	}	//要注意 当代码进行到此处的时候 _start是有可能成为野指针的

			//	_start = tmp;	//在此处_start才为正常的指针
			//	//这里+的是size() 因为是将原来的数据拷贝过来
			//	_finish = _start + size();
			//	//这里+的是n,而不是capacity()
			//	//因为n是扩容好的新空间长度
			//	_end_of_storage = _start + n;

			//	//上面通过size()计算的_finish是有问题的 
			//	//因为出现了扩容
			//	//原本的_start是先被delete了 然后重新赋值
			//	//此时的_start已经不是原先的位置了
			//	//但是size()的计算是通过_finish - _start得到的
			//	//而此时_start已经被更改了 而_finish的位置还在原来的位置
			//	//这就导致了_finish的值计算错误
			//	//我们需要的是原先_start的位置或者是原vector的长度
			//	//因此需要先保存起来我们需要的数据

			//	假设我这么写:
			//	//_finish = tmp + size();
			//	//_start = tmp;
			//	//_end_of_storage = _start + n;
			//	这种写法是这样的:此时_start和_finish还为被修改
			//	我们先求得_finish的值  求出只会就不会出现上面的情况
			//	但是这个也有条件限制的  前提是_start没有被delete
			//	否则_start就是野指针了  不能进行使用
			//	而且上面的这种写法的顺序也不美观 因此不采用

			//	//我们采取先保存vector数组的长度的方法
			//	//在修改_start和_finish之前  先计算出长度 并保存起来

			//}
			//正确的写法:
			if (n > capacity())
			{
				size_t oldsize = size();	//先保存原来数组的元素个数
				T* tmp = new T[n];

				if (_start)
				{
					//注意:memcpy是浅拷贝 其是按字节拷贝的
					//memcpy(tmp, _start, sizeof(T) * size());
					//实现深拷贝
					for (size_t = 0; i < oldsize; i++)
					{
						tmp[i] = _start;  
					}
					delete[] _start;
				}
				//此时tmp和_start是等效的
				_start = tmp;					//修改_start
				_finish = tmp + oldsize;		//修改_finish
				_end_of_storage = _start + n;	
			}

		}

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

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

		T& operator[](size_t i)
		{
			assert(i < size());

			return _start[i];
		}

		const T& operator[](size_t i) const
		{
			assert(i < size());

			return _start[i];
		}

		void push_back(const T& x)
		{
			if (_finish == _end_of_storage)
			{
				size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;
				reserve(newcapacity);
				//reserve中已经完成了_finish和_end_of_storage的修改
			}

			*_finish = x;
			++_finish;
		}

		void pop_back()
		{
			assert(size() > 0);
			--_finish;
		}

		//但凡涉及扩容和迭代器使用时 都要注意迭代器失效的问题
		void insert(iterator& pos, const T& x)
		{	
			assert(pos >= _start);
			assert(pos <= _finish);	//此处可以等于_finish 可以在尾端进行插入

			if (_finish == _end_of_storage)
			{
				size_t len = pos - _start;
				size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;
				reserve(newcapacity);
				pos = _start + len;
			}

			iterator end = _finish - 1;

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

			*pos = x;
			++_finish;
		}

		//erase是否会迭代器器失效呢?
		//迭代器失效的充分条件是扩容或缩容导致的原生迭代器成为野指针
		//倘若erase的实现是通过缩容 那么erase操作必然会导致迭代器失效
		//我们这里是通过指针移动和数据覆盖的方式实现erase的
		//没有进行缩容 那么自然不存在迭代器失效的问题
		//不过 在不同的环境下 erase的实现是不同的 是否导致迭代器失效
		//要根据底层实现来进行判断
		void erase(iterator pos)
		{
			assert(pos >= _start);
			assert(pos < _finish);

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

			//使用标准库中的erase
			//在执行erase操作后 不能再访问erase处的迭代器 
			//我们设定的标准是这样的:
			//只要erase 迭代器就当作失效
			//那么原erase处的迭代器 不允许进行访问
			//注意:迭代器失效不一定会导致原迭代器成为野指针
			//		迭代器为野指针只是迭代器失效的一种方式
		}

		//在系统的insert erase 其返回值是迭代器
		//insert erase 想要访问原迭代器 可以通过接收返回值的方式 
		//来更新原迭代器 从而达到访问原迭代器的目的(即使原迭代器的实际地址已经更改)

	private:
		iterator _start = nullptr;			//相当于_a
		iterator _finish = nullptr;			//相当于_a + _size
		iterator _end_of_storage = nullptr;	//相当于_a + _capacity

	};

	void test_vector1()
	{
		bit::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++)
		{
			cout << v1[i] << " ";
		}
		cout << endl;

		//只有实现了对应的迭代器 才能使用范围for
		for (auto e : v1)
		{
			cout << e << " ";
		}
		cout << endl;

		vector<int>::iterator it = v1.begin();	//一种具有封装性丶广泛性的通用写法 
		//它等价于:int* v1.begin()
		//通过typeid我们可以查看一下
		cout << typeid(it).name() << endl;

		while (it != v1.end())
		{
			cout << *it << " ";
			++it;
		}
		cout << endl;
		//不过些许风霜罢了
	}

	void test_vector2()
	{
		using namespace std;
		bit::vector<int> v1;
		v1.push_back(1);
		v1.push_back(2);
		v1.push_back(3);
		v1.push_back(4);

		/*for (auto e : v1)
		{
			cout << e << " ";
		}
		cout << endl;

		v1.insert(v1.begin(), 0);
		for (auto e : v1)
		{
			cout << e << " ";
		}
		cout << endl;

		v1.erase(v1.begin());
		for (auto e : v1)
		{
			cout << e << " ";
		}
		cout << endl;*/
		//对于push_front  pop_front这种操作要谨慎使用
		//涉及数据的挪动 效率较低

		/*v1.insert(v1.end(), 30);
		for (auto e : v1)
		{
			cout << e << " ";
		}
		cout << endl;*/

		int x;
		cin >> x;
		
		//没有x就不插入 有x则插入到x的前面
		bit::vector<int>::iterator it = find(v1.begin(), v1.end(), x);
		if (it != v1.end())
		{
			//此处在插入数据后 如果扩容 迭代器就会失效
			//此时在VS编程环境下再访问对应的迭代器 会报错
			//在不同的环境下 迭代器可能会失效 也可能不会
			//不过我们把他统一当作会失效来处理 确保它的通用性
			v1.insert(it, 1000);
			//当insert在插入时扩容 原先的it迭代器会被delete掉
			//而it作为形参是不给改变实参的 那么就会导致原先的it
			//成为了野指针  那么再次访问it时 就会出现错误
			cout << *it << endl;
			//解决方法是什么呢? 修改成引用传入是否可以呢?
			//修改为引用型 对于下面这种情况 是不行的
			//v1.insert(v1.begin(), 0);//因为v1.begin返回是一个临时变量 具有常性
			//那么它是不能传递给引用类型的  显然这种写法是不行的
			//我们要寻找一个满足各种传递方式的写法
			//比如将扩容后的地址返回给it 通过更新it 来达到目的

		}

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

	}

	void test_vector3()
	{
		//using namespace std;
		bit::vector<int> v1;
		v1.push_back(1);
		v1.push_back(2);
		v1.push_back(3);
		v1.push_back(4);
		v1.push_back(5);

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

		v1.erase(v1.begin());

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

		int x;
		cin >> x;
		bit::vector<int>::iterator it = find(v1.begin(), v1.end(), x);
		if (it != v1.end())
		{
			//在erase it以后 我们统一认为it失效
			v1.erase(it);

			//在Linux环境下  此处不会失效

			//系统是这样设计的:在erase后  会给原迭代器设置一个标志
			// 通过该标志来判断迭代器是否能进行访问 
		}

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

	void test_vector4()
	{
		using namespace std;
		std::vector<int> v1;
		v1.push_back(1);
		v1.push_back(2);
		v1.push_back(3);
		v1.push_back(4);
		v1.push_back(5);

		std::vector<int>::iterator it = v1.begin();

		while (it != v1.end())
		{
			if (*it % 2 == 0)
			{
				//v1.erase(it);
				//注意:这里的写法是错误的  因为it如果进行erase后 
				//		不允许在进行访问  VS编译器进行了强制的检查
				//		但是在while处又进行it的访问 这很明显会报错
				//正确的写法:
				it = v1.erase(it);
				//要更新it 在erase后 it指向的是删除元素的下一个位置

				//同理 Linux下不会强制让迭代器失效
			}
			else
			{
				++it;
			}
		}

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

	}

	void test_vector6()
	{
		bit::vector<int> v1;
		v1.push_back(1);
		v1.push_back(2);
		v1.push_back(3);
		v1.push_back(4);
		v1.push_back(5);

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

		bit::vector<int> v2;
		v2 = v1;

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

	}

	void test_vector7()
	{
		/*bit::vector<int> v1;
		v1.push_back(1);
		v1.push_back(2);
		v1.push_back(3);
		v1.push_back(4);
		v1.push_back(5);

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

		vector<int> v2(v1.begin() + 1, v1.end() - 1);
		for (auto e : v2)
		{
			cout << e << " ";
		}
		cout << endl;*/
		using namespace std;
		list<int> lt;
		lt.push_back(100);
		lt.push_back(200);
		lt.push_back(300);

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

	}

	void test_vector8()
	{
		int i = 0;
		int j(1);
		int k = int();
		int x = int(2);
		//C++的内置类型也有构造 这是为了满足模板的需求

		//调用n个值的构造
		vector<string> v1(10);
		vector<string> v2(10, "xxx"); //存在隐式类型转换
		for (auto e : v1)
		{
			cout << e << " ";
		}
		cout << endl;

		for (auto e : v2)
		{
			cout << e << " ";
		}
		cout << endl;
		
		vector<int> v3(10, 1);	//想生成10个1的vector<int>  但是失败了
		//想调用的是vector(size_t n, const T & val = T())
		//实际上调用的是vector(InputIterator first, InputIterator last)
		//因为第一个参数列是size_t, T;第二个参数列是 T, T
		//那么明显第二个会很合适  
		//因为10个1是同种类型 都会被编译器识别为同一类型

		//它报出的错误是:非法的间接寻址
		//因为调用的是vector(InputIterator first, InputIterator last)
		//在该构造内部会对形参first和last解引用
		//由于我们传入的数据类型会被编译器解释为int
		//但是int不能被*(解引用)  于是就出现了非法的间接寻址的错误

		//解决方法:
		//一种是给数据后面加相应后辍来表示数据类型 来让编译器直接识别
		//vector<int> v3(10u, 1);  表示为unsigned int 即是size_t
		//另一种方法是提供vector(int n, const T& val = T())
		for (auto e : v3)
		{
			cout << e << " ";
		}
		cout << endl;

	}

	class A
	{
	public:
		A(int a1 = 0)
			:_a1(a1)
			,_a2(0)
		{}

		A(int a1, int a2)
			:_a1(a1)
			, _a2(a2)
		{}

	private:
		int _a1;
		int _a2;
	};

	void test_vector9()
	{
		//以前学过 单参数和多参数的隐式类型转换
		//单参数是对象名后面直接跟() 多参数是跟={}
		//单参数隐式类型转换
		A aa1(1);
		A aa2 = 1;
		A aa3 = { 1 }; //也可以这么写 但是不建议哦
		//多参数隐式类型转换
		A aa4(1,1);
		A aa5 = {2,2};
		A aa6{ 1,1 };	//可省略等号
		const A& aa8 = { 1,1 };

		//此处也是隐式类型转换 是参数列表初始化std::initializer_list
		// std::initializer_list
		//标准库中对应的原型:
		//		vector (initializer_list<value_type> il,
		//			const allocator_type& alloc = allocator_type());
		std::vector<int> v1({ 1,2,3,4,5,6 });	//直接构造
		std::vector<int> v2 = { 1,2,3 };		//隐式类型转换

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

		auto il1 = { 1,2,3,4,5,6 };
		//initializer_list内部存有两个指针_First和_Last
		//分别指向开始和超尾
		//其内部还有size() begin() end() 也就是说它支持范围for
		initializer_list<int> il2 = { 1,2,3,4,5,6,7,8 };
		for (auto e : il2)
		{
			cout << e << " ";
		}
		cout << endl;
		cout << typeid(il1).name() << endl;
		cout << sizeof(il2) << endl;

		vector<A> v3 = { 1, A(1), A(2,2), A{1}, A{2,2},{1},{2,2} };
		//分别对应:隐式 构造 构造 隐式 隐式...
	}

	void test_vector10()
	{
		vector<string> v1;
		v1.push_back("11111111111111");
		v1.push_back("11111111111111");
		v1.push_back("11111111111111");
		v1.push_back("11111111111111");
		v1.push_back("11111111111111");

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

}

vector.cpp

#include <iostream>
#include <algorithm>
#include <string>

//注意:命名空间展开必须放在#include "vector.h"之上,否则在"vector.h"中将找不到std的声明而报错,这是因为在"vector.h"展开的说话std还未展开造成的。
using namespace std;

#include "vector.h"
int main()
{
	//bit::test_vector1();
	//bit::test_vector2();
	//bit::test_vector3();
	//bit::test_vector6();
	//bit::test_vector7();
	//bit::test_vector8();
	//bit::test_vector8();
	//bit::test_vector9();
	bit::test_vector10();

	return 0;
}


本博客仅供个人参考,如有错误请多多包含。
Aruinsches-C++日志-5/28/2024
  • 34
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值