C++笔记---vector

news2024/9/22 2:06:03

1. vector的介绍

vector其实就是我们所熟知的顺序表,但其是作为STL中的一个类模板而存在。

也就是说,vector是可以用来存储任意类型数据的顺序表,既可以是内置类型,也可以是自定义类型,或是STL中的其他容器。

vector的使用方式与string基本相似,但其接口相对于string来说要简洁地多,也与之后会介绍地各种STL的类模板跟为接近。

具体以vector - C++ Reference为准,本文为该文档的一个简单总结与标注。

2. vector的重要接口

以下表格中的函数都不是官方库中的函数原型,而是为了方便学习和理解而进行了简化的。

2.1 默认成员函数

2.1.1 构造函数

四种构造方式
(1) vector();默认构造函数
(2) vector(size_t n, cosnt T& val = T());构造并用val初始化前n个数据

(3) template <class InputIterator>

      vector(InputIterator first, InputIterator last);

用迭代器区间进行初始化
(4) vector(const vector& x);拷贝构造
 2.1.2 析构函数

释放掉容器内的资源。

2.1.3 赋值重载

 分配新的数据到容器中,替代他本来的数据,并根据新数据修改其size和capacity(深拷贝)。

2.2 迭代器相关

begin返回开始位置的迭代器
end返回最后一个数据的下一个位置的迭代器
rbegin用于逆向迭代
rend用于逆向迭代
cbegin用于const修饰的容器的迭代
cend用于const修饰的容器的迭代
crbegin用于const修饰的容器的逆向迭代
crend用于const修饰的容器的逆向迭代

2.3 大小容量相关

size_t size() const;返回数据个数
size_t max_size() const;返回容量大小
void resize(size_t n, T val = T());修改数据个数,若n<size则删除数据,若n>size则用val初始化多出来的数据
size_t capacity() const;返回容量大小
bool empty() const;判断容器是否为空
void reserve(size_t n);

确保capacity>=n,若capacity<n则扩容,若capacity>=n则无任何影响

void shrink_to_fit();是capacity减小到size,以减少空间浪费

2.4 访问相关

(1) vector<T>& operator[](size_t n);

(2) const vector<T>& operator[](size_t n) const;

使vector中的元素可像数组一样被访问

(1) vector<T>& at(size_t n);

(2) const vector<T>& at(size_t n) const;

返回vector中下标为n的元素的引用

(1) vector<T>& front();

(2) const vector<T>& front() const;

返回vector中第一个元素的引用

(1) vector<T>& back();

(2) const vector<T>& back() const;

返回vector中最后一个元素的引用

(1) T* data();

(2) const T*data() const; 

返回存储vector中元素的数组的地址

2.5 元素修改相关

(1) template<class InputIterator>

     void assign(InputIterator first, InputIterator last);

(2) void assign(size_t n, const T& val);

给vector赋新的值,效果类似于重新构造这个vector
void push_back(const T& val); 在vector尾部插入一个元素
void pop_back();删除vector尾部的一个元素
(1) iterator insert(iterator pos, const T& val);
(2) void insert(iterator pos, size_t n, const T& val);
(3) template <class InputIterator>
     void insert(iterator position, InputIterator first,InputIterator last);
在指定位置插入元素,效率低,非特殊情况不推荐使用
(1) iterator erase(iterator pos);
(1) iterator erase(iterator first, iterator last);
删除指定位置的数据,效率低,非特殊情况不推荐使用
void swap(vector<T>& x);交换两个vector的数据
void clear();清空vector,使size变为0

3. vector不完全模拟实现示例

STL标准库中,vector包括三个成员变量:

_start起始位置的迭代器
_finish最后一个元素的下一个位置的迭代器
_end_of_storage最大容量的下一个位置的迭代器
#pragma once
#include<iostream>
#include<assert.h>

namespace lbz
{
    template<class T>
    class vector
    {
    public:
        // Vector的迭代器是一个原生指针
        typedef T* iterator;

        typedef const T* const_iterator;

        iterator begin()
        {
            return _start;
        }

        iterator end()
        {
            return _finish;
        }

        const_iterator cbegin() const
        {
            return _start;
        }

        const_iterator cend() const
        {
            return _finish;
        }



        // construct and destroy
        // 可以不写初始化列表
        vector()
            :_start(nullptr)
            ,_finish(nullptr)
            ,_end_of_storage(nullptr)
        {}

        // C++11强制生成默认构造
        // vector() = default;

        vector(int n, const T& value = T())
        {
            reserve(n);
            while (n--)
            {
                push_back(value);
            }
        }

