【C++】vector的认识+模拟实现

news2025/4/16 18:34:52

目录

  • 1️⃣vector的概念
  • 2️⃣STL中vector的使用
    • 2.1 vector的定义
    • 2.2 iterator的使用
    • 2.3 vector的空间问题
    • 2.4 vector的增删查改
    • 2.5 迭代器失效问题
      • 2.5.1 什么是迭代器?
      • 2.5.2 迭代器失效
  • 3️⃣vector的模拟实现
    • 3.1 迭代器
    • 3.2 构造函数
      • 🔎memcpy的拷贝异常问题
    • 3.3 析构函数
    • 3.4 有关空间的接口
    • 3.5 增删查改
    • 3.6 构造函数的现代写法
      • 🔎range构造和fill构造的冲突问题
      • 📝构造函数现代写法的整体代码


1️⃣vector的概念

vector是C++在STL库中提供的一个模板类,是一个容器(container),通过指定类模板类型可以用来存储不同类型的数据,它有连续和动态的两个特点。

  1. 连续(contiguous):开辟一块连续的空间用于存储数据,类比数组。
  2. 动态(dynamic):根据存储需求,所开辟的空间可以动态变化

在这里插入图片描述

(本文暂且不讨论空间配置器allocator)

🔎特性:

  1. vector与数组一样采用连续存储,意味着它也具有随机访问的特点,可以通过操作符[]加下标来访问某个位置的元素,十分高效。与数组不同的是,它的容量会根据元素个数的增加而自动改变。

在这里插入图片描述

  1. vector内部的扩容机制也十分有讲究。vector的扩容是一个比较耗费时间的过程,不是简单的原地扩容(即在原有的空间后面追加新空间),而是重新开辟一块新的大小合适的空间,将原空间的数据转移过去,再将原空间回收的一个过程。就时间而言,这个过程的代价很高,因此vector不可能每新增一个元素就扩一次容,而是会分配一些额外的空间以适应可能的增长(这可能会导致空间浪费),因此存储空间往往比实际需要的空间要大。

(假设将上图的vector进行扩容)
在这里插入图片描述

  1. 综上所述,vector扩容采取的策略是空间换时间。不同编译器下每次自动扩容增长的倍数有所不同,例如vs2019下扩容的倍数约为1.5,g++下扩容的倍数则为2。
  2. 与其它动态序列容器相比(deque, list and forward_list),vector在访问元素的时候更加高效,在末尾添加和删除元素相对高效。对于其它不在末尾的删除和插入操作,效率更低(因为需要挪动数据)。

2️⃣STL中vector的使用

💭学习STL不同容器时,包括学习vector,查看官方的文档最为重要(点击查看:vector官方文档),追根溯源方能修行成道。vector在我们日常使用中十分场景,也十分重要,因此我们需要掌握vector的一些常用的接口。

2.1 vector的定义

构造函数(constructor)的声明接口说明
vector()无参构造
vector( size_type n, const value_type& val = value_type() )构造并用n个val初始化(如果val没有传值,缺省调用val的默认构造函数,内置类型也有默认构造函数,如:int的默认构造将其初始化为0)
vector(InputIterator first, InputIterator last)迭代器范围初始化构造
vector(const vector<value_type>& other)拷贝构造

💬测试代码

// 定义一个函数模板PrintVector,方便我们观察测试用例
template <class T>
void PrintVector(const vector<T>& v)
{
	for (auto e : v)
	{
		cout << e << " ";
	}
	cout << endl;
}

void test1()
{
	// 1.
	vector<int> v1; // 类模板,注意指定类型
	PrintVector(v1);

	// 2.
	vector<int> v2(10, 2);
	PrintVector(v2);
	
	vector<int> vv2(10);
	PrintVector(vv2);

	// 3.
	vector<int> v3(v2.begin(), v2.end()); // v2.begin()和v2.end()是v2的迭代器,下文会提到
	PrintVector(v3);

	string s("abcde");
	vector<int> v4(s.begin(), s.end()); // 试试用指向不同类型元素的迭代器范围初始化
	PrintVector(v4);

	// 4.
	vector<int> v5(v4);
	PrintVector(v5);
}

⭕运行结果
在这里插入图片描述


2.2 iterator的使用

接口使用说明
begin获取第一个元素位置的iterator
end获取最后一个元素下一个位置的iterator
rbegin获取最后一个元素位置的reverse_iterator
rend获取第一个元素前一个位置reverse_iterator

