这里写目录标题
- vector和stirng的细节
- 对于string
- list的使用
- list的迭代器
- 反向迭代器
- 构造函数
- 关于list::sort的排序
- unique
- list的底层模拟实现
- 结点类的实现
- 迭代器模拟实现
- list实现
- 插入的实现
- 迭代器失效
- insert
- erase
- 析构函数
- 拷贝构造
- 赋值构造函数
vector和stirng的细节
复习vector的深浅拷贝。
下图是vector<vector< int>> 的底层模型
实际和动态二维数组的图一样。
vv[i][j] 表示什么意思?
vv[ i]表示访问第几个vector,
vv[i][ j]表示访问第i个vector的第j个下表的元素。
对于string
class string
{
private:
char _buf[16];
char* _ptr;
size_t _size;
size_t _capacity;
}
string设计如下,string在设计时用了一个buf的数组,buf大小是16,最后一个空间是给\0的。
这样设计的目的是小于16byte的字符串,存在buf数组中。大于等于16的字符串存在_ptr指向的空间。
list的使用
概念:list是一个容器,允许在常数O(1)时间,在任意位置进行insert和erase。他的迭代器是双向的。
链表在使用上和vector和string差不多。
因为支持O(1)的时间,所以它是双向循环链表的数据结构。
如图
对于迭代器的位置,begin()是第一个元素的位置,end()是最后一个元素的下一个位置,也就是哨兵位头结点。
list的迭代器
对于list为什么要学迭代器?
对于string和vector来说,使用的是原生指针,所以迭代器是原生的,对于list,我们也要封装迭代器,因为list底层是地址,不是连续的地址。为了方便用户操作,比如for循环遍历,范围for等。
小细节:因为list的结构,所以while循环内不能用小于<,因为都是地址,地址不能比大小,迭代器的条件都是不等于!=
void test1()
{
list<int> lt;
lt.push_back(1);
lt.push_back(2);
lt.push_back(3);
lt.push_back(4);
list<int>::iterator it = lt.begin();
while (it != lt.end())
{
cout << *it << " ";
++it;
}
cout << endl;
}
反向迭代器
rbegin()就是最后一个元素的下一个位置。
auto rit = lt.rbegin();
while (rit != lt.rend())
{
cout << *rit << " ";
++rit;
}
构造函数
四个:无参的构造,n个value的构造,迭代器区间构造,拷贝构造。
和vector非常相似不说,查文档。
关于list::sort的排序
如果大量的排序不建议用list自带的。可以用vector进行排序。
对于std算法库里面的sort,需要传随机迭代器才可以使用。
但是list不支持随机访问,list的迭代器是双向迭代器。
list lt;
lt.sort();
对于迭代器实际上分为三类。
1.单向。++ forward_list
2.双向。致辞 ++ – list
3.随机。支持++ – + - vector
所以要求传双向迭代器的,也可以传随机迭代器。
总结:list不支持随机迭代器,他是双向迭代器,不能用库的sort的原因是因为,库里的sort用的快排,快排用的是随机访问,所以一般不用链表进行排序。
解决方法:可以先把数据转移到vector中排序后,再转移到list中。
unique
这个函数的作用是用来去重。
但是去重是有条件的,他只能对排序的list进行去重
list的底层模拟实现
结点类的实现
template<class T>
struct list_node
{
list_node<T>* _next;
list_node<T>* _prev;
T _data;
list_node(const T& val = T())
:_next(nullptr)
, _prev(nullptr)
, _data(val)
{}
};
迭代器模拟实现
因为底层空间不连续,地址不连续,所以需要封装迭代器
用模板T的原因是迭代器里封装了结点的指针。
版本1
template <class T>
struct _list_iterator
{
typedef list_node<T> Node;
Node* _node;
T& operator*()
{
return _node->_data;
}
}
const迭代器,一般写法就是再定义一个const迭代器的类。这样复用性很差。达不到软件工程复用的思想。
大神写法就是用模板参数 来控制。
这里不用管第一个参数T,第一个参数是数据类型,比如int,只用控制解引用和箭头访问数值的就行。也就是控制Ref和 Ptr
const迭代器第一个位置不加const的原因是因为:需要保持一致的类型。因为在begin()函数中用结点的指针构造迭代器时,传递的是int的类型。但是模板传递过去是cosnt int类型。这时候类型不匹配。
//迭代器实现
// typedef __list_iterator<T, T&, T*> iterator;
// typedef __list_iterator<T, const T&, const T*> const_iterator;
template<class T, class Ref, class Ptr>
struct __list_iterator
{
typedef list_node<T> Node;
typedef __list_iterator<T, Ref, Ptr> self;
Node* _node;
__list_iterator(Node* node)
:_node(node)
{}
// 析构函数 -- 节点不属于迭代器,不需要迭代器释放
// 拷贝构造和赋值重载 -- 默认生成的浅拷贝就可以
// *it
Ref operator*()
{
return _node->_data;
}
Ptr operator->()
{
//return &(operator*());
return &_node->_data;
}
self& operator++()
{
_node = _node->_next;
return *this;
}
self operator++(int)
{
self tmp(*this);
_node = _node->_next;
return tmp;
}
self& operator--()
{
_node = _node->_prev;
return *this;
}
self operator--(int)
{
self tmp(*this);
_node = _node->_prev;
return tmp;
}
bool operator!=(const self& it)
{
return _node != it._node;
}
bool operator==(const self& it)
{
return _node == it._node;
}
};
list实现
template<class T>
class list
{
typedef list_node<T> Node;
public:
typedef __list_iterator<T, T&, T*> iterator;
typedef __list_iterator<T, const T&, const T*> const_iterator;
const_iterator begin() const
{
// list_node<int>*
return const_iterator(_head->_next);
}
const_iterator end() const
{
return const_iterator(_head);
}
iterator begin()
{
return iterator(_head->_next);
//return _head->_next;
}
iterator end()
{
return iterator(_head);
}
list()
{
_head = new Node();
_head->_next = _head;
_head->_prev = _head;
}
// lt2(lt1)
/*list(const list<T>& lt)
{
_head = new Node();
_head->_next = _head;
_head->_prev = _head;
for (auto e : lt)
{
push_back(e);
}
}*/
void empty_init()
{
_head = new Node();
_head->_next = _head;
_head->_prev = _head;
}
template <class InputIterator>
list(InputIterator first, InputIterator last)
{
empty_init();
while (first != last)
{
push_back(*first);
++first;
}
}
// 17:00 继续
void swap(list<T>& lt)
{
std::swap(_head, lt._head);
}
// lt2(lt1) -- 现代写法
list(const list<T>& lt)
{
empty_init();
list<T> tmp(lt.begin(), lt.end());
swap(tmp);
}
// lt2 = lt1
list<T>& operator=(list<T> lt)
{
swap(lt);
return *this;
}
~list()
{
clear();
delete _head;
_head = nullptr;
}
void clear()
{
iterator it = begin();
while (it != end())
{
it = erase(it);
}
}
void push_back(const T& x)
{
//Node* tail = _head->_prev;
//Node* newnode = new Node(x);
_head tail newnode
//tail->_next = newnode;
//newnode->_prev = tail;
//newnode->_next = _head;
//_head->_prev = newnode;
insert(end(), x);
}
void push_front(const T& x)
{
insert(begin(), x);
}
void pop_back()
{
erase(--end());
}
void pop_front()
{
erase(begin());
}
// 插入在pos位置之前
iterator insert(iterator pos, const T& x)
{
Node* newNode = new Node(x);
Node* cur = pos._node;
Node* prev = cur->_prev;
// prev newnode cur
prev->_next = newNode;
newNode->_prev = prev;
newNode->_next = cur;
cur->_prev = newNode;
return iterator(newNode);
}
iterator erase(iterator pos)
{
assert(pos != end());
Node* cur = pos._node;
Node* prev = cur->_prev;
Node* next = cur->_next;
// prev next
prev->_next = next;
next->_prev = prev;
delete cur;
return iterator(next);
}
private:
Node* _head;
};
void print_list(const list<int>& lt)
{
list<int>::const_iterator it = lt.begin();
while (it != lt.end())
{
//*it = 10; // 不允许修改
cout << *it << " ";
++it;
}
cout << endl;
}
插入的实现
只用实现insert就好,头插尾插复用即可。
这个很简单,轻松实现
迭代器失效
insert
list的insert不存在迭代器失效的问题。因为list不需要像vector一样挪动数据,也不像vector一样扩容出现野指针。lsit的每个结点都是独立的。
erase
erase删掉一个结点时,已经delete了,空间已经被释放了。所以会导致迭代器失效。经典野指针失效
所以erase返回删除位置的下一个位置的迭代器。所以需要重新接收迭代器。
析构函数
析构函数内部直接调用clear(),因为析构时指这个结构不要了,所以直接delete哨兵位结点。
void clear()
{
iterator it = begin();
while(it != end())
{
it = erase(it);
}
}
~list()
{
clear();
delete _head;
_head = nullptr;
}
拷贝构造
拷贝构造函数的规则:我们不写,完成浅拷贝。所以_head被拷贝了一份,指向了同一块空间。
浅拷贝不一定有问题,看对应场合,比如在迭代器中,浅拷贝没有错。因为不用析构。
lsit中我们要完成深拷贝。这里需要析构。
这里先用迭代器区间进行拷贝构造
void empty_init()
{
_head = new Node();
_head ->_next = _head;
_head->_prev = _head;
}
template <class InputIterator>
list(InputIterator first, InputIterator last)
{
//哨兵位结点必须有
empty_init();
while(first != last)
{
push_pack(*first);
++first;
}
}
void swap(list<T>& lt)
{
std::swap(_head, lt._head);
}
//lt2(lt1)
//现代写法
list(const list<T>& lt)
{
empty_init();
list<T> tmp(lt.begin(), lt.end());
swap(tmp);
}
赋值构造函数
赋值构造函数返回引用,减少拷贝。返回list的原因是支持连续赋值。
//lt2 = lt1;
list<T>& operator=(list<T> lt)
{
swap(lt);
return *this;
}