在本篇当中我们将学习STL中的list,在此list就是我们之前在数据结构学习过的链表,在本篇中我们要来了解list当中的成员函数该如何使用,由于list各个函数的接口和之前学习过的vector类型,因此在学习list的使用就较为轻松。在lis篇章中我们要重点了解的是在下一个篇章当中的list模拟实现中的迭代器实现,由于list底层的物理空间不一定是连续的,因此list迭代器的实现相比之前学习过的容器就复杂多了,在下一篇中将带来细致的讲解。在此之前我们先来了解list该如何使用吧!!
1.构造函数
通过以上的文档所示就可以看出list类提供的构造函数接口和之前我们学习过的vector非常的相识,都实现了迭代器区间构造;n个指定元素的构造;使用initializer_list实现构造;拷贝构造
例如以下实现:
#include<iostream> #include<list> #include<string> using namespace std; int main() { string a("abcdef"); list<int> lt1; list<int> lt2({ 1,2,3,4,5 }); list<int> lt3(a.begin(), a.end()); list<int> lt4(10, 9); list<int> lt5(lt2); return 0; }
2.析构函数
在list类当中也实现了析构函数,在此该函数不需要我们在使用list时显示调用,这是由于析构函数编译器会在list对象销毁时自动调用
3.赋值运算符重载
list::operator= - C++ Reference
在list实现了赋值运算符重载的函数,这就使得将一个已经初始化的list对象赋值给另一个已经初始化的list对象
例如以下示例:
#include<iostream> #include<list> using namespace std; int main() { list<int> lt1; list<int> lt2({ 1,2,3,4,5 }); lt1 = lt2; return 0; }
4. 容量操作
在list当中由于和之前学习的string、vector不同在存储数据时,每存储一个指定的元素就申请相应的对应合适的内存空间,因此在list就不会提前将空间开好,这就使得list当中容量操作相关的函数只有以下所示:
4.1 size
在list提供了用于得到list对象内有效元素个数的成员函数size,到了这里你就可以了解到为什么之前在之前string当中得到有效元素建议使用size而不是length,这就使因为size在其他STL当中也是有提供的,而length只是string特有的,都使用size就利于记忆
4.2 empty
在list也提供用于判断list对象内有效元素是否为空的函数empty,当为空时就返回true,否则就为flase
5. 迭代器
在list类当中由于底层存储数据的内存空间不像string和vector一样物理结构是连续的,因此在list就没有提供下标+[ ]的方式实现对list对象内元素的访问,因此在list就只能使用迭代器来实现
在此list内的迭代器和之前学习过的容器一样也提供了普通迭代器和反向迭代器,并且在这些当中都实现了const和非const版本
5.1 begin与end
在此list内提供的begin函数还是指向对象的第一个元素,返回值为迭代器
在此list内提供的end函数还是指向对象的最后一个元素后一个位置,返回值为迭代器
有了begin()和end()就可以实现对list对象遍历,例如以下示例:
#include<iostream> #include<list> using namespace std; int main() { list<int> lt2({ 1,2,3,4,5 }); lt1 = lt2; list<int>::iterator t1 = lt1.begin(); while (t1 != lt1.end()) { (*t1)++; t1++; } t1 = lt1.begin(); while (t1 != lt1.end()) { cout << *t1 << " "; t1++; } cout << endl; return 0; }
6.范围for
在此在list由于实现了迭代器那么就可以使用范围for来对list对象遍历其的元素
例如以下示例:
#include<iostream> #include<list> using namespace std; int main() { list<int> lt1; lt1 = lt2; for (auto x : lt1) { cout << x << " "; } cout << endl; return 0; }
7.取头尾元素
在list提供了front函数和back函数来分别实现得到list对象内的首元素与尾元素
例如以下示例:
#include<iostream> #include<list> using namespace std; int main() { list<int> lt2({ 1,2,3,4,5 }); cout << "lt2front:" << lt2.front()<<endl; cout << "lt2back:" << lt2.back() << endl; return 0; }
8.元素修改操作
在list当中提供了以上所示的各种用于修改list对象内元素的函数
8.1 push_front与pop_front
list::push_front - C++ Reference
list::pop_front - C++ Reference
在list内实现了push_front和pop_front来放分别实现在对象内头插和头删元素
在此要了解到为什么在list会提供该函数而之前学习的string与vector都未实现该函数,这是由于在在链表当中头插和头删元素不需要像顺序表一样在头插和头删时将大量的元素移动,这样就不会出现效率低下的问题
8.2 push_back与pop_back
list::push_back - C++ Reference
list::pop_back - C++ Reference
在list内实现了push_back和pop_back来放分别实现在对象内尾插和尾删元素
8.3 insert和erase
在list也提供了inert和erase来实现任意位置的插入和删除,在此实现的接口和vector相识参数都是迭代器或者迭代器区间
8.4 emplace_front与emplace_back
list::emplace_front - C++ Reference
list::emplace_back - C++ Reference
在C++11之后实现了以上的两个函数emplace_front和emplace_back,那么这两个函数有什么作用呢?
在此你可以认为emplace_front和emplace_back与push_front和push_back的功能是类似的,功能分别是在list对象头插和尾插指定的数据,但其实这两个函数与push_front和push_back还是有差异的,接下来就来大体的讲解
注:在此emplace_front和emplace_back函数的参数涉及到可变模板参数、右值引用等概念在此不进行讲解,这些要到之后C++11篇章才进行
通过之前的学习我们知道push_front和push_back是可以实现在相应的类对象内插入内置类型的数据,也可以插入自定义类型的数据;并且在此支持隐式类型转换
例如以下示例:
#include<iostream>
#include<list>
using namespace std;
class Postion
{
public:
Postion(int row,int col)
:_row(row)
,_col(col)
{
}
private:
int _row;
int _col;
};
int main()
{
list<Postion> lt1;
Postion p1(1,2);
lt1.push_back(p1);
//使用匿名对象
lt1.push_back(Postion(1, 2));
//使用多参数隐式类型转换
lt1.push_back({ 1, 2 });
return 0;
}
那么以上使用emplace_back不同的参数也可以使用吗
#include<iostream>
#include<list>
using namespace std;
class Postion
{
public:
Postion(int row,int col)
:_row(row)
,_col(col)
{
}
private:
int _row;
int _col;
};
int main()
{
list<Postion> lt1;
Postion p1(1, 2);
lt1.emplace_back(p1);
lt1.emplace_back(Postion(1, 2));
lt1emplace_back({ 1, 2 });
return 0;
}
以上代码在VS下就会出现以下的编译报错,这是为什么呢?
要解决以上的问题就先了解到emplace_back的形参的类型是根据实参的类型来推导的,那么在以上使用隐式类型转换形参就无法根据实参{ 1, 2 }来推导具体的类型,这里具体的原因就是形参是模板
在此emplace_back和emplace_front最大的特点是支持直接将构造对象的参数直接传emplace_back和emplace_front函数的参数
因此以上代码正确的使用方法是如下所示:
#include<iostream> #include<list> using namespace std; class Postion { public: Postion(int row,int col) :_row(row) ,_col(col) { } private: int _row; int _col; }; int main() { list<Postion> lt1; Postion p1(1,2); lt1.push_back(p1); lt1.push_back(Postion(1, 2)); lt1.push_back({ 1, 2 }); list<Postion> lt1; Postion p1(1, 2); lt1.emplace_back(p1); lt1.emplace_back(Postion(1, 2)); //lt.1emplace_back({ 1, 2 }); lt1.emplace_back( 1, 2 ); return 0; }
注:在此由于emplace_back是直接构造去初始化节点,而push_back需要通过构造外加拷贝实现的,因此emplace_back相比push_back效率更高
9. list内特有的操作
在list中提供了以上特有的函数来实现逆置、排序等的操作,这些操作是之前我们在string和vector当中没有提供的,那么接下来我们就来了解这些函数的作用以及使用方法
9.1 splice
在此splice的作用是将一个list对象的值转移给另一个list的对象,并且转移之后被转移对象会变为空
例如以下示例:
#include<iostream> #include<list> using namespace std; int main() { list<int> mylist1, mylist2; list<int>::iterator it; // set some initial values: for (int i = 1; i <= 4; ++i) mylist1.push_back(i); // mylist1: 1 2 3 4 for (int i = 1; i <= 3; ++i) mylist2.push_back(i * 10); // mylist2: 10 20 30 it = mylist1.begin(); ++it; // points to 2 mylist1.splice(it, mylist2); // mylist1: 1 10 20 30 2 3 4 // mylist2 (empty) // "it" still points to 2 (the 5th element) return 0; }