在这里插入图片描述
图片来源:cppreference.com

在这里插入图片描述

💦迭代器的各种操作与指针类似,解引用操作用操作符 *,自增自减用 ++ --,但迭代器并不一定等于指针
💦迭代器的类型有四种,iterator相当于普通迭代器,而加上了const_表示iterator指向的元素不可修改,加上reverse_表示该迭代器是反向迭代器,++和–的方向与iterator相反。

在这里插入图片描述

begin接口有不带const和带const两个重载,当const类型的vector对象调用begin时,传入const类型的this指针,调用的就是带const的begin。其他几个iterator接口同理。
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

💬 测试
在这里插入图片描述


2.3 vector的空间问题

接口接口说明
size返回容器中的数据个数
capacity返回容器的容量
empty判断容器是否为空
resize( size_type n, const value_type& val = value_type() )改变容器的数据个数
reserve( size_type n )(重点)改变容器的容量

🔎 关于reserve和resize

  • reserve
    💭reserve可以改变容器的容量,分为两种情况:
    n > capacity:改变capacity为n,这里的改变是重新开辟一块大小为n的新空间,将数据挪到新空间并释放原空间
    n <= capacity:不做任何事

If new_cap is greater than the current capacity(), new storage is allocated, otherwise the function does nothing.(引用自cppreference.com)

  • resize
    💭resize不仅仅是更改容器的数据个数,还可以往容器里填充数据。具体情况根据resize的参数n和容器的size而定。

看如下数轴:

在这里插入图片描述

💬测试代码

// 为了方便观察每次resize后的size和capacity变化,定义一个PrintVectorSizeCap函数模板
template <class T>
void PrintVectorSizeCap(const vector<T>& v)
{
	for (auto e : v)
	{
		cout << e << " ";
	}
	printf("size:%d capacity:%d\n", v.size(), v.capacity());
}

void test3()
{
	vector<int> v1(10, 1);

	v1.resize(4);
	PrintVectorSizeCap(v1);

	v1.resize(8);
	PrintVectorSizeCap(v1);

	v1.resize(16, 1);
	PrintVectorSizeCap(v1);
}

⭕运行结果
在这里插入图片描述

📌注意:有关capacity扩容的代码在vs和g++下分别运行会发现,vs下capacity是按1.5倍增长的,g++是按2倍增长的。这个问题经常会考察,不要固化的认为,vector增容都是2倍,具体增长多少是根据具体的需求定义的。vs是PJ版本STL,g++是SGI版本STL。

验证:

// 观察这段代码在vs和g++下的运行情况
void TestVectorExpand()
{
	size_t sz;
	vector<int> v;
	sz = v.capacity();
	cout << "making v 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';
		}
	}
}

vs
在这里插入图片描述
g++
在这里插入图片描述


2.4 vector的增删查改

接口接口说明
push_back尾插
pop_back尾删
insert在某位置插入
erase在某位置删除
find( iterator first, iterator last, value_type val )查找(算法模块实现,并不是vector的成员函数)
swap交换两个vector
[ ]像数组一样通过下标随机访问容器数据

💭这些接口都比较简单,不再过多介绍,这里主要注意一些insert和erase

  • insert有几种不同的接口,在pos位置插入一个元素、n个元素、也可以插入一段区间
// 1个val
iterator insert( const_iterator pos, const T& val );

// n个val
iterator insert( const_iterator pos, size_type n, const T& val );

// range
template< class InputIt >
iterator insert( const_iterator pos, InputIt first, InputIt last );
  • erase
// 删除pos迭代器指向的元素
iterator erase( iterator pos );

// 删除区间[first, last)
iterator erase( iterator first, iterator last );

2.5 迭代器失效问题

2.5.1 什么是迭代器?

💡 迭代器设计的本意就是为了给所有容器提供一种通用的访问方式,让使用者无需关注底层的数据结构。迭代器本质上就是一个指针,或是将指针封装成一个类,按照需要控制它的行为。 例如:vector的迭代器就是一个原生指针value_type*,因为vector的空间是连续的,原生指针恰好能满足其访问需求。

2.5.2 迭代器失效

💡 指针会有野指针问题,类似的,迭代器存在失效的问题。迭代器失效实际就是迭代器底层对应指针所指向的空间被销毁了,或使用一块已经被释放的空间,造成的后果是程序崩溃(即如果继续使用已经失效的迭代器,程序可能会崩溃)

