C++ list类

news2024/12/28 8:26:44

目录

0.前言

1.list介绍

1.1优势

1.2劣势

1.3容器属性

2.list使用

2.1构造函数

2.1.1默认构造函数

2.1.2填充构造函数

2.1.3范围构造函数

2.1.4拷贝构造函数

2.1.5初始化列表构造函数 

2.2迭代器

2.2.1 begin()

2.2.2 end()

2.2.3 cbegin()

2.2.4 cend()

2.2.5 rbegin()

2.2.6 rend()

2.2.7 crbegin()

2.2.8 crend()

2.3容量获取及元素访问函数

2.3.1 empty()

2.3.2 size()

2.3.3 front()

2.3.4 back()

2.4修改函数

2.4.1 assign

2.4.2 push_front 和 pop_front

 2.4.3 push_back 和 pop_back

 2.4.4 insert

2.4.5 erase

2.4.6 swap

2.4.7 clear

2.5其他功能函数

2.5.1 splice

2.5.2 unique

3.list的模拟实现

3.1基本实现代码

3.2 list正/反向迭代器实现

3.2.1正向迭代器 (ListIterator)

3.2.2反向迭代器 (ListReverseIterator)

4.list与vector的比较

4.1 内部实现

4.2 存储方式

4.3 访问和遍历

4.4 插入和删除操作

4.5 内存管理

4.6 使用场景

5.结语


(图像由AI生成) 

0.前言

在前面的文章中,我们已经详细介绍了 C++ 标准模板库中的 stringvector 类的用法及实现。接下来,我们将深入探讨 list 类,这是一种双向链表结构,适合于频繁插入和删除操作的场景。通过这篇文章,我们将全面了解 list 的构造函数、迭代器、容量获取与元素访问函数、修改函数、以及其他功能函数,并比较 listvector 的异同,帮助你在实际编程中做出更合适的数据结构选择。

1.list介绍

list 是 C++ 标准模板库中的一种序列容器,允许在序列中的任何位置进行常数时间的插入和删除操作,并且支持双向迭代。它的实现基于双向链表,这种结构允许元素存储在不同且不相关的内存位置,内部通过每个元素与前一个和后一个元素的链接来保持顺序。

双向链表与单向链表(如 forward_list)非常相似,主要区别在于单向链表只能向前迭代,而双向链表可以在两个方向上迭代。尽管单向链表在内存占用和效率上略优,但双向链表提供了更灵活的迭代和操作方式。

1.1优势

与其他基本标准序列容器(如 arrayvectordeque)相比,list 在以下方面通常表现更好:

  • 插入和删除操作:在任意位置插入、提取和移动元素的性能更优,特别是在已获得迭代器的位置。
  • 排序算法:在需要频繁插入和删除操作的排序算法中表现更佳。

1.2劣势

listforward_list 与其他序列容器相比存在一些缺点:

  • 缺乏直接访问:无法通过位置直接访问元素。例如,要访问 list 中的第六个元素,需要从已知位置(如开头或结尾)迭代到该位置,这需要线性时间。
  • 额外的内存开销:为了维护元素之间的链接信息,list 需要额外的内存,这在处理大量小元素时可能是一个重要因素。

1.3容器属性

  • 序列:序列容器中的元素按严格的线性顺序排列。可以通过其在序列中的位置访问单个元素。
  • 双向链表:每个元素都包含信息以定位下一个和前一个元素,从而允许在特定元素之前或之后(甚至是整个范围)进行常数时间的插入和删除操作,但不支持直接随机访问。
  • 分配器感知:容器使用分配器对象来动态管理其存储需求。

2.list使用

2.1构造函数

default (1)
explicit list (const allocator_type& alloc = allocator_type());
fill (2)
explicit list (size_type n);
         list (size_type n, const value_type& val,
                const allocator_type& alloc = allocator_type());
range (3)
template <class InputIterator>
  list (InputIterator first, InputIterator last,
         const allocator_type& alloc = allocator_type());
copy (4)
list (const list& x);
list (const list& x, const allocator_type& alloc);
initializer list (5)
list (initializer_list<value_type> il,
       const allocator_type& alloc = allocator_type());

C++ 中的 list 提供了多种构造函数,用于不同的初始化需求。下面我们详细介绍每种构造函数,并提供示例代码来说明它们的使用。

2.1.1默认构造函数

explicit list (const allocator_type& alloc = allocator_type());

创建一个空链表,可以指定一个分配器。

示例代码:

#include <iostream>
#include <list>

int main() {
    std::list<int> mylist;
    std::cout << "Size of mylist: " << mylist.size() << std::endl;
    return 0;
}
//输出:Size of mylist: 0

2.1.2填充构造函数

explicit list (size_type n);
list (size_type n, const value_type& val, const allocator_type& alloc = allocator_type());

创建一个包含 n 个默认值元素的链表,或创建一个包含 n 个指定值 val 的链表。

示例代码:

#include <iostream>
#include <list>

int main() {
    std::list<int> mylist1(5); // 创建一个包含5个默认值元素的链表
    std::list<int> mylist2(5, 100); // 创建一个包含5个值为100的元素的链表
    
    std::cout << "mylist1 contains:";
    for (int& x : mylist1) std::cout << ' ' << x;
    std::cout << '\n';
    
    std::cout << "mylist2 contains:";
    for (int& x : mylist2) std::cout << ' ' << x;
    std::cout << '\n';
    
    return 0;
}
//输出:
//mylist1 contains: 0 0 0 0 0
//mylist2 contains: 100 100 100 100 100

2.1.3范围构造函数

