LIST容器
list容器基本概念
链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成,每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。
相对于vector的连续性空间,list就显得负责许多,它的好处是每次插入或者删除一个元素,就是配置或者释放一个元素的空间,因此,list对于空间的运用有绝对精准,一点也不浪费,而且,对于任何位置的元素插入或者元素的移除,list永远是常数时间。
list和vector是两个最常被使用的容器。
list容器是一个双向链表。
采用动态存储分配,不会造成内存浪费和溢出
链表执行插入和删除操作十分方便,修改指针即可,不需要移动大量元素
链表灵活,但是空间和时间额外耗费较大
list容器的迭代器
list容器不能像vector一样以普通指针作为迭代器,因为其结点不能保证在同一块连续的内存空间上,list迭代器必须有能力指向list结点,并有能力进行正确的递增、递减、取值、成员存取操作。所谓list正确的递增、递减、取值、成员存取是指,递增时指向下一个结点,递减时指向上一个结点,取值时取得是结点的数据值,成员取用时取得是结点的成员。
由于list是一个双向链表,迭代器必须具备前移、后移的能力,所以list容器提供的就是Bidirectional Iterators。
list有一个重要的性质,插入操作和删除操作都不会造成原有的list迭代器失效,这在vector中是不成立的,因为vector的插入操作可能造成记忆体重新配置,导致原有的迭代器全部失效,甚至list元素的删除,也只有被删除的那个元素的迭代器失效,其他迭代器不受影响。
list容器的数据结构
list容器不仅是一个双向链表,而且还是一个循环的双向链表。
//An highlighted block
//list是双向循环链表
void test1()
{
list<int>myList;
for(int i=0;i<10;i++)
{
myList.push_back(i);
}
list<int>::_Nodeptr node = myList._Myhead->_Next;
for(int i=0;i<myList._Mysize*2;i++)
{
cout << "Node:" << node->_Myval << endl;
node = node->_Next;
if(node == myList._Myhead)
{
node = node ->_Next;
}
}
}
list常用API
list构造函数
listlstT; //list采用模板类实现,对象的默认构造形式
list(beg,end); //构造函数将(beg,end)区间中的元素拷贝给本身
list(n,elem); //构造函数将n个elem拷贝给本身
list(const list &lst); //拷贝构造函数
list数据元素插入和删除操作
push_back(elem); //在容器尾加入一个元素
pop_back(); //删除容器中的最后一个元素
push_front(elem); //在容器开头插入一个人元素
pop_front(); //从容器开头移除第一个元素
insert(pos,elem); //在pos位置插入elem元素的拷贝,返回新数据的位置
insert(pos,n,elem); //在pos位置插入n个elem数据,无返回值
insert(pos,beg,end); //在pos位置插入(beg,end)区间的数据,无返回值
clear(); //移除容器的所有数据
erase(beg,end); //删除(beg,end)区间的数据,返回下一个数据的位置
erase(pos); //删除pos位置的数据,返回下一个数据的位置
remove(elem); //删除容器中所有与elem值匹配的元素
unique(elem); //移除连续的重复元素,只留下其中的第一个。 unique() 函数使用 == 运算符比较连续元素。可以在对元素进行排序后,再使用 unique(),这样可以保证移除序列中全部的重复元素。
//An highlighted block
void print(list<int>&L)
{
for(list<int>::iterator it = L.begin();it != L.end();it++)
{
cout << *it << " ";
}
cout << endl;
}
void test2()
{
list<int>L(10,10);
list<int>L2(L.begin(),L.end());
print(L);
print(L2);
L2.push_back(100);
//逆序打印
for(list<int>::reverse_iterator it = L2.rbegin();it !=L2.rend();it++)
{
cout << *it << " ";
}
cout << endl;
//list迭代器不支持随机访问
list<int>::iterator itBegin = L2.begin();
//itBegin = itBegin +1; 报错
//插入数据
list<int>L3;
L3.push_back(10);
L3.push_back(30);
L3.push_back(40);
L3.push_front(55);
L3.push_back(20);
L3.push_front(100);
print(L3);
//删除两端数据
L3.pop_front();
L3.pop_back();
print(L3);
//插入数据
L3.insert(L3.begin(),1000);
print(L3);
//删除数据
L3.push_back(10);
L3.push_back(10);
L3.remove(10); //删除所有的10
print(L3);
}
list大小操作
size(); //返回容器中元素的个数
empty(); //判断容器是否为空
resize(num); //重新指定容器的长度为num,若容器变长,则以默认值填充新位置,如果容器变短,则末尾超出容器长度的元素被删除
resize(num,elem); //重新指定容器的长度为num,若容器变长,则以elem填充新位置,如果容器变短,则末尾超出容器长度的元素被删除
list赋值操作
assign(beg,end); //将(beg,end)区间的数据拷贝赋值给本身
assign(n,elem); //将n个elem拷贝赋值给本身
list &operator = (const list &lst); //重载等号操作符
swap(lst); //与lst本身的元素互换
list数据存取
front(); //返回第一个元素
back(); //返回最后一个元素
//An highlighted block
void test3()
{
list<int>L3;
L3.push_back(10);
L3.push_back(30);
L3.push_back(40);
L3.push_front(55);
L3.push_back(20);
L3.push_front(100);
cout << "大小:" << L3.size() << endl;
if(L3.empty())
{
cout << "L3为空" << endl;
}
else
{
cout << "L3不为空" << endl;
}
L3.resize(10);
print(L3);
L3.resize(3);
print(L3);
list<int>L4;
L4.assign(L3.begin(),L3.end());
cout << "front:" << L4.front() << endl;
cout << "back:" << L4.back() << endl;
}
list反转排序
reverse(); //反转链表,比如lst包含1,3,5元素,运行此方法后,lst就包含5,3,1元素
sort(); //list排序 默认从小到大
sort() 函数模板定义在头文件 algorithm 中,要求使用随机访问迭代器。但 list 容器并不提供随机访问迭代器,只提供双向迭代器,因此不能对 list 中的元素使用 sort() 算法。但是,还是可以进行元素排序,因为 list 模板定义了自己的 sort() 函数。sort() 有两个版本:无参 sort() 函数将所有元素升序排列。第二个版本的 sort() 接受一个函数对象或 lambda 表达式作为参数,这两种参数都定义一个断言用来比较两个元素。
//An highlighted block
void test4()
{
list<int>L;
L.push_back(10);
L.push_back(20);
L.push_back(40);
L.push_back(30);
L.reverse();
print(L);
//所有不支持随机访问的迭代器,不可以用系统提供的算法
//如果不支持用系统算法,那么这个类内会提供
//sort(L.begin(),L.end());
L.sort();
print(L); //从小到大
}
//An highlighted block
//自定义数据类型
class Person
{
public:
Person(string name,int age,int height)
{
this ->m_Name = name;
this ->m_Age = age;
this ->m_Height = height;
}
//重载==让remove可以删除自定义的person类型
bool operator == (const Person& p)
{
if( this ->m_Age == p.m_Age && this ->m_Height == p.m_Height && this ->m_Name == p.m_Name )
return true;
return false;
}
string m_Name;
int m_Age;
int m_Height;
};
//Person排序规则 如果年龄相同 则按照身高升序排序
bool myComparePerson(Person &p1,Person&p2)
{
//if(p1.m_Age > p2.m_Age)
// {
// return true;
// }
//return false;
if(p1.m_Age == p2.m_Age)
{
return p1.m_Height < p2.m_Height;
}
else
{
return p1.m_Age > p2.m_Age;
}
}
void test5()
{
list<Person>L;
Person p1("德玛",27,190);
Person p2("阿木木",6,100);
Person p3("薇恩",27,170);
Person p4("提莫",11,99);
Person p5("炮手",18,98);
L.push_back(p1);
L.push_back(p2);
L.push_back(p3);
L.push_back(p4);
L.push_back(p5);
//需求 打印数据时按照年龄降序输出
//对于自定数据类型,必须要指定排序规则
//L.sort(myComparePerson);
for(list<Person>::iterator it = L.begin();it != L.end();it++)
{
cout << "姓名:" << it ->m_Name << " 年龄:" << it ->m_Age << "身高:" << it ->m_Height << endl;
}
cout << endl;
//删除p4
L.remove(p4);
for(list<Person>::iterator it = L.begin();it != L.end();it++)
{
cout << "姓名:" << it ->m_Name << " 年龄:" << it ->m_Age << "身高:" << it ->m_Height << endl;
}
cout << endl;
}
list 的成员函数 merge() 以另一个具有相同类型元素的 list 容器作为参数。两个容器中的元素都必须是升序。参数 list 容器中的元素会被合并到当前的 list 容器中。例如:
std::list<int> my_values {2, 4, 6, 14};
std::list<int> your_values{ -2, 1, 7, 10};
my_values.merge (your_values);//my_values contains: -2 1 2 4 6 7 10 14
your_values.empty(); // Returns true
元素从 your_values 转移到 my_values,因此,在执行完这个操作后,your_values 中就没有元素了。改变每个 list 节点的指针,在适当的位置将它们和当前容器的元素链接起来,这样就实现了元素的转移。list 节点在内存中的位置不会改变;只有链接它们的指针变了。在合并的过程中,两个容器中的元素使用 operator()() 进行比较。
在另一个版本的 merge() 函数中,可以提供一个比较函数作为该函数的第二个参数,用来在合并过程中比较元素。例如:
std::list<std::string> my_words { "three","six", "eight"};
std::list<std::string> your_words { "seven", "four", "nine"};
auto comp_str = [](const std::strings s1, const std::strings s2){ return s1[0]<s2[0];};
my_words.sort (comp_str); //"eight" "six" "three"
your_words.sort (comp_str) ; //"four" "nine" "seven"
my_words.merge (your_words, comp_str) ; // "eight" "four" "nine" "six" "seven" "three"
这里的字符串对象比较函数是由 lambda 表达式定义的,这个表达式只比较第一个字符。比较的效果是,在合并的 list 容器中,"six”在”seven”之前。在上面的代码中,也可以无参调用 merge(),这样"seven"会在"six"之前,这是一般的排序。
splice() 的第一个参数是指向目的容器的迭代器。第二个参数是元素的来源。第三个参数是一个指向源list容器中被粘接元素的迭代器,它会被插入到第一个参数所指向位置之前。代码执行完中后,容器中的内容如下:
your_words: "seven", "nine"
my_words : "three", "four", "six", "eight"
当要粘接源 list 容器中的一段元素时,第 3 和第 4 个参数可以定义这段元素的范围。 例如:
your_words.splice(++std::begin(your_words),my_words,++std::begin(my_words), std::end(my_words));
上面的代码会将 my_words 从第二个元素直到末尾的元素,粘接到 your_words 的第二个元素之前。上面两个 list 容器的内容如下:
your_words:"seven", "four", "six", "eight","nine" my_words: "three"
下面的语句可以将 your_words 的全部元素粘接到 my_words 中:
my_words.splice(std::begin(my_words), your_words);
your_words 的所有元素被移到了 my_words 的第一个元素"three”之前。然后,your_words 会变为空。即使 your_words 为空,也仍然可以向它粘接元素:
your_words.splice(std::end(your_words), my_words);
现在,my_words 变为空,your_words 拥有全部元素。第一个参数也可以是 std::begin (your_words),因为当容器为空时,它也会返回一个结束迭代器。