C++入门 vector深度剖析及模拟实现

目录

vector析构函数模拟实现

vector赋值拷贝模拟实现

vector拷贝构造模拟实现

vector构造函数模拟实现

类模板的成员函数

n个val构造

单参数和多参数对象隐式类型转换

使用memcpy拷贝问题


在上两篇有关vector的模拟实现中,还有构造,拷贝构造,赋值拷贝以及析构函数没有实现,本篇主要实现四个函数。  


vector析构函数模拟实现

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

如果start指向空间,那么就释放掉该空间,并给其余成员变量置为空指针nullptr。


vector赋值拷贝模拟实现

void swap(vector<T>& v)
{
	std::swap(_start, v._start);
	std::swap(_finish, v._finish);
	std::swap(_end_of_storage, v._end_of_storage);
}

// v1 = v2
vector<T>& operator=(vector<T> v)
{
	swap(v);
	return *this;
}

直接交换两个自定义类型变量里的成员变量即可。


vector拷贝构造模拟实现

// 强制编译器生成默认的
vector() = default;

// v2(v1)
vector(const vector<T>& v)
{
	reserve(v.capacity());
	for (auto e : v)
	{
		push_back(e);
	}
}

例如要用v1拷贝构造v2,先开辟一个和v1大小相同的空间,再把v1里的元素依次尾插至v2。


vector构造函数模拟实现

类模板的成员函数

// 类模板的成员函数
// 函数模板 -- 目的支持任意容器的迭代器区间初始化
template <class InputIterator>
vector(InputIterator first, InputIterator last)
{
	while (first != last)
	{
		push_back(*first);
		++first;
	}
}

类模板的成员函数也可以构造函数模板。使用方法如下图所示:

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

	vector<int> v2(v1.begin() + 1, v1.end());
	for (auto e : v2)
	{
		cout << e << " ";
	}
	cout << endl;
}

它和拷贝构造的区别是可以使拷贝的空间可控,如果我们只要第二个元素开始进行拷贝,可以begin + 1。

此处的InputIterator意味着任意的迭代器都可以使用,例如下代码所示:

	string s("hello");
	vector<int> v3(s.begin(), s.end());
	for (auto e : v3)
	{
		cout << e << " ";
	}
	cout << endl;

这里利用了string的迭代器,又因为是要int类型的v3,所以hello中五个字符按照ascii码转换成int类型进行拷贝v3。


n个val构造

vector(size_t n, const T& val = T())    //T()不能用0代替
{
	reserve(n);
	for (size_t i = 0; i < n; i++)
	{
		push_back(val);
	}
}

此处T( )不能用0代替,此处为匿名对象,如果模板为string,list的时候,用0作为缺省值就会发生错误。此外,C++对内置类型进行了优化升级,也有自己的构造。

// C++内置类型进行了升序,也有构造
int i = 0;
int j(1);
int k = int();
int x = int(2);

就是为了兼容上述n个val构造的函数,是函数能实现更多的初始化。以下代码发生了错误:

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


单参数和多参数对象隐式类型转换

以A类型为例:

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;
};

// 单参数和多参数对象隐式类型转换
// 省略赋值符号
A aa1(1, 1);         //一个参数
A aa2 = { 2, 2 };    //两个参数
A aa9{ 2, 2 }; // 不要
const A& aa8 = { 1,1 };	//引用的是中间产生的临时变量

A aa3(1);
A aa4 = 1;

那么vector也可以用容器{ } 来进行初始化吗?

vector<int> v1{ 1,2,3,4,5,6 };

这里的隐式类型转换,跟上面不一样,这里参数个数不固定。

	void test_vector()
	{
		// 这里的隐式类型转换,跟上面不一样,这里参数个数不固定
		vector<int> v1({ 1,2,3,4,5,6 });  //直接构造
		vector<int> v2 = { 10, 20, 30 };  //隐式类型转换

		auto il1 = { 1, 2, 3, 4, 5, 6 };
		initializer_list<int> il2 = { 1, 2, 3 };
		cout << typeid(il1).name() << endl;  //class std::initializer_list<int>
		cout << sizeof(il2) << endl;	//内部为两个指针,指向它的开始和结束
		for (auto e : il1)
		{
			cout << e << " ";
		}
		cout << endl;

		vector<A> v3 = { 1, A(1), A(2,2), A{1}, A{2,2}, {1}, {2,2} };
	}

 这里我们要模拟实现一个initializer_list

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

使用memcpy拷贝问题

	void test_vector9()
	{
		vector<string> v1;
		v1.push_back("111111111111111111");
		v1.push_back("111111111111111111");
		v1.push_back("111111111111111111");
		v1.push_back("111111111111111111");
		v1.push_back("111111111111111111");
		for (auto e : v1)
		{
			cout << e << " ";
		}
		cout << endl;
	}

问题分析:

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

此处发生了错误,打印出了其他值,大概原因可以考虑为扩容出现问题了。 

memcpy都是浅拷贝,现_str与原_str指向的同一个空间,delete空间释放后,tmp的_str也跟着小时,这就导致了现在的_str成为了野指针。解决办法如下所示:

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

		if (_start)
		{
			//memcpy(tmp, _start, sizeof(T) * oldsize);

			for (size_t i = 0; i < oldsize; i++)
			{
				tmp[i] = _start[i];
			}
			delete[] _start;
		}

		_start = tmp;
		_finish = _start + oldsize;
		_end_of_storage = _start + n;
	}
}

 这里调用string的赋值拷贝,开辟一个新的空间,将原string里的值搬到新开辟空间的string,并且两者指向不同的空间,这样delete原vector就没影响了。

结论:如果对象中涉及到资源管理时,千万不能使用memcpy进行对象之间的拷贝,因为memcpy是浅拷贝,否则可能会引起内存泄漏甚至程序崩溃。

  • 26
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
引用和提供了关于实现vector的两种方法。其中,引用展示了一个使用reserve和push_back方法的示例,而引用展示了一个使用new和memcpy函数的示例。这两种方法都是常见的实现vector的方式。 在第一种方法中,通过reserve函数可以预留足够的内存空间,然后使用push_back函数逐个将元素添加到vector中。这种方法的好处是可以避免不必要的内存重分配,提高了效率。 而第二种方法使用new操作符在堆上分配内存空间,并使用memcpy函数将已有的vector对象的数据复制到新的内存空间中。通过这种方式,可以实现深拷贝,即两个vector对象拥有独立的内存空间。这种方法的好处是可以在不修改原始vector对象的情况下创建一个新的vector对象。 除了以上两种方法,还可以使用其他方式实现vector类。例如,可以使用动态数组来实现vector的底层数据结构,然后通过成员函数实现vector的各种操作,如增加、删除、查找等。 总结来说,c语言模拟实现vector的关键是动态内存管理和对元素的增删改查操作。可以使用预留空间和逐个添加元素的方式,也可以使用动态数组和复制数据的方式来实现vector类。具体的实现方式可以根据需求和实际情况选择。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [C++——vector模拟实现](https://blog.csdn.net/weixin_49449676/article/details/126813526)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值