template <class InputIterator>
list (InputIterator first, InputIterator last, const allocator_type& alloc = allocator_type());

使用迭代器范围 [first, last) 构造链表。

#include <iostream>
#include <list>

int main() {
    std::list<int> original = {1, 2, 3, 4, 5};
    std::list<int> copy(original); // 使用拷贝构造函数
    
    std::cout << "copy contains:";
    for (int& x : copy) std::cout << ' ' << x;
    std::cout << '\n';
    
    return 0;
}

示例代码:

#include <iostream>
#include <list>
#include <vector>

int main() {
    std::vector<int> vec = {1, 2, 3, 4, 5};
    std::list<int> mylist(vec.begin(), vec.end()); // 使用vector的迭代器范围构造链表
    
    std::cout << "mylist contains:";
    for (int& x : mylist) std::cout << ' ' << x;
    std::cout << '\n';
    
    return 0;
}
//输出:mylist contains: 1 2 3 4 5

2.1.4拷贝构造函数

list (const list& x);
list (const list& x, const allocator_type& alloc);

创建一个与现有链表 x 相同的新链表。

示例代码:

#include <iostream>
#include <list>

int main() {
    std::list<int> original = {1, 2, 3, 4, 5};
    std::list<int> copy(original); // 使用拷贝构造函数
    
    std::cout << "copy contains:";
    for (int& x : copy) std::cout << ' ' << x;
    std::cout << '\n';
    
    return 0;
}
//输出:copy contains: 1 2 3 4 5

2.1.5初始化列表构造函数 

list (initializer_list<value_type> il, const allocator_type& alloc = allocator_type());

使用初始化列表 il 构造链表。

示例代码:

#include <iostream>
#include <list>

int main() {
    std::list<int> mylist = {10, 20, 30, 40, 50}; // 使用初始化列表构造链表
    
    std::cout << "mylist contains:";
    for (int& x : mylist) std::cout << ' ' << x;
    std::cout << '\n';
    
    return 0;
}
//输出:mylist contains: 10 20 30 40 50

2.2迭代器

2.2.1 begin()

begin() 返回指向容器中第一个元素的迭代器。

2.2.2 end()

end() 返回指向容器末尾后一个位置的迭代器,这个位置不包含任何元素。

2.2.3 cbegin()

cbegin() 返回指向容器中第一个元素的常量迭代器,不能用于修改元素。

2.2.4 cend()

cend() 返回指向容器末尾后一个位置的常量迭代器,这个位置不包含任何元素,不能用于修改元素。

2.2.5 rbegin()

rbegin() 返回指向容器中最后一个元素的反向迭代器。

2.2.6 rend()

rend() 返回指向容器第一个元素前一个位置的反向迭代器,这个位置不包含任何元素。

2.2.7 crbegin()

crbegin() 返回指向容器中最后一个元素的常量反向迭代器,不能用于修改元素。

2.2.8 crend()

crend() 返回指向容器第一个元素前一个位置的常量反向迭代器,这个位置不包含任何元素,不能用于修改元素。

下面是一个示例代码,展示了这些迭代器的使用:

#include <iostream>
#include <list>

int main() {
    std::list<int> mylist = {10, 20, 30, 40, 50};

    // 使用 begin() 和 end() 迭代器
    std::cout << "Elements using begin() and end(): ";
    for (auto it = mylist.begin(); it != mylist.end(); ++it) {
        std::cout << *it << " ";
    }
    std::cout << std::endl;

    // 使用 cbegin() 和 cend() 迭代器
    std::cout << "Elements using cbegin() and cend(): ";
    for (auto it = mylist.cbegin(); it != mylist.cend(); ++it) {
        std::cout << *it << " ";
    }
    std::cout << std::endl;

    // 使用 rbegin() 和 rend() 迭代器
    std::cout << "Elements using rbegin() and rend(): ";
    for (auto it = mylist.rbegin(); it != mylist.rend(); ++it) {
        std::cout << *it << " ";
    }
    std::cout << std::endl;

    // 使用 crbegin() 和 crend() 迭代器
    std::cout << "Elements using crbegin() and crend(): ";
    for (auto it = mylist.crbegin(); it != mylist.crend(); ++it) {
        std::cout << *it << " ";
    }
    std::cout << std::endl;

    return 0;
}

代码解析

  • begin() 和 end():用于从前向后遍历并访问列表中的元素。
  • cbegin() 和 cend():常量迭代器,用于从前向后遍历但不允许修改元素。
  • rbegin() 和 rend():反向迭代器,用于从后向前遍历并访问列表中的元素。
  • crbegin() 和 crend():常量反向迭代器,用于从后向前遍历但不允许修改元素。

2.3容量获取及元素访问函数

在使用 C++ 的 list 类时,容量获取和元素访问函数是非常重要的,它们可以帮助我们了解和操作容器中的元素。主要包括以下几个函数。

2.3.1 empty()

empty() 函数用于检查容器是否为空。如果容器中没有元素,则返回 true,否则返回 false

示例代码:

#include <iostream>
#include <list>

int main() {
    std::list<int> mylist;

    if (mylist.empty()) {
        std::cout << "The list is empty." << std::endl;
    } else {
        std::cout << "The list is not empty." << std::endl;
    }

    mylist.push_back(10);

    if (mylist.empty()) {
        std::cout << "The list is empty." << std::endl;
    } else {
        std::cout << "The list is not empty." << std::endl;
    }

    return 0;
}
//输出:
//The list is empty.
//The list is not empty.

2.3.2 size()

size() 函数返回容器中元素的数量。

示例代码:

#include <iostream>
#include <list>