造成迭代器失效的行为有以下几种:reserve、insert、erase、push_back等,下面具体分析这几种行为如何导致迭代器失效。

  1. reserve

reserve导致的迭代器失效很好理解,因为reserve的扩容机制是重新开辟一块空间,所以指向原空间的迭代器就会失效。

假设容器vector有一个迭代器it
在这里插入图片描述

  1. insert

insert主要有两种情况。第一种是需要扩容的insert,第二种是不需要扩容的insert。

  • 情况一:当insert插入数据需要扩容时,很显然迭代器肯定会失效,道理和reserve相同。
  • 情况二:当insert插入数据不需要扩容时,插入后迭代器依然指向原空间,按理说应该不会失效。我们通过画图分析一下。

假设我们现在有下面这样一个容器,迭代器it指向它的begin位置,it1在it后两个位置
在这里插入图片描述

该容器目前capacity>size,进行insert操作不会扩容。若在it位置插入元素0,如下
在这里插入图片描述

插入后it、it1虽然指向的内容发生变化,但是依然指向的是有效空间,那么这到底算不算失效呢?我们来看看编译器怎么说

// 实现代码模拟上图过程,分别在vs和g++下编译
void test4()
{
	vector<int> v;
	// 先开好足够的空间
	v.reserve(9);
	// 填入数据
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);
	v.push_back(5);
	v.push_back(6);
	
	// 定义迭代器it、it1
	vector<int>::iterator it = v.begin();
	vector<int>::iterator it1 = it + 2;
	v.insert(it, 0);

    // 试着访问一下迭代器指向内容
	cout << *it << endl;
	cout << *it1 << endl;
}

vs下引发异常,在vector头文件中的*运算符重载函数出现断点,解引用访问失败,说明在vs下这种做法是不允许的,认为迭代器失效了。

在这里插入图片描述

同样的代码在g++下运行,检查并没有那么严格,运行成功,打印出it和it1指向的新元素0和2

在这里插入图片描述

所以insert第二种情况在不同编译器下会产生不同结果。

  1. erase
    erase不会开辟新空间,都是在原空间上删除数据,erase操作前后迭代器都指向未被释放的空间,但是和insert的第二种情况一样,在不同编译器下有不同的结果。

⭕结论
不同库版本的STL对迭代器失效问题的判别有所差异(vs是PJ版本,g++是SGI版本),因此,为了保证代码的可移植性,我们默认insert和erase函数在任何情况下都会导致迭代器(插入、删除位置及之后的迭代器)失效。
而解决这一问题的方法STL已经帮我们提供了,insert和erase的返回值是插入和删除后的第一个位置的迭代器,因此每次插入或删除后,下次使用迭代器之前,接收函数返回值更新一下迭代器即可。

💭与vector类似,string在 插入+扩容+erase 之后,迭代器也会失效

void TestString()
{
	string s("hello");
	auto it = s.begin();
	//s.resize(20, '!');
	// 上面这行代码放开后会崩溃,因为resize到20会string会进行扩容
	// 扩容之后,it指向之前旧空间已经被释放了,该迭代器就失效了
	// 后续打印时,再访问it指向的空间程序就会崩溃
	while (it != s.end())
	{
		cout << *it;
		++it;
	}
	cout << endl;

	it = s.begin();
	while (it != s.end())
	{
		it = s.erase(it);// right
	    //s.erase(it);   
		//++it;
	    // 按照屏蔽掉的这种方式写,运行时程序会崩溃
	    // 因为erase(it)之后,it位置的迭代器就失效了,后续再erase就会出错
	}
}

3️⃣vector的模拟实现

🧿掌握了STL中vector的使用,我们可以尝试模拟实现一下vector。模拟失效不是为了造出更好的“轮子”,而是在摸清底层实现逻辑后,更好地使用vector,达到融会贯通的效果。

// 整体框架
template <class T>
class vector
{
public:
    // definition
	typedef T* iterator;
	typedef const T* const_iterator;
	typedef T* pointer;
	typedef T value_type;
	
	// Member functions
	// ...

private:
    // 与string不同的是,vector用了三个指针来控制空间
	iterator start; // 指向目前使用空间的头
	iterator finish; // 指向目前使用空间的尾
	iterator end_of_storage; // 指向目前可用空间的尾
};

