vector的模拟实现

namespace bit
{
	template<class T>
	class vector
	{
	public:
		typedef T* iterator;

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

		iterator begin()
		{
			return _start;
		}
		size_t capacity()
		{
			return _end_of_storage - _start;
		}
		
		size_t size()
		{
			return _finsh - _start;
		}
        
        iterator end()
        {
	        return _finsh;
        }

		void reverse(size_t n)
		{
			if (n > capacity())
			{
				size_t oldsize = size();
				T* temp = new T[n];
				if (_start)
				{
					//memcpy(temp, _start, size() * sizeof(T));
                    for (size_t i = 0; i < oldsize; i++)
                    {
				        temp[i] = _start[i];
                    }
delete[] _start;
					delete[] _start;
				}
				_start = temp;
				_finsh = _start + oldsize;
				_end_of_storage = _start + n;

			}
		}
		
		void push_back(const T& x)
		{
			if (_finsh == _end_of_storage)
			{
				size_t newcapacity = (capacity() == 0 ? 4 : 2 * capacity());
				reverse(newcapacity);
			}

			*_finsh = x;
			_finsh++;

		}

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

		void insert(iterator pos, T  x)
		{
			assert(pos >= _start);
			assert(pos < _finsh);

			if (_finsh == _end_of_storage)
			{
				size_t len = pos - _start;
				size_t newcapacity = (capacity() == 0 ? 4 : 2 * capacity());
				reverse(newcapacity);

				pos = _start + len;             //为了预防迭代器失效
			}

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

		void pop_back()
		{
			assert(size());
			_finsh--;
		}

		void erase(iterator pos)
		{
			assert(pos >= _start);
			assert(pos < _finsh);

			iterator cur = pos;
			while (pos < _finsh)
			{
				*pos = *(pos + 1);
				pos++;
			}
			--_finsh;
		}
		
	private:
		iterator _start = nullptr;
		iterator _finsh = nullptr;
		iterator _end_of_storage = nullptr;
	};
}

在之前的博客里面,我们好像讲过,有关于模板的程序我们最好不要把申明和定义写在不同的文件里面,这样会引起链接错误,具体原因在后面的博客中应该会提到

上面一些函数的几点说明

1.关于reserve函数:

reverse的实现思路

通过上面的代码,我们不难看出,reverse的实现思路其实新申请一块空间,然后将原理的数据拷贝到新的空间里面去

但是,上面为什么要那样些嘞?那就请你先看看下面的代码

这样些编译器立马报错,为什么嘞?

这时候_start已经指向新空间temp了,但是_finsh还没有指向新空间,这句代码的下一句代码

_finsh=_start+size(),中的size()函数,不就是旧空间里面的_finsh减去新空间里面的_start吗,两块空间压根就不连续,这样写不就乱了吗

2.关于push_back函数

在第一个红色的方框之后,重新申请了一块空间,如果不记录一下pos的相对位置,后面将旧的空间释放了,会导致pos迭代器失效的

注意

迭代器失效1

这里的迭代器的本质收T*,但是我们为什么不写成T*嘞,而要写成iterator嘞,其实,这里体现的一个思想是封装的思想,不同容器的iterator是不一样的

上面的代码中的迭代器it是会失效的(在扩容的情况下),因为这里的实参传递给形参,并没有引起实参的改变,但是不知道它什么时候失效,所以,不管有没有扩容,我们统一认为失效了,我们的建议就是不要访问失效的迭代器,除非我们更新一下迭代器

这里的一个解决方法就是写一个返回值,那么问题来了,string有没有迭代器失效的场景?答案是肯定的?string里面的insert有下标控制的,也有迭代器控制的

迭代器失效2 

其实这里的迭代器失效还有一种场景

当然就是这里的erase函数,删除了迭代器下对应的元素,那么,该迭代器也是会失效的,

当然,在vector的模拟实现里面并不会失效,只是在C++标准库里面会失效,C++标准库下面的迭代器类型很复杂,在这里可以认为,当erase迭代器下对应的元素了的话,会迭代器里面的有一个标志会改变,改变了你就无法去访问对应的结果

当然,这里也有解决方案

这里的erase函数是有返回值的,他会更新迭代器的,不至于你去访问失效的迭代器,导致程序崩溃,当然,这种迭代器失效的情况在vs的环境下会有但是在linux环境下是不会出现的,所以我们为了代码在任何平台上面都能运行,我们最好能够更新一下迭代器

构造函数

这里编译器报的是少默认构造函数,原因是我们实现的上面的拷贝构造函数,注意:拷贝构造函数也是构造,我们可以用下面的语句让编译器强制生成默认构造函数

迭代区间的默认构造函数

 

下面还有一个问题

加入实现了这两个构造函数,那么,下面的程序会报错的

这里涉及到的就是函数匹配的问题

 

构造函数里面传递的是两个整形变量,上面的两个构造函数,一个是拥有模板的构造函数,一个是参数是size_t和 T的,我们的本意是想要构造一个拥有10个2的顺序表,想要让他去调用第二个构造函数,但是他却去调用了第一个构造函数,原因就是第二个的参数是size_t,并非是int,第一个构造函数更符合,直接调用第一构造函数去了,结果去对整形变量解引用,程序里面出错

那是否有解决方案?答案是肯定的

 

 这里可以直接在10的后面加上一个u,因为u是无符号整形的标志嘛,对应的就是size_t

还有一种解决方案,就是再写一个更匹配的构造函数

 上面有一句代码需要注意:

这里的T()是一个匿名对象,无论是内置类型还是自定义类型都可以这样写

还有一种构造函数

这里是一个序列,方便我们像下面一样去构造一个对象

memcpy的浅拷贝问题

首先,我们明确一个问题memcpy进行拷贝的时候,进行的是浅拷贝,这对于内置类型几乎是没有什么影响,但是对于自定义类型的话,问题可就大了去了

我们来举一个例子

这里为什么打印的是随机值嘞,首先明确一点,这里肯定是因为扩容导致的,因为写插入四个string的时候,程序是好的,但是插入第五个的时候,程序开始奔溃了

 

 都知道,红方框里面的是string的内部组成,memcpy拷贝的只是_str,_str是指向内容的地址,这里把地址拷贝过去了,实际内容没有拷贝过去,导致delete[]_start的时候把_start指向的内容给析构掉了,temp指向的也析构掉了,因为两个指向的是同一个内容嘛,最后出来的结果不就是随机值了嘛

将memcpy换成上面注释掉的部分就行了,让两个变量分别指向不同的内存

现在结果不就正确了吗

引用和提供了关于实现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、付费专栏及课程。

余额充值