C++ - vector

文章目录

vector的介绍及使用

介绍

在 C++ 中,vector 是标准模板库(STL)提供的动态数组容器,它可以存储同类型元素,并且支持动态大小调整,是日常开发中非常常用的数据结构。

使用

构造函数

构造函数 接口说明
vector() 无参数构造
vector(size_type n, const value_type& val =value_type()) 构造并初始化n个val
vector (const vector& x); 拷贝构造
vector (InputIterator first, InputIterator last); 使用迭代器进行初始化构造
vector() :无参数构造
cpp 复制代码
#include <iostream>
#include <vector>
using namespace std;

int main() {
    // 构造一个空的 vector,不包含任何元素
    vector<int> v1;
    cout << "v1 的大小: " << v1.size() << endl;
    cout << "v1 是否为空: " << (v1.empty() ? "是" : "否") << endl;
    return 0;
}
vector(size_type n, const value_type& val =value_type()):构造并初始化n个val
cpp 复制代码
#include <iostream>
#include <vector>
using namespace std;

int main() {
    // 构造一个包含 5 个值为 10 的 int 类型 vector
    vector<int> v2(5, 10);
    cout << "v2 的元素: ";
    for (int num : v2) {
        cout << num << " ";
    }
    cout << endl;
    return 0;
}
vector (const vector& x) : 拷贝构造
cpp 复制代码
#include <iostream>
#include <vector>
using namespace std;

int main() {
    vector<int> v3 = {1, 2, 3, 4, 5};
    // 使用拷贝构造函数,构造一个与 v3 内容相同的 vector v4
    vector<int> v4(v3);
    cout << "v4 的元素: ";
    for (int num : v4) {
        cout << num << " ";
    }
    cout << endl;
    return 0;
}
vector (InputIterator first, InputIterator last) :使用迭代器进行初始化构造
cpp 复制代码
#include <iostream>
#include <vector>
#include <list>
using namespace std;

int main() {
    list<int> l = {6, 7, 8, 9, 10};
    // 使用 list 的迭代器范围 [l.begin(), l.end()) 来构造 vector v5
    vector<int> v5(l.begin(), l.end());
    cout << "v5 的元素: ";
    for (int num : v5) {
        cout << num << " ";
    }
    cout << endl;
    return 0;
}

iterator的使用

iterator的使用 接口说明
begin+end 获取第一个数据位置的iterator/const_iterator, 获取最后一个数据的下一个位置的iterator/const_iterator
rbegin+rend 获取最后一个数据位置的reverse_iterator,获取第一个数据前一个位置
begin+end
cpp 复制代码
#include <iostream>
#include <vector>
using namespace std;

int main() {
    vector<int> v = {1, 2, 3, 4, 5};

    // 使用正向迭代器遍历 vector
    cout << "正向遍历:";
    for (vector<int>::iterator it = v.begin(); it != v.end(); ++it) {
        cout << *it << " ";
    }
    cout << endl;

    return 0;
}
rbegin+rend
cpp 复制代码
#include <iostream>
#include <vector>
using namespace std;

int main() {
    vector<int> v = {1, 2, 3, 4, 5};

    // 使用反向迭代器遍历 vector
    cout << "反向遍历:";
    for (vector<int>::reverse_iterator rit = v.rbegin(); rit != v.rend(); ++rit) {
        cout << *rit << " ";
    }
    cout << endl;

    return 0;
}

vector的空间增长

容量空间 接口说明
size 获取数据个数
capacity 获取容量大小
empty 判断是否为空
resize 改变vector的size
reserve 改变vector的capacity
  • capacity的代码在vs和g++下分别运行会发现vs下capacity是按1.5倍增长的,g++是按2倍增长的。所以不同编译器容量增长策略可能不同,实际以编译器为准)
  • reserve只负责开辟空间,如果确定知道需要用多少空间,reserve可以缓解vector增容的代价缺陷问题
  • resize在开空间的同时还会进行初始化,影响size。
函数基础使用
cpp 复制代码
#include <iostream>
#include <vector>
using namespace std;

int main() {
    vector<int> v = {1, 2, 3, 4, 5};

    // size:获取数据个数
    cout << "size: " << v.size() << endl;

    // capacity:获取容量大小
    cout << "capacity: " << v.capacity() << endl;

    // empty:判断是否为空
    cout << "empty: " << (v.empty() ? "是" : "否") << endl;

    // resize:改变 vector 的 size
    v.resize(8, 10);  // 扩容到 8 个元素,新增元素值为 10
    cout << "resize 后 size: " << v.size() << endl;
    cout << "resize 后元素: ";
    for (int num : v) {
        cout << num << " ";
    }
    cout << endl;

    // reserve:改变 vector 的 capacity
    v.reserve(10);
    cout << "reserve 后 capacity: " << v.capacity() << endl;

    return 0;
}
reserve缓解增容代价
cpp 复制代码
#include <iostream>
#include <vector>
#include <chrono>  // 仅用于计时,不涉及其他容器
using namespace std;
using namespace chrono;