int main() {
    std::list<int> mylist = {10, 20, 30, 40, 50};

    std::cout << "The size of the list is: " << mylist.size() << std::endl;

    mylist.push_back(60);
    std::cout << "After adding an element, the size of the list is: " << mylist.size() << std::endl;

    return 0;
}
//输出:
//The size of the list is: 5
//After adding an element, the size of the list is: 6

2.3.3 front()

front() 函数返回容器中第一个元素的引用。注意,front() 不能用于空容器,否则行为是未定义的。

示例代码:

#include <iostream>
#include <list>

int main() {
    std::list<int> mylist = {10, 20, 30};

    std::cout << "The first element is: " << mylist.front() << std::endl;

    mylist.front() = 100; // 修改第一个元素的值
    std::cout << "After modification, the first element is: " << mylist.front() << std::endl;

    return 0;
}
//输出:
//The first element is: 10
//After modification, the first element is: 100

2.3.4 back()

back() 函数返回容器中最后一个元素的引用。注意,back() 不能用于空容器,否则行为是未定义的。

示例代码:

#include <iostream>
#include <list>

int main() {
    std::list<int> mylist = {10, 20, 30};

    std::cout << "The last element is: " << mylist.back() << std::endl;

    mylist.back() = 300; // 修改最后一个元素的值
    std::cout << "After modification, the last element is: " << mylist.back() << std::endl;

    return 0;
}
//输出:
//The last element is: 30
//After modification, the last element is: 300

2.4修改函数

C++ list 类提供了多种修改容器内容的函数。这些函数允许我们在列表中插入、删除和替换元素。下面我们将详细介绍以下修改函数,并提供示例代码展示它们的使用:

2.4.1 assign

assign 函数用于替换当前容器内容,接受多个重载版本,可以使用指定的元素、范围或初始化列表。

示例代码:

#include <iostream>
#include <list>

int main() {
    std::list<int> mylist;
    mylist.assign(5, 100); // 使用5个100替换当前内容

    std::cout << "List after assign(5, 100): ";
    for (const int& x : mylist) {
        std::cout << x << " ";
    }
    std::cout << std::endl;

    std::list<int> another_list = {1, 2, 3};
    mylist.assign(another_list.begin(), another_list.end()); // 使用另一个list的范围替换当前内容

    std::cout << "List after assign from another list: ";
    for (const int& x : mylist) {
        std::cout << x << " ";
    }
    std::cout << std::endl;

    mylist.assign({10, 20, 30}); // 使用初始化列表替换当前内容

    std::cout << "List after assign({10, 20, 30}): ";
    for (const int& x : mylist) {
        std::cout << x << " ";
    }
    std::cout << std::endl;

    return 0;
}
// 输出:
// List after assign(5, 100): 100 100 100 100 100 
// List after assign from another list: 1 2 3 
// List after assign({10, 20, 30}): 10 20 30 

2.4.2 push_frontpop_front

  • push_front 函数在容器开头插入一个元素。
  • pop_front 函数移除容器开头的元素。

示例代码:

#include <iostream>
#include <list>

int main() {
    std::list<int> mylist = {20, 30};

    mylist.push_front(10); // 在开头插入元素10
    std::cout << "List after push_front(10): ";
    for (const int& x : mylist) {
        std::cout << x << " ";
    }
    std::cout << std::endl;

    mylist.pop_front(); // 移除开头的元素
    std::cout << "List after pop_front(): ";
    for (const int& x : mylist) {
        std::cout << x << " ";
    }
    std::cout << std::endl;

    return 0;
}
// 输出:
// List after push_front(10): 10 20 30 
// List after pop_front(): 20 30 

 2.4.3 push_backpop_back

  • push_back 函数在容器末尾插入一个元素。
  • pop_back 函数移除容器末尾的元素。

示例代码:

#include <iostream>
#include <list>

int main() {
    std::list<int> mylist = {10, 20};

    mylist.push_back(30); // 在末尾插入元素30
    std::cout << "List after push_back(30): ";
    for (const int& x : mylist) {
        std::cout << x << " ";
    }
    std::cout << std::endl;

    mylist.pop_back(); // 移除末尾的元素
    std::cout << "List after pop_back(): ";
    for (const int& x : mylist) {
        std::cout << x << " ";
    }
    std::cout << std::endl;

    return 0;
}
// 输出:
// List after push_back(30): 10 20 30 
// List after pop_back(): 10 20 

 2.4.4 insert

insert 函数用于在指定位置插入一个或多个元素,可以通过单个元素、初始化列表或迭代器范围进行插入。

示例代码:

#include <iostream>
#include <list>

int main() {
    std::list<int> mylist = {10, 20, 30};

    auto it = mylist.begin();
    ++it; // 指向第二个元素

    mylist.insert(it, 15); // 在第二个元素前插入15
    std::cout << "List after insert(15): ";
    for (const int& x : mylist) {
        std::cout << x << " ";
    }
    std::cout << std::endl;

    mylist.insert(it, 2, 25); // 在第二个元素前插入两个25
    std::cout << "List after insert(2, 25): ";
    for (const int& x : mylist) {
        std::cout << x << " ";
    }
    std::cout << std::endl;

    std::list<int> another_list = {1, 2, 3};
    mylist.insert(it, another_list.begin(), another_list.end()); // 在第二个元素前插入另一个list的范围
    std::cout << "List after insert from another list: ";
    for (const int& x : mylist) {
        std::cout << x << " ";
    }
    std::cout << std::endl;

    return 0;
}
// 输出:
// List after insert(15): 10 15 20 30 
// List after insert(2, 25): 10 25 25 15 20 30 
// List after insert from another list: 10 1 2 3 25 25 15 20 30 

