vector, list 模拟实现

vector 实现

成员属性/迭代器

cpp 复制代码
template<class T>
class vector
{
public:
	typedef T* iterator;
	typedef const T* const_iterator;

iterator begin()
{
	return _first;
}

iterator end()
{
	return _end;
}

const_iterator begin() const
{
	return _first;
}

const_iterator end() const
{
	return _end;
}

private:
    iterator _first = nullptr;
	iterator _end = nullptr;
	iterator _capacity = nullptr;
};

这里的迭代器可以看到就是指针, 这只是一种实现方式

也可以创建一个 struct iterator {}; 进行一层封装.

同样的这里没有使用 size 和 capacity, 而是通过三个指针来代替

也可以使用 size 和 capacity 来实现

构造/析构/拷贝构造/赋值重载函数

cpp 复制代码
vector()
{}

vector(size_t n, const T& val = T()) // T& val = T(): 缺失值写法, 没有传参就会调用 T类型 的默认构造, 创建一个对象
{
	_first = new T[n];
	for (int i = 0; i < n; i++)
	{
		_first[i] = val;
	}
	_end = _first + n;
	_capacity = _end;
}

template<class It>
vector(It begin, It end) // 迭代器构造
{
 // 这里用上模板, 因为迭代器类型有很多种, 我们不能为每一个迭代器实现具体方法,
 // 所以这里用模板, 让编译器帮助我们生成对应类型的迭代器构造函数
	while (begin != end)
	{
		push_back(*begin);
		begin++;
	}
}

~vector()
{
	delete[] _first;
}

vector(vector<T>& v)  // 拷贝构造
{
	for (auto tem : v) // 复用尾插函数
	{
		push_back(tem);
	}
}

void swap(vector<T>& v)
{
	std::swap(_first, v._first);
	std::swap(_end, v._end);
	std::swap(_capacity, v._capacity);
}

vector<T>& operator=(vector<T> v)
{
	swap(v); // v 是一个零时变量, 函数结束时, 就会被销毁, 我们可以将 v 的空间拿过来使用
             // 将零时变量的空间替换过来, 这样我们就不用自己去开辟空间
	return *this;
}

这里使用模板, 让编译器来完成不同类型迭代器的构造方法.

空间函数

cpp 复制代码
size_t size()
{
	return _end - _first;
    // 两个指针相减得到的结果是它们之间的"距离",
    // 这个距离是以它们指向的元素类型的"大小"为单位的整数
}

size_t capacity()
{
	return _capacity - _first;
}

void reserve(size_t n)
{
	int len = size();
    // 获取 vector 的size, 如果size < n, 就不做处理, 保证空间至少能存储现有数据
	if (len < n)
	{
		T* tem = new T[n];
		for (int i = 0; i < len; i++)
		{
			tem[i] = _first[i];
		}
		std::swap(_first, tem); // 空间开辟完成后, 记得修改三个指针
		delete[] tem; // 原空间需要销毁, 否者造成空间泄漏
		_end = _first + len;
		_capacity = _first + n;
	}
}

void resize(size_t n, const T& val = T())
{
	int len = size();
	if (len >= n)
	{
		_end = _first + n;
	}
	else
	{
		reserve(n); // 交给 reserve 判断是否需要开辟空间
		int i = 0;
		for (int i = 0; len + i < n; i++)
		{
			_first[len + i] = val;
		}
		_end = _first + n;
	}
}

修改

cpp 复制代码
void push_back(const T& val)
{
	size_t len = size();
	if (_capacity == _end) // 判断空间是否使用完了
	{
		reserve(len > 0 ? 2 * len : 4);
	}
	*_end = val;
	++_end;
}

void pop_back()
{
	if (size() > 0)
	{
		--_end;
	}
}

iterator insert(iterator it, const T& val)
{
	size_t len = size();
	size_t x = it - _first; // 记录插入的位置
	if (_capacity == _end)
	{
		reserve(len > 0 ? 2 * len : 4);
	}
	iterator end = _end;
	it = _first + x; // it 需要更新, 重新分配空间后, it 失效了
	while (end >= it)
	{
		*end = *(end - 1); // 挪动数据
		--end;
	}
	*it = val;
	++_end;
	return it; // 返回插入元素的迭代器
}

iterator erase(iterator it)
{
	iterator newit = it;
	if (size() > 0)
	{
		while (it < _end - 1)
		{
			*it = *(it + 1);
			it++;
		}
	}
	return newit; // 返回删除元素后一个元素的迭代器
}

T& operator[](size_t pos)
{
	return _first[pos];
}

const T& operator[](size_t pos) const
{
	return _first[pos];
}

list 实现

ListNode 实现

list 底层是双向链表实现的. 所以节点中不仅存储下一个节点的位置, 还会存储上一个节点的位置.

cpp 复制代码
template<class T>
struct ListNode
{
	ListNode(const T& val = T())
		:_data(val),
		_next(nullptr),
		_prev(nullptr)
	{}

	~ListNode()
	{}

	T _data; // 存储的数据
	struct ListNode* _next; // 指向下一个节点
	struct ListNode* _prev; // 指向上一个节点
};

list 成员属性

cpp 复制代码
class list
{
private:
	typedef struct ListNode<T> LNode;

public:
	typedef __list_iterator<T, T&, T*> iterator;
	typedef __list_iterator<T, const T&, const T*> const_iterator;
    // 迭代器后面实现

private:
	LNode* _head = nullptr;
	size_t _size = 0; // 记录链表的长度
}

构造函数

cpp 复制代码
list()
{
	_head = new LNode;
	_head->_next = _head->_prev = _head;
	_size = 0;
}

