【玩转c++】List讲解和模拟底层实现
创始人
2024-05-31 09:11:56
0
本期主题:list的讲解和模拟实现
博客主页:小峰同学
分享小编的在Linux中学习到的知识和遇到的问题
小编的能力有限,出现错误希望大家不吝赐

1.list的介绍和使用

1.1.list的介绍

1.list是可以在常数范围内在任意位置进行插入和删除的序列式容器,并且该容器可以前后双向迭代。 2.list的底层是双向链表结构,双向链表中每个元素存储在互不相关的独立节点中,在节点中通过指针指向 其前一个元素和后一个元素。 3. list与forward_list非常相似:最主要的不同在于forward_list是单链表,只能朝前迭代,已让其更简单高 效。 4. 与其他的序列式容器相比(array,vector,deque),list通常在任意位置进行插入、移除元素的执行效率 更好。 5. 与其他序列式容器相比,list和forward_list最大的缺陷是不支持任意位置的随机访问,比如:要访问list 的第6个元素,必须从已知的位置(比如头部或者尾部)迭代到该位置,在这段位置上迭代需要线性的时间 开销;list还需要一些额外的空间,以保存每个节点的相关联信息(对于存储类型较小元素的大list来说这 可能是一个重要的因素)

1.2.list的使用

1.2.1.成员函数

这个和vector的差不多

1.2.2.迭代器

和vector相似
从此时开始就要知道迭代器的重要性了,不支持下标访问了,这时候迭代器就很重要了。
所有的容器真正的,统一的遍历方式就是迭代器。3

1.2.3.容量相关

1.2.4.访问相关

1.2.5.修改相关

1.2.6.操作相关

2.List的模拟实现

2.1.源码