2.4.5 erase

erase 函数用于移除一个或多个元素,可以通过单个迭代器或迭代器范围进行删除。

示例代码:

#include <iostream>
#include <list>

int main() {
    std::list<int> mylist = {10, 20, 30, 40, 50};

    auto it = mylist.begin();
    ++it; // 指向第二个元素

    mylist.erase(it); // 移除第二个元素
    std::cout << "List after erase second element: ";
    for (const int& x : mylist) {
        std::cout << x << " ";
    }
    std::cout << std::endl;

    auto it1 = mylist.begin();
    auto it2 = mylist.end();
    --it2; // 指向最后一个元素

    mylist.erase(it1, it2); // 移除范围 [it1, it2)
    std::cout << "List after erase range [begin, end-1): ";
    for (const int& x : mylist) {
        std::cout << x << " ";
    }
    std::cout << std::endl;

    return 0;
}
// 输出:
// List after erase second element: 10 30 40 50 
// List after erase range [begin, end-1): 50 

2.4.6 swap

swap 函数用于交换两个容器的内容。

示例代码:

#include <iostream>
#include <list>

int main() {
    std::list<int> list1 = {1, 2, 3};
    std::list<int> list2 = {10, 20, 30};

    list1.swap(list2); // 交换list1和list2的内容

    std::cout << "List1 after swap: ";
    for (const int& x : list1) {
        std::cout << x << " ";
    }
    std::cout << std::endl;

    std::cout << "List2 after swap: ";
    for (const int& x : list2) {
        std::cout << x << " ";
    }
    std::cout << std::endl;

    return 0;
}
// 输出:
// List1 after swap: 10 20 30 
// List2 after swap: 1 2 3 

2.4.7 clear

clear 函数用于移除容器中的所有元素,使其变为空容器。

示例代码:

#include <iostream>
#include <list>

int main() {
    std::list<int> mylist = {10, 20, 30};

    mylist.clear(); // 清空list中的所有元素

    std::cout << "List after clear: ";
    if (mylist.empty()) {
        std::cout << "The list is empty." << std::endl;
    } else {
        for (const int& x : mylist) {
            std::cout << x << " ";
        }
        std::cout << std::endl;
    }

    return 0;
}
// 输出:
// List after clear: The list is empty.

2.5其他功能函数

除了基本的修改函数,C++ 的 list 类还提供了一些其他功能函数,这些函数可以更高效地操作链表中的元素。

2.5.1 splice

splice 函数用于将另一个 list 中的元素移到当前 list 的指定位置。splice 函数有三个重载版本:

  1. splice(iterator position, list& x):将 x 的所有元素移动到当前 listposition 之前的位置。
  2. splice(iterator position, list& x, iterator i):将 x 中的元素 i 移动到当前 listposition 之前的位置。
  3. splice(iterator position, list& x, iterator first, iterator last):将 x[first, last) 范围内的元素移动到当前 listposition 之前的位置。

示例代码:

#include <iostream>
#include <list>

int main() {
    std::list<int> list1 = {1, 2, 3, 4, 5};
    std::list<int> list2 = {10, 20, 30};

    auto it = list1.begin();
    ++it; // 指向第二个元素

    // 将list2的所有元素移动到list1中第二个元素之前
    list1.splice(it, list2);

    std::cout << "List1 after splice all elements from List2: ";
    for (const int& x : list1) {
        std::cout << x << " ";
    }
    std::cout << std::endl;

    std::cout << "List2 after splice all elements to List1: ";
    for (const int& x : list2) {
        std::cout << x << " ";
    }
    std::cout << std::endl;

    // 初始化list2
    list2 = {10, 20, 30};
    it = list1.begin();
    ++it; // 指向第二个元素

    auto it2 = list2.begin();
    ++it2; // 指向list2中的第二个元素

    // 将list2中的第二个元素移动到list1中第二个元素之前
    list1.splice(it, list2, it2);

    std::cout << "List1 after splice one element from List2: ";
    for (const int& x : list1) {
        std::cout << x << " ";
    }
    std::cout << std::endl;

    std::cout << "List2 after splice one element to List1: ";
    for (const int& x : list2) {
        std::cout << x << " ";
    }
    std::cout << std::endl;

    // 初始化list2
    list2 = {10, 20, 30};
    it = list1.begin();
    ++it; // 指向第二个元素

    // 将list2中的第一个和第二个元素移动到list1中第二个元素之前
    list1.splice(it, list2, list2.begin(), ++(++list2.begin()));

    std::cout << "List1 after splice range from List2: ";
    for (const int& x : list1) {
        std::cout << x << " ";
    }
    std::cout << std::endl;

    std::cout << "List2 after splice range to List1: ";
    for (const int& x : list2) {
        std::cout << x << " ";
    }
    std::cout << std::endl;

    return 0;
}
// 输出:
// List1 after splice all elements from List2: 1 10 20 30 2 3 4 5
// List2 after splice all elements to List1:
// List1 after splice one element from List2: 1 20 10 20 30 2 3 4 5
// List2 after splice one element to List1: 10 30
// List1 after splice range from List2: 1 10 20 20 10 20 30 2 3 4 5
// List2 after splice range to List1: 30

2.5.2 unique

unique 函数用于移除容器中相邻的重复元素,只保留一个。它有两个重载版本:

  1. unique():移除相邻重复的元素。
  2. unique(BinaryPredicate pred):使用自定义的二元谓词函数来判断元素是否相邻重复。