        vector(size_t n, const T& value = T())
        {
            reserve(n);
            while (n--)
            {
                push_back(value);
            }
        }

        template<class InputIterator>
        vector(InputIterator first, InputIterator last)
        {
            while (first != last)
            {
                push_back(*first);
                first++;
            }
        }

        /*vector(const vector<T>& v)
        {
            vector<T> tmp(v.cbegin(), v.cend());
            swap(tmp);
        }*/

        vector(const vector<T>& v)
        {
            reserve(v.size());
            auto it = v.cbegin();
            while (it != v.cend())
            {
                push_back(*it);
                it++;
            }
        }

        vector<T>& operator= (vector<T> v)
        {
            swap(v);
            return *this;
        }

        ~vector()
        {
            if(_start)
                delete[] _start;

            _start = nullptr;
            _finish = nullptr;
            _end_of_storage = nullptr;
        }

         // capacity

        size_t size() const
        {
            return (_finish - _start);
        }

        size_t capacity() const
        {
            return (_end_of_storage - _start);
        }

        /*void reserve(size_t n)
        {
            size_t old_size = size();
            size_t old_capacity = capacity();
            if (n > old_capacity)
            {
                while (n > old_capacity)
                {
                    old_capacity = old_capacity == 0 ? 4 : old_capacity * 2;
                }
                T* tmp = new T[old_capacity];

                //元素有动态资源时,memcpy存在问题
                memcpy(tmp, _start, sizeof(T) * old_size);
                delete[] _start;
                _start = tmp;
                _finish = _start + old_size;
                _end_of_storage = _start + old_capacity;
            }
        }*/

        void reserve(size_t n)
        {
            size_t old_size = size();
            size_t old_capacity = capacity();
            if (n > old_capacity)
            {
                while (n > old_capacity)
                {
                    old_capacity = old_capacity == 0 ? 4 : old_capacity * 2;
                }
                T* tmp = new T[old_capacity];
                for (size_t i = 0; i < old_size; i++)
                {
                    tmp[i] = _start[i];
                }
                delete[] _start;
                _start = tmp;
                _finish = _start + old_size;
                _end_of_storage = _start + old_capacity;
            }
        }

        void resize(size_t n, const T& value = T())
        {
            if (n < size())
            {
                _finish = _start + n;
            }
            else
            {
                reserve(n);
                while (size() < n)
                {
                    push_back(value);
                }
            }
        }



        ///access///
        T& operator[](size_t pos)
        {
            assert(pos < size());
            return _start[pos];
        }

        const T& operator[](size_t pos)const
        {
            assert(pos < size());
            return _start[pos];
        }



        ///modify/

        void push_back(const T& x)
        {
            reserve(size() + 1);
            *_finish = x;
            _finish++;
        }

        void pop_back()
        {
            _finish--;
        }

        void swap(vector<T>& v)
        {
            std::swap(_start, v._start);
            std::swap(_finish, v._finish);
            std::swap(_end_of_storage, v._end_of_storage);
        }

        iterator insert(iterator pos, const T& x)
        {
            assert(pos >= begin() && pos < end());
            size_t old_pos = pos - _start;
            reserve(size() + 1);
            pos = _start + old_pos;
            //typename vector<int>::iterator end = end();
            auto end = this->end();
            while (end != pos)
            {
                *end = *(end - 1);
                end--;
            }
            *end = x;

            _finish++;

            return pos;
        }

        iterator erase(iterator pos)
        {
            assert(pos >= begin() && pos < end());
            iterator it = pos;
            while (it != end() - 1)
            {
                *it = *(it + 1);
                it++;
            }
            _finish--;
            return pos;
        }

    private:
        iterator _start = nullptr; // 指向数据块的开始

        iterator _finish = nullptr; // 指向有效数据的尾

        iterator _end_of_storage = nullptr; // 指向存储容量的尾
    };
}

4. 迭代器失效问题

顾名思义,就是迭代器失去访问容器中元素的能力。

导致迭代器失效发生的有两种情况:(1)底层空间改变,(2)指定位置元素删除(erase)

4.1 底层空间改变导致迭代器失效

会引起其底层空间改变的操作(扩容或者赋值),都有可能是迭代器失效,比如:resize、reserve、insert、assign、push_back等。

