STL之list容器代码详解

本文详细介绍了C++STL库中的双向链表list,包括其基本概念、优点与缺点,以及在实际编程中的应用场景,如高效插入删除、避免动态数组重新分配和任务调度。

1 基础概念

在这里插入图片描述

  • 功能: 将数据进行链式存储

链表(list)是一种物理存储单元上非连续的存储结构,数据元素的逻辑顺序是通过链表中的指针链接实现的

链表的组成:链表由一系列结点组成。

结点的组成:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域

在这里插入图片描述

STL中的链表是一个双向循环链表
在这里插入图片描述

由于链表的存储方式并不是连续的内存空间,因此链表list中的迭代器只支持前移和后移,属于双向迭代器

list的优点:

采用动态存储分配,不会造成内存浪费和溢出,用多少开多少空间,不像vector那样预留很多空间;

链表执行插入和删除操作十分方便,修改指针即可,不需要移动大量元素。

list的缺点:

链表灵活,但是空间(指针域) 和 时间(遍历)额外耗费较大;

List有一个重要的性质,插入操作和删除操作都不会造成原有list迭代器的失效,这在vector是不成立的。

2 代码解释

Talk is cheap, show me the code.

#include<iostream>
using namespace std;
#include<list>

void printList(const list<int>& ll)
{
	for (list<int>::const_iterator it = ll.begin(); it != ll.end(); it++)
	{
		cout << *it << " ";
	}
	cout << endl;
}

/*
构造函数原型:
list<T> lst; //list采用采用模板类实现,对象的默认构造形式:
list(beg,end); //构造函数将[beg, end)区间中的元素拷贝给本身。
list(n,elem); //构造函数将n个elem拷贝给本身。
list(const list &lst); //拷贝构造函数。
*/

void test01()
{
	list<int> l1;
	l1.push_back(10);
	l1.push_back(20);
	l1.push_back(30);
	l1.push_back(40);

	printList(l1);

	list<int> l2(l1.begin(), l1.end());
	printList(l2);

	list<int> l3(10, 88);
	printList(l3);

	list<int> l4(l3);
	printList(l4);
}

/*
赋值和交换函数原型:
assign(beg, end); //将[beg, end)区间中的数据拷贝赋值给本身。
assign(n, elem); //将n个elem拷贝赋值给本身。
list& operator=(const list &lst); //重载等号操作符
swap(lst); //将lst与本身的元素互换。
*/

void test02()
{
	list<int> l1;
	l1.push_back(10);
	l1.push_back(20);
	l1.push_back(30);
	l1.push_back(40);

	printList(l1);

	list<int> l2;
	l2.assign(l1.begin(), l1.end());
	printList(l2);

	list<int> l3;
	l3.assign(10, 88);
	printList(l3);

	list<int> l4;
	l4 = l3;
	printList(l4);

	//swap 需不需要元素个数相等
	l4.swap(l1);
	printList(l1);
	printList(l4);
}

/*
大小操作函数原型:
size(); //返回容器中元素的个数
empty(); //判断容器是否为空
resize(num); //重新指定容器的长度为num,若容器变长,则以默认值填充新位置。
//如果容器变短,则末尾超出容器长度的元素被删除。
resize(num, elem); //重新指定容器的长度为num,若容器变长,则以elem值填充新位置。
//如果容器变短,则末尾超出容器长度的元素被删除。
*/

void test03()
{
	list<int> l1;
	l1.push_back(10);
	l1.push_back(20);
	l1.push_back(30);
	l1.push_back(40);

	printList(l1);

	if (l1.empty())
	{
		cout << "Empty" << endl;
	}
	else
	{
		cout << "the size of l1: " << l1.size() << endl;
	}

	l1.resize(10, 1000);
	printList(l1);

	l1.resize(4);
	printList(l1);
}

/*
插入和删除函数原型:
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值匹配的元素。
*/

