List容器

代码展示

#include<iostream>
#include<string>
#include<list>
using namespace std;
void printlist(const list<int>&l) {
	for (list<int>::const_iterator it = l.begin(); it != l.end(); it++) {
		cout << *it << " ";
	}
	cout << endl;
}
void test01() {
	list<int>l1;
	l1.push_back(10);
	l1.push_back(20);
	l1.push_back(30);
	l1.push_back(40);
	l1.push_back(50);
	printlist(l1);
	list<int>l2(l1.begin(), l1.end());
	printlist(l2);
	list<int>l3(l2);
	printlist(l3);
	list<int>l4(10, 100);
	printlist(l4);
}
void test02() {
	list<int>l1;
	l1.push_back(10);
	l1.push_back(20);
	l1.push_back(30);
	l1.push_back(40);
	list<int>::iterator it = l1.begin();
	it++;
	it--;

}
void test03() {
	list<int>l1;
	l1.push_back(10);
	l1.push_back(50);
	l1.push_back(20);
	l1.push_back(15);
	l1.push_back(70);
	l1.push_back(90);
	printlist(l1);

	l1.reverse();
	printlist(l1);

	l1.sort();
	printlist(l1);

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

}

写者总结 

1.List容器的优点:可以对任意位置进行快速插入或者删除元素

   List容器的缺点:容器遍历速度没有数组快,占用的空间比数组大

   但是这些优点和缺点同时又是数组的缺点和优点,因此我们说链表和数组在性质上是互补的

2.STL中的链表是双向循环链表

    List链表迭代器只支持前移和后移

    List链表的优点是采用动态存储分配,不会造成内存浪费和溢出,并且执行插入和删除操作十分方便,修改指针即可,不用移动大量元素。

     但同时list链表的缺点就是时间和空间耗费大

3.L1[0]不可以用[]的方式访问list中的元素

    L1.at(0)同样也不可以用at访问

    原因是List本质链表,不是用连续性空间存储数据,迭代器也不支持随机访问

     it=it+1就是不行的,因为list不是连续存储,it=it+2,it=it+3也是不行

    但是,it++和it--都是可以的,list链表支持双向访问

在C++的STL(标准模板库)中,list容器是一个双向链表结构,它允许以常数时间复杂度O(1)在容器的任意位置插入和删除元素,这种特性使得list非常适合频繁进行插入和删除操作的场景。然而,list并不支持快速随机访问,因为它的元素不是连续存储的,这意味着我们不能通过下标直接访问某个节点。

list容器的优点

  • 高效的插入和删除:由于list内部实现为双向链表,所以在列表中的任何位置执行插入或删除操作的时间复杂度都是O(1),这与基于数组实现的容器如vector形成对比,在后者中插入或删除中间元素可能需要移动大量的数据来保持连续性。

  • 灵活性:可以轻松地创建循环链接的数据结构,并且支持多种构造方式,包括但不限于从另一个list实例复制、使用区间构造等。

list容器的缺点

  • 不支持随机访问:不像vector那样可以通过索引迅速定位到特定位置,list只能通过迭代器从前向后或者从后向前逐步遍历到达目标位置,因此查询效率较低。

  • 额外的空间开销:每个节点除了保存实际数据外,还需要额外存储指针信息指向前后相邻的节点,这就导致了相比同样数量元素但使用连续内存分配的容器而言,list会占用更多的内存空间。

与数组的比较

  • 存取速度:数组是连续分配的一块内存区域,因此提供了非常快的随机访问性能;而list则不具备这样的优势,因为其元素分散存储,必须逐个访问才能到达指定位置。

  • 动态调整大小:数组一旦初始化后其大小固定不变,若要改变长度,则需重新分配一块新的内存并复制旧内容过去;相反,list能够方便地调整自身尺寸而不影响现有元素的位置。

  • 内存布局:数组所有元素都紧密排列在一起,便于缓存命中率优化;而list则是松散分布,不利于CPU缓存机制发挥最佳效能。