int main() {
    // 情况1:不使用 reserve,依赖 vector 自动增容
    vector<int> v1;
    auto start1 = high_resolution_clock::now();  // 记录开始时间
    for (int i = 0; i < 1000000; ++i) {  // 插入100万个元素
        v1.push_back(i);
        // 每次容量不足时,vector 会重新分配更大的内存(通常是当前容量的2倍)
        // 并将原有元素复制到新内存,这个过程会消耗额外时间
    }
    auto end1 = high_resolution_clock::now();    // 记录结束时间
    cout << "不使用 reserve 耗时: " 
         << duration_cast<microseconds>(end1 - start1).count() << " 微秒" << endl;

    // 情况2:使用 reserve 预先开辟足够空间
    vector<int> v2;
    v2.reserve(1000000);  // 提前申请能容纳100万个元素的空间
    auto start2 = high_resolution_clock::now();
    for (int i = 0; i < 1000000; ++i) {
        v2.push_back(i);
        // 由于提前开辟了足够空间,不会触发重新分配内存,插入效率更高
    }
    auto end2 = high_resolution_clock::now();
    cout << "使用 reserve 耗时: " 
         << duration_cast<microseconds>(end2 - start2).count() << " 微秒" << endl;

    return 0;
}
reszie 开空间的同时初始化、影响size
cpp 复制代码
#include <iostream>
#include <vector>
using namespace std;

int main() {
    vector<int> v;
    // resize 开空间到 5,并初始化为 10,size 变为 5
    v.resize(5, 10);
    cout << "resize 后 size: " << v.size() << endl;
    cout << "resize 后元素: ";
    for (int num : v) {
        cout << num << " ";
    }
    cout << endl;

    return 0;
}

增删查改

增删查改 说明
push_back 尾插
pop_back 尾删
insert 在position之前插入val
erase 删除position位置的数据
swap 交换两个vector的数据空间
operator[] 像数组一样访问
cpp 复制代码
#include <iostream>
#include <vector>
using namespace std;

int main() {
    // 初始化一个 vector
    vector<int> v = {1, 2, 3, 4, 5};

    // push_back:尾插
    v.push_back(6);
    cout << "push_back 后元素:";
    for (int num : v) {
        cout << num << " ";
    }
    cout << endl;

    // pop_back:尾删
    v.pop_back();
    cout << "pop_back 后元素:";
    for (int num : v) {
        cout << num << " ";
    }
    cout << endl;

    // insert:在 position 之前插入 val
    // 这里在索引为 2 的位置(即元素 3 之前)插入 10
    v.insert(v.begin() + 2, 10);
    cout << "insert 后元素:";
    for (int num : v) {
        cout << num << " ";
    }
    cout << endl;

    // erase:删除 position 位置的数据
    // 这里删除索引为 3 的位置的元素
    v.erase(v.begin() + 3);
    cout << "erase 后元素:";
    for (int num : v) {
        cout << num << " ";
    }
    cout << endl;

    // swap:交换两个 vector 的数据空间
    vector<int> v2 = {100, 200, 300};
    v.swap(v2);
    cout << "v 交换后元素:";
    for (int num : v) {
        cout << num << " ";
    }
    cout << endl;
    cout << "v2 交换后元素:";
    for (int num : v2) {
        cout << num << " ";
    }
    cout << endl;

    // operator[]:像数组一样访问
    cout << "v[1] 的值:" << v[1] << endl;

    return 0;
}

vector 迭代器失效问题

迭代器的主要作用就是让算法能够不用关心底层数据结构,其底层实际就是一个指针,或者是对 指针进行了封装,比如:vector的迭代器就是原生态指针T* 。因此迭代器失效,实际就是迭代器 底层对应指针所指向的空间被销毁了,而使用一块已经被释放的空间,造成的后果是程序崩溃(即 如果继续使用已经失效的迭代器,程序可能会崩溃)。

插入操作导致迭代器失效
  • push_backemplace_back
    • vector的容量(capacity)足够时,在尾部插入元素不会导致迭代器失效,因为元素插入在末尾其他元素的位置不变。
    • 但当vector容量不足,需要重新分配内存时,所有指向vector的迭代器都会失效。这是因为重新分配内存后,vector中元素的存储地址发生了改变。