void test04()
{
	list<int> l1;
	l1.push_back(100); //100
	l1.push_front(200); //200 100
	l1.push_front(300); //300 200 100
	l1.push_front(400); //400 300 200 100

	l1.pop_back(); //400 300 200
	l1.pop_front(); //300 200
	printList(l1);

	list<int>::iterator it = l1.begin();
	cout << "*it=" << *it << endl;
	l1.insert(++it, 1000); //300 1000 200
	printList(l1);
	cout << "*it=" << *it << endl; //特别注意这个时候迭代器的位置!!!

	l1.insert(++it, 3, 33); //300 1000 200 33 33 33
	printList(l1);

	list<int> l2(l1);
	l2.insert(l2.begin(), l1.begin(), l1.end());
	printList(l2); //300 1000 200 33 33 33  300 1000 200 33 33 33

	it = l1.begin();
	cout << "*it=" << *it << endl;
	printList(l1);
	//l1.erase(it, ++it); //注意并没有这个操作!!!!!正确的应该只有起始迭代器到结束迭代器的操作
	printList(l1);

	l1.erase(++it);
	printList(l1); //33 33 200

}

/*
数据存取函数原型:
//移除 L.push_back(10000); L.push_back(10000); L.push_back(10000); printList(L); L.remove(10000); printList(L); //清空 L.clear(); printList(L); }int main() { test01(); system("pause"); return 0; } 43444546474849505152535455565758596061626364
front(); //返回第一个元素。
back(); //返回最后一个元素。
*/

void test05()
{
	list<int> l1;
	l1.push_back(100); //100
	l1.push_front(200); //200 100
	l1.push_front(300); //300 200 100
	l1.push_front(400); //400 300 200 100

	cout << l1.front() << endl;
	cout << l1.back() << endl;
}

/*
反转和排序函数原型:
reverse(); //反转链表
sort(); //链表排序
*/
bool myCompare(int a, int b)
{
	return a > b;
}

void test06()
{
	list<int> l1;
	l1.push_back(123210); //100
	l1.push_front(2422410); //200 100
	l1.push_front(343430); //300 200 100
	l1.push_front(403242130); //400 300 200 100

	printList(l1);
	l1.reverse();
	printList(l1);
	l1.sort();
	printList(l1);

	l1.sort(myCompare);
	printList(l1);

}

/*
排序案例
案例描述:将Person自定义数据类型进行排序,Person中属性有姓名、年龄、身高
排序规则:按照年龄进行升序,如果年龄相同按照身高进行降序
*/
class Person
{
public:
	Person(string name, int age, int height)
	{
		this->name = name;
		this->age = age;
		this->height = height;
	}

	string name;
	int age;
	int height;
};

bool comparePerson(const Person&p1,const Person&p2)
{
	if (p1.age == p2.age)
	{
		return p1.height > p2.height;
	}
	else
	{
		return p1.age < p2.age;
	}
}

void printPersonInfo(const list<Person>& ll)
{
	for (list<Person>::const_iterator it = ll.begin(); it != ll.end(); it++)
	{
		cout << (*it).name << " " << (*it).age << " " << (*it).height << endl;
	}
}

void test07()
{
	Person p1("zhao",99,170);
	Person p2("qian", 89, 190);
	Person p3("sun", 79, 200);
	Person p4("li", 89, 180);
	Person p5("zhou", 109, 210);

	list<Person> p;
	p.push_back(p1);
	p.push_back(p2);
	p.push_back(p3);
	p.push_back(p4);
	p.push_back(p5);

	printPersonInfo(p);
	p.sort(comparePerson);
	printPersonInfo(p);

}

int main()
{
	test07();
	system("pause");
	return 0;
}

3 应用场景