template<class T>
list(T begin, T end) // 迭代器构造
{
	_head = new LNode;
	_head->_next = _head->_prev = _head;
	_size = 0;
	while (begin != end)
	{
		push_back(*begin);
		begin++;
	}
}

修改

这里插入删除和双向链表的插入删除是一样的

参考: C语言实现链表-CSDN博客

cpp 复制代码
void push_back(const T& val)
{
	LNode* prev = _head->_prev;
	LNode* newnode = new LNode(val);
	prev->_next = newnode;
	newnode->_prev = prev;
	_head->_prev = newnode;
	newnode->_next = _head;
	_size++;
}

void pop_back(const T& val)
{
	if (_head->_next == _head)
	{
		return;
	}

	LNode* prev = _head->_prev->_prev;
	delete _head->_prev;
	prev->_next = _head;
	_head->_prev = prev;
	_size--;
}

void push_fornt(const T& val)
{
	LNode* prev = _head->_prev;
	LNode* newnode = new LNode(val);
	LNode* next = _head->_next;
	_head->_next = newnode;
	newnode->_prev = _head;
	newnode->_next = next;
	next->_prev = newnode;
	_size++;
}

void pop_front()
{
	if (_head->_next == _head)
	{
		return;
	}

	LNode* next = _head->_next->_next;
	delete _head->_next;
	_head->_next = next;
	next->_prev = _head;
	_size--;
}

其他函数

cpp 复制代码
void swap(list& x)
{
	std::swap(_head, x._head);
	std::swap(_size, x._size);
}

void clear()
{
	LNode* cur = _head->_next;
	while (cur != _head)
	{
		LNode* next = cur->_next;
		delete cur;
		next = cur;
	}
	_size = 0;
}

迭代器实现

list 的迭代器不能像 vector 一样, 原始指针就是当作迭代器来使用.

vector 底层就是一块连续的空间, 指针的 ++, -- 或是 * 等运算符都能直接使用.

cpp 复制代码
template<class T, class Ref, class Por>
struct __list_iterator
{
	typedef struct ListNode<T> LNode;
	typedef __list_iterator<T, Ref, Por> self;

	__list_iterator(LNode* node)
		:_node(node)
	{}

	self& operator++() // 前置 ++ 就是向后走一个节点, 返回后一个节点的迭代器
	{
		_node = _node->_next;
		return *this;
	}

	self& operator--() // 前置 -- 就是向前走一个节点, 返回前一个节点的迭代器
	{
		_node = _node->_prev;
		return *this;
	}

	self& operator++(int) // 后置 ++ 就是向后走一个节点, 返回当前节点迭代器
	{
		LNode* tem = _node;
		_node = _node->_next;
		return tem;
	}

	self& operator--(int) // 后置 -- 就是向前走一个节点, 返回当前节点迭代器
	{
		LNode* tem = _node;
		_node = _node->_prev;
		return tem;
	}

	Ref operator*() // * 返回这个节点存储的数据本身, int* arr, *arr 返回一个 int 类型数据.
	{
		return _node->_data;
	}

	Por operator->() // -> 返回这个节点存储的数据的地址
	{
		return &_node->_data;
	}

	bool operator==(self& s)
	{
		return _node == s._node;
	}

	bool operator!=(self& s)
	{
		return _node != s._node;
	}

	LNode* _node;
    // 底层还是 ListNode 指针
};

可以看到, 这个迭代器底层就是 ListNode* , 然后通过重载 ++, -- 等运算符来完成像 vector指针++的作用. 原始的ListNode* ++ 并不能实现向后走一个节点这样的功能, 所以需要重载.

operator-> 的特殊优化

cpp 复制代码
class Data
{
public:
    Data(int year = 2024, int month = 5, int day = 2)
    :_year(year),
    _month(month),
    _day(day);
    {}
    int _year;
    int _month;
    int _day;
};

list<Data> l1;
Data d1;
l1.push_back(v1);
auto it = l1.begin();
cout << it->_year << ":" << it->_month << ":" << it->day;

当 list 中存储的是一个自定义类型时, 通过 operator-> 可以得到自定义类型的指针,

指针应该再使用 -> 来访问自定义类型的内部成员属性

这里的写法是经过了优化.

cpp 复制代码
class Data
{
public:
    Data(int year = 2024, int month = 5, int day = 2)
    :_year(year),
    _month(month),
    _day(day);
    {}
    int _year;
    int _month;
    int _day;
};

list<Data> l1;
Data d1;
l1.push_back(v1);
auto it = l1.begin();

// 我们上面自己实现的
Data* operator->() // -> 返回这个节点存储的数据的地址
{
	return &_node->_data;
}

cout << it->()->_year; // 未做特殊处理的写法
// 可以看到 it->() 得到了指针, 然后再通过指针来访问自定义类型内部的成员变量

这里的优化是方便了我们使用, 使用上面未作特殊处理写法也是可以的.

相关推荐
豚豚糯3 小时前
栈和队列——考研笔记
数据结构·笔记·考研
蓝莓星冰乐3 小时前
数据结构-二叉树_堆
数据结构·算法
阿史大杯茶5 小时前
CodeTON Round 9 (Div. 1 + Div. 2, Rated, Prizes! ABCDE题) 视频讲解
数据结构·c++·算法
@小博的博客6 小时前
C++初阶学习 第十二弹——stack与queue的介绍和使用
开发语言·数据结构·c++·学习
A Man Of Mould6 小时前
【数据结构】—— 堆
数据结构·算法
佑冰6 小时前
C++ 矩阵旋转
数据结构·c++·算法·c
qystca6 小时前
蓝桥杯不知道叫什么题目
数据结构·算法
益达爱喝芬达6 小时前
力扣11.23
数据结构·算法·leetcode
秋说6 小时前
【数据结构 | C++】部落
数据结构·c++·算法