示例代码:

#include <iostream>
#include <list>

int main() {
    std::list<int> mylist = {10, 20, 20, 30, 30, 30, 40, 40, 50};

    mylist.unique(); // 移除相邻重复的元素
    std::cout << "List after unique(): ";
    for (const int& x : mylist) {
        std::cout << x << " ";
    }
    std::cout << std::endl;

    mylist = {10, 20, 21, 30, 31, 32, 40, 41, 50};

    // 自定义二元谓词函数,移除相邻差值小于等于1的元素
    mylist.unique([](int a, int b) { return std::abs(a - b) <= 1; });

    std::cout << "List after unique with custom predicate: ";
    for (const int& x : mylist) {
        std::cout << x << " ";
    }
    std::cout << std::endl;

    return 0;
}
// 输出:
// List after unique(): 10 20 30 40 50 
// List after unique with custom predicate: 10 20 30 32 40 50

3.list的模拟实现

3.1基本实现代码

#pragma once
#include <cstddef> // for size_t

template<class T>
struct ListNode
{
    T data;
    ListNode<T>* _next;
    ListNode<T>* _prev;

    ListNode(const T& d = T())
        : data(d), _next(nullptr), _prev(nullptr)
    {}
};

template<class T, class Ref, class Ptr>
struct ListIterator
{
    typedef ListNode<T> Node;
    typedef ListIterator<T, Ref, Ptr> Self;

    Node* _node;

    ListIterator(Node* node)
        : _node(node)
    {}

    Ref operator*()
    {
        return _node->data;
    }

    Ptr operator->()
    {
        return &_node->data;
    }

    Self& operator++()
    {
        _node = _node->_next;
        return *this;
    }

    Self operator++(int)
    {
        Self tmp(*this);
        _node = _node->_next;
        return tmp;
    }

    Self& operator--()
    {
        _node = _node->_prev;
        return *this;
    }

    Self operator--(int)
    {
        Self tmp(*this);
        _node = _node->_prev;
        return tmp;
    }

    bool operator!=(const Self& it) const
    {
        return _node != it._node;
    }

    bool operator==(const Self& it) const
    {
        return _node == it._node;
    }
};

template<class T, class Ref, class Ptr>
struct ListReverseIterator
{
    typedef ListNode<T> Node;
    typedef ListReverseIterator<T, Ref, Ptr> Self;

    Node* _node;

    ListReverseIterator(Node* node)
        : _node(node)
    {}

    Ref operator*()
    {
        return _node->data;
    }

    Ptr operator->()
    {
        return &_node->data;
    }

    Self& operator++()
    {
        _node = _node->_prev;
        return *this;
    }

    Self operator++(int)
    {
        Self tmp(*this);
        _node = _node->_prev;
        return tmp;
    }

    Self& operator--()
    {
        _node = _node->_next;
        return *this;
    }

    Self operator--(int)
    {
        Self tmp(*this);
        _node = _node->_next;
        return tmp;
    }

    bool operator!=(const Self& it) const
    {
        return _node != it._node;
    }

    bool operator==(const Self& it) const
    {
        return _node == it._node;
    }
};

template<class T>
class list
{
public:
    typedef ListNode<T> Node;
    typedef ListIterator<T, T&, T*> iterator;
    typedef ListIterator<T, const T&, const T*> const_iterator;
    typedef ListReverseIterator<T, T&, T*> reverse_iterator;
    typedef ListReverseIterator<T, const T&, const T*> const_reverse_iterator;
    typedef T& reference;
    typedef const T& const_reference;

private:
    Node* _head;

public:
    list()
    {
        _head = new Node;
        _head->_next = _head;
        _head->_prev = _head;
    }

    ~list()
    {
        clear();
        delete _head;
        _head = nullptr;
    }

    list(const list<T>& l)
    {
        _head = new Node;
        _head->_next = _head;
        _head->_prev = _head;

        for (const auto& e : l)
        {
            push_back(e);
        }
    }

    list(size_t n, const T& data = T())
    {
        _head = new Node;
        _head->_next = _head;
        _head->_prev = _head;

        while (n--)
        {
            push_back(data);
        }
    }

    list<T>& operator=(const list<T>& l)
    {
        if (this != &l)
        {
            list<T> tmp(l);
            swap(tmp);
        }
        return *this;
    }

    bool empty() const
    {
        return _head->_next == _head;
    }

    size_t size() const
    {
        size_t count = 0;
        Node* cur = _head->_next;
        while (cur != _head)
        {
            ++count;
            cur = cur->_next;
        }
        return count;
    }

    reference front()
    {
        return _head->_next->data;
    }

    const_reference front() const
    {
        return _head->_next->data;
    }

    reference back()
    {
        return _head->_prev->data;
    }

    const_reference back() const
    {
        return _head->_prev->data;
    }

    iterator begin()
    {
        return iterator(_head->_next);
    }

    iterator end()
    {
        return iterator(_head);
    }

    const_iterator begin() const
    {
        return const_iterator(_head->_next);
    }

    const_iterator end() const
    {
        return const_iterator(_head);
    }

    reverse_iterator rbegin()
    {
        return reverse_iterator(_head->_prev);
    }

    reverse_iterator rend()
    {
        return reverse_iterator(_head);
    }

    const_reverse_iterator crbegin() const
    {
        return const_reverse_iterator(_head->_prev);
    }

    const_reverse_iterator crend() const
    {
        return const_reverse_iterator(_head);
    }