#include <iostream>
using namespace std;
#include <vector>
int main()
{
    vector<int> v{1,2,3,4,5,6};
    auto it = v.begin();

    // 将有效元素个数增加到100个,多出的位置使用8填充,操作期间底层会扩容
    // v.resize(100, 8);

    // reserve的作用就是改变扩容大小但不改变有效元素个数,操作期间可能会引起底层容量改变
    // v.reserve(100);

    // 插入元素期间,可能会引起扩容,而导致原空间被释放
    // v.insert(v.begin(), 0);
    // v.push_back(8);

    // 给vector重新赋值,可能会引起底层容量改变
    v.assign(100, 8);

    while(it != v.end())
    {
        cout<< *it << " " ;
        ++it;
    } 
    cout<<endl;
    return 0;
}

出错原因:

以上操作,都有可能会导致vector发生扩容,而vector只要发生扩容都是异地扩,并且vector的迭代器实质上是指针。

这意味着,一旦发生扩容,原本的空间就会被释放掉,_start,_finish,_end_of_storage会分别指向新空间。但此时,it仍旧指向旧空间,变成了一个野指针。

在对it迭代器操作时,实际操作的是一块已经被释放的空间,而引起代码运行时崩溃。

解决方式:在以上操作完成之后,如果想要继续通过迭代器操作vector中的元素,就需要给it重新赋值。

其中,insert函数会返回指向插入元素的迭代器(插入单个元素时)。

4.2 指定位置元素删除(erase)导致迭代器失效

#include <iostream>
using namespace std;
#include <vector>
int main()
{
    int a[] = { 1, 2, 3, 4 };
    vector<int> v(a, a + sizeof(a) / sizeof(int));

    // 使用find查找3所在位置的iterator
    vector<int>::iterator pos = find(v.begin(), v.end(), 3);

    // 删除pos位置的数据,导致pos迭代器失效。
    v.erase(pos);

    cout << *pos << endl; // 此处会导致非法访问
    return 0;
}

这里有人会说:删掉3之后pos不是指向4吗?

你说的对,但是假如被删除的是最后一个元素呢?假如此处不是vector(顺序表)而是list(链表)呢?假如迭代器的底层根本就不是指针呢?

如果是以上三种情况,被删除元素的迭代器还会被下一个元素继承吗?显然不会,那么此时对pos解引用就会导致非法访问。

由于STL的实现是希望用户能够忽略掉底层实现,那么关于这个问题最好是要做到所有容器统一,认为对pos的访问是非法访问。

解决方法:和insert类似,erase函数在删除数据之后,会返回被删除数据的下一个位置的迭代器,让pos重新接收一下即可。

4.3 其他

windows下,vs编译器对迭代器的检测十分严格,一旦发生对失效的迭代器解引用的操作就一定会报错。

Linux下,g++编译器对迭代器失效的检测并不是非常严格,处理也没有vs下极端。

前面讲到的string和vector存在相同的迭代器失效的问题,注意警惕。

5. 使用memcpy拷贝的问题

前面在string的实现中,我们在进行异地扩容时使用了memcpy将原本的数据拷贝到新空间中。

但是在这里,我们则采用了手动赋值的方式,这是为什么呢?

void reserve(size_t n)
{
    size_t old_size = size();
    size_t old_capacity = capacity();
    if (n > old_capacity)
    {
        while (n > old_capacity)
        {
            old_capacity = old_capacity == 0 ? 4 : old_capacity * 2;
        }
        T* tmp = new T[old_capacity];

        //元素有动态资源时,memcpy存在问题
        memcpy(tmp, _start, sizeof(T) * old_size);
        delete[] _start;
        _start = tmp;
        _finish = _start + old_size;
        _end_of_storage = _start + old_capacity;
    }
}

void reserve(size_t n)
{
    size_t old_size = size();
    size_t old_capacity = capacity();
    if (n > old_capacity)
    {
        while (n > old_capacity)
        {
            old_capacity = old_capacity == 0 ? 4 : old_capacity * 2;
        }
        T* tmp = new T[old_capacity];
        for (size_t i = 0; i < old_size; i++)
        {
            tmp[i] = _start[i];
        }
        delete[] _start;
        _start = tmp;
        _finish = _start + old_size;
        _end_of_storage = _start + old_capacity;
    }
}

我们设想下面这样的情况:

int main()
{
    lbz::vector<lbz::string> v;
    v.push_back("1111");
    v.push_back("2222");
    v.push_back("3333");
    
    reserve(5);

    return 0;
}

在三次push_back完成之后,v中存储了三个string类型的对象,他们的_str分别指向str1 = "1111",str2 = "2222",str3 = "3333"。

接着,我们对vector进行了扩容,如果我们采用memcpy进行拷贝,那么只能完成对三个string对象的浅拷贝,也就是tmp中的三个string对象的_str依然指向str1,str2和str3。

