C++List插入、删除和遍历操作详解

C++ List 插入、删除和遍历操作详解

在 C++ 标准模板库 (STL) 中,std::list 是一个双向链表容器,它提供了在常数时间内在任何位置插入和删除元素的能力,而序列访问则需要线性时间。这使得 std::list 非常适合于频繁插入和删除操作的场景,但不太适合需要随机访问元素的场景。本文将详细介绍 std::list 的插入、删除和遍历操作。

一、List 简介

std::list 是一个模板类,定义在 <list> 头文件中。与 std::vectorstd::deque 不同,std::list 不支持随机访问,也就是说,你不能通过索引直接访问元素(如 myList[5])。这是因为链表中的元素在内存中不是连续存储的,而是通过指针链接在一起的。

优点:

  • 高效的插入和删除: 无论在列表的哪个位置插入或删除元素,时间复杂度都是 O(1),即常数时间。
  • 动态大小: 列表可以根据需要动态地增长或缩小。
  • 不需要移动元素: 插入或删除元素时,不需要像 std::vector 那样移动其他元素。

缺点:

  • 不支持随机访问: 访问特定位置的元素需要遍历链表,时间复杂度为 O(n),其中 n 是元素的位置。
  • 额外的内存开销: 每个元素都需要额外的内存来存储指向前一个和后一个元素的指针。

二、插入操作

std::list 提供了多种插入元素的方法,以下是常用的几种:

1. push_front(const T& value)push_back(const T& value)

  • push_front():在列表的头部插入一个新元素。
  • push_back():在列表的尾部插入一个新元素。

```c++

include

include

int main() {
std::list myList;

myList.push_front(10); // 在头部插入 10
myList.push_back(20); // 在尾部插入 20
myList.push_front(5); // 在头部插入 5
myList.push_back(30); // 在尾部插入 30

// 现在 myList 包含: 5, 10, 20, 30

for (int x : myList) {
std::cout << x << " ";
}
std::cout << std::endl; // 输出: 5 10 20 30

return 0;
}
```

2. insert(iterator position, const T& value)

  • 在指定迭代器 position 之前插入一个新元素。
  • 返回一个指向新插入元素的迭代器。

```c++

include

include

int main() {
std::list myList = {1, 2, 3, 4, 5};

// 在第三个元素之前插入 10
std::list::iterator it = myList.begin();
std::advance(it, 2); // 将迭代器移动到第三个元素的位置
myList.insert(it, 10);

// 现在 myList 包含: 1, 2, 10, 3, 4, 5

for (int x : myList) {
std::cout << x << " ";
}
std::cout << std::endl; // 输出: 1 2 10 3 4 5

return 0;
}
```

3. insert(iterator position, size_type n, const T& value)

  • 在指定迭代器 position 之前插入 n 个值为 value 的元素。
  • 返回一个指向第一个新插入元素的迭代器。

```c++

include

include

int main() {
std::list myList = {1, 2, 3};

// 在第二个元素之前插入 3 个值为 5 的元素
std::list::iterator it = myList.begin();
std::advance(it, 1);
myList.insert(it, 3, 5);

// 现在 myList 包含: 1, 5, 5, 5, 2, 3

for (int x : myList) {
std::cout << x << " ";
}
std::cout << std::endl; // 输出: 1 5 5 5 2 3

return 0;
}
```

4. insert(iterator position, InputIterator first, InputIterator last)

  • 在指定迭代器 position 之前插入一个范围内的元素,范围由迭代器 firstlast 指定(不包括 last)。
  • 返回一个指向第一个新插入元素的迭代器。

```c++

include

include

include

int main() {
std::list myList = {1, 2, 3};
std::vector myVector = {4, 5, 6};

// 在列表的开头插入 myVector 的内容
myList.insert(myList.begin(), myVector.begin(), myVector.end());

// 现在 myList 包含: 4, 5, 6, 1, 2, 3

for (int x : myList) {
std::cout << x << " ";
}
std::cout << std::endl; // 输出: 4 5 6 1 2 3

return 0;
}
```

5. emplace_front(Args&&... args)emplace_back(Args&&... args)

  • emplace_front():在列表的头部构造一个新元素。
  • emplace_back():在列表的尾部构造一个新元素。
  • Args&&... args 是传递给元素构造函数的参数。
  • 这些方法比 push_front()push_back() 更高效,因为它们避免了不必要的拷贝或移动操作。

```c++

include

include

include

int main() {
std::list myList;

myList.emplace_front("Hello"); // 直接在列表头部构造一个字符串
myList.emplace_back("World"); // 直接在列表尾部构造一个字符串

for (const std::string& str : myList) {
std::cout << str << " ";
}
std::cout << std::endl; // 输出: Hello World

return 0;
}
```

6. emplace(iterator position, Args&&... args)

  • 在指定迭代器 position 之前构造一个新元素。
  • Args&&... args 是传递给元素构造函数的参数。
  • 返回一个指向新构造的元素的迭代器。
  • 同样,emplace()insert() 更高效,因为它避免了不必要的拷贝或移动操作。

```c++

include

include

int main() {
std::list> myList;

// 在列表的开头构造一个 pair 对象
myList.emplace(myList.begin(), 1, "One");
myList.emplace(myList.end(), 2, "Two");

for (const auto& p : myList) {
std::cout << "(" << p.first << ", " << p.second << ") ";
}
std::cout << std::endl; // 输出: (1, One) (2, Two)

return 0;
}
```