    iterator insert(iterator pos, const T& data)
    {
        Node* newNode = new Node(data);

        Node* cur = pos._node;
        Node* prev = cur->_prev;

        prev->_next = newNode;
        newNode->_prev = prev;

        newNode->_next = cur;
        cur->_prev = newNode;

        return iterator(newNode);
    }

    void push_back(const T& data)
    {
        insert(end(), data);
    }

    void push_front(const T& data)
    {
        insert(begin(), data);
    }

    iterator erase(iterator pos)
    {
        Node* cur = pos._node;
        Node* prev = cur->_prev;
        Node* next = cur->_next;

        prev->_next = next;
        next->_prev = prev;

        delete cur;
        return iterator(next);
    }

    iterator erase(iterator first, iterator last)
    {
        while (first != last)
        {
            first = erase(first);
        }
        return last;
    }

    void pop_front()
    {
        erase(begin());
    }

    void pop_back()
    {
        erase(--end());
    }

    void swap(list<T>& l)
    {
        std::swap(_head, l._head);
    }

    void clear()
    {
        Node* cur = _head->_next;
        while (cur != _head)
        {
            Node* next = cur->_next;
            delete cur;
            cur = next;
        }
        _head->_next = _head;
        _head->_prev = _head;
    }
};

3.2 list正/反向迭代器实现

list 容器的模拟实现中,迭代器是一个非常重要的部分。迭代器允许我们遍历、访问和修改链表中的元素。为了支持标准库中 list 的功能,我们需要实现正向迭代器和反向迭代器。下面是对正向迭代器和反向迭代器实现的详细介绍。

3.2.1正向迭代器 (ListIterator)

ListIterator 是一个模板结构体,用于表示 list 容器的正向迭代器。它支持常见的迭代器操作,如解引用、前进、后退和比较。

定义和成员

template<class T, class Ref, class Ptr>
struct ListIterator
{
    typedef ListNode<T> Node;
    typedef ListIterator<T, Ref, Ptr> Self;

    Node* _node; // 指向当前节点的指针

    // 构造函数
    ListIterator(Node* node)
        : _node(node)
    {}

    // 解引用运算符,返回当前节点的数据
    Ref operator*()
    {
        return _node->data;
    }

    // 指针访问运算符,返回指向当前节点数据的指针
    Ptr operator->()
    {
        return &_node->data;
    }

    // 前置递增运算符,移动到下一个节点
    Self& operator++()
    {
        _node = _node->_next;
        return *this;
    }

    // 后置递增运算符,移动到下一个节点,返回旧值
    Self operator++(int)
    {
        Self tmp(*this);
        _node = _node->_next;
        return tmp;
    }

    // 前置递减运算符,移动到前一个节点
    Self& operator--()
    {
        _node = _node->_prev;
        return *this;
    }

    // 后置递减运算符,移动到前一个节点,返回旧值
    Self operator--(int)
    {
        Self tmp(*this);
        _node = _node->_prev;
        return tmp;
    }

    // 不等比较运算符
    bool operator!=(const Self& it) const
    {
        return _node != it._node;
    }

    // 相等比较运算符
    bool operator==(const Self& it) const
    {
        return _node == it._node;
    }
};

3.2.2反向迭代器 (ListReverseIterator)

ListReverseIterator 是一个模板结构体,用于表示 list 容器的反向迭代器。反向迭代器允许我们从后向前遍历容器。

定义和成员

template<class T, class Ref, class Ptr>
struct ListReverseIterator
{
    typedef ListNode<T> Node;
    typedef ListReverseIterator<T, Ref, Ptr> Self;

    Node* _node; // 指向当前节点的指针

    // 构造函数
    ListReverseIterator(Node* node)
        : _node(node)
    {}

    // 解引用运算符,返回当前节点的数据
    Ref operator*()
    {
        return _node->data;
    }

    // 指针访问运算符,返回指向当前节点数据的指针
    Ptr operator->()
    {
        return &_node->data;
    }

    // 前置递增运算符,移动到前一个节点
    Self& operator++()
    {
        _node = _node->_prev;
        return *this;
    }

    // 后置递增运算符,移动到前一个节点,返回旧值
    Self operator++(int)
    {
        Self tmp(*this);
        _node = _node->_prev;
        return tmp;
    }

    // 前置递减运算符,移动到下一个节点
    Self& operator--()
    {
        _node = _node->_next;
        return *this;
    }

    // 后置递减运算符,移动到下一个节点,返回旧值
    Self operator--(int)
    {
        Self tmp(*this);
        _node = _node->_next;
        return tmp;
    }

    // 不等比较运算符
    bool operator!=(const Self& it) const
    {
        return _node != it._node;
    }

    // 相等比较运算符
    bool operator==(const Self& it) const
    {
        return _node == it._node;
    }
};

4.list与vector的比较

listvector 是 C++ 标准模板库(STL)中两个常用的序列容器,它们在内部实现、使用场景和性能方面都有显著差异。下面将详细介绍 listvector 的比较,从以下几个方面进行探讨:

4.1 内部实现

  • listlist 是一种双向链表,每个节点包含一个数据元素以及指向前一个和后一个节点的指针。它的实现基于动态分配的节点,节点通过指针相互连接。
  • vectorvector 是一种动态数组,底层实现为一个连续的内存块。它通过动态调整大小来适应插入和删除操作。

4.2 存储方式

  • list:存储的元素不连续,每个元素通过指针连接到前一个和后一个元素。由于链表节点是分散存储的,因此元素的物理位置在内存中不是连续的。
  • vector:存储的元素是连续的,这意味着 vector 的元素在内存中是紧挨着的。这样有利于缓存的利用,访问速度较快。