创建list容器的方式

  1. 默认构造函数:创建一个空的list对象,例如list<int> l1;

  2. 加数构造函数:用n个相同值填充新创建的list,如list<int> l4(10, 100);表示创建包含十个100的整数列表。

  3. 区间构造:从给定范围内的元素初始化list,比如list<int> l2(l1.begin(), l1.end());是从已有list的一部分生成一个新的list

  4. 拷贝构造:通过复制另一个list的内容来初始化当前list,即list<int> l3(l2);

  5. 初始化列表:C++11引入了初始化列表语法,可以直接用大括号包围一系列初始值,如list<int> l = {1, 2, 3};

数据存取功能

  • 迭代器list提供的迭代器仅支持前移(++)和后退(--),不允许跳跃式前进,也不支持算术运算符(如+, -)。此外,list还拥有特殊的成员函数如front()back()用于获取第一个和最后一个元素。

  • 反转与排序list具备内置的方法来进行反转(reverse())和排序(sort()),它们会按照一定规则重新排列内部元素顺序。

 

代码的解释 

1. 包含头文件与命名空间声明

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

这里包含了三个必要的头文件:<iostream>用于输入输出流操作,<string>虽然在这个例子中没有直接用到字符串类,但通常是为了完整性而包含的,<list>则是为了使用std::list容器。此外,通过using namespace std;语句,我们避免了每次调用标准库成员时都需要加上std::前缀

2. 定义打印函数

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

此函数接收一个常量引用参数l,表示要打印的list<int>对象。它遍历整个列表,将每个元素输出到控制台,之后换行。注意这里使用了const_iterator迭代器,确保不会修改列表内容

3. 测试函数 test01()

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

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

    list<int> l3(l2);
    printlist(l3);

    list<int> l4(10, 100);
    printlist(l4);
}
  • 创建了一个空的list<int>实例l1,然后向其中添加了五个整数值。
  • 调用了printlist()函数显示l1的内容。
  • 使用区间构造法从l1复制所有元素到新的list<int>实例l2,并再次调用printlist()显示结果。
  • 通过拷贝构造函数创建另一个list<int>实例l3,它是l2的一个副本。
  • 最后,通过指定大小和初始值的方式创建了含有十个相同元素(均为100)的新列表l4,同样调用了printlist()进行输出。

4. 测试函数 test02()

void test02() {
    list<int> l1;
    l1.push_back(10);
    l1.push_back(20);
    l1.push_back(30);
    l1.push_back(40);
    list<int>::iterator it = l1.begin();
    it++;
    it--;
}

这个测试函数主要是用来演示迭代器的基本操作。首先创建了一个包含四个元素的list<int>实例l1。接着获取了指向第一个元素的迭代器it,并通过自增(++)和自减(--)操作展示了如何在双向链表上前后移动迭代器的位置。需要注意的是,由于list是双向链表结构,所以它的迭代器支持前后双向迭代

5. 测试函数 test03()

void test03() {
    list<int> l1;
    l1.push_back(10);
    l1.push_back(50);
    l1.push_back(20);
    l1.push_back(15);
    l1.push_back(70);
    l1.push_back(90);
    printlist(l1);

    l1.reverse();
    printlist(l1);

    l1.sort();
    printlist(l1);
}
  • 构建了一个包含六个不同整数值的list<int>实例l1
  • 使用printlist()函数打印原始顺序下的列表内容。
  • 调用了reverse()成员函数反转列表内元素的顺序,并再次打印。
  • 接着调用了sort()成员函数对列表内的元素进行了排序,并最终打印排序后的结果。

6. 主函数 main()

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

主函数仅调用了test03()以执行上述一系列操作,并且通过system("pause")命令暂停程序,以便用户可以查看输出信息。这种方法并不推荐用于实际项目中,因为它依赖于特定平台的支持

