首页 > 编程语言 >C++中的vector

C++中的vector

时间:2024-05-29 12:31:46浏览次数:26  
标签:迭代 back C++ v1 vector push size

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

标签:迭代,back,C++,v1,vector,push,size
From: https://blog.csdn.net/m0_73968399/article/details/139194707

相关文章

  • C++ - 结构体转cha*
    c语言结构体转cha*在C语言中,将结构体转换为char*通常意味着你想要获取结构体的内存表示,并将其视为字符数组。这种转换可以通过使用memcpy函数来实现。下面是一个简单的例子,展示了如何将结构体转换为char*: #include<stdio.h>#include<stdlib.h>#include<string.......
  • C++ - tcp网络传输如何发送结构体类型
    1、tcp网络传输如何发送结构体类型 在C++中,要通过TCP网络传输结构体类型,你需要将结构体序列化为字节流,然后在另一端反序列化。这里有一个简单的例子:#include<iostream>#include<cstring>#include<sys/socket.h>#include<netinet/in.h>#include<unistd.h>//假设......
  • c++ string 使用例子
      ===============一文读懂C++String类在算法竞赛中的常见用法string相较于C语言的字符数组可方便太多了,在算法竞赛中能大大节省我们的时间。以下是我在刷题中会使用到的常见String用法。注释都写好了。#include<iostream>#include<string>usingnamespacestd;int......
  • 【C++】【YOLO】搭建环境运行YOLO模型,完成目标识别
    1、安装VisualStudio,勾选C++和Python负荷 2、安装CUDA|Pytorch|Python这三者之间的版本关系很重要详情参考:Pycharm搭建CUDA,Pytorch教程(匹配版本,安装,搭建全保姆教程)_cuda12.3对应的pytorch版本-CSDN博客3、下载ultralytics所有代码进行修改(https://github.com/ultralyt......
  • (免费领源码)Java/Mysql数据库+01012大学生爱心互助代购网站,计算机毕业设计项目推荐上万
    摘 要在网络信息的时代,众多的软件被开发出来,给用户带来了很大的选择余地,而且人们越来越追求更个性的需求。在这种时代背景下,企业只能以用户为导向,按品种分类规划,以产品的持续创新作为企业最重要的竞争手段。系统采用了B/S结构,将所有业务模块采用以浏览器交互的模式,选择My......
  • (免费领源码)Java/Mysql数据库+00895springboot的校园二手书销售平台,计算机毕业设计项目
    本科学生毕业设计校园二手书销售平台设计与实现                院系名称:    计算机科学与技术学院    专业班级:                            学生姓名:                           ......
  • (免费领源码)Java/Mysql数据库+00750基于python的音乐电台推荐系统设计,计算机毕业设计项
    毕业设计(论文)Django音乐电台推荐系统学   院:                           专   业:                           年   级:                           姓   名:   ......
  • (免费领源码)Java/Mysql数据库+00485 个性化音乐推荐系统的设计与实现,计算机毕业设计项
    毕业设计(论文)NodeJS个性化音乐推荐系统学   院:                           专   业:                           年   级:                           姓   名:  ......
  • C++数据结构之Hash table(哈希表|散列表)
    目录一、基本组成部分二、使用方法 三、代码实现四、代码中如何遍历链表来避免冲突哈希表(HashTable),也称为散列表(思考:vs平衡二叉树),是一种数据结构,它提供了通过键(key)直接访问存储的值(value)的能力。哈希表的工作原理基于哈希函数(HashFunction),该函数将输入的键映射到表中的......
  • C++ Primer Plus第十八章复习题
    1、使用用大括号括起的初始化列表语法重写下述代码。重写后的代码不应使用数组ar。classz200{private:intj;charch;doublez;public:Z200(intjv,charchv,zv):j(jv),ch(chv),z(zv){}};doublex=8.8;std::strings="whatabracingeff......