• 侯捷 C++ STL标准库和泛型编程 —— 3 容器(序列式容器)


    3 容器

    3.1 容器结构分类

    分类:序列式容器 Sequence Container,关联式容器 Associative Container

    • 序列式容器:按照放入的次序进行排列

      image-20230818103748215
      • Array 数组,固定大小
      • Vector 向量,会自动扩充大小
      • Deque 双向队列,双向都可以扩充
      • List 链表,双向链表
      • Forward-List 链表,单向链表
    • 关联式容器:有 keyvalue,适合快速的查找

      STL中实现使用红黑树(高度平衡二叉树)哈希表

      • Set,key 就是 value,元素不可重复

      • Map,keyvalue 是分开的,元素不可重复

      • Multi~,元素是可以重复的

      • Unordered~,HashTable Separate Chaining

        image-20230818103522538

    其中 ArrayForward-ListUnordered~ 都是C++11的

    3.2 序列式容器

    3.2.1 array
    测试
    image-20230819103001457
    #include 
    #include 
    #include  
    #include  //qsort, bsearch, NULL
    
    void test_array() {
        cout << "\n test_array().......... \n";
    
        // 创建一个包含long型元素的array容器,ASIZE为数组的大小
        array<long, ASIZE> c;
    
        // 记录开始时间
        clock_t timeStart = clock();
    
        // 填充数组 c 中的元素,使用 rand() 生成随机数
        for (long i = 0; i < ASIZE; ++i) {
            c[i] = rand();
        }
        // 输出填充数组所花费的毫秒数
        cout << "milli-seconds : " << (clock() - timeStart) << endl;
    
        // 输出数组的大小、第一个元素、最后一个元素、起始地址
        cout << "array.size()= " << c.size() << endl;
        cout << "array.front()= " << c.front() << endl;
        cout << "array.back()= " << c.back() << endl;
        cout << "array.data()= " << c.data() << endl;
    
        // 获取目标值
        long target = get_a_target_long();
    
        // 记录开始时间
        timeStart = clock();
        // 使用标准库的 qsort 函数(快排)对数组 c 进行排序
        ::qsort(c.data(), ASIZE, sizeof(long), compareLongs);
    
        // 使用标准库的 bsearch 函数(二分查找)在排序后的数组中搜索目标值
        long* pItem = (long*)::bsearch(&target, c.data(), ASIZE, sizeof(long), compareLongs);
        // 输出排序和搜索所花费的毫秒数
        cout << "qsort()+bsearch(), milli-seconds : " << (clock() - timeStart) << endl;
    
        // 如果找到目标值,输出该值;否则输出未找到消息
        if (pItem != NULL)
            cout << "found, " << *pItem << endl;
        else
            cout << "not found! " << endl;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46

    运行结果:

    image-20230818113016596

    随机数据填充容器:47ms;排序和搜索:187ms


    深度探索

    C++TR1下(比较简单):

    template <typename _Tp, std::size_t _Nm>
    struct array
    {
    	typedef _Tp value_type;
    	typedef _Tp* pointer;
    	typedef value_type* iterator; // 迭代器为_Tp*
    
    
    	value_type _M_instance[_Nm ? _Nm : 1]; // 如果_Nm为0,就分配一个空间
    
    	iterator begin() { return iterator(&_M_instance[0]); }
    	iterator end() { return iterator(&_M_instance[_Nm]); }
    	...
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    GCC4.9下(复杂且无益处):

    image-20230827201155808
    // GCC4.9通过多个typedef以下面的逻辑创建的array里的data
    typedef int T[100]; // T即类型int[100] 
    T c; // 与int c[100]一样
    
    • 1
    • 2
    • 3
    3.2.2 vector
    测试
    image-20230819102940829
    #include 
    #include 
    #include 
    #include  //abort()
    #include   //snprintf()
    #include 
    #include  
    #include  	//sort()
    
    // 测试函数,接受一个引用类型的长整型参数
    void test_vector(long& value)
    {
        cout << "\ntest_vector().......... \n";
         
        vector<string> c;  	// 创建一个字符串类型的向量
        char buf[10];
        
        clock_t timeStart = clock();	// 记录开始时间							
        for(long i=0; i< value; ++i)	// 循环插入随机生成的字符串
        {
            try {
                snprintf(buf, 10, "%d", rand());	// 将随机整数转换为字符串
                c.push_back(string(buf));     	// 将字符串添加到向量中
            } // 这里是处理异常,如内存不够
            catch(exception& p) {
                cout << "i=" << i << " " << p.what() << endl;	
                // 输出出现异常的信息以及对应的索引值
                // 曾經最高 i=58389486 then std::bad_alloc
                abort();	// 异常处理后中止程序
            }
        }
        cout << "milli-seconds : " << (clock()-timeStart) << endl;	// 输出填充向量花费时间
        cout << "vector.max_size()= " << c.max_size() << endl;	// 输出向量的最大容量
        cout << "vector.size()= " << c.size() << endl;	// 输出向量的实际大小
        cout << "vector.front()= " << c.front() << endl;	// 输出向量的首元素
        cout << "vector.back()= " << c.back() << endl;	// 输出向量的末尾元素
        cout << "vector.data()= " << c.data() << endl;	// 输出向量地址
        cout << "vector.capacity()= " << c.capacity() << endl << endl;	// 输出向量的容量
    
        // 直接find来查找————次序查找
        string target = get_a_target_string();	// 获取一个目标字符串
        {
            timeStart = clock();	// 记录开始时间
            auto pItem = find(c.begin(), c.end(), target);	// 在向量中查找目标字符串
            cout << "std::find(), milli-seconds : " << (clock()-timeStart) << endl;  
            
            if (pItem != c.end())
                cout << "found, " << *pItem << endl << endl;	// 输出找到的目标字符串
            else
                cout << "not found! " << endl << endl;	// 输出未找到目标字符串
        }
    
        // 先排序再二分法查找
        {
            timeStart = clock();	// 记录开始时间
            sort(c.begin(), c.end());	// 对向量中的字符串进行排序
            cout << "sort(), milli-seconds : " << (clock()-timeStart) << endl; 
            
            timeStart = clock();	    
            string* pItem = (string*)::bsearch(&target, (c.data()), 
                                               c.size(), sizeof(string), compareStrings); 
            cout << "bsearch(), milli-seconds : " << (clock()-timeStart) << endl; 
           
            if (pItem != NULL)
                cout << "found, " << *pItem << endl << endl;	// 输出在排序后向量中找到的目标字符串
            else
                cout << "not found! " << endl << endl;	// 输出在排序后向量中未找到目标字符串
        }
        
        c.clear();	// 清空向量中的数据
        test_moveable(vector<MyString>(),vector<MyStrNoMove>(), value);	// 调用另一个函数进行测试
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73

    这是 array 在后面插入元素,其中若空间 capacity 不够,其会进行两倍扩充——即空间不够时会将原来的空间 *2

    c.push_back(string(buf));
    
    • 1

    运行结果:

    随机数据填充容器:3063ms;直接搜索:0ms(运气很好);排序后二分查找:2765ms


    深度探索

    GCC2.9下:

    一共3个指针:startfinishend_of_storage

    所以 sizeof(vector)12

    image-20230827163726770
    template <class T, class Alloc = alloc>
    class vector
    {
    public:
    	typedef T value_type;
    	typedef value_type* iterator; // 迭代器就是T*
    	typedef value_type& reference;
    	typedef size_t size_type;
    protected:
    	iterator start;
    	iterator finish;
    	iterator end_of_storage;
    public:
    	iterator begin() { return start; }
    	iterator end() { return finish; }
    	size_type size() const { return size_type(end() - begin()); }
    	size_type capacity() const { return size_type(end_of_storage - begin()); }
    	bool empty() const { return begin() == end(); }
    	reference operator[](size_type n) { return *(begin() + n); }
        // 所有连续储存的容器都有[]的重载
    	reference front() { return *begin(); }
    	reference back() { return *(end() - 1); }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    vector 每次成长会大量调用元素的拷贝构造函数和析构函数,是一个大成本

    void push_back(const T& x)
    {
        if (finish != end_of_storage) // 还有备用空间
        {
            construct(finish, x); // 全局函数
            ++finish;
        }
        else // 无备用空间
            insert_aux(end(), x);
    }
    
    template <class T, class Alloc>
    void vector<T, Alloc>::insert_aux(iterator position, const T& x){
    if (finish != end_of_storage){ // insert_aux还会被其他函数调用所以还有检查
        // 在‘备用空间起始处’构建一个元素以vector最后一个元素为初值
        // insert_aux也可能被insert调用,元素插入位置不定
        construct(finish, *(finish - 1));
        ++finish;
        T x_copy = x;
        copy_backward(position, finish - 2, finish - 1);
        *position = x_copy;
    }
    else{
        const size_type old_size = size();
        const size_type len = old_size != 0 ? 2 * old_size : 1;
        // 原大小为0,则分配1;否则,分配原大小的2倍
        
        iterator new_start = data_allocator::allocate(len);
        iterator new_finish = new_start;
        try{
            // 拷贝安插点前的原内容
            new_finish = uninitialized_copy(start, position, new_start);
            construct(new_finish, x);
            ++new_finish;
            // 拷贝安插点后的原内容
            new_finish = uninitialized_copy(position, finish, new_finish);
        }
        catch (...){
            destroy(new_start, new_finish);
            data_allocator::deallocate(new_start, len);
            throw;
        }
        // 解构并释放原vector
        destroy(begin(), end());
        deallocate();
        // 调整迭代器,指向新vector
        start = new_start;
        finish = new_finish;
        end_of_storage = new_start + len;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50

    GCC4.9下变得复杂:

    image-20230827174519929

    且迭代器也变得乱七八糟,舍近求远,何必如此!!

    image-20230827175349603
    3.2.3 list
    测试
    image-20230819103100219
    // 同理
    void test_list(long& value)
    { 
        ...
            
        list<string> c;  // 创建一个字符串列表  	
        char buf[10];  // 字符串缓冲区
    	
        ...
    		
        string target = get_a_target_string();  // 获取目标字符串		
        timeStart = clock();		
        auto pItem = find(c.begin(), c.end(), target);  // 在列表中查找目标字符串						
        cout << "std::find(),milli-seconds : " << (clock()-timeStart) << endl;  // 输出查找时间		
    	
        ...
        	
        timeStart = clock();		
        c.sort();  // 对列表进行排序						
        cout << "c.sort(), milli-seconds : " << (clock()-timeStart) << endl;  // 输出排序时间		    	
    
        c.clear();  // 清空	 
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    注意: c.sort(); 是容器自带的排序函数,如果容器自带肯定是要比全局的排序函数好的

    list 同样也是用 c.push_back(string(buf)); 往里添加元素的

    运行结果:

    image-20230819105152408

    随机数据填充容器:3265ms;直接搜索:16ms;排序:2312ms


    深度探索

    GCC2.9

    image-20230822105307837
    // list class
    template <class T, class Alloc = alloc>
    class list
    {
    protected:
    	typedef __list_node<T> list_node;
    public:	
    	typedef list_node* link_type;
    	typedef __list_iterator<T, T&, T*> iterator; // 迭代器,每一个容器都会 typedef
    	// 只传一个参数就行了 不理想
    protected:
    	link_type node; // 一个 __list_node 的指针
    ...
    };
    
    // 节点 class
    template <class T>
    struct __list_node
    {
    	typedef void* void_pointer; // 每次用还要转换类型 不理想
    	void_pointer prev;
    	void_pointer next;
    	T data;
    };
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    除了 array,vector 这样是连续存储的容器,其他容器的 iterator 都是智能指针,其有大量的操作符重载 —— 模拟指针

    基本上所有的 iterator 都有下面5typedef 和一大堆操作符重载

    // iterator class
    template <class T, class Ref, class Ptr>
    struct __list_iterator
    {
    	typedef __list_iterator<T, T&, T*> self;
    	typedef bidirectional_iterator_tag iterator_category; // (1)双向迭代器	
    	typedef T value_type; // (2)迭代器所指对象的类型
    	typedef Ptr pointer; // (3)迭代器所指对象的指针类型
    	typedef Ref reference; // (4)迭代器所指对象的引用类型
    	typedef __list_node<T>* link_type;
    	typedef ptrdiff_t difference_type; // (5)两个迭代器之间的距离类型
    
    	link_type node; // iterator本体,一个指向__list_node的指针
    
    	reference operator*() const { return (*node).data; }
    	pointer operator->() const { return &(operator*()); }
    	self& operator++() // ++i
        {
            node = (link_type)((*node).next); // 移到下一个节点
            return *this; 
        }
    	self operator++(int) // i++ 为了区分加上了一个参数其实无用
        {
            self tmp = *this; 
            ++*this; 
            return tmp; 
        }
    	...
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    注意:self operator++(int){...}self tmp = *this; 中,由于先调用了 = 唤起了 copy ctor 用以创建 tmp 并以 *this 为初值,所以不会唤起 operator* —— *this 已经被解释为 ctor 的参数

    下面的 ++*this; 同理

    与 int 类似:iterator 可以连续前++,但不能连续后++

    image-20230822173147636image-20230822173354379

    所以前++是返回引用,后++返回值

    因为要符合前闭后开原则,所以在 list 尾端加上了一个空白节点

    image-20230827092146933

    GCC4.9中做出了改进:

    • 迭代器模板参数从三个 --> 只有一个
    • 节点 class 中的前后指针类型从 void* --> _LIst_node_base*
    image-20230827091438719

    在GCC4.9中 sizeof(list)8

    在GCC2.9中 sizeof(list)4

    3.2.4 forward_list
    测试
    image-20230819103623779
    // 同理
    void test_forward_list(long& value)
    {
        ...
         
        forward_list<string> c;  // 创建一个前向列表  	
        char buf[10];  // 字符串缓冲区
    			
        ...
        
        
        string target = get_a_target_string();  // 获取目标字符串	
        timeStart = clock();	
        auto pItem = find(c.begin(), c.end(), target);  // 在前向列表中查找目标字符串	
        cout << "std::find(),milli-seconds : " << (clock()-timeStart) << endl;  // 输出查找时间		
    	
        ...
        	
        timeStart = clock();		
        c.sort();  // 进行排序					
        cout << "c.sort(), milli-seconds : " << (clock()-timeStart) << endl;  // 输出排序时间		
    	
        c.clear();  // 清空	 
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    注意:forward_list 只有 c.push_front(); 且没有 forward_list.back() forward_list.size()

    运行结果:

    image-20230819110505646

    随机数据填充容器:3204ms;直接搜索:15ms;排序:2656ms

    深度探索

    list 相似,略

    image-20230827201331283
    3.2.6 deque
    测试
    image-20230819103846501

    类似vector,两边都能扩充,实际上是分段连续的

    其是通过 map(是一个vector,但在扩充时会 copy 到中间)里的指针指向各个 bufferbuffer 里再存数据,每个 buffer 的大小一致,每次扩充都是扩充一个指针指向一个新的 buffer

    image-20230819111424969
    void test_deque(long& value)
    {
        ...
         
        deque<string> c;  // 创建一个双端队列  	
        char buf[10];  // 字符串缓冲区
    	
        ...
        
        string target = get_a_target_string();  // 获取目标字符串	
        timeStart = clock();	
        auto pItem = find(c.begin(), c.end(), target);  // 在队列中查找目标字符串	
        cout << "std::find(),milli-seconds : " << (clock()-timeStart) << endl;  // 输出查找时间		
    	
        ...
        	
        timeStart = clock();		
        sort(c.begin(), c.end());  // 对队列进行排序					
        cout << "sort(),milli-seconds : " << (clock()-timeStart) << endl;  // 输出排序时间		
    	
        c.clear();  // 清空队列
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    运行结果:

    image-20230819112747434

    随机数据填充容器:2704ms;直接搜索:15ms;排序:3110ms

    下面的 stackqueue 内部都是一个 deque,所以技术上这两个可以看作容器适配器 Container Adapter


    深度探索

    GCC2.9

    template <class T, class Alloc = alloc, size_t BufSiz = 0>
    class deque
    {
    public:
    	typedef T value_type;
    	typedef __deque_iterator<T, T&, T*, BufSiz> iterator;
    	typedef size_t size_type;
    	typedef T* pointer;
    protected:
    	typedef pointer* map_pointer; // T** 指向指针的指针
    protected:
    	iterator start;
    	iterator finish;
    	map_pointer map;
    	size_type map_size;
        // 两个迭代器:16*2,一个指针:4,一个size_t:4,一共40字节
    public:
    	iterator begin() { return start; }
    	iterator end() { return finish; }
        size_type size() const { return finish - start; }
        ...
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    注意:第三个模板参数 size_t BufSiz = 0 有一个函数:

    如果不为0,则 buffer size 就是传入的数据

    如果为0,表示预设值,那么

    如果 sz = sizeof(value_type) < 512,传回 512/sz
    如果 sz = sizeof(value_type) >= 512,传回 1

    迭代器四个指针,cur 指向当前元素,first 指向当前 buffer 的第一个元素,last 指向当前 buffer 的最后一个元素的下一个,node 指向当前 buffer 在 map(控制中心)的指针

    image-20230828084817056
    // deque迭代器
    template <class T, class Ref, class Ptr, size_t BufSiz>
    struct __deque_iterator
    {
    	typedef random_access_iterator_tag iterator_category; // (1)
    	typedef T value_type; // (2)
    	typedef Ptr pointer; // (3)
    	typedef Ref reference; // (4)
    	typedef size_t size_type;
    	typedef ptrdiff_t difference_type; // (5)
    	typedef T** map_pointer;
    	typedef __deque_iterator self;
    
    	T* cur;
    	T* first;
    	T* last;
    	map_pointer node; // 指向指针的指针
        // 四个指针,一共16字节
    	...
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    deque 中的 insert 函数:

    iterator insert(iterator position, const T& x)
    {
        if (position.cur == start.cur) // 插入点在deque最前端      
        {							// 交给push_front
            push_front(x);
            return start;
        }
        else if (position.cur == finish.cur) // 插入点在deque最尾端
        {								  // 交给push_front
            push_back(x);
            iterator tmp = finish;
            --tmp;
            return tmp;
        }
        else // 在中间插入
        {
            return insert_aux(position, x);
        }   
    }
    
    iterator insert_aux(iterator pos, const T& x)
    {
        difference_type index = pos - start; // 安插点前元素个数
        value_type x_copy = x;
        if (index < size() / 2) // 安插点前的元素少————搬前面的
        {
            push_front(front());
            ...
            copy(front2, pos1, front1); // 搬元素
        }
        else // 安插点后的元素少————搬后面的
        {
            push_back(back());
            ...
            copy_backward(pos, back2, back1);
        }
        *pos = x_copy; // 安插点设新值
        return pos;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    deque 模拟连续空间(deque iterator 的功能):

    image-20230828093535797
    • -:两个位置之间的距离——前闭后开的元素个数

      image-20230828093602891

      两个位置之间的距离 = buffer_size * 两个位置之间 buffer 的数量 + 末尾位置到 buffer 前端的长度 + 起始位置到 buffer 末尾的长度

    • ++/--:注:下面带参数的是后++(i++)

      image-20230828183715764
    • +=/+

      self& operator+=(difference_type n)
      {
          difference_type offset = n + (cur - first);  
          if (offset >= 0 && offset < difference_type(buffer_size()))  
              // 若+了之后在缓冲区大小范围内
              cur += n;  // 直接移动迭代器 n 步
          else
          {
              difference_type node_offset = offset > 0 ? offset / difference_type(buffer_size()) 
                  : -difference_type((-offset - 1) / buffer_size()) - 1;
              // 计算偏移的节点数,offset > 0判断是为了之后的-=/-
              // 这里(-offset - 1)后除buffer_size()再-1是为了offset==buffer_size()的情况
              set_node(node + node_offset);  // 调整节点,使迭代器指向正确的节点
              cur = first + (offset - node_offset * difference_type(buffer_size()));  // 调整迭代器位置
          }
          return *this;
      }
      
      self operator+(difference_type n) const
      {
          self tmp = *this;  // 复制当前迭代器
          return tmp += n;   // 返回向前移动 n 步后的迭代器副本
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
    • -=/-

      // -就等于+负的
      self& operator-=(difference_type n) { return *this += -n; }
      self operator-(difference_type n) const
      {
          self tmp = *this;
          return tmp -= n;
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
    • []

      reference operator[](difference_type n) const 
      { return *(*this + n); }
      
      • 1
      • 2

    GCC4.9下:其实没必要这样

    image-20230829210932604

    G2.91 允许指派 buffer_size

    G4.53 不允许了

    3.2.7 stack,queque
    测试

    stack:

    image-20230819104008973

    queue:

    image-20230819104029805

    stackqueue 是通过 push()pop() 来放取元素的,且无iterator 的操作


    深度探索

    stackqueue 内部默认用 deque 来实现,所以有时候不会将这两个认为容器而是一个适配器

    • 底层函数可以使用 listdeque(deque默认更快)

    • queue 不能用 vector,stack 可以用 vector

    • set,map 都不能用

    用时编译器可以通过的,但在具体使用函数时,若遇到底层容器没有这个函数时,就会报错

    // queue
    template<class T, class Sequence = deque<T>>
    class queue
    {
    	...
    protected:
    	Sequence c; // 底层容器
    public:
        // 都是通过底层容器来实现
    	bool empty() const { return c.empty(); }
    	size_type size() const { return c.size(); }
    	reference front() { return c.front(); }
    	const_reference front() const { return c.front(); }
    	reference back() { return c.back(); }
    	const_reference back() const { return c.back(); }
    	void push(const value_type& x) { c.push_back(x); }
    	void pop() { c.pop_front(); }
    };
    
    // stack
    template<class T, class Sequence = deque<T>>
    class stack
    {
    	...
    protected:
    	Sequence c; // 底层容器
    public:
        // 都是通过底层容器来实现
    	bool empty() const { return c.empty(); }
    	size_type size() const { return c.size(); }
    	reference top() { return c.back(); }
    	const_reference top() const { return c.back(); }
    	void push(const value_type& x) { c.push_back(x); }
    	void pop() { c.pop_back(); }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    stack,queue 都不允许遍历,也不提供 iterator

  • 相关阅读:
    UWP与WPF:微软两大UI框架
    自用的一些网址,码住!
    初探Vue.js及Vue-Cli
    链表常见面试题分析
    【YOLOv7/YOLOv5系列算法改进NO.48】构建新的轻量网络—Slim-neck by GSConv(2022CVPR)
    博弈论(Nim游戏 , 有向图游戏)
    【java深入学习第2章】Spring Boot 结合 Screw:高效生成数据库设计文档之道
    Android自定义控件(四) 自定义百度贴吧水波纹Loading效果
    计算机毕业设计(附源码)python自助旅游平台
    C/C++教程 从入门到精通《第二十四章》——Qt制作天气预报
  • 原文地址:https://blog.csdn.net/WJwwwwwww/article/details/133337933