在这里插入图片描述

3.1 迭代器

// Iterators:

		iterator begin()
		{
			return start;
		}

		const_iterator begin() const
		{
			return start;
		}

		iterator end()
		{
			return finish;
		}

		const_iterator end() const
		{
			return finish;
		}
		

3.2 构造函数

// Constructors:

		// 1.无参构造
		vector()
			:start(nullptr),
			finish(nullptr),
			end_of_storage(nullptr)
		{}
		
		// 2.n个val
		vector(size_t n, const value_type& val = value_type()) 
		// 缺省值用匿名对象,调用了类value_type的默认构造函数
		{
			// 开辟空间
			start = new value_type[n];
			
			// 导入数据
			for (size_t i = 0;i < n;++i)
			{
				start[i] = val;
			}

			// 值处理
			finish = start + n;
			end_of_storage = finish;
		}
		
		 // 3.copy
		 // 拷贝构造
		vector(const vector<value_type>& v)
		{
			// 开辟空间
			size_t n = v.capacity();
			start = new value_type[n];

			// 传输数据
		    //memcpy(start, v.start, n * sizeof(value_type));//err
		    // 这种写法是错误的,这里涉及到一个深浅拷贝的问题
		    // 下面这种写法才是正确的
		    for (size_t i = 0;i < v.size();++i)
		    {
				start[i] = v.start[i];
			}

			// 值处理
			finish = start + v.size();
			end_of_storage = start + n;
		}
		
		// 赋值重载
		vector<value_type>& operator=(const vector<value_type>& v)
		{
			// 开辟空间
			size_t n = v.capacity();
			pointer newStart = new value_type[n];

			// 传输数据
			size_t len = v.size();
			for (size_t i = 0;i < len;++i)
			{
				newStart[i] = v.start[i];
			}

			// 释放旧空间
			delete[] start;

			//值处理
			start = newStart;
			finish = newStart + len;
			end_of_storage = newStart + n;

			return *this;
		}

🔎memcpy的拷贝异常问题

传输数据为什么不能用memcpy?

📃先来看看memcpy函数的文档介绍

在这里插入图片描述

读完文档我们知道,memcpy是按内存的二进制格式拷贝的,也就是source指向的内存里面存着什么它就原封不动地拷贝到destination上。若是存储内置类型的vector,尚且能够完成传输数据的工作。如下图。

在这里插入图片描述

⭕但若容器中存放的是自定义类型, 且自定义类型的元素涉及内存空间管理,事情就没那么简单了。
例如,vector<vector<int>>类,vector中存放的类型是vector<int>,就会出错,因为memcpy其实是浅拷贝。如下图。

在这里插入图片描述

vector<int>类的成员变量是三个指针,所以vector<int>类在内存空间里存放的是三个指针。那么,vector<vector<int>>类若用memcpy来传输数据,就会直接将每个vector<int>类元素的指针传过去,传输成功后,就会出现两个指针同时指向一块空间的情况。这样一来,不仅两个容器不能独立开来进行各种操作,而且会导致析构同一块空间两次,出现错误。

💡 为了解决这个问题,我们可以采用下面这个方法来传输数据,无论是容器装的是什么类型,如果是自定义类型,只要该类型有赋值重载,就不会出现错误。

// 传输数据
for (size_t i = 0;i < v.size();++i)
{
	start[i] = v.start[i];
}

3.3 析构函数

// Destructor:
~vector()
{
	delete[] start;
	start = finish = end_of_storage = nullptr;
}

💭其实上面实现的构造函数都过于冗余,我们称之为传统写法。我们可以借助vector的其它成员函数的复用来优化构造函数,也就是构造函数的现代写法。那废话不多说,先把vector的其它成员函数都是实现了,再来看怎么运用它们优化构造函数。


3.4 有关空间的接口