三、删除操作

std::list 提供了多种删除元素的方法,以下是常用的几种:

1. pop_front()pop_back()

  • pop_front():删除列表的第一个元素。
  • pop_back():删除列表的最后一个元素。

```c++

include

include

int main() {
std::list myList = {1, 2, 3, 4, 5};

myList.pop_front(); // 删除第一个元素 (1)
myList.pop_back(); // 删除最后一个元素 (5)

// 现在 myList 包含: 2, 3, 4

for (int x : myList) {
std::cout << x << " ";
}
std::cout << std::endl; // 输出: 2 3 4

return 0;
}
```

2. erase(iterator position)

  • 删除指定迭代器 position 指向的元素。
  • 返回一个指向被删除元素之后元素的迭代器。

```c++

include

include

int main() {
std::list myList = {1, 2, 3, 4, 5};

// 删除第三个元素 (3)
std::list::iterator it = myList.begin();
std::advance(it, 2);
it = myList.erase(it);

// 现在 myList 包含: 1, 2, 4, 5
// it 指向 4

for (int x : myList) {
std::cout << x << " ";
}
std::cout << std::endl; // 输出: 1 2 4 5

std::cout << *it << std::endl; // 输出: 4

return 0;
}
```

3. erase(iterator first, iterator last)

  • 删除一个范围内的元素,范围由迭代器 firstlast 指定(不包括 last)。
  • 返回一个指向最后一个被删除元素之后元素的迭代器。

```c++

include

include

int main() {
std::list myList = {1, 2, 3, 4, 5};

// 删除前三个元素 (1, 2, 3)
std::list::iterator it = myList.begin();
std::advance(it, 3);
myList.erase(myList.begin(), it);

// 现在 myList 包含: 4, 5

for (int x : myList) {
std::cout << x << " ";
}
std::cout << std::endl; // 输出: 4 5

return 0;
}
```

4. remove(const T& value)

  • 删除列表中所有值等于 value 的元素。

```c++

include

include

int main() {
std::list myList = {1, 2, 3, 2, 4, 2, 5};

myList.remove(2); // 删除所有值为 2 的元素

// 现在 myList 包含: 1, 3, 4, 5

for (int x : myList) {
std::cout << x << " ";
}
std::cout << std::endl; // 输出: 1 3 4 5

return 0;
}
```

5. remove_if(UnaryPredicate pred)

  • 删除列表中所有满足谓词 pred 的元素。
  • UnaryPredicate 是一个一元谓词,它接受一个元素作为参数,并返回一个布尔值。

```c++

include

include

// 一个谓词,用于判断一个数是否为偶数
bool isEven(int n) {
return n % 2 == 0;
}

int main() {
std::list myList = {1, 2, 3, 4, 5, 6};

myList.remove_if(isEven); // 删除所有偶数

// 现在 myList 包含: 1, 3, 5

for (int x : myList) {
std::cout << x << " ";
}
std::cout << std::endl; // 输出: 1 3 5

return 0;
}
```

6. clear()

  • 删除列表中的所有元素,使列表为空。

```c++

include

include

int main() {
std::list myList = {1, 2, 3};

myList.clear(); // 清空列表

std::cout << myList.size() << std::endl; // 输出: 0

return 0;
}
```

四、遍历操作

由于 std::list 不支持随机访问,所以遍历 std::list 通常使用迭代器。

1. 使用前向迭代器

```c++

include

include

int main() {
std::list myList = {1, 2, 3, 4, 5};

for (std::list::iterator it = myList.begin(); it != myList.end(); ++it) {
std::cout << *it << " ";
}
std::cout << std::endl; // 输出: 1 2 3 4 5

return 0;
}
```

2. 使用范围基于的 for 循环 (C++11 及以后)

```c++

include

include

int main() {
std::list myList = {1, 2, 3, 4, 5};

for (int x : myList) {
std::cout << x << " ";
}
std::cout << std::endl; // 输出: 1 2 3 4 5

return 0;
}
```

3. 使用反向迭代器

```c++

include

include

int main() {
std::list myList = {1, 2, 3, 4, 5};

for (std::list::reverse_iterator rit = myList.rbegin(); rit != myList.rend(); ++rit) {
std::cout << *rit << " ";
}
std::cout << std::endl; // 输出: 5 4 3 2 1

return 0;
}
```

4. 使用 const_iterator 遍历常量列表

```c++

include

include

int main() {
const std::list myList = {1, 2, 3, 4, 5};

for (std::list::const_iterator it = myList.begin(); it != myList.end(); ++it) {
std::cout << *it << " ";
}
std::cout << std::endl; // 输出: 1 2 3 4 5

return 0;
}
```

五、总结

std::list 是一个强大的双向链表容器,它提供了高效的插入和删除操作。了解如何正确地使用插入、删除和遍历方法对于有效地使用 std::list 至关重要。

关键点回顾:

  • 插入: push_front(), push_back(), insert(), emplace_front(), emplace_back(), emplace()
  • 删除: pop_front(), pop_back(), erase(), remove(), remove_if(), clear()
  • 遍历: 前向迭代器, 范围基于的 for 循环, 反向迭代器, const_iterator

希望这篇文章能够帮助你更好地理解和使用 C++ 中的 std::list 容器。 在实际编程中,应根据具体需求选择合适的容器。 如果需要频繁插入和删除元素,std::list 是一个不错的选择。 如果需要随机访问元素,std::vectorstd::deque 可能更合适。

THE END