源码
#pragma once
#include 
#include
#include
using namespace std;namespace zxf2
{templatestruct list_node{//共有成员变量list_node* _next;list_node* _prev;T _data;//每个节点的构造函数。list_node(const T& val = T()):_next(nullptr),_prev(nullptr),_data(val){}};//这里实现的迭代器是普通版本的,那如果想要一个const版本的迭代器怎么办?//const list_iterator it = lt.begin();//这样  显然不是我们想要的结果,//上述的写法只能是it 不能改,但是 const迭代器是 *it不能改动。//为什么vector模拟实现的时候 直接 定义了 : typedef const T* const_iterator;自然就是实现了 it可以改,而 *it不能改的目的,(利用了指针的特性)////template//struct list_iterator//{//    //迭代器本质上是在控制节点的指针 _pnode;//    //是对节点(node)指针的封装。//    typedef list_node node;//    typedef list_iterator self;//迭代器类型重命名。//    //成员变量//    node* _pnode;//    //迭代器在创建的时候必须初始化//    //使用节点的指针来构造//    list_iterator(node* pnode){//        _pnode = pnode;//    }//    //前置++ ,--; 前置可以返回引用,也就返回迭代器本身。//    self& operator++()//    {//        _pnode = _pnode->_next;//        return *this;//    }//    self& operator--()//    {//        _pnode = _pnode->_prev;//        return *this;//    }//    //后置++ , -- ;不能返回引用。//    self operator++(int)//    {//        self tmp(*this);//出了函数tmp会被销毁的。//        _pnode = _pnode->_next;//        return tmp;//    }//    self operator--(int)//    {//        self tmp(*this);//出了函数tmp会被销毁的。//        _pnode = _pnode->_prev;//        return tmp;//    }//    //所以一般使用前置++,减少拷贝照成的空间和时间浪费。//    //判断两个迭代器是否相同就是迭代器里面的成员变量的地址是否相同。//    bool operator!=(const self& lt)const//    {//        return _pnode != lt._pnode;//    }//    bool operator==(const self& lt)const//    {//        return _pnode == lt._pnode;//    }//    T& operator*()//    {//        return _pnode->_data;//    }//};//template//struct list_const_iterator//{//    //迭代器本质上是在控制节点的指针 _pnode;//    //是对节点(node)指针的封装。//    typedef list_node node;//    typedef list_const_iterator self;//迭代器类型重命名。//    //成员变量//    node* _pnode;//    //迭代器在创建的时候必须初始化//    //使用节点的指针来构造//    list_const_iterator(node* pnode) {//        _pnode = pnode;//    }//    //前置++ ,--; 前置可以返回引用,也就返回迭代器本身。//    self& operator++()//    {//        _pnode = _pnode->_next;//        return *this;//    }//    self& operator--()//    {//        _pnode = _pnode->_prev;//        return *this;//    }//    //后置++ , -- ;不能返回引用。//    self operator++(int)//    {//        self tmp(*this);//出了函数tmp会被销毁的。//        _pnode = _pnode->_next;//        return tmp;//    }//    self operator--(int)//    {//        self tmp(*this);//出了函数tmp会被销毁的。//        _pnode = _pnode->_prev;//        return tmp;//    }//    //所以一般使用前置++,减少拷贝照成的空间和时间浪费。//    //判断两个迭代器是否相同就是迭代器里面的成员变量的地址是否相同。//    bool operator!=(const self& lt)const//    {//        return _pnode != lt._pnode;//    }//    bool operator==(const self& lt)const//    {//        return _pnode == lt._pnode;//    }//    const T& operator*()//这里是const迭代器和 普通迭代器真的区别,区别就这一点点。就是在解引用的时候,返回的是const类型还是普通类型。//        //返回const类型就是不能修改(const迭代器),返回普通类型就是可以修改(普通迭代器)。//    {//        return _pnode->_data;//    }//};//上面两个迭代器多少有点代码冗余。可以把他们合并在一起。template
struct list_iterator
{//迭代器本质上是在控制节点的指针 _pnode;//是对节点(node)指针的封装。typedef list_node node;typedef list_iterator self;//迭代器类型重命名。//成员变量node* _pnode;//迭代器在创建的时候必须初始化//使用节点的指针来构造list_iterator(node* pnode) {_pnode = pnode;}//前置++ ,--; 前置可以返回引用,也就返回迭代器本身。self& operator++(){_pnode = _pnode->_next;return *this;}self& operator--(){_pnode = _pnode->_prev;return *this;}//后置++ , -- ;不能返回引用。self operator++(int){self tmp(*this);//出了函数tmp会被销毁的。_pnode = _pnode->_next;return tmp;}self operator--(int){self tmp(*this);//出了函数tmp会被销毁的。_pnode = _pnode->_prev;return tmp;}bool operator!=(const self& lt)const//这里的const有什么用?{return _pnode != lt._pnode;}bool operator==(const self& lt)const{return _pnode == lt._pnode;}ref operator*(){return _pnode->_data;}ptr operator->(){return &_pnode->_data;}self operator+=(size_t n){while (n--){_pnode = _pnode->_next;}return *this;}self operator-=(size_t n){while (n--){_pnode = _pnode->_prev;}return *this;}};template class list{//类名 list//类型 listtypedef list_node node;public://typedef list_iterator iterator;//typedef list_const_iterator const_iterator;typedef list_iterator iterator;typedef list_iterator const_iterator;//创建出头节点void empty_list(){_phead = new node(T());_phead->_next = _phead;_phead->_prev = _phead;}//无参构造list(){empty_list();}void push_back(const T& val){//node* tmp = new node(val);//tmp->_next = _phead;//tmp->_prev = _phead->_prev;//_phead->_prev->_next = tmp;//_phead->_prev = tmp;insert(iterator(_phead), val);}void push_front(const T& val){//node* tmp = new node(val);//tmp->_next = _phead;//tmp->_prev = _phead->_prev;//_phead->_prev->_next = tmp;//_phead->_prev = tmp;insert(iterator(_phead->_next), val);}//单参数构造list(const T& val){empty_list();push_back(val);}//迭代器构造的模板templatelist(input_iterator first, input_iterator last){//创建头节点empty_list();while (first != last){push_back(*first);//(*first)是 T/const T 类型的数据++first;}}//        //拷贝构造(古法) 里 lt1 = lt2;
//        list(const list& lt)
//            //注意拷贝构造的参数也是引用,防止无线循环,
//        {
//            empty_list();
需要迭代器的支持
//            for (auto& e : lt){//注意这样的引用,const 可加可不加。
//                //引用1,防止e的空间浪费;2,list>这种情况出现时,死循环。
//                push_back(e);
//            }
//        }void swap(list& lt){std::swap(lt._phead, _phead);}~list(){clear();delete _phead;_phead = nullptr;}void clear(){//注意这里只能是 iterator ,不能是 const_iterator //const list 才会返回const_iterator,但是 const list 不能修改iterator first = begin();//while (first != end())//{//    erase(first);//    ++first;//}//上述写法是错误的while (first != end()){first = erase(first);}}size_t size(){return _size;}bool empty(){return _size == 0;}//lt1 =lt2;list operator=(list lt){swap(lt);return *this;}//注意删除的时候要返回欲删除元素下一个元素的迭代器,iterator erase(iterator it){assert(it != end());node* next = it._pnode->_next;node* prev = it._pnode->_prev;next->_prev = prev;prev->_next = next;delete it._pnode;return iterator(next);// 匿名对象, 这里的生命周期只有一行,不能引用返回。--_size;}iterator insert(iterator pos, const T& val){node* newnode = new node(val);node* next = pos._pnode;node* prev = pos._pnode->_prev;newnode->_next = next;newnode->_prev = prev;prev->_next = newnode;next->_prev = newnode;return iterator(newnode);++_size;}void pop_back(){erase(iterator(_phead->_prev));}void pop_front(){erase(iterator(_phead->_next));}//拷贝构造(现代) 里 lt1 = lt2;list(const list& lt)//注意拷贝构造的参数也是引用,防止无线循环,{//无论什么构造,都要先创建头节点empty_list();list tmp(lt.begin(), lt.end());//先创建一个list类型的临时局部变量,出了作用域会被销毁。swap(tmp);}//这里也不能返回引用iterator begin(){return iterator(_phead->_next);//匿名对象,生命周期只有一行}const_iterator begin()const{return const_iterator(_phead->_next);//匿名对象,生命周期只有一行}iterator end(){return iterator(_phead);//匿名对象,生命周期只有一行}const_iterator end()const{return const_iterator(_phead);//匿名对象,生命周期只有一行}private://list私有的成员node* _phead;size_t _size;};}

2.2.常见问题

2.2.1. list的迭代器失效

前面说过,此处大家可将迭代器暂时理解成类似于指针,迭代器失效即迭代器所指向的节点的无效,即该节 点被删除了。因为list的底层结构为带头结点的双向循环链表,因此在list中进行插入时是不会导致list的迭代 器失效的,只有在删除时才会失效,并且失效的只是指向被删除节点的迭代器,其他迭代器不会受到影响。
void TestListIterator1()
{int array[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };list l(array, array+sizeof(array)/sizeof(array[0]));auto it = l.begin();while (it != l.end()){// erase()函数执行后,it所指向的节点已被删除,因此it无效,在下一次使用it时,必须先给
其赋值l.erase(it); ++it;}
}// 改正
void TestListIterator()
{int array[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };list l(array, array+sizeof(array)/sizeof(array[0]));auto it = l.begin();while (it != l.end()){l.erase(it++); // it = l.erase(it);}
}

2.2.2.类模板中类名和类型的问题

类一般只有两种:
普通类 和 类模板
对于普通类: 类名就是类型,是相同的。
对于类模板:类名是类名,给T赋上对应的类型后才是类的类型
class BBB
{int _data;
};
//在这里类名就是类型
//类名:BBB
//类型:BBBtemplate
class AAA
{T _data;
};
//在这里
//类名是:AAA
//类型是:AAA
但是在类模板里面可以使用类名去代表类型,但是在类外面类名就是类名,类型就是类型。
但是我个人习惯不喜欢,这个特例。

2.2.3.迭代器it的 -> 的访问问题

看一下示例代码
struct Pos
{int _row;int _col;Pos(int row = 0, int col = 0):_row(row), _col(col){}
};void print_list(const zxf2::list& lt)
{zxf2::list::const_iterator it = lt.begin();while (it != lt.end()){//it->_row++;//这里还能++ ,明明是 const类型的list为啥里面的数据可以++,//因为这里又会出现一个问题 T*  和 const T* 的  问题,//const_iterator 对应的应该是 const T&(解引用的返回值不可修改)  和 const T* (it-> 返回值不可修改) 的问题。//iterator 对应的应该是 T& (解引用的返回值可修改)  和  T* (it-> 返回值不可修改) 的问题。//迭代器是什么?//功能类似于指针,可以 * 和 -> 访问指针元素的类模板//他是通过 对 T* 进行封装的得到的一个类。//typedef list_iterator iterator;//普通迭代器:// 重载operator* 的时候 用T&返回。 // 重载operator->  的时候用 T* 返回//typedef list_iterator const_iterator;//const迭代器:// operator* 的时候 用const T&返回。// operator-> 的时候用const T* 返回cout << it->_row << ":" << it->_col << endl;++it;}cout << endl;
}void test_list5()
{zxf2::list lt;Pos p1(1, 1);lt.push_back(p1);lt.push_back(p1);lt.push_back(p1);lt.push_back(Pos(2, 2));lt.push_back(Pos(3, 3));// int* p  -> *p// Pos* p  -> p->zxf2::list::iterator it = lt.begin();//list::iterator it2 = it;while (it != lt.end()){it->_row++;//cout << it->;//错误写法。//cout << it.operator->();//it.operator->()->_col++;//cout << (&(*it))->_row << ":" << (*it)._col << endl;cout << it->_row << ":" << it->_col << endl;//cout << it.operator->()->_row << ":" << it->_col << endl;++it;}cout << endl;print_list(lt);
}
首先第一个问题:operator->()如何重载的问题:
        ptr operator->()//ptr是 T* 类型或者 const T* 类型的节点里面存放数据的地址。{return &(_pnode->_data);}
我们说迭代器是一个类似于指针一样的东西,所以如果list节点数据是一个自定义类型不用->,但是如果list节点数据是一个结构体那么 -> 成员变量应该就可以访问到成员,所以应该 it -> 成员变量 就可以访问到。
但是根据我们迭代器->的重载来看的话它返回的是结构的指针,
所以按理来说访问成员应该是 it -> -> 成员变量
但是我们看到在实例中我们访问的时候 直接是使用 it -> 成员变量 ,即可
所以这里编译器做了优化。我们可以看到下面代码都可以实现,对结构体里面对象的访问。
        cout << (&(*it))->_row << ":" << (*it)._col << endl;cout << it->_row << ":" << it->_col << endl;cout << it.operator->()->_row << ":" << it->_col << endl;
其实本质就是,编译器把 it -> -> _row 优化为 it ->_row
可以从:it.operator->()->_row 证明得出。
这里是编译器的一个特殊处理。在以后的智能指针中很常用。

相关内容

热门资讯

Python|位运算|数组|动... 目录 1、只出现一次的数字(位运算,数组) 示例 选项代...
张岱的人物生平 张岱的人物生平张岱(414年-484年),字景山,吴郡吴县(今江苏苏州)人。南朝齐大臣。祖父张敞,东...
西游西后传演员女人物 西游西后传演员女人物西游西后传演员女人物 孙悟空 六小龄童 唐僧 徐少华 ...
名人故事中贾岛作诗内容简介 名人故事中贾岛作诗内容简介有一次,贾岛骑驴闯了官道.他正琢磨着一句诗,名叫《题李凝幽居》全诗如下:闲...
和男朋友一起优秀的文案? 和男朋友一起优秀的文案?1.希望是惟一所有的人都共同享有的好处;一无所有的人,仍拥有希望。2.生活,...
戴玉手镯的好处 戴玉手镯好还是... 戴玉手镯的好处 戴玉手镯好还是碧玺好 女人戴玉?戴玉好还是碧玺好点佩戴手镯,以和田玉手镯为佳!相嫌滑...
依然什么意思? 依然什么意思?依然(汉语词语)依然,汉语词汇。拼音:yī    rán基本解释:副词,指照往常、依旧...
高尔基的散文诗 高尔基的散文诗《海燕》、《大学》、《母亲》、《童年》这些都是比较出名的一些代表作。
心在飞扬作者简介 心在飞扬作者简介心在飞扬作者简介如下。根据相关公开资料查询,心在飞扬是一位优秀的小说作者,他的小说作...
卡什坦卡的故事赏析? 卡什坦卡的故事赏析?讲了一只小狗的故事, 我也是近来才读到这篇小说. 作家对动物的拟人描写真是惟妙...
林绍涛为简艾拿绿豆糕是哪一集 林绍涛为简艾拿绿豆糕是哪一集第三十二集。 贾宽认为是阎帅间接导致刘映霞住了院,第二天上班,他按捺不...
小爱同学是女生吗小安同学什么意... 小爱同学是女生吗小安同学什么意思 小爱同学,小安同学说你是女生。小安是男的。
内分泌失调导致脸上长斑,怎么调... 内分泌失调导致脸上长斑,怎么调理内分泌失调导致脸上长斑,怎么调理先调理内分泌,去看中医吧,另外用好的...
《魔幻仙境》刺客,骑士人物属性... 《魔幻仙境》刺客,骑士人物属性加点魔幻仙境骑士2功1体质
很喜欢她,该怎么办? 很喜欢她,该怎么办?太冷静了!! 太理智了!爱情是需要冲劲的~不要考虑着考虑那~否则缘...
言情小说作家 言情小说作家我比较喜欢匪我思存的,很虐,很悲,还有梅子黄时雨,笙离,叶萱,还有安宁的《温暖的玄》 小...
两个以名人的名字命名的风景名胜... 两个以名人的名字命名的风景名胜?快太白楼,李白。尚志公园,赵尚志。
幼儿教育的代表人物及其著作 幼儿教育的代表人物及其著作卡尔威特的《卡尔威特的教育》,小卡尔威特,他儿子成了天才后写的《小卡尔威特...
海贼王中为什么说路飞打凯多靠霸... 海贼王中为什么说路飞打凯多靠霸气升级?凯多是靠霸气升级吗?因为之前刚到时确实打不过人家因为路飞的实力...
运气不好拜财神有用吗运气不好拜... 运气不好拜财神有用吗运气不好拜财神有没有用1、运气不好拜财神有用。2、拜财神上香前先点蜡烛,照亮人神...