4.3 访问和遍历

  • list:访问任意位置的元素需要线性时间复杂度 (O(n)),因为必须从链表的头部或尾部开始遍历到目标位置。list 支持双向迭代。
  • vector:可以在常数时间内 (O(1)) 通过索引直接访问任意位置的元素。vector 支持随机访问和双向迭代。

4.4 插入和删除操作

  • list:在任意位置插入或删除元素的时间复杂度为常数时间 (O(1)),只需调整指针。然而,找到插入或删除位置可能需要线性时间。
  • vector:在末尾插入或删除元素的时间复杂度为常数时间 (O(1)),但是在中间位置插入或删除元素的时间复杂度为线性时间 (O(n)),因为需要移动其他元素以保持连续性。

4.5 内存管理

  • list:每个元素都需要额外的内存来存储前后指针,因此内存开销较大。链表节点是分散分配的,适合频繁的插入和删除操作。
  • vector:当需要增加容量时,vector 会重新分配一块更大的内存,并将现有元素复制到新位置,这可能涉及大量的元素移动。为了减少内存重新分配的频率,vector 通常会预留比实际需要更多的内存。

4.6 使用场景

  • list

    • 适用于频繁在中间插入或删除元素的场景。
    • 不需要随机访问,只需顺序访问的场景。
    • 内存管理分散,不需要连续内存的场景。
  • vector

    • 适用于需要频繁随机访问元素的场景。
    • 在末尾进行插入或删除操作较多的场景。
    • 数据量较小,不需要频繁调整大小的场景。

示例代码比较

下面的代码展示了 listvector 在插入、删除和访问操作上的不同:

#include <iostream>
#include <list>
#include <vector>

int main() {
	// list 示例
	std::list<int> myList = {10, 20, 30, 40, 50};
	auto it = myList.begin();
	++it;
	myList.insert(it, 15); // 在第二个位置插入15
	myList.erase(it);      // 删除第二个位置元素
	
	std::cout << "List elements: ";
	for (auto elem : myList) {
		std::cout << elem << " ";
	}
	std::cout << std::endl;
	
	// vector 示例
	std::vector<int> myVector = {10, 20, 30, 40, 50};
	myVector.insert(myVector.begin() + 1, 15); // 在第二个位置插入15
	myVector.erase(myVector.begin() + 1);      // 删除第二个位置元素
	
	std::cout << "Vector elements: ";
	for (auto elem : myVector) {
		std::cout << elem << " ";
	}
	std::cout << std::endl;
	
	// 访问元素
	std::cout << "List element at position 2: " << *std::next(myList.begin(), 2) << std::endl;
	std::cout << "Vector element at position 2: " << myVector[2] << std::endl;
	
	return 0;
}
// 输出:
// List elements: 10 15 30 40 50
// Vector elements: 10 20 30 40 50
// List element at position 2: 30
// Vector element at position 2: 30

5.结语

通过这篇文章,我们深入探讨了 C++ 标准模板库中的 list 类,包括其构造函数、迭代器、容量获取与元素访问函数、修改函数及其他功能函数。我们还实现了一个简单的 list 模拟,并详细比较了 listvector 的异同。希望通过这些内容,读者能够更全面地理解 list 的使用场景和实现原理,从而在实际编程中做出更加合理的数据结构选择,提高代码的效率和性能。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1714361.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

CI/CD(基于ESP-IDF)

主要参考资料 B站乐鑫信息科技《【乐鑫全球开发者大会】DevCon23 #15 &#xff5c;通过 CI/CD 进行流水线开发》 pytest-embedded乐鑫文档: https://docs.espressif.com/projects/pytest-embedded/en/latest/api.html 目录 CI/CD简介乐鑫内部CI/CD测试GitLab CI/CDGitHub Actio…

Thingsboard规则链:Customer Attributes节点详解

在物联网(IoT)平台Thingsboard的规则引擎中&#xff0c;Customer Attributes节点扮演了至关重要的角色&#xff0c;它允许用户访问和操作与客户&#xff08;Customer&#xff09;实体相关的属性数据。这些属性可以是静态信息&#xff0c;如客户名称、联系信息&#xff0c;或是动…

【PHP项目实战训练】——laravel框架的实战项目中mysql数据库的数据的数据在blade.php中展示

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;开发者-曼亿点 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 曼亿点 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a…

Kafka原生API使用Java代码-生产者-异步发送消息回调

文章目录 1、异步发送消息&回调1.1、pom.xml1.2、KafkaProducer1.java 1、异步发送消息&回调 回调就是接收kafka的响应 1.1、pom.xml <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0&q…

创建QT项目后只有一个pro文件(已解决) QT Creator

1. 问题回顾 工程仅有xx.pro文件 工程文件夹里面却有文件 这种情况并非创建工程失败&#xff0c;而是没有配置好文件 界面左下角 2. 解决方法 点击项目模式 配置不了 有点生气。。卸载了 重新安装&#xff0c;这次选择了多个kit勾选 回到项目模式&#xff0c;点进去 发现这…

短视频矩阵系统源码---开发BS架构B/S(Browser/Server Architecture)架构

短视频矩阵系统源头开发------- 第一款叫做筷子科技&#xff0c;这个筷子科技剪辑和发布都是没有问题的&#xff0c;但是前一段时间他的剪辑发个公告&#xff0c;每个账号只能发两条&#xff0c;另外它的唯一缺点就是它成本比较高的&#xff0c;入门门槛应该在12800左右&#…

视频汇聚EasyCVR视频监控平台GA/T 1400协议特点及应用领域解析