<think>我们讨论的是C++中的list容器(双向链表)如何形成一个循环链表(圈)。STL中的list本身是双向链表,但默认不是循环的。我们可以通过调整链表头尾节点的指针来形成循环结构。 核心思路: 1. 创建一个list实例。 2. 将链表的首尾相连:即第一个节点的前驱指向最后一个节点,最后一个节点的后继指向第一个节点。 但是,STL的list容器并没有直接提供循环链表的实现,所以我们需要自己操作迭代器来连接首尾。然而,需要注意的是,一旦我们手动连接了首尾,就破坏了STL list原有的结构(因为STL list要求end()迭代器指向最后一个元素的下一个位置,即一个哨兵节点),所以这种做法并不推荐。 替代方案: 我们可以自定义一个循环链表类,内部使用STL list,但重新实现迭代器,使其在到达end()时跳转到begin(),从而模拟循环。或者,我们也可以不改变底层链表结构,而在使用时通过迭代器手动实现循环遍历(即当迭代器到达end()时,我们将其重置为begin())。 这里我们提供两种方法: 方法一:在遍历时模拟循环 这种方法不改变list的内部结构,而是在遍历到末尾时手动跳转回开头。 方法二:使用自定义循环迭代器 封装一个循环迭代器类,重载++和--操作符,使其在达到end时回到begin,达到begin时(反向)回到end。 由于用户的问题是如何用list容器将数据形成一个圈,我们更关注如何使用。下面分别给出两种方法的示例。 方法一:遍历时模拟循环 例如,我们可以这样遍历一个循环(假设循环n次): ```cpp #include <iostream> #include <list> int main() { std::list<int> mylist = {1, 2, 3, 4, 5}; int n = 10; // 循环10次 auto it = mylist.begin(); for (int i = 0; i < n; i++) { if (it == mylist.end()) { it = mylist.begin(); } std::cout << *it << " "; ++it; } // 输出:1 2 3 4 5 1 2 3 4 5 return 0; } ``` 方法二:自定义循环迭代器(更优雅的方式) 我们可以创建一个循环迭代器适配器,它包装了list的迭代器,并在到达end时自动回到begin。 下面是一个简单的循环迭代器类的实现: ```cpp #include <list> #include <iterator> template <typename Container> class CircularIterator { public: using iterator_type = typename Container::iterator; using value_type = typename Container::value_type; CircularIterator(Container& container, iterator_type current) : container_(container), current_(current) {} value_type& operator*() { return *current_; } CircularIterator& operator++() { ++current_; if (current_ == container_.end()) { current_ = container_.begin(); } return *this; } CircularIterator operator++(int) { CircularIterator tmp = *this; ++(*this); return tmp; } bool operator==(const CircularIterator& other) const { return current_ == other.current_; } bool operator!=(const CircularIterator& other) const { return !(*this == other); } private: Container& container_; iterator_type current_; }; // 为了方便,可以创建一个函数来生成循环迭代器 template <typename Container> CircularIterator<Container> make_circular_iterator(Container& container, typename Container::iterator it) { return CircularIterator<Container>(container, it); } ``` 使用示例: ```cpp #include <iostream> #include <list> int main() { std::list<int> mylist = {1, 2, 3, 4, 5}; // 创建从第一个元素开始的循环迭代器 auto it = make_circular_iterator(mylist, mylist.begin()); for (int i = 0; i < 10; i++) { std::cout << *it << " "; ++it; // 当到达end时会自动回到begin } // 输出:1 2 3 4 5 1 2 3 4 5 return 0; } ``` 注意:上述自定义迭代器是一个前向迭代器,如果需要双向遍历,还需重载--操作符。 方法三:使用循环链表容器(第三方或自定义) 如果项目需要频繁使用循环链表,可以考虑使用已经实现的循环链表容器,或者自己实现一个循环链表类(内部可能使用list,但管理首尾连接)。 但是,需要注意的是,STL的list本身是一个双向链表,其end()迭代器指向的是最后一个元素之后的位置(哨兵节点)。如果我们强行将最后一个节点的next指向第一个节点,那么end()迭代器就会失效(因为最后一个节点的next不再是end())。因此,我们不建议直接修改STL list的指针来形成循环,因为这会破坏STL容器的约定,导致未定义行为。 结论: 推荐使用方法二(自定义循环迭代器)来模拟循环遍历,这样既利用了STL list的稳定性,又实现了循环访问。 另外,引用[1]中提到手搓List容器,那么如果我们自己实现一个双向链表,就可以在节点层面实现循环链表。但这里用户问的是使用STL的list容器,所以我们不展开。 总结:使用STL list形成循环遍历的两种方式: 1. 在遍历逻辑中手动重置迭代器(简单,但每次循环都要检查)。 2. 使用自定义的循环迭代器(封装性好,可复用)。 根据引用[3]中STL list的描述,它是一个双向链表,在任意位置插入删除操作都很快,因此适合用于需要频繁插入删除的循环结构。 最后,关于循环链表的应用,常见于需要循环访问数据的场景,如轮询调度、游戏循环等。 相关问题: 1. 如何用C++的vector容器模拟循环访问? 2. 循环链表和普通链表在性能上有什么差异? 3. 如何实现一个安全的循环双向链表容器? </think> ### 如何使用 C++ STL 的 `list` 容器形成循环结构 在 C++ 中,使用 STL 的 `list` 容器(双向链表)实现循环结构需通过**自定义迭代器逻辑**或**手动连接首尾节点**。以下是两种实现方法,均利用 `list` 的节点特性形成循环访问: --- #### 方法一:遍历时重置迭代器(推荐) 通过判断迭代器位置实现循环遍历,不改变 `list` 内部结构,安全且符合 STL 规范: ```cpp #include <iostream> #include <list> int main() { std::list<int> data = {1, 2, 3, 4, 5}; // 创建双向链表 // 模拟循环访问:遍历10次(超出链表长度) auto it = data.begin(); for (int i = 0; i < 10; i++) { if (it == data.end()) { // 到达末尾时重置到头部 it = data.begin(); } std::cout << *it << " → "; ++it; } std::cout << "(head)\n"; // 输出:1 → 2 → 3 → 4 → 5 → 1 → 2 → 3 → 4 → 5 → (head) } ``` **特点**: - 不修改原始容器,保持 STL 完整性 - 时间复杂度:$O(n)$ 遍历,空间复杂度 $O(1)$ - 适用于需要临时循环访问的场景[^3] --- #### 方法二:手动连接首尾节点(高级) 通过操作节点指针直接形成物理循环结构(需谨慎使用): ```cpp #include <iostream> #include <list> void make_circular(std::list<int>& lst) { if (lst.empty()) return; // 获取头节点和尾节点的指针 auto& head_node = *lst.begin()._M_node; // 编译器特定实现(GCC) auto& tail_node = *std::prev(lst.end())._M_node; // 连接首尾:尾节点指向头节点 tail_node._M_next = &head_node; head_node._M_prev = &tail_node; } int main() { std::list<int> data = {10, 20, 30}; make_circular(data); // 物理连接首尾 // 验证循环:遍历两圈 auto it = data.begin(); for (int i = 0; i < 6; i++) { // 2圈 x 3元素 std::cout << *it << " → "; ++it; if (it == data.end()) it = data.begin(); // 仍需要逻辑重置 } std::cout << "(head)\n"; // 输出:10 → 20 → 30 → 10 → 20 → 30 → (head) } ``` **注意**: - 直接操作 `_M_node` 是编译器特定实现(GCC),非标准行为 - 物理循环后 `end()` 迭代器失效,遍历需额外逻辑 - 仅推荐在需要最高性能的底层开发中使用[^1] --- ### 关键区别与选择建议 | **方法** | 实现方式 | 安全性 | 性能 | 适用场景 | |------------------|---------------------|--------|-------|----------------------| | **迭代器重置** | 逻辑循环 | ★★★★★ | $O(n)$ | 通用场景,临时循环访问 | | **首尾物理连接** | 修改底层指针 | ★★☆ | $O(1)$ | 高频访问,性能敏感场景 | > **推荐**:优先使用迭代器重置法,避免破坏 STL 容器完整性。物理连接法需处理迭代器失效问题[^3]。 --- ### 循环链表的应用场景 1. **轮询调度系统** 循环遍历任务队列,如线程池任务分配。 2. **游戏开发** 循环访问实体列表(如 NPC 行为更新)。 3. **数据缓冲区** 环形缓冲区(Ring Buffer)实现高效 I/O 处理。 4. **循环动画序列** 帧序列的循环播放控制。 --- ### 相关问题 1. 如何在循环链表中安全地插入/删除节点? 2. STL 的 `forward_list`(单向链表)如何实现循环结构? 3. 循环链表与环形缓冲区(Ring Buffer)有何性能差异? 4. 如何检测并避免循环链表中的无限循环问题? : C++造轮子:手搓 List 容器 [^3]: STL中顺序容器的类型与特性
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值