cpp 复制代码
#include <iostream>
#include <vector>
using namespace std;

int main() {
    vector<int> v = {1, 2, 3};
    vector<int>::iterator it = v.begin();

    v.push_back(4);  // 假设此时容量足够,迭代器it仍有效
    cout << *it << endl; 

    v.push_back(5);  // 假设此时容量不足,重新分配内存,迭代器it失效
    // cout << *it << endl;  // 这会导致未定义行为
    return 0;
}
  • insert :在vector的任意位置插入元素,如果插入操作导致内存重新分配,所有迭代器都会失效。若没有重新分配内存,插入点之后的迭代器会失效,因为插入元素后,插入点之后的元素位置都发生了改变
cpp 复制代码
#include <iostream>
#include <vector>
using namespace std;

int main() {
    vector<int> v = {1, 2, 3};
    vector<int>::iterator it = v.begin() + 1;

    v.insert(v.begin(), 0);  // 插入操作,假设未导致内存重新分配
    // cout << *it << endl;  // 迭代器it已失效,会导致未定义行为
    return 0;
}
删除操作导致的迭代器失效
  • pop_back :删除vector的尾部元素,如果删除后vector的容量没有改变,除了指向被删除元素的迭代器外,其他迭代器仍然有效。但如果删除后vector的容量发生变化(比如释放了部分内存),则指向vector的迭代器可能会失效。
cpp 复制代码
#include <iostream>
#include <vector>
using namespace std;

int main() {
    vector<int> v = {1, 2, 3};
    vector<int>::iterator it = v.begin() + 1;

    v.pop_back();  // 迭代器it仍然有效
    cout << *it << endl; 
    return 0;
}
  • erase:删除指定位置的元素,指向被删除元素以及其后的迭代器都会失效,因为被删除元素之后的元素会向前移动。
cpp 复制代码
#include <iostream>
#include <vector>
using namespace std;

int main() {
    vector<int> v = {1, 2, 3};
    vector<int>::iterator it = v.begin() + 1;

    v.erase(v.begin());  // 迭代器it已失效
    // cout << *it << endl;  // 会导致未定义行为
    return 0;
}
resizereserve 操作导致的迭代器失效
  • resize :调整vector的大小,如果新大小比原来小,超出新大小部分的迭代器会失效;如果新大小比原来大且需要重新分配内存,所有迭代器都会失效;如果新大小比原来大但不需要重新分配内存,插入新元素位置之后的迭代器会失效。
  • reserve :如果reserve的参数大于当前capacity,会导致内存重新分配,所有迭代器都会失效;如果reserve的参数小于等于当前capacity,迭代器不会失效。
避免迭代器失效的方法
  • 捕获插入或删除操作返回的迭代器inserterase操作会返回一个新的迭代器,指向插入或删除操作之后的位置,通过更新迭代器,可以确保迭代器的有效性。
  • 在循环中使用迭代器时特别注意 :在对vector进行插入或删除操作的循环中,每次操作后及时更新迭代器,以避免迭代器失效带来的问题。

vector的模拟实现

模拟实现

cpp 复制代码
#pragma once
#include <iostream>
#include <assert.h>
#include <cstring>
using namespace std;

namespace myvector
{
    template <class T>
    class vector
    {
    public:
        // 迭代器重命名
        typedef T *iterator;
        typedef const T *const_iterator;

        // 无参数构造函数
        vector() {}

        // 获取当前有效数组长度
        size_t size() const
        {
            return _finish - _start;
        }

        // 获取当前申请的内存空间大小
        size_t capacity() const
        {
            return _endofstorage - _start;
        }

        // 改变vector的capacity
        void reserve(size_t n)
        {
            if (n > capacity())
            {
                size_t oldSize = size();
                T *tmp = new T[n]; // 申请内存空间

                // 如果_start不是空指针,就释放之前申请的内存空间
                if (_start)
                {
                    memcpy(tmp, _start, sizeof(T) * oldSize); // 复制旧数组数据
                    delete[] _start;                          // 释放就数组内存
                }

                // 更新
                _start = tmp;
                _finish = _start + oldSize;
                _endofstorage = _start + n;
            }
        }

        // 在指定位置之前插入数据
        iterator insert(iterator pos, const T &x)
        {
            // 判断指定位置是否越界
            assert(pos >= _start && pos <= _finish);

            // 如果已分配内存已经被全部使用
            if (_finish == _endofstorage)
            {
                size_t len = pos - _start;                     // 保存pos在旧数组中的位置
                reserve(capacity() == 0 ? 4 : capacity() * 2); // 申请新内存空间
                pos = _start + len;                            // 更新pos
            }

            // 插入
            iterator i = _finish - 1;
            while (i >= pos)
            {
                *(i + 1) = *i;
                --i;
            }
            *pos = x;
            ++_finish;

            return pos;
        }