当我们delete[] _start时,三个string类型的对象调用对应的析构函数分别释放掉str1,str2和str3。

此时,tmp中三个string对象的_str就都成了野指针,如果对其访问,就会造成程序的崩溃。

结论:

如果对象中涉及到资源管理时,千万不能使用memcpy进行对象之间的拷贝,因为memcpy是浅拷贝,否则可能会引起内存泄漏甚至程序崩溃。

 6. 动态二维数组

对于vector<vector<int>>的理解有困难的小伙伴,可以参考这篇文章:如何开辟动态二维数组(C语言)_动态开辟二维数组-CSDN博客

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

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

相关文章

学习算法的类型

学习算法的类型 一、说明 嘿&#xff0c;好奇的伙伴们&#xff01;今天&#xff0c;让我们踏上一段激动人心的机器学习算法领域之旅。&#x1f680; 如果你和我一样&#xff0c;你可能会发现机器学习的世界非常迷人&#xff0c;有时甚至有点让人不知所措。但不要害怕&#xf…

Qt Widget核心属性

文章目录 前言enabledgeometrywindowTitlewindowIconwindowOpacitycursorfonttoolTipfocusPolicystyleSheet 前言 Qt中的各种控件&#xff0c;都是继承自QWidget类&#xff0c;了解这个类的属性方法之后&#xff0c;后续的控件也通用 enabled enabled描述了一个控件是否处于…

未来工作趋势:零工小程序在共享经济中的作用

经济在不断发展的同时&#xff0c;科技也在飞速发展。零工经济作为一种新兴的工作模式&#xff0c;正在全球范围内迅速崛起。特别是在中国&#xff0c;随着数字经济的蓬勃发展和共享经济模式的深入推广&#xff0c;零工小程序在促进就业、提升资源利用效率方面显示出了巨大的潜…

2024年AMC10美国数学竞赛倒计时两个月:吃透1250道真题和知识点(持续)

根据通知&#xff0c;2024年AMC10美国数学竞赛的报名还有两周&#xff0c;正式比赛还有两个月就要开始了。计划参赛的孩子们要记好时间&#xff0c;认真备考&#xff0c;最后冲刺再提高成绩。 那么如何备考2024年AMC10美国数学竞赛呢&#xff1f;做真题&#xff0c;吃透真题和…

FreeRTOS学习笔记—②RTOS的认识及任务管理篇

由于正在学习韦东山老师的RTOS课程&#xff0c;结合了网上的一些资料&#xff0c;整理记录了下自己的感悟&#xff0c;用于以后自己的回顾。如有不对的地方请各位大佬纠正。 文章目录 一、RTOS的优势二、RTOS的核心功能2.1 任务管理2.1.1 任务的创建2.1.2 任务的删除*2.1.3 任…

Windows系统安装node.js环境并创建本地服务使用内网穿透发布至公网

目录 前言 1.安装Node.js环境 2.创建node.js服务 3. 访问node.js 服务 4.内网穿透 4.1 安装配置cpolar内网穿透 4.2 创建隧道映射本地端口 5.固定公网地址 前言 作者简介&#xff1a; 懒大王敲代码&#xff0c;计算机专业应届生 今天给大家聊聊Windows系统安装node.js环…

Arch - 架构安全性_认证(Authentication)的标准和实现

文章目录 OverView认证的标准认证的基础认证的范围认证的标准与实践HTTP认证框架Web认证&#xff08;表单认证&#xff09;WebAuthn标准认证流程示例&#xff1a;WebAuthn 小结 认证的实现JAASSpring Security 和 Shiro小结 OverView 即使只限定在“软件架构设计”这个语境下&…

MonoHuman: Animatable Human Neural Field from Monocular Video 精读

一、共享双向变形模块 1. 模块的核心思想 共享双向变形模块的核心目标是解决从单目视频中生成不同姿态下的3D人体形状问题。因为视频中的人物可能处于各种动态姿态下&#xff0c;模型需要能够将这些不同姿态的几何形状进行变形处理&#xff0c;以适应标准的姿态表示并生成新的…

# VMware 共享文件

VMware tools快速安装 VMware 提供了 open-vm-tools&#xff0c;这是 VMware 官方推荐的开源工具包&#xff0c;通常不需要手动安装 VMware Tools&#xff0c;因为大多数 Linux 发行版&#xff08;包括 Ubuntu、CentOS 等&#xff09;都包含了 open-vm-tools&#xff0c;并且已…

FreeRTOS内部机制学习02(消息队列深度学习)