// Capacity:
		size_t capacity() const
		{
			return end_of_storage - start;
		}

		size_t size() const
		{
			return finish - start;
		}

		bool empty() const
		{
			return finish == start;
		}

        // !!!很重要的一个!!!
		void reserve(size_t n)
		{
			// 要注意新旧指针位置不匹配问题
			if (n > capacity())
			{
				// 开辟新空间
				pointer newStart = new value_type[n];

				// 传输数据
				size_t len = size();
				//memcpy(newStart, start, len * sizeof(value_type));//err
				// 这里同样不能用memcpy传输数据,就拿vector<vector<int>>举例
				// 用memcpy传输后,释放旧空间后,容器内部的vector元素指向的空间就被释放掉了
				// 而传到新空间上的指针自然也失效了
				for (size_t i = 0;i < len;++i)
				{
					newStart[i] = start[i];
				}

				// 释放旧空间
				delete[] start;
				
				// 值处理
				start = newStart;
				finish = newStart + len; 
				// 不能finish = newStart + size(),size()函数内部会出问题,
				// finish还在旧空间,而start已经在新空间了
				end_of_storage = newStart + n;
			}
		}

		void resize(size_t n, const value_type& val = value_type())
		{
			if (n > size())
			{
				reserve(n); // reserve会自动判断需不需要扩容
				while (finish < start + n)
				{
					*(finish++) = val;
				}
			}
			else
			{
				finish = start + n;
			}
		}

3.5 增删查改

		// Modifiers:
		value_type& operator[](size_t pos)
		{
			assert(pos < size()); // 检查pos的合法性
			return *(start + pos);
		}

		void push_back(const value_type& val)
		{
			// 当finish == end_of_storage为满额状态,需要扩容才能插入数据
			if (finish == end_of_storage)
			{
				size_t newCapacity = capacity() == 0 ? 4 : 2 * capacity();
				// 当capacity为0时,给一个初始值为4
				reserve(newCapacity);
			}
			
			// 尾插数据
			*finish = val;
			++finish;
		}

		void pop_back()
		{
			assert(!empty());
			--finish;
		}

		void swap(vector<value_type>& v)
		{
			std::swap(start, v.start);
			std::swap(finish, v.finish);
			std::swap(end_of_storage, v.end_of_storage);
		}

		iterator insert(iterator pos, const value_type& val)
		{
			assert(pos < finish && pos >= start);

			// check capacity
			if (finish == end_of_storage)
			{
				size_t lentoPos = pos - start;
				size_t newCapacity = capacity() == 0 ? 4 : 2 * capacity();
				reserve(newCapacity);

				// 扩容会导致迭代器失效,要更新迭代器
				pos = start + lentoPos;
			}

			// 挪动数据
			iterator tmp = finish;
			while (tmp > pos)
			{
				*tmp = *(tmp - 1);
				--tmp;
			}

			// 值处理
			*pos = val;
			++finish;

			return pos;
		}

		void insert(iterator pos, size_t n, const value_type& val)
		{
			size_t lentoPos = pos - start;

			// check capacity
			if (finish + n > end_of_storage)
			{
				reserve(capacity() + n);
				pos = start + lentoPos;
			}

			// 挪动数据
			iterator it = finish - 1;
			while (it >= pos)
			{
				*(it + n) = *it;
				--it;
			}

			// 导入数据
			iterator end = pos + n;
			while (pos < end)
			{
				*(pos++) = val;			
			}

			// 值处理
			finish += n;
		}

		iterator erase(iterator pos)
		{
			assert(pos < size());
 			
 			// 挪动数据,直接覆盖预删除数据
			iterator tmp = pos;
			while (tmp < finish - 1)
			{
				*tmp = *(tmp + 1);
				++tmp;
			}

			--finish;
			return pos;
		}

		iterator erase(iterator first, iterator last)
		{
			iterator tmp = first;
			while (last < finish)
			{
				*(first++) = *(last++);
			}

			finish = first;
			return tmp;
		}

🔎 A little problem:
为什么库里面已经有一个swap函数了,vector不选择直接用,而是设计一个属于自己的swap呢?

查阅文档了解到库里面swap的实现是这样的。
在这里插入图片描述

💡若T是vector类,这里还要调用一次拷贝构造+两次赋值重载,效率比较低。而vector自己实现的swap则是直接交换两个vector的指针,即交换两个vector指向的空间,效率很高。


3.6 构造函数的现代写法

        // 1.n个val
		vector(size_t n, const value_type& val = value_type())
			:start(nullptr),
			finish(nullptr),
			end_of_storage(nullptr)
		{
			reserve(n);
			while (n--)
			{
				push_back(val); // 在push_back函数中发生变化
			}
		}
		// 复用了reserve和push_back函数,
		// finish和end_of_storage两个指针都在成员函数中处理了
		// 代码简洁了很多。

       // 为了实现拷贝构造的现代写法,先实现一个迭代器范围构造函数
       	template <class InputIterator>
		vector(InputIterator first, InputIterator last)
			:start(nullptr)
			,finish(nullptr)
			,end_of_storage(nullptr)
		{
			while (first != last)
			{
				push_back(*first); 
				// *first传入push_back会有一个隐式类型转换
				// *first的类型会转换成value_type
				++first;
			}
		}