        // 尾插
        void push_back(const T &x)
        {
            insert(_finish, x);
        }

        // 支持列表初始化的构造函数
        vector(initializer_list<T> il)
            : _start(nullptr), _finish(nullptr), _endofstorage(nullptr)
        {
            // 申请内存空间
            reserve(il.size());
            // 将数据循环尾插
            for (auto &e : il)
            {
                push_back(e);
            }
        }

        // 析构函数
        ~vector()
        {
            // 如果指针不为空
            if (_start)
            {
                delete[] _start;
                _start = _finish = _endofstorage = nullptr;
            }
        }

        // 判断数组是否为空
        bool empty()
        {
            return _start == _finish;
        }

        // 尾删
        void pop_back()
        {
            // 判断数组是否为空
            assert(!empty());

            --_finish;
        }

        // 迭代器实现
        iterator begin()
        {
            return _start;
        }

        iterator end()
        {
            return _finish;
        }

        const_iterator begin() const
        {
            return _start;
        }

        const_iterator end() const
        {
            return _finish;
        }

        // 数组下标访问实现
        T &operator[](size_t i)
        {
            assert(i < size()); // 判断下标是否超过数组长度

            return _start[i];
        }

        // 删除pos位置的数据
        iterator erase(iterator pos)
        {
            assert(pos >= _start);
            assert(pos >= _finish);

            iterator i = pos + 1;

            while (i < _finish)
            {
                *(i - 1) = *i;
                ++i;
            }

            --_finish;

            return pos;
        }

        // vector 容器的迭代器范围构造函数,用于通过一个迭代器区间 [first, last) 来初始化 vector
        // int arr[] = {1, 2, 3, 4};
        // vector<int> v(arr, arr + 4); // 用数组的全部元素初始化 v
        // vector<int> v2(v.begin() + 1, v.end() - 1); // 用 v 中 [1,3) 范围的元素初始化 v2
        template <class InputIterator>
        vector(InputIterator first, InputIterator last)
        {
            while (first != last)
            {
                push_back(*first);
                ++first;
            }
        }

        //填充构造函数
        // 创建一个包含 5 个 int 类型元素 10 的 vector
        //vector<int> v1(5, 10); // 元素:10,10,10,10,10
        vector(size_t n, const T& val = T())
		{
			reserve(n);
			for (size_t i = 0; i < n; i++)
			{
				push_back(val);
			}
		}

        vector(int n, const T& val = T())
		{
			reserve(n);
			for (int i = 0; i < n; i++)
			{
				push_back(val);
			}
		}

        //拷贝构造函数
        vector(const vector<T>& v)
		{
			reserve(v.capacity());
			for (auto& e : v)
			{
				push_back(e);
			}
		}

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

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

        const T& operator[](size_t i) const
		{
			assert(i < size());

			return _start[i];
		}

        //改变 vector 容器中元素的数量
        void resize(size_t n, T val = T())
		{
			if (n <= size())
			{
				_finish = _start + n;
			}
			else
			{
				reserve(n);

				while (_finish < _start + n)
				{
					*_finish = val;
					++_finish;
				}
			}
		}


    private:
        iterator _start = nullptr;        // 底层存储区的起始位置
        iterator _finish = nullptr;       // 最后一个有效元素的下一个位置
        iterator _endofstorage = nullptr; // 底层已分配内存的末尾的下一个位置
    };
}

测试

cpp 复制代码
#include <iostream>
#include <cassert>
#include "myvector.hpp"

using namespace std;