GA/T 1400协议&#xff0c;也被称为视图库标准&#xff0c;全称为《公安视频图像信息应用系统》。这一标准在公安系统中具有举足轻重的地位&#xff0c;它详细规定了公安视频图像信息应用系统的设计原则、系统结构、视频图像信息对象、统一标识编码、系统功能、系统性能、接口协…

大规模服务治理中etcd的实践与深度应用

导读&#xff1a;随着企业对于服务治理的日益重视&#xff0c;特别是在云原生和微服务架构的广泛应用下&#xff0c;百度小程序团队基于大模型服务治理的实战经验&#xff0c;结合分布式开源KV产品etcd&#xff0c;分享了其核心技术Raft与boltdb的实现原理&#xff0c;并深入剖…

文件上传漏洞简介

目录 漏洞原理 漏洞危害 利用场景 检测方法 防御方法 绕过手段 前端JS绕过 构造可解析后缀 修改Content-Type&#xff08;MIME&#xff09; 大小写绕过 文件头绕过 图片马 截断与特殊文件名 其他绕过 尝试绕过的步骤 漏洞原理 原理 攻击者构造恶意文件进行上传…

RTPS协议之Behavior Module

目录 交互要求基本要求RTPS Writer 行为RTPS Reader行为 RTPS协议的实现与Reader匹配的Writer的行为涉及到的类型RTPS Writer实现RTPS WriterRTPS StatelessWriterRTPS ReaderLocatorRTPS StatefulWriterRTPS ReaderProxyRTPS ChangeForReader RTPS StatelessWriter BehaviorBe…

keil4和5版本代码编译错误问题

需求: 在工作中, 遇到了keil4工程的老代码, 需要烧录到板子中. 问题: 电脑中只有keil5软件, 使用keil5软件打开, 编译后报了一堆错, 还是官方库文件的错误, 这就是版本不兼容了. 解决方法: 下载keil4软件, 不要和keil5放到一起. 进行如下操作. 0. 根据如下链接来下载keil4.7…

拥塞控制的微观行为与力学解释

本文以 tcptrace 图为基&#xff0c;描述传输的微观行为&#xff0c;并给出一个初中几何描述的压水井模型。 统计复用网络的拥塞控制&#xff0c;宏观看 inflight&#xff0c;微观看 pacing rate&#xff0c;宏观大方向不对&#xff0c;微观再正确也不行。 而网络的统计动力学…

推荐丨一键申请SSL证书,让网站实现HTTPS访问!

申请HTTPS证书可以简化为以下几个直接步骤&#xff0c;以便您能快速理解和操作&#xff1a; 1. 确定证书类型&#xff1a; - 单域名证书&#xff1a;适用于一个特定域名。 - 通配符证书&#xff1a;适用于同一主域名下的所有子域名。 - 多域名证书&#xff1a;覆盖多个不同的域…

polarctf靶场[reverse]shell、PE结构、拼接

[reverse]shell 考点&#xff1a;脱壳 将所解压的文件拖入DIE有无有壳&#xff0c;文件类型 发现有UPX壳&#xff0c;是32位的文件&#xff0c;先脱壳 用FFI工具脱壳 将脱壳后的程序用32位IDA进行反汇编 点开_main_0函数进行查看 看到flag&#xff0c;&#xff08;F5&#…

【PostgreSQL17新特性之-事务级别超时参数transaction_timeout】

PostgreSQL数据库里有多个和会话相关的参数&#xff0c;PostgreSQL17-beta1版本新增了一个transaction_timeout参数&#xff0c;来限制事务的持续时间。 当前的一些和会话相关的超时参数如下 -----------------------------------------------------------------------------…

【学习Day2】计算机基础

✍&#x1f3fb;记录学习过程中的输出&#xff0c;坚持每天学习一点点~ ❤️希望能给大家提供帮助~欢迎点赞&#x1f44d;&#x1f3fb;收藏⭐评论✍&#x1f3fb;指点&#x1f64f; 1.4 校验码 奇偶校验 ● 奇偶校验码的编码方法是&#xff1a; 由若干位有效信息的头部或者…

PowerPivot-跨表取值

在PowerPivot中&#xff0c;跨表取值通常涉及创建关系和使用DAX&#xff08;数据分析表达式&#xff09;函数。 以下是一些基本步骤和常用的DAX函数&#xff0c;帮助你在PowerPivot中实现跨表取值&#xff1a; 步骤1&#xff1a;创建关系 加载数据&#xff1a;确保你已将需要…

FreeRTOS【9】计数信号量使用

1.开发背景 FreeRTOS 基于上一篇了解了二值信号量后&#xff0c;如果需要设计需要累计信号量的次数就不行了&#xff0c;所以有了计数信号量&#xff0c;可以设置计数的最大值。同样&#xff0c;可以理解二值信号量是计数信号量的一种特例&#xff0c;即二值信号量是计数信号量…

利用QtScrcpy与Power Automate 实现微信群批量自动清理

Power Automate 一、Power Automate window系统自带软件 在开始使用Power Automate之前&#xff0c;需要熟悉它的基本概念和功能。Power Automate的核心概念包括触发器、操作和连接器。触发器是指触发自动化流程的事件&#xff0c;操作是指在自动化流程中执行的操作&#xff0…

数据结构(十)图

文章目录 图的简介图的定义图的结构图的分类无向图有向图带权图&#xff08;Wighted Graph&#xff09; 图的存储邻接矩阵&#xff08;Adjacency Matrix&#xff09;邻接表代码实现 图的遍历深度优先搜索&#xff08;DFS&#xff0c;Depth Fisrt Search&#xff09;遍历抖索过程…