🔎range构造和fill构造的冲突问题

问题:

⭕要特别注意一个问题。我们将迭代器范围构造函数称为range构造,n个val构造函数称为fill构造。这两个构造函数会发生意想不到的冲突,看下面代码

void test6()
{
	vector<int> v(10, 'a');
	for (auto e : v)
	{
		cout << e << " ";
	}
	cout << endl;
}

用10个字符'a'去构造一个vector<int>类对象,除了'a'存入时会发生一个小小的类型转换之外,没有任何问题。

⭕运行结果

在这里插入图片描述
再看这个代码

void test6()
{
	vector<int> v(10, 1);
	for (auto e : v)
	{
		cout << e << " ";
	}
	cout << endl;
}

程序运行出错,并且报了非法的间接寻址这个错误。
在这里插入图片描述

原因:

观察fill构造range构造的参数列表。

// fill
vector (size_type n, const value_type& val = value_type()// range
template <class InputIterator>
vector (InputIterator first, InputIterator last)
  • 当实例化对象时是:vector<int> v(10,'a')

第一个参数10的类型是int,第二个参数'a'的类型是char,构造函数中有两个参数的只有range构造和fill构造。range构造是函数模板,要求两个参数必须是同类型。显然,这里只能调用fill构造,传参时会发生隐式类型转换,int->size_t,char->value_type。

  • 当实例化对象时是:vector<int> v(10,1)

第一个参数10的类型是int,第二个参数1的类型也是int,两个参数类型一样,而传入fill构造又要隐式类型转换,编译器会偷懒,调用它觉得最省事的那个构造函数,那就是range构造了。传入range构造函数模板后,本来InputIterator希望得到迭代器类型,这下传入了int类型,range构造函数内部解引用first时就会引发非法的间接寻址的错误。

在这里插入图片描述

解决方法:

// 效仿PJ库版STL的做法,重载两个fill构造函数即可,n的类型一个为size_t一个为int
	   vector(int n, const value_type& val = value_type()) 
	   // 两个参数类型都为int时,调用该构造函数
			:start(nullptr),
			finish(nullptr),
			end_of_storage(nullptr)
		{
			reserve(n);
			while (n--)
			{
				push_back(val);
			}
		}

		vector(size_t n, const value_type& val = value_type())
			:start(nullptr),
			finish(nullptr),
			end_of_storage(nullptr)
		{
			reserve(n);
			while (n--)
			{
				push_back(val);
			}
		}

⭕ 运行通过
在这里插入图片描述

🧐OK,解决了这个棘手的问题,继续我们的构造函数现代写法。

        // copy
        // 构造函数
		vector(const vector<value_type>& v)
			:start(nullptr),
			finish(nullptr),
			end_of_storage(nullptr)
		{
			vector<value_type> tmp(v.begin(), v.end());
			swap(tmp);
		}

💡 构造函数的现代写法复用了range构造函数,相当于让“打工人”tmp先帮我们把v的数据和空间拷贝过来,然后再把tmp和*this一交换,*this就成功拷贝了v,而出了作用域tmp也就被销毁了,就没他什么事了(纯纯地压榨)。这样一来,代码十分简洁,复用性也很强。

有了如此简洁的拷贝构造,作为它的孪生兄弟赋值重载,不也得进化一下?

		// 赋值重载的现代写法
		vector<value_type>& operator=(vector<value_type> v)
		{
			swap(v);
			return *this;
		}

看起来是不是十分简洁?这里其实不仅复用了swap,还复用了拷贝构造。注意到这里的参数并不是引用,所以v是调用拷贝构造实例化出来的,与外部传入的类对象相等却无关。所以我们都不需要自己创造一个“打工人”,直接有了一个现成的“打工人”!!直接让*this和v交换一下就实现了。


📝构造函数现代写法的整体代码

		// 1. fill
		vector(int n, const value_type& val = value_type())
			:start(nullptr),
			finish(nullptr),
			end_of_storage(nullptr)
		{
			reserve(n);
			while (n--)
			{
				push_back(val);
			}
		}

		vector(size_t n, const value_type& val = value_type())
			:start(nullptr),
			finish(nullptr),
			end_of_storage(nullptr)
		{
			reserve(n);
			while (n--)
			{
				push_back(val);
			}
		}


		// 2.range
		template <class InputIterator>
		vector(InputIterator first, InputIterator last)
			:start(nullptr)
			,finish(nullptr)
			,end_of_storage(nullptr)
		{
			while (first != last)
			{
				push_back(*first); 
				++first;
			}
		}

		 // 3.拷贝构造
		vector(const vector<value_type>& v)
			:start(nullptr),
			finish(nullptr),
			end_of_storage(nullptr)
		{
			vector<value_type> tmp(v.begin(), v.end());
			swap(tmp);
		}


		// 4."="
		vector<value_type>& operator=(vector<value_type> v)
		{
			swap(v);
			return *this;
		}

ending:
如果本文对你有帮助的话,不妨点赞关注收藏支持一下博主呀🍻

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/50450.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Java面试】为什么重写equals方法必须同时重写HashCode方法?

众所周知再JDK1.8之后&#xff0c;Java修改了String类型的底层源码&#xff0c;因为他们发现其实对于-128~127范围的字符更加常用&#xff0c;因此将底层的数组从char类型修改为了byte类型。 看到上面的方法可以发现&#xff0c;String类型的equals方法会先比较两个字符串的…

Caffeine 源码、架构、原理(史上最全,10W字 超级长文)

文章很长&#xff0c;而且持续更新&#xff0c;建议收藏起来&#xff0c;慢慢读&#xff01;疯狂创客圈总目录 博客园版 为您奉上珍贵的学习资源 &#xff1a; 免费赠送 :《尼恩Java面试宝典》 持续更新 史上最全 面试必备 2000页 面试必备 大厂必备 涨薪必备 免费赠送 经典…

软件实训-技术学习心得

一、MoOtA 前端主要负责动态界面的编写&#xff0c;主要学习JavaScript&#xff0c;HTML&#xff0c;CSS三件套 1、前端项目文件夹 .idea&#xff1a;无需关注 node_modules&#xff1a;无需关注 public&#xff1a;存放媒体资源&#xff0c;比如图片&#xff08;images&…

CSS网页页面图像灰色滤镜写法示例

阿酷TONY / 原创 2022-12-1 / 长沙 / grayscale()函数是一个内置函数&#xff0c;用于对图像应用滤镜以设置图像的灰度。 用法: grayscale( amount ) 参数&#xff1a;此函数接受包含灰度值的单个参数量。灰度值根据数量和百分比设置。值0&#xff05;表示原始图像&…

Java并发编程—Thread类中的start()方法如何启动一个线程?

一、java线程的介绍&#xff1a; 在java的开发过程中&#xff0c;很多铁子对于java线程肯定不感到陌生&#xff0c;作为java里面重要的组成部分&#xff0c;这里就从如何创建一个线程给大家进行分析&#xff1b; 二、相关知识引入&#xff1a; ​ 之前我了解过&#xff0c;j…

jQuery 简介

jQuery 库可以通过一行简单的标记被添加到网页中。 您需要具备的基础知识 在您开始学习 jQuery 之前&#xff0c;您应该对以下知识有基本的了解&#xff1a; HTMLCSSJavaScript 如果您需要首先学习这些科目&#xff0c;请在我们的 首页 查找这些教程。 什么是 jQuery &#x…

[附源码]Python计算机毕业设计Django的高校资源共享平台

项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等等。 环境需要 1.运行环境&#xff1a;最好是python3.7.7&#xff0c;…

无人机机械臂爪机器臂爪的安装调试试验。以大疆哪吒为例乐迪T8Fb遥控器。

首先将机械爪安装完毕。注意不要装的太紧&#xff0c;否则会损害舵机&#xff0c;应力度合适&#xff0c;可以先使用舵机测试仪测试。 安装舵机到位。要使用机械爪配套的舵机型号&#xff0c;否则孔位对不上。 调整螺丝孔位&#xff0c;确保齿轮能够吻合。 调整螺丝松紧&#…

JavaScript 基础笔记

初识 JavaScript JavaScript负责页面中的的行为&#xff0c;是一门运行在客户端的脚本语言。 解释型语言与编译型语言区别&#xff1a; JavaScript 作用 表单动态校验&#xff08;密码校验&#xff09;网页特效服务端开发桌面端开发等作用 JS引擎&#xff1a;浏览器使用Js引…

Vue3多个弹窗同时出现解决思路

Vue3多个弹窗同时出现解决思路 弹窗或者说对话框是我们在开发系统或者页面很常用的元素&#xff0c;所以博主想对出现多个弹窗的情况下的解决思路进行一个整理 有时候我们一个页面可能存在多个弹窗&#xff0c;当多个弹窗出现的时候可能屏幕就变的很黑&#xff0c;如下图所示…

图观引擎V3.3.4 功能更强、操作更便捷!最新升级一睹为快

图观™引擎&#xff0c;自去年内测版推出上线以来&#xff0c;已帮助众多合作伙伴开发出自己的数字孪生应用、落地交付数字孪生项目&#xff0c;实现了自身软件产品的全面升级、技术能力的大幅拓展。 经过不断的升级迭代&#xff0c;图观™引擎V3.3.4版本&#xff0c;更加注重…

Seata中TC服务部署及微服务集成Seata

seata的部署和集成 一、部署Seata的tc-server 1.下载 首先我们要下载seata-server包&#xff0c;地址在http&#x1f615;/seata.io/zh-cn/blog/download.html 2.解压 在非中文目录解压缩这个zip包&#xff0c;其目录结构如下&#xff1a; 3.修改配置 修改conf目录下的re…

微服务框架 SpringCloud微服务架构 8 Gateway 网关 8.1 网关作用介绍

微服务框架 【SpringCloudRabbitMQDockerRedis搜索分布式&#xff0c;系统详解springcloud微服务技术栈课程|黑马程序员Java微服务】 SpringCloud微服务架构 文章目录微服务框架SpringCloud微服务架构8 Gateway 网关8.1 网关作用介绍8.1.1 为什么需要网关8.1.2 网关的技术实现…

高数 | 周洋鑫 冲刺预测题自用整理复习

自用笔记整理复习。 内容来自2023周洋鑫冲刺班。 加油ヾ(◍∇◍)ノ゙ 1、函数极限计算 【加项减项】 ☆ 二次积分求极限

极值分析:分块极大值BLOCK-MAXIMA、阈值超额法、广义帕累托分布GPD拟合降雨数据时间序列...

全文链接&#xff1a;http://tecdat.cn/?p25348 你们可能知道&#xff0c;实际极值分析有两种常用方法&#xff1a;分块极大值Block-maxima、阈值超额法threshold excess&#xff08;点击文末“阅读原文”获取完整代码数据&#xff09;。今天&#xff0c;我们将分别介绍这两种…

【C++】vector 的使用及其模拟实现

一、vector 的使用 vector 是我们学习的第一个真正的 STL 容器&#xff0c;它接口的使用方式和 string 有一点点的不同&#xff0c;但大部分都是一样的&#xff0c;所以这里我们就只演示其中一些接口的使用&#xff0c;大家如果有疑惑的地方直接在 cplusplus 是上面查看对应的…

[附源码]计算机毕业设计springboot抗疫医疗用品销售平台

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

智引未来,利尔达成功入选首批“5G应用解决方案供应商推荐名录”名单

近日&#xff0c;5G应用产业方阵&#xff08;5G AIA&#xff09;在“2022年中国5G发展大会5G应用产业发展论坛”发布了“5G应用解决方案供应商推荐名录&#xff08;第一批&#xff09;”入库名单&#xff0c;旨在强化5G应用供需对接&#xff0c;推动5G应用解决方案成熟&#xf…

基于PHP+MySQL共享自行车租赁管理系统的设计与实现

随着环保意识的增加,人们的出行越来越简单便捷,其中共享自行车是现在很多年轻人最热衷的出行方式之一,本系统主要是对共享自行车的信息进行管理。该系统的基本功能包括用户登录,区域信息管理,用户信息管理,用户充值管理,车辆信息管理,租借信息管理,损耗信息管理,统计报表信息,修…

【自然语言处理概述】“海量”文件遍历

【自然语言处理概述】“海量”文件遍历 作者简介&#xff1a;在校大学生一枚&#xff0c;华为云享专家&#xff0c;阿里云专家博主&#xff0c;腾云先锋&#xff08;TDP&#xff09;成员&#xff0c;云曦智划项目总负责人&#xff0c;全国高等学校计算机教学与产业实践资源建设…