C++的STL(Standard Template Library)中的list容器是一个双向链表,它提供了在两端进行高效插入和删除操作的能力。在实际项目中,list容器常常用于以下一些应用场景:

  1. 高效的插入和删除操作: 由于list是一个双向链表,插入和删除元素的时间复杂度是O(1),这使得它在需要频繁插入或删除元素的场景中非常高效。例如,某些算法需要在中间插入或删除元素时,list相比于数组或向量更具优势。

    #include <list>
    #include <iostream>
    
    int main() {
        std::list<int> myList;
    
        myList.push_back(1);
        myList.push_back(2);
        myList.push_back(3);
    
        // 在第二个位置插入元素
        auto it = std::next(myList.begin());
        myList.insert(it, 4);
    
        // 删除第一个元素
        myList.pop_front();
    
        // 输出:2 4 3
        for (const auto &element : myList) {
            std::cout << element << " ";
        }
    
        return 0;
    }
    
    
  2. 不需要随机访问的情况: list不支持通过索引直接访问元素,但在某些情况下,我们并不需要随机访问,而是更关注在序列中进行插入和删除操作的性能。

    #include <list>
    #include <algorithm>
    #include <iostream>
    
    int main() {
        std::list<std::string> wordList = {"apple", "banana", "orange", "grape"};
    
        // 删除所有长度小于 6 的单词
        wordList.remove_if([](const std::string &word) {
            return word.length() < 6;
        });
    
        // 输出:banana orange
        for (const auto &word : wordList) {
            std::cout << word << " ";
        }
    
        return 0;
    }
    
    
  3. 避免动态数组重新分配的开销:vector不同,list的元素在内存中不是连续存储的,因此在插入和删除操作时,不需要进行动态数组的重新分配,从而避免了重新分配的开销。

    #include <list>
    #include <iostream>
    
    int main() {
        std::list<int> myList;
    
        for (int i = 0; i < 10000; ++i) {
            // 在列表末尾插入元素,不会导致重新分配
            myList.push_back(i);
        }
    
        // 执行大量的插入和删除操作而不触发数组重新分配
        // ...
    
        return 0;
    }
    
    

这些场景中,list容器的特性使得它成为一个合适的选择。然而,需要注意的是,由于其非连续存储的特性,list在访问元素时性能较差,因此在需要频繁随机访问元素的情况下,可能会考虑其他容器,如vector

4 代码示例

假设你正在开发一个任务调度器,需要存储和管理一系列待执行的任务,并且需要支持高效的插入和删除操作。在这种情况下,使用std::list容器可以提供一些优势。以下是一个简单的示例,演示了如何使用list来管理任务列表:

#include <iostream>
#include <list>
#include <string>

// 任务结构体
struct Task {
    std::string name;
    int priority;

    Task(const std::string& taskName, int taskPriority)
        : name(taskName), priority(taskPriority) {}
};

// 任务调度器类
class TaskScheduler {
private:
    std::list<Task> taskList;

public:
    // 添加任务
    void addTask(const Task& task) {
        // 在任务列表末尾添加任务
        taskList.push_back(task);
    }

    // 删除指定优先级的所有任务
    void removeTasksByPriority(int priority) {
        // 使用 erase-remove 惯用法删除指定优先级的任务
        taskList.remove_if([priority](const Task& task) {
            return task.priority == priority;
        });
    }

    // 打印所有任务
    void printTasks() const {
        std::cout << "Tasks in the scheduler:\\n";
        for (const auto& task : taskList) {
            std::cout << "Name: " << task.name << ", Priority: " << task.priority << "\\n";
        }
    }
};

int main() {
    TaskScheduler scheduler;

    // 添加一些任务
    scheduler.addTask(Task("TaskA", 2));
    scheduler.addTask(Task("TaskB", 1));
    scheduler.addTask(Task("TaskC", 3));
    scheduler.addTask(Task("TaskD", 2));

    // 打印所有任务
    scheduler.printTasks();

    // 删除优先级为2的任务
    scheduler.removeTasksByPriority(2);

    // 打印删除后的任务列表
    scheduler.printTasks();

    return 0;
}

在这个示例中,std::list容器使得在任务调度器中添加和删除任务变得简单高效。使用remove_if算法可以方便地删除符合特定条件的任务,而不需要手动管理元素的移动。这样,list容器提供了在实际项目中处理类似场景的便利性和性能优势。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值