int main()
{
	using myvector::vector;

	cout << "[Test 1] default constructor" << endl;
	vector<int> v;
	cout << "  size=" << v.size() << " capacity=" << v.capacity() << endl;
	assert(v.size() == 0);

	cout << "  -> OK\n";

	cout << "[Test 2] push_back and basic access" << endl;
	for (int i = 1; i <= 5; ++i)
	{
		v.push_back(i);
		cout << "  pushed " << i << " size=" << v.size() << " capacity=" << v.capacity() << "\n";
	}
	assert(v.size() == 5);
	for (size_t i = 0; i < v.size(); ++i)
	{
		assert(v[i] == (int)i + 1);
	}
	cout << "  contents:";
	for (auto x : v) cout << ' ' << x;
	cout << "\n  -> OK\n";

	cout << "[Test 3] insert in middle" << endl;
	auto it = v.begin() + 2; // insert before element 3
	cout << "  before insert size=" << v.size() << " capacity=" << v.capacity() << "\n";
	v.insert(it, 99);
	cout << "  after insert size=" << v.size() << " capacity=" << v.capacity() << "\n";
	assert(v.size() == 6);
	assert(v[2] == 99);
	cout << "  contents:";
	for (auto x : v) cout << ' ' << x;
	cout << "\n  -> OK\n";

	cout << "[Test 4] copy constructor" << endl;
	vector<int> vcopy(v);
	cout << "  original size=" << v.size() << " copy size=" << vcopy.size() << "\n";
	assert(vcopy.size() == v.size());
	for (size_t i = 0; i < v.size(); ++i) assert(vcopy[i] == v[i]);
	cout << "  -> OK\n";

	cout << "[Test 5] assignment operator" << endl;
	vector<int> vassign;
	vassign = v;
	cout << "  assigned size=" << vassign.size() << "\n";
	assert(vassign.size() == v.size());
	for (size_t i = 0; i < v.size(); ++i) assert(vassign[i] == v[i]);
	cout << "  -> OK\n";

	cout << "[Test 6] initializer_list constructor" << endl;
	vector<int> vil = {10, 20, 30};
	cout << "  vil size=" << vil.size() << " contents:";
	for (auto x : vil) cout << ' ' << x;
	cout << "\n";
	assert(vil.size() == 3);
	assert(vil[0] == 10 && vil[1] == 20 && vil[2] == 30);
	cout << "  -> OK\n";

	cout << "[Test 7] reserve and capacity growth" << endl;
	size_t oldcap = v.capacity();
	cout << "  old capacity=" << oldcap << "\n";
	v.reserve(oldcap + 10);
	cout << "  new capacity=" << v.capacity() << "\n";
	assert(v.capacity() >= oldcap + 10);
	cout << "  -> OK\n";

	cout << "[Test 8] resize larger and smaller" << endl;
	v.resize(10, -1);
	cout << "  resized up size=" << v.size() << " capacity=" << v.capacity() << "\n";
	assert(v.size() == 10);
	for (size_t i = 6; i < 10; ++i) assert(v[i] == -1);
	v.resize(3);
	cout << "  resized down size=" << v.size() << "\n";
	assert(v.size() == 3);
	cout << "  -> OK\n";

	cout << "[Test 9] pop_back" << endl;
	cout << "  before pop size=" << v.size() << "\n";
	int last = v[v.size() - 1];
	v.pop_back();
	cout << "  after pop size=" << v.size() << " popped=" << last << "\n";
	assert(v.size() == 2);
	cout << "  -> OK\n";

	cout << "[Test 10] iteration (const and non-const)" << endl;
	int sum = 0;
	cout << "  vassign contents:";
	for (auto &x : vassign) { sum += x; cout << ' ' << x; }
	cout << "\n";
	const vector<int> &cref = vassign;
	int csum = 0;
	for (auto it2 = cref.begin(); it2 != cref.end(); ++it2) csum += *it2;
	cout << "  sum=" << sum << " csum=" << csum << "\n";
	assert(sum == csum);
	cout << "  -> OK\n";

	// Print final contents of vassign
	cout << "Final vassign contents:";
	for (auto x : vassign) cout << ' ' << x;
	cout << '\n';

	cout << "ALL TESTS PASSED\n";

	return 0;
}
相关推荐
YJlio3 小时前
Process Monitor 学习笔记(5.7):长时间运行追踪与日志文件体积的控制
java·笔记·学习
杨福瑞3 小时前
C语言数据结构:算法复杂度(1)
c语言·开发语言·数据结构
派森先生3 小时前
sk09.【scikit-learn基础】--『无监督学习』之K均值聚类
学习·均值算法·scikit-learn
JJJJ_iii3 小时前
【机器学习03】学习率与特征工程、多项式回归、逻辑回归
人工智能·pytorch·笔记·学习·机器学习·回归·逻辑回归
郭源潮14 小时前
《Muduo网络库:实现one loop per thread设计模式》
开发语言·c++·网络库
_dindong4 小时前
Linux网络编程:Socket编程预备
linux·运维·网络·学习
deng-c-f4 小时前
Linux C/C++ 学习日记(25):KCP协议:普通模式与极速模式
linux·学习·kcp
_dindong4 小时前
Linux网络编程:宏观网络体系
linux·网络·笔记·学习
linksinke4 小时前
html案例:制作一个图片水印生成器,防止复印件被滥用
开发语言·前端·程序人生·html