C++初阶—list类
第一章:list的介绍及使用
1.1 list的介绍
- list是可以在常数范围内在任意位置进行插入和删除的序列式容器,并且该容器可以前后双向迭代。
- list的底层是双向链表结构,双向链表中每个元素存储在互不相关的独立节点中,在节点中通过指针指向其前一个元素和后一个元素。
- list与forward_list非常相似:最主要的不同在于forward_list是单链表,只能朝前迭代,已让其更简单高效。
- 与其他的序列式容器相比(array,vector,deque),list通常在任意位置进行插入、移除元素的执行效率更好。
- 与其他序列式容器相比,list和forward_list最大的缺陷是不支持任意位置的随机访问,比如:要访问list的第6个元素,必须从已知的位置(比如头部或者尾部)迭代到该位置,在这段位置上迭代需要线性的时间开销;list还需要一些额外的空间,以保存每个节点的相关联信息(对于存储类型较小元素的大list来说这可能是一个重要的因素
1.2 list的使用
list中的接口比较多,此处类似,只需要掌握如何正确的使用,然后再去深入研究背后的原理,已达到可扩展的能力。以下为list中一些常见的重要接口。
1.2.1 list的构造
| 构造函数( (constructor)) | 接口说明 |
| list (size_type n, const value_type& val = value_type()) | 构造的list中包含n个值为val的元素 |
| list() | 构造空的list |
| list (const list& x) | 拷贝构造函数 |
| list (InputIterator first, InputIterator last) | 用[first, last)区间中的元素构造list |
1.2.2 list iterator的使用
此处,大家可暂时将迭代器理解成一个指针,该指针指向list中的某个节点。
| 函数声明 | 接口说明 |
| begin + end | 返回第一个元素的迭代器+返回最后一个元素下一个位置的迭代器 |
| rbegin + rend | 返回第一个元素的reverse_iterator,即end位置,返回最后一个元素下一个位置的reverse_iterator,即begin位置 |

【注意】
- begin与end为正向迭代器,对迭代器执行++操作,迭代器向后移动
- rbegin(end)与rend(begin)为反向迭代器,对迭代器执行++操作,迭代器向前移动
1.2.3 list capacity
| 函数声明 | 接口说明 |
| empty | 检测list是否为空,是返回true,否则返回false |
| size | 返回list中有效节点的个数 |
1.2.4 list element access
| 函数声明 | 接口说明 |
| front | 返回list的第一个节点中值的引用 |
| back | 返回list的最后一个节点中值的引用 |
1.2.5 list modifiers
| 函数声明 | 接口说明 |
| push_front | 在list首元素前插入值为val的元素 |
| pop_front | 删除list中第一个元素 |
| push_back | 在list尾部插入值为val的元素 |
| pop_back | 删除list中最后一个元素 |
| insert | 在list position 位置中插入值为val的元素 |
| erase | 删除list position位置的元素 |
| swap | 交换两个list中的元素 |
| clear | 清空list中的有效元素 |
1.2.6 list的迭代器失效
前面说过,此处可将迭代器暂时理解成类似于指针,迭代器失效即迭代器所指向的节点的无效,即该节点被删除了。因为list的底层结构为带头结点的双向循环链表,因此在list中进行插入时是不会导致list的迭代器失效的,只有在删除时才会失效,并且失效的只是指向被删除节点的迭代器,其他迭代器不会受到影响。
Use of List.cpp
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <vector>
#include <list>
#include <algorithm>using namespace std;void test_list1() {list<int> lt;lt.push_back(1);lt.push_back(2);lt.push_back(3);lt.push_back(4);lt.push_back(5);list<int>::iterator it = lt.begin();while (it != lt.end()) { //遍历cout << *it << " ";++it;}cout << endl;for (auto e : lt) //范围forcout << e << " ";cout << endl;
}void test_list2() {list<int> lt;lt.push_back(1);lt.push_back(2);lt.push_back(3);lt.push_back(4);lt.push_back(5);lt.reverse();//逆置for (auto e : lt)cout << e << " ";//5 4 3 2 1cout << endl;//sort(lt.begin(), lt.end());//报错(从访问使用的角度划分)迭代器功能上分类:正向/反向 及 正向/反向 + const(从容器底层实现的角度划分)迭代器性质上分类:单向 ++ 单链表/哈希表双向 ++/-- 双向链表/红黑树(map和set)随机 ++/--/+/- vector/string/deque算法库的sort要求传随机迭代器,而list的是双向迭代器。所以list有自己的sort//less<int> less_lt;//升序//lt.sort(less_lt);lt.sort();//sort默认升序for (auto e : lt)cout << e << " ";cout << endl;//降序//greater<int> gt;//lt.sort(gt);lt.sort(greater<int>());//实际中这种传匿名对象的方式更常用//数据量小可以用这个sort,数据量大效率不高。
}void test_list3() { //验证list模板中sort的效率vecor排序比list快,数据量越大,两者差距越大//srand((int)time(0));//const int N = 1000000;//vector<int> v;//v.reserve(N);//list<int> lt1;//for (int i = 0; i < N; ++i) {// auto e = rand();// lt1.push_back(e);// v.push_back(e);//}排序//int begin1 = clock();//sort(v.begin(), v.end());//int end1 = clock();////int begin2 = clock();//lt1.sort();//int end2 = clock();//printf("vector sort:%d\n", end1 - begin1);//printf("list sort:%d\n", end2 - begin2);//比较:lt1直接排序,lt2数据拷贝到vector在排序srand((int)time(0));const int N = 1000000;list<int> lt1;list<int> lt2;for (int i = 0; i < N; ++i) {auto e = rand();lt1.push_back(e);lt2.push_back(e);}// 排序int begin1 = clock();vector<int> v(lt2.begin(), lt2.end());//将lt2的数据拷贝到vector再排序sort(v.begin(), v.end());lt2.assign(v.begin(), v.end());//将vector的数据再拷贝回lt2int end1 = clock();int begin2 = clock();lt1.sort();int end2 = clock();printf("list2 sort:%d\n", end1 - begin1);printf("list1 sort:%d\n", end2 - begin2);
}void test_list4() {//merge() 归并两个链表,取小的尾插。前提是两个链表有序//去重演示,需要保证有序list<int> lt;lt.push_back(1);lt.push_back(2);lt.push_back(3);lt.push_back(4);lt.push_back(3);lt.push_back(3);lt.push_back(3);lt.push_back(5);lt.push_back(5);lt.push_back(3);for (auto e : lt)cout << e << " ";//1 2 3 4 3 3 3 5 5 3cout << endl;lt.sort();lt.unique();for (auto e : lt)cout << e << " ";//1 2 3 4 5cout << endl;lt.remove(3);//删除全部3//lt.remove(30);//删除的数据不存在,什么都不做for (auto e : lt)cout << e << " ";//1 2 4 5cout << endl;
}void test_list5() {list<int> lt1, lt2;for (int i = 1; i <= 4; ++i)lt1.push_back(i);// lt1: 1 2 3 4for (int i = 1; i <= 3; ++i)lt2.push_back(i * 10);// lt2: 10 20 30list<int>::iterator it = lt1.begin();++it;//it指向2//lt1.splice(it, lt2);//1 10 20 30 2 3 4 //将整个lt2转移到lt1 第二节点的前面lt1:1 10 20 30 2 3 4 lt2为空,it依旧指向2//lt1.splice(it, lt2, ++lt2.begin());//1 20 2 3 4 //将lt2的第二节点转移到lt1的第二节点前面lt1.splice(it, lt2, ++lt2.begin(), lt2.end());//1 20 30 2 3 4 //将lt2从第二节点至末尾转移到lt1的第二节点前面for (auto e : lt1)cout << e << " ";cout << endl;
}int main() {//test_list1();test_list2();//test_list3();//test_list4();//test_list5();return 0;
}
第二章:list的模拟实现
2.1 模拟实现list
List Class.h
#pragma oncenamespace bit {//封装目的: 封装成 list_node 结构体是为了将链表节点与具体的操作逻辑分离,保持代码的清晰性和模块化。//在链表实现中多次使用这个结构体来管理节点,而不仅仅是在 list 类里,因此单独封装是合理的做法。template<class T>//(如果使用class封装就会变成私有,后期可能需要友元)struct list_node { //节点结构体。T _data;list_node<T>* _next;list_node<T>* _prev;list_node(const T& x = T())//如果T是自定义类型,则x会是该类型的默认构造对象,取决于该类型的默认构造函数如何定义。:_data(x), _next(nullptr), _prev(nullptr) {}};vector中原生指针符合迭代器需要的场景。但list不行,对list指针解引用得到的是一个节点结构体,而不是该节点储存的数据;且单纯的++也不支持这种操作所以需要对list的迭代器进行封装+运算符重载(即封装节点指针,可以参考日期类的++)将迭代器封装成一个独立的类,使得链表的遍历更加简洁和灵活。迭代器是 STL 容器设计中的重要概念,单独封装可以提高代码的复用性和可维护性。//template<class T>//struct __list_iterator { //迭代器// typedef list_node<T> Node;// typedef __list_iterator<T> self;// Node* _node;// __list_iterator(Node* node)// :_node(node) {// }// //迭代器不需要析构函数,因为不需要释放资源(即节点)// //迭代器的赋值和拷贝构造需不需要深拷贝?// //不需要。迭代器通常只是一个轻量级的指针包装类,它指向的资源归容器管理,迭代器不负责资源的分配或释放。// //深拷贝的必要性:深拷贝(Deep Copy)通常用于动态分配资源的类,如 new 申请的内存。// //目的是避免多个对象共享同一块动态内存,导致释放时的重复释放或悬空指针问题。// self& operator++() { //前置++// _node = _node->_next;// return *this;// }// self& operator--() { //前置--// _node = _node->_prev;// return *this;// }// self operator++(int) { //后置++// self tmp(*this);// _node = _node->_next;// return tmp;// }// self operator--(int) { //后置--// self tmp(*this);// _node = _node->_prev;// return tmp;// }// T& operator*() { return _node->_data; }// T* operator->() { return &_node->_data; }//提供给自定义类型解引用。自定义类型解引用是 指针->形式,所以返回指针// bool operator!= (const self& s) { return _node != s._node; }// bool operator== (const self& s) { return _node == s._node; }//};const迭代器非const迭代器是可读可写,const迭代器是只读const T*(指向的内容不能修改)、T* const(指针本身不能修改,即不能指向其他内容)const迭代器依然要实现++等(即迭代器指针要改变指向),所以模拟的是第一种const迭代器不是非const迭代器加const修饰,那样会造成迭代器本身不能修改。const迭代器是一种单独的类型 //template<class T>//struct __list_const_iterator { //迭代器// typedef list_node<T> Node;// typedef __list_const_iterator<T> self;// Node* _node;// __list_const_iterator(Node* node)// :_node(node) {// }// //self& operator++() const该写法不对,这里的const修饰指的是this指向的成员不能修改,// //那么_node就不能被重新赋值,无法向下一节点移动// self& operator++() { //前置++// _node = _node->_next;// return *this;// }// self& operator--() { //前置--// _node = _node->_prev;// return *this;// }// self operator++(int) { //后置++// self tmp(*this);// _node = _node->_next;// return tmp;// }// self operator--(int) { //后置--// self tmp(*this);// _node = _node->_prev;// return tmp;// }// //const迭代器不能修改指向的内容,但本身可以移动。所以不能改为const T& operator*() const { return _node->_data; }// const T& operator*() { return _node->_data; }// const T* operator->() { return &_node->_data; }// //迭代器通过上面两个运算符重载修改指向内容,所以const迭代器要对他们返回值进行const修饰防止修改// bool operator!= (const self& s) { return _node != s._node; }// bool operator== (const self& s) { return _node == s._node; }//};//非const迭代器和const迭代器仅仅只是operator*()和operator->()返回值不同,//又因为同一个类模板,实例化参数不同,就是完全不同的类型。所以两个迭代器通过模板参数列表可以合并为一个。//将非const和const迭代器合并为一个类模板//vector中原生指针符合迭代器需要的场景。//但list不行,对list指针解引用得到的是一个节点结构体,而不是该节点储存的数据//所以需要对list的迭代器进行封装+运算符重载(即封装节点指针,可以参考日期类的//将迭代器封装成一个独立的类,使得链表的遍历更加简洁和灵活。//迭代器是 STL 容器设计中的重要概念,单独封装可以提高代码的复用性和可维护性。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) {}//迭代器不需要析构函数,因为不需要释放资源(即节点)//迭代器的赋值和拷贝构造需不需要深拷贝?//不需要。迭代器通常只是一个轻量级的指针包装类,它指向的资源归容器管理,迭代器不负责资源的分配或释放。//深拷贝的必要性:深拷贝(Deep Copy)通常用于动态分配资源的类,如 new 申请的内存。//目的是避免多个对象共享同一块动态内存,导致释放时的重复释放或悬空指针问题。self& operator++() { //前置++_node = _node->_next;return *this;}self& operator--() { //前置--_node = _node->_prev;return *this;}self operator++(int) { //后置++self tmp(*this);_node = _node->_next;return tmp;}self operator--(int) { //后置--self tmp(*this);_node = _node->_prev;return tmp;}Ref operator*() { return _node->_data; }Ptr operator->() { return &_node->_data; }//提供给自定义类型解引用。自定义类型解引用是 指针->形式,所以返回指针//非const和const迭代器中只有上面这两个运算符重载的返回值不同bool operator!= (const self& s) { return _node != s._node; }bool operator== (const self& s) { return _node == s._node; }};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;//上方并没有实例化迭代器,而是根据list<T>模板的不同类型创建了适用于该类型的迭代器类型别名。这些别名只是便于使用而已。//对迭代器模板的重命名:因为迭代器模板内部因为需要同时兼容非const和const迭代器,所以参数表达更宽泛。//而list类模板内部知道需要使用哪几种迭代器,所以对类模板的参数进行了进一步的限定表达。//typedef __list_const_iterator<T> const_iterator;//非const和const迭代器合并为一个类模板,所以不需要这个//const_iterator begin() const { return const_iterator(_head->_next); }const_iterator begin() const { return _head->_next; }//支持隐式类型转换const_iterator end() const { return _head; }//支持隐式类型转换iterator begin() {//在return iterator(_head->_next); 这一行,_head->_next是list_node<T>*类型的指针,代表链表中第一个有效节点。//iterator(_head->_next) 会通过 __list_iterator 构造函数将这个指针传递给 _node 成员。//然后返回一个 iterator 对象,该对象持有一个指向节点的指针。//return iterator(_head->_next);return _head->_next;}iterator end() { return _head; }void empty_init() {_head = new Node;_head->_next = _head;_head->_prev = _head;_size = 0;}list() { empty_init(); } //构造函数本质是创建一个哨兵位节点//lt2(lt1)list(const list<T>& lt) { //lt是const对象,需要const迭代器empty_init();//创建(lt2的)哨兵位for (auto e : lt)//遍历lt1每个节点赋值给epush_back(e);//lt2尾插每个e节点}传统写法 lt3 = lt1 思路:释放lt3,将lt1的节点尾插到lt3//list<T>& operator=(const list<T>& lt) {// if (this != <) {// clear();// for (auto e : lt)// push_back(e);// }// return *this;//}//现代写法void swap(list<T>& lt) {std::swap(_head, lt._head);std::swap(_size, lt._size);}list<T>& operator=(list<T> lt) {swap(lt);return *this;}~list() {clear();delete _head;//清除哨兵位_head = nullptr;}void clear() {iterator it = begin();//begin指向哨兵位的下一节点,即有效数据的头节点while (it != end())it = erase(it);}//void push_back(const T& x) {// Node* newnode = new Node(x);// Node* tail = _head->_prev;// tail->_next = newnode;// newnode->_prev = tail;// newnode->_next = _head;// _head->_prev = newnode;//}//优化版本 - 复用insertvoid push_back(const T& x) { insert(end(), x); }//end指向哨兵位,在哨兵位之前插入void push_front(const T& x) { insert(begin(), x); }void pop_back() { erase(--end()); }//哨兵位前一节点就是尾结点void pop_front() { erase(begin()); }//list中insert之后迭代器理论上不会失效。因为不涉及扩容,且迭代器依旧指向原先位置。//但库里面有返回值,所以和库里保持一致。//pos是迭代器对象,现在需要使用的是节点指针,所以需要通过迭代器对象来访它自己的成员变量来获取节点指针。iterator insert(iterator pos, const T& x) { Node* cur = pos._node;Node* newnode = new Node(x);Node* prev = cur->_prev;//prev newnode curprev->_next = newnode;newnode->_prev = prev;newnode->_next = cur;cur->_prev = newnode;++_size;return iterator(newnode);}iterator erase(iterator pos) { //erase后迭代器失效,因为迭代器指向的节点被释放了,所以返回下一个节点的位置Node* cur = pos._node;Node* prev = cur->_prev;Node* next = cur->_next;delete cur;prev->_next = next;next->_prev = prev;--_size;return iterator(next);}size_t size() { return _size; }private:Node* _head;size_t _size;//增加该成员避免每次获取size都要遍历一遍链表};void test_list1() {list<int> lt;lt.push_back(1);lt.push_back(2);lt.push_back(3);lt.push_back(4);lt.push_back(5);//封装屏蔽底层差异和实现细节//提供统一的访问修改方式list<int>::iterator it = lt.begin();while (it != lt.end()) {cout << *it << " ";++it;}cout << endl;for (auto e : lt)cout << e << " ";cout << endl;}void test_list2() {list<int> lt;lt.push_back(1);lt.push_back(2);lt.push_back(3);lt.push_back(4);lt.push_back(5);for (auto e : lt)cout << e << " ";cout << endl;list<int> lt1(lt);for (auto e : lt1)cout << e << " ";cout << endl;list<int> lt2;lt2.push_back(10);lt2.push_back(20);lt2.push_back(30);lt2.push_back(40);lt2.push_back(50);lt1 = lt2;for (auto e : lt1)cout << e << " ";cout << endl;for (auto e : lt2)cout << e << " ";cout << endl;}struct AA {AA(int a1 = 0, int a2 = 0):_a1(a1), _a2(a2) {}int _a1;int _a2;};void test_list3() {list<AA> lt;lt.push_back(AA(1, 1));lt.push_back(AA(2, 2));lt.push_back(AA(3, 3));list<AA>::iterator it = lt.begin();while (it != lt.end()) {//cout << *it << " ";//报错。//该*it(AA)是自定义对象,不支持流插入//第一种方式让AA支持流插入,但这种方式怎么分隔两个值有歧义,并且提供这种方式的流插入就不能变了(写死了)//cout << (*it)._a1 << " " << (*it)._a2 << endl;//_a1和_a2是公有的,可以直接访问cout << it->_a1 << " " << it->_a2 << endl;//迭代器模拟的是指针的行为,所以自定义类型指针解引用就是->cout << it.operator->()->_a1 << " " << it.operator->()->_a2 << endl;//根据上面显示调用可知,本应该是it->->_a1。第一个箭头是运算符重载,返回一个指针,第二个箭头才是解引用。//但是这种写法可读性不好,所以编译器特殊处理,省略了一个箭头++it;}cout << endl;}该版本只能打印list<int>//void print_list(const list<int>& lt) {// list<int>::const_iterator it = lt.begin();// while (it != lt.end()) {// cout << *it << " ";// ++it;// }// cout << endl;// for (auto e : lt)// cout << e << " ";// cout << endl;//}该版本只能打印listtemplate<class T>//template<typename T>//void print_list(const list<T>& lt) {// //下方list<T>还没有实例化(list<int>才是实例化)// //编译器不确定list<T>::const_iterator是静态成员变量还是内嵌类型// //所以加typename告诉编译器这是一个类型,等类模板(list<T>)实例化再去类里面取// typename list<T>::const_iterator it = lt.begin();// while (it != lt.end()) {// cout << *it << " ";// ++it;// }// cout << endl;//}该版本只能打印list//template<typename T>//void print_container(const list<T>& lt) {// //下方list<T>还没有实例化(list<int>才是实例化)// //编译器不确定list<T>::const_iterator是静态成员变量还是内嵌类型// //所以加typename告诉编译器这是一个类型,等类模板(list<T>)实例化再去类里面取// typename list<T>::const_iterator it = lt.begin();// while (it != lt.end()) {// cout << *it << " ";// ++it;// }// cout << endl;//}template<typename Container> //Container是一个自定义模板参数void print_container(const Container& con) {typename Container::const_iterator it = con.begin();//Container可以是list<T>、vector<T>while (it != con.end()) {cout << *it << " ";++it;}cout << endl;}void test_list4() {list<int> lt;lt.push_back(1);lt.push_back(2);lt.push_back(3);lt.push_back(4);lt.push_back(5);print_container(lt);//vector的push_back涉及深拷贝问题,因为vector需要扩容,//当vector中元素拷贝到新空间时,vector中元素指向的内容也需要拷贝。但list不存在扩容问题。list<string> lt1;lt1.push_back("1111111111111");lt1.push_back("1111111111111");lt1.push_back("1111111111111");lt1.push_back("1111111111111");lt1.push_back("1111111111111");print_container(lt1);vector<string> v;v.push_back("2222222222222");v.push_back("2222222222222");v.push_back("2222222222222");v.push_back("2222222222222");print_container(v);}
}
Test.cpp
#define _CRT_SECURE_NO_WARNIN
#include <iostream>
#include <string>
#include <vector>using namespace std;
#include "List Class.h"int main() {//bit::test_list1();//bit::test_list2();//bit::test_list3();bit::test_list4();return 0;
}
第三章:list与vector的对比
vector与list都是STL中非常重要的序列式容器,由于两个容器的底层结构不同,导致其特性以及应用场景不同,其主要不同如下:
| vector | list | |
| 底 层 结 构 | 动态顺序表,一段连续空间 | 带头结点的双向循环链表 |
| 随 机 访 问 | 支持随机访问,访问某个元素效率 O(1) | 不支持随机访问,访问某个元素效率O(N) |
| 插 入 和 删 除 | 任意位置插入和删除效率低,需要搬移元素,时间复杂度为O(N),插入时有可能需要增容,增容:开辟新空间,拷贝元素,释放旧空间,导致效率更低 | 任意位置插入和删除效率高,不需要搬移元素,时间复杂度为O(1) |
| 空 间 利 用 率 | 底层为连续空间,不容易造成内存碎片,空间利用率高,缓存利用率高 | 底层节点动态开辟,小节点容易造成内存碎片,空间利用率低,缓存利用率低 |
| 迭 代 器 | 原生态指针 | 对原生态指针 ( 节点指针 ) 进行封装 |
| 迭 代 器 失 效 | 在插入元素时,要给所有的迭代器重新赋值,因为插入元素有可能会导致重新扩容,致使原来迭代器失效,删除时,当前迭代器需要重新赋值否则会失效 | 插入元素不会导致迭代器失效,删除元素时,只会导致当前迭代器失效,其他迭代器不受影响 |
| 使 用 场 景 | 需要高效存储,支持随机访问,不关心插入删除效率 | 大量插入和删除操作,不关心随机访问 |
作业
1. 以下程序输出结果为( )
int main() {int ar[] = { 0,1, 2, 3, 4, 5, 6, 7, 8, 9 };int n = sizeof(ar) / sizeof(int);list<int> mylist(ar, ar + n);list<int>::iterator pos = find(mylist.begin(), mylist.end(), 5);reverse(mylist.begin(), pos);reverse(pos, mylist.end());list<int>::const_reverse_iterator crit = mylist.crbegin();while (crit != mylist.crend()) {cout << *crit << " ";++crit;}cout << endl;
}
A.4 3 2 1 0 5 6 7 8 9
B.0 1 2 3 4 9 8 7 6 5
C.5 6 7 8 9 0 1 2 3 4
D.5 6 7 8 9 4 3 2 1 0
答案:C
分析 : list<int>::iterator pos = find(mylist.begin(), mylist.end(), 5); //找到5的位置
reverse(mylist.begin(), pos);//逆置0 1 2 3 4 为 4 3 2 1 0
reverse(pos, mylist.end()); //逆置5 6 7 8 9 为 9 8 7 6 5
逆置完成之后其数据为:4 3 2 1 0 9 8 7 6 5
list<int>::const_reverse_iterator crit = mylist.crbegin(); //反向迭代器进行反向访问
while (crit != mylist.crend()) {}
所以答案为:5 6 7 8 9 0 1 2 3 4
2. 以下代码实现了从表中删除重复项的功能,请选择其中空白行应填入的正确代码( )
template<typename T>
void removeDuplicates(list<T>& aList) {T curValue;list<T>::iterator cur, p;cur = aList.begin();while (cur != aList.end()) {curValue = *cur;//空白行 1while (p != aList.end()) {if (*p == curValue) //空白行 2 else p++; }}
}
A. p=cur+1;aList.erase(p++);
B.p=++cur; p == cur ? cur = p = aList.erase(p) : p = aList.erase(p);
C.p=cur+1;aList.erase(p);
D.p=++cur;aList.erase(p);
答案:B
分析:迭代p需要迭代不重复节点的下一节点,重要的是cur迭代器需要往下迭代,因此cur需要往前移动,二答案A C的cur都不会改变,空白行2是当需要找到重复值时进行节点删除,当删除时当前迭代器会失效,因此需要将迭代器p往后迭代,所以答案为 B
3. 对于list有迭代器it, 当erase(it)后,说法错误的是( )
A.当前迭代器it失效
B.it前面的迭代器仍然有效
C.it后面的迭代器失效
D.it后面的迭代器仍然有效
答案:C
分析:删除节点后,只有指向当前节点的迭代器失效了,其前后的迭代器仍然有效,因为底层为不连续空间,只有被删除的节点才会失效, 所以答案为 C
4. 下面程序的输出结果正确的是( )
int main() {int array[] = { 1, 2, 3, 4, 0, 5, 6, 7, 8, 9 };int n = sizeof(array) / sizeof(int);list<int> mylist(array, array + n);auto it = mylist.begin();while (it != mylist.end()) {if (*it != 0)cout << *it << " ";elseit = mylist.erase(it);++it;}return 0;
}
A.1 2 3 4 5 6 7 8 9
B. 1 2 3 4 6 7 8 9
C.程序运行崩溃
D.1 2 3 4 0 5 6 7 8 9
答案:B
分析:程序在使用迭代器取值时,如果不等于0就进行打印,为0时不打印并删除当前节点,所以答案为 B
5. 下面有关vector和list的区别,描述正确的是( )
A.两者在尾部插入的效率一样高
B.两者在头部插入的效率一样高
C.两者都提供了push_back和push_front方法
D.两者都提供了迭代器,且迭代器都支持随机访问
答案:A
A.vector在尾部插入数据不需要移动数据,list为双向循环链表也很容易找到尾部,因此两者在尾部插入数据效率相同
B.vector头部插入效率极其低,需要移动大量数据
C.vector由于在头部插入数据效率很低,所以没有提供push_front方法
D.list不支持随机访问
6. 下面有关vector和list的区别,描述错误的是( )
A.vector拥有一段连续的内存空间,因此支持随机存取,如果需要高效的随机读取,应该使用vector
B.list拥有一段不连续的内存空间,如果需要大量的插入和删除,应该使用list
C.vector<int>::iterator支持“+”、“+=”、“<”等操作符
D.list<int>::iterator则不支持“+”、“+=”、“<”等操作符运算,但是支持了[]运算符
答案:D
A.如果想大量随机读取数据操作,vector是首选的容器
B.如果想大量的插入和删除数据,list效率较高,是首选
C.由于vector底层是连续空间,其迭代器就是相应类型的指针,所以支持对应的操作
D.list迭代器不支持[]运算符
相关文章:
C++初阶—list类
第一章:list的介绍及使用 1.1 list的介绍 list是可以在常数范围内在任意位置进行插入和删除的序列式容器,并且该容器可以前后双向迭代。list的底层是双向链表结构,双向链表中每个元素存储在互不相关的独立节点中,在节点中通过指…...
【子网掩码计算器:Python + Tkinter 实现】
子网掩码计算器:Python Tkinter 实现 引言代码功能概述代码实现思路1. 界面设计2. 功能实现3. 事件处理 子网掩码计算器实现步骤1. 导入必要的库2. 定义主窗口类 SubnetCalculatorApp3. 创建菜单栏4. 创建界面组件5. 判断 IP 地址类别6. 计算子网信息7. 其他功能函…...
入门基础项目(SpringBoot+Vue)
文章目录 1. css布局相关2. JS3. Vue 脚手架搭建4. ElementUI4.1 引入ElementUI4.2 首页4.2.1 整体框架4.2.2 Aside-logo4.2.3 Aside-菜单4.2.4 Header-左侧4.2.5 Header-右侧4.2.6 iconfont 自定义图标4.2.7 完整代码 4.3 封装前后端交互工具 axios4.3.1 安装 axios4.3.2 /src…...
Nginx+PHP+MYSQL-Ubuntu在线安装
在 Ubuntu 上配置 Nginx、PHP 和 MySQL 的步骤如下: 1. 更新系统包 首先,确保系统包是最新的: sudo apt update sudo apt upgrade2. 安装 Nginx 安装 Nginx: sudo apt install nginx启动并启用 Nginx 服务: sudo…...
车载以太网-基于linux的ICMP协议
对于车载以太网-ICMP的技术要求: /** ICMP报文格式解析* -----------------* ICMP协议用于网络诊断和错误报告,常见应用包括Ping测试。* ICMP报文结构包括:IP头部、ICMP头部和ICMP数据部分。* 下面详细介绍每个部分的结构、字段的作用以及如何解析它们。* * ICMP头部结构:*…...
虚拟机快照与linux的目录结构
虚拟机快照是对虚拟机某一时刻状态的完整捕获,包括内存、磁盘、配置及虚拟硬件状态等,保存为独立文件。 其作用主要有数据备份恢复、方便系统测试实验、用于灾难恢复以及数据对比分析。具有快速创建和恢复、占用空间小、可多个快照并存的特点。在管理维…...
Unity小功能实现:鼠标点击移动物体
1、功能描述 当玩家点击鼠标时,场景中的物体会移动到鼠标点击的位置。这个功能可以用于控制角色移动、放置物体等场景。 2、实现步骤 创建Unity项目:首先,打开Unity并创建一个新的3D项目。 添加3D物体:在场景中创建一个3D物体&am…...
算法题笔记(自用)——Python
目录 一. 进制&位运算&ASCAII 二. format格式化输出 1. 基本用法 2. 位置参数 3. 格式化数字 4. 对齐和填充 5. 格式化二进制、八进制、十六进制 6. 格式化百分比 7. 格式化科学计数法 8. 格式化字符串字面量(f-string) 三. 字符串 使…...
爬虫系列之【数据解析之JSON】《三》
目录 前置知识 一、 json.loads():JSON 转 Python 数据 二、json.dump():python数据 转 json 并写入文件 三、json.loads() :json 转 python数据 四、json.load() :json 转 python数据(在文件操作中更方便…...
了解Java集合的概念和体系:Collection<T>、Collections与Stream的使用
学习目标 本文知识是对集合层级的介绍,应用开发中实际使用的是他们的子级,感兴趣的小伙伴或者想深入了解有关Java集合知识的朋友可以选择阅读! Stream的方法使用使用部分代码块内大多有两种实现方式,是为了更好的理解方法底层的代…...
进程优先级和进程切换 ─── linux第12课
目录 Z(zombie)-僵尸进程 僵尸进程危害 孤儿进程 编辑 进程优先级 查看系统进程 用top命令更改已存在进程的nice: 其他概念 进程切换 进程如何切换 进程的调度 进程 内核数据结构 代码和数据 创建进程时 ,先创建内核数据结构 再加载代码和数据 进程退…...
光速解决phpstudy无法启动MySQL服务
问题描述 在初步安装使用phpstudy时,会出现mysql服务启动失败的情况,具体表现为点击一键启动后,mysql启动又立即停止 原因及解决方法: phpstudy数据库无法启动有以下几个原因,可以看看自己是第几种: 服务名…...
深度学习五大模型:CNN、Transformer、BERT、RNN、GAN详细解析
# 深度学习五虎将:当CNN遇见Transformer的奇幻漂流 ## 序章:AI江湖的兵器谱排行 2012年,多伦多大学的厨房里,Hinton的学生们用GPU煎了个"AlexNet"荷包蛋,从此开启了深度学习的热兵器时代。如今五大模型各显…...
txt 转 json 使用python语言
需求: 把如下的txt文档转成json输出 代码 import jsondef txt_to_json(input_file, output_file):data_list []with open(input_file, r, encodingutf-8) as f:for line in f:# 分割数据并去除换行符parts line.strip().split(,)print(f"{parts}")print(type(par…...
FPGA开发,使用Deepseek V3还是R1(4):Deepseek参数配置
以下都是Deepseek生成的答案 FPGA开发,使用Deepseek V3还是R1(1):应用场景 FPGA开发,使用Deepseek V3还是R1(2):V3和R1的区别 FPGA开发,使用Deepseek V3还是R1&…...
Cargo, the Rust package manager, is not installed or is not on PATH.
今天在Windows操作系统上通过pip 安装jupyter的时候遇到这个报错,Cargo, the Rust package manager, is not installed or is not on PATH.。 解决办法 官网:https://rustup.rs/# 下载:https://win.rustup.rs/x86_64 安装完成之后,…...
Go开发框架Sponge+AI助手协同配合重塑企业级开发范式
在互联网高速发展的今天,企业级应用系统面临着日益复杂的业务逻辑和不断增长的开发需求。如何在保证高质量、高效率的前提下快速交付项目,成为了开发者亟需解决的问题。本文将详细介绍如何利用开源的 go 开发框架 Sponge 与 AI 助手协同配合全过程&#…...
从递归到动态规划(三维)
问题描述 假设有一个三维空间的网格,其大小为 m x n x p。我们从坐标 (0, 0, 0) 出发,要到达坐标 (m - 1, n - 1, p - 1)。每次只能在三个方向上移动:向前(x 坐标加 1)、向右(y 坐标加 1)或向上…...
JavaWeb个人笔记
技术栈 前端 : HTML CSS JavaScript ES6 Nodejs npm vite vue3 router pinia axios element-plus 后端:HTTP xml Tomcat Servlet Request Response Cookie Sesssion Filter Listener MySQL JDBC Druid Jackson lombok jwt . HTML <!DOCTYPE html> 文档声…...
【vue-echarts】——01.认识echarts
文章目录 前言一、echarts二、使用步骤1.vue cli创建项目并安装第三方模块echarts2.显示图表总结前言 定制的数据可视化图表。ECharts最初由百度团队开源,并于2018年初捐赠给Apache基金会,成为ASF孵化级项目。2021年1月26日晚,Apache基金会官方宣布ECharts项目正式毕业。 一…...
http报文的content-type参数和spring mvc传参问题
很早之前博主聊过HTTP的报文结构以及其中和传参相关的重要参数content-type还有spring mvc,以前的三篇文章: HTTP与HTTPS协议详解:基础与安全机制-CSDN博客 详解Http的Content-Type_content-type application-CSDN博客 如何在Spring Boot中…...
005 公网访问 docker rocketmq
文章目录 创建自定义网络创建NameServer容器创建Broker容器正式开始启动 Nameserver 容器启动 Broker 容器并关联 Nameserverdocker exec -it rmqbroker vi /etc/rocketmq/broker.conf检查 namesrv 解析检查 Broker 注册状态Nameserver 日志Broker 日志检查容器日志手动指定 Br…...
14. LangChain项目实战1——基于公司制度RAG回答机器人
教学视频: 12. 基于Gradio搭建基于公司制度RAG_哔哩哔哩_bilibilihttps://www.bilibili.com/video/BV11VXRYTErZ/ 环境配置: python版本:3.10.8 服务器:Ubuntu 依赖包requirements.txt文件内容: aiofiles23.2.1 …...
FPGA开发,使用Deepseek V3还是R1(6):以滤波器为例
以下都是Deepseek生成的答案 FPGA开发,使用Deepseek V3还是R1(1):应用场景 FPGA开发,使用Deepseek V3还是R1(2):V3和R1的区别 FPGA开发,使用Deepseek V3还是R1&#x…...
JVM常用概念之垃圾回收设计与停顿
在我们应用程序运行期间,我们是需要尽可能避免垃圾回收。 图1:不同垃圾回收器的设计(黄色代表STW,绿色代表并发) 实验 计算机配置 Hardware Overview:Model Name: MacBook ProModel Identifier: MacBookPro14,2Pro…...
uniapp-原生android插件开发摘要
uni-app在App侧的原生扩展插件,支持使用java、object-c等原生语言编写,从HBuilderX 3.6起,新增支持了使用uts来开发原生插件。 基础项目 UniPlugin-Hello-AS工程请在App离线SDK中查找 基础项目(App离线SDK)已经配置好了自定义插件所需要的…...
Python之参数星号(*)使用笔记
背景 在学习python时发现方法调用和方法定义会经常发现有带星号的标记,为了弄明白是怎么使用的。特此做个笔记。 一、参数符号对比速查表 符号类使用场景作用描述示例无符号函数定义/调用普通位置参数或关键字参数.def func(a, b)*函数定义收集多余位置参数为元组…...
2025年AI网络安全攻防战:挑战深度解析与全链路防御体系构建指南
2025年AI网络安全攻防战:挑战深度解析与全链路防御体系构建指南 引言:AI技术是一把双刃剑 随着ChatGPT、Sora等生成式AI技术的爆发式应用,2025年被称为“AI应用元年”。然而,AI在赋能网络安全防御的同时,也为攻击者提供了新型武器。根据瑞星《2024年中国网络安全报告》,…...
派可数据BI接入DeepSeek,开启智能数据分析新纪元
派可数据BI产品完成接入DeepSeek,此次接入标志着派可数据BI在智能数据分析领域迈出了重要一步,将为用户带来更智能、更高效、更便捷的数据分析体验。 派可数据BI作为国内领先的商业智能解决方案提供商,一直致力于为用户提供高效、稳定易扩展…...
M4 Mac mini运行DeepSeek-R1模型
前言 最近DeepSeek大模型很火,实际工作中也有使用,很多人觉得需要很好的显卡才能跑起来,至少显存需要很高,但实际上一般的核显机器也能跑起来,只不过内存要求要大,对于个人而言,实际上Mac M芯片…...