文章目录 队列的核心以及好处队列的核心队列的好处 深入源码了解队列机制深入队列读取操作深入队列写入操作读写队列出超时时间 信号量深入信号量获取以及释放操作 互斥量互斥量和信号量的不同深入源码看优先级继承是怎么操作到的 队列的核心以及好处 队列的核心 队列的核心就…

如何打造高效办公楼物业管理系统?Java SpringBoot+Vue架构详解,实现智能化管理,提升工作效率

&#x1f34a;作者&#xff1a;计算机毕设匠心工作室 &#x1f34a;简介&#xff1a;毕业后就一直专业从事计算机软件程序开发&#xff0c;至今也有8年工作经验。擅长Java、Python、微信小程序、安卓、大数据、PHP、.NET|C#、Golang等。 擅长&#xff1a;按照需求定制化开发项目…

GraphPad Prism 10 for Mac/Win:高效统计分析与精美绘图的科学利器

GraphPad Prism 10 是一款专为科研工作者设计的强大统计分析与绘图软件&#xff0c;无论是Mac还是Windows用户&#xff0c;都能享受到其带来的便捷与高效。该软件广泛应用于生物医学研究、实验设计和数据分析领域&#xff0c;以其直观的操作界面、丰富的统计方法和多样化的图表…

Redis中使用布隆过滤器解决缓存穿透问题

一、缓存穿透(失效)问题 缓存穿透是指查询一个一定不存在的数据&#xff0c;由于缓存中没有命中&#xff0c;会去数据库中查询&#xff0c;而数据库中也没有该数据&#xff0c;并且每次查询都不会命中缓存&#xff0c;从而每次请求都直接打到了数据库上&#xff0c;这会给数据…

认知杂谈53

今天分享 有人说的一段争议性的话 I I 1.自助者天助 首先呢&#xff0c;咱得好好琢磨琢磨“自助者天助”这句话。这话说起来好像有点高深莫测的感觉&#xff0c;其实啊&#xff0c;道理特别简单。 就是说要是你自己都不乐意努力&#xff0c;那老天爷也不会平白无故地来帮你…

[环境配置]ubuntu20.04安装后wifi有图标但是搜不到热点解决方法

最近刚入手一台主机&#xff0c;暗影精灵8plus电竞主机&#xff0c;安装ubuntu后wifi怎么都搜不到热点&#xff0c;前后重装系统6次才算解决问题。这个心酸历程只有搞技术人才明白。下面介绍我解决过程。 首先主机到手后是个windows10系统&#xff0c;我用无线网连接了一下&am…

【每日刷题】Day113

【每日刷题】Day113 &#x1f955;个人主页&#xff1a;开敲&#x1f349; &#x1f525;所属专栏&#xff1a;每日刷题&#x1f34d; &#x1f33c;文章目录&#x1f33c; 1. 91. 解码方法 - 力扣&#xff08;LeetCode&#xff09; 2. LCR 098. 不同路径 - 力扣&#xff08;…

服务器流量监控工具vnStat的简单使用以及关于Linux的软中断信号(signal)的一点内容

一、服务器流量监控工具vnStat的简单使用 vnStat是为Linux和BSD设计的基于控制台的网络流量监控工具&#xff0c;通过它可以非常方便在命令行查看流量统计情况。它可以保留某个或多个所选择的网络接口的网络流量日志。为了生成日志&#xff0c;vnStat使用内核提供的信息。换句话…

2024年必看的4款录屏新星,谁才是你的菜?

嘿&#xff0c;小伙伴们&#xff0c;你们的职场好帮手来啦。今天我们要说说办公室里经常被忽略但实际上超有用的东西——录屏软件。现在大家都用数字化工具办公了&#xff0c;不管是做教学视频、记录会议&#xff0c;还是直播玩游戏&#xff0c;录屏软件都是必不可少的。可是市…

FRP内网穿透使用常见问题

本文解答一些关于FRP内网穿透的常见问题 FRP简介 FRP是一款开源的高性能反向代理应用&#xff0c;支持多种协议的内网穿透。它允许用户在外网环境中访问位于内网中的服务器和服务&#xff0c;如Web服务器、MySQL数据库、以及其他基于TCP/UDP的应用程序。FRP以其灵活的配置选项…

C++实现俄罗斯方块(Windows控制台版)

C实现俄罗斯方块&#xff08;Windows控制台版&#xff09; 在油管上看到一个使用C控制台编写的俄罗斯方块小游戏&#xff0c;源代码200多行&#xff0c;B站上也有相关的讲解视频&#xff0c;非常不错&#xff0c;值得学习。 B站讲解视频地址为&#xff1a;【百万好评】国外技术…