首页 > 编程语言 >【C++修行之道】vector

【C++修行之道】vector

时间:2024-10-20 08:50:41浏览次数:3  
标签:std 初始化 修行 int 元素 C++ vector size

 一、 vector的介绍

1.1 vector的介绍

vector的文档介绍

1. vector是表示可变大小数组的序列容器。

2. 就像数组一样,vector也采用的连续存储空间来存储元素。也就是意味着可以采用下标对vector的元素进行访问,和数组一样高效。但是又不像数组,它的大小是可以动态改变的,而且它的大小会被容器自动处理。

3. 本质讲,vector使用动态分配数组来存储它的元素。当新元素插入时候,这个数组需要被重新分配大小为了增加存储空间。其做法是,分配一个新的数组,然后将全部元素移到这个数组。就时间而言,这是一个相对代价高的任务,因为每当一个新的元素加入到容器的时候,vector并不会每次都重新分配大小。

4. vector分配空间策略:vector会分配一些额外的空间以适应可能的增长,因为存储空间比实际需要的存储空间更大。不同的库采用不同的策略权衡空间的使用和重新分配。但是无论如何,重新分配都应该是对数增长的间隔大小,以至于在末尾插入一个元素的时候是在常数时间的复杂度完成的。

5. 因此,vector占用了更多的存储空间,为了获得管理存储空间的能力,并且以一种有效的方式动态增长。

6. 与其它动态序列容器相比(deque, list and forward_list), vector在访问元素的时候更加高效,在末尾添加和删除元素相对高效。对于其它不在末尾的删除和插入操作,效率更低。比起listforward_list统一的迭代器和引用更好。

1.2 vector的模拟实现

template<class T> class vector

template<class T>
class vector
{
private:
	T* _a;
	size_t _size;
	size_t _capacity;
};

 成员变量

  • T* _a; :一个指向元素存储空间的指针,动态分配内存。
  • size_t _size;:当前存储的元素数量。
  • size_t _capacity;:已分配的空间容量,即容纳的最大元素个数。

模拟了标准库中的 std::vector,这种类的实现旨在管理动态数组,并在需要时自动调整其大小。

vector<int> 的实现

vector<int>
class vector
{
public:
	int& operator[](size_t i)
	{
		//....
		return _a[i];
	}
private:
	int* _a;
	size_t _size;
	size_t _capacity;
};
  • 通过重载 operator[] 运算符,可以方便地实现下标访问,类似数组的使用方式。 
  • 这个类特化了 vector 模板,将其限制为 int 类型的数据存储,即它是 vector<int> 的一种特化实现(虽然这里没有使用模板语法,而是硬编码了 int 类型)。
  • int& operator[](size_t i):重载了下标操作符 [],使得可以通过下标访问 vector 中的元素,类似于数组的访问方式。返回一个 int 类型的引用,允许对元素进行修改。
  • int* _a;:指向 int 类型的动态数组。
  • size_t _size;size_t _capacity;:分别表示元素的个数和容量,与上面模板类相同。

vector<vector<int>> 的实现

//vector<vector<int>>
class vector
{
public:
	vector<int>& operator[](size_t i)
	{
		//....
		return _a[i];
	}
private:
	vector<int>* _a;
	size_t _size;
	size_t _capacity;
};
  • 多维 vector 的实现:
    • 旨在存储 vector<int> 类型的对象,即 vector<vector<int>>。可以将它看作是一个二维数组或多维 vector 的简单模拟。
      vector<int> vi[100];
      //vi[0] ~ vi[100 - 1]中每一个都是一个vector容器
    • vector<int>& operator[](size_t i):重载了下标操作符 [],使得可以通过下标 i 访问存储的 vector<int> 元素。返回一个 vector<int> 类型的引用。
    • vector<int>* _a;:指向 vector<int> 类型的动态数组。即 _a 是一个指针数组,其中每个元素都是一个 vector<int> 对象。
    • size_t _size;size_t _capacity;:分别表示 vector<int> 的个数和容量,与前面版本一致。

vector<vector<int>> 演示了如何使用嵌套 vector 实现多维数据结构。通过嵌套使用 vector,可以轻松地表示矩阵或多维数组等复杂的数据结构。

二、 vector的使用

2.1 vector使用的表格

表格1: vector构造函数声明

构造函数声明接口说明
vector()无参构造,创建一个空的vector
vector(size_type n, const value_type& val = value_type())构造并初始化nval,用于创建并初始化指定大小和值的vector
vector(const vector& x)拷贝构造,通过复制另一个vector来创建新的vector
vector(InputIterator first, InputIterator last)使用迭代器进行初始化构造,通过迭代器范围来初始化vector

InputIterator 表示输入迭代器类型,size_type 表示 vector 中元素数量的大小类型,value_type 表示 vector 中存储的元素类型。

表格2: iterator使用接口说明

iterator使用接口说明
begin()获取指向vector中第一个元素的迭代器(iterator/const_iterator
end()获取指向vector中最后一个元素之后位置的迭代器(iterator/const_iterator),常用于循环结束条件
rbegin()获取指向vector中最后一个元素的反向迭代器(reverse_iterator
rend()获取指向vector中第一个元素之前位置的反向迭代器(reverse_iterator),常用于反向遍历结束条件

2.2 vector的遍历方式

第一种遍历方式:通过下标访问元素

for (size_t i = 0; i < v1.size(); i++)
{
	cout << v1[i] << " ";
}
cout << endl;

下标访问方式:适用于需要通过索引随机访问元素的场景,如 vectordeque。但对于不支持随机访问的容器(如 list),这种方法不适用。 

第二种遍历方式:使用迭代器

vector<int>::iterator it1 = v1.begin();
while (it1 != v1.end())
{
	cout << *it1 << " ";
	++it1;
}
cout << endl;

迭代器遍历方式:是 STL 中的通用遍历方式,所有容器都支持。了解迭代器的用法对于深入理解 STL 的实现细节和灵活性非常重要。 

第三种遍历方式:基于范围的 for 循环(C++11 引入)

for (auto e : v1)
{
	cout << e << " ";
}
cout << endl;

基于范围的 for 循环:语法简洁,在遍历整个容器时非常高效且可读性强。C++11 之后,推荐在不需要修改元素或关心索引时使用此方法。

2.3 vector 容器在存储 string 类型数据时的使用

vector 容器存储 string 类型
void test_vector2()
{
    vector<string> v2;

    string s1("张三");
    v2.push_back(s1);
    v2.push_back(string("李四"));
    v2.push_back("王五");

    v2[1] += "来";

    for (auto e : v2)
    {
        cout << e << " ";
    }
    cout << endl;
}

vector<string> v2; 定义了一个 vector 容器,用于存储 string 类型的元素。

  • vector 容器不仅可以存储基础类型(如 int),还可以存储自定义对象或复杂类型(如 string),且依然保留动态调整大小的特性。
  • 这是 C++ 模板机制的灵活性体现,vector 可以通过指定模板类型来存储任意数据类型。
使用 push_back 添加元素
string s1("张三");
v2.push_back(s1);
v2.push_back(string("李四"));
v2.push_back("王五");

push_back() 方法用于将新元素添加到 vector 容器的末尾。这里展示了三种不同的字符串添加方式。

修改 vector 中的元素
v2[1] += "来";

vector 提供了对元素的随机访问功能,可以通过下标轻松访问和修改。

2.4 vector的排序操作

void test_vector2()
{
	vector<int> v1;
	v1.push_back(10);
	v1.push_back(2);
	v1.push_back(30);
	v1.push_back(4);

	//greater<int> gt;
	//cout << gt(2, 3) << endl;
	//cout << gt.operator()(2, 3) << endl;
	//cout << gt(3, 2) << endl;
	
	//sort(v1.begin(), v1.end(),gt);
	//sort(v1.begin(), v1.end() - 1);
	//sort(v1.begin(), v1.end() + v1.size() / 2);
    //越界

	// 默认是升序
	// 降序
	sort(v1.begin(), v1.end(), greater<int>());
	for(const auto &e : v1)
	{
		cout << e << " ";
	}
	cout << endl;
}
sort(v1.begin(), v1.end(), greater<int>());

 使用了 std::sort 算法对 std::vector 进行排序。std::sort 是 C++ 标准库中用于对范围内元素进行原地排序的高效排序函数。默认情况下,std::sort 使用升序排列。 

展示了一个 STL 提供的函数对象 greater<int>,它是一个仿函数,用于实现大于比较。greater<int><functional> 头文件中的标准函数对象,用于方便实现降序排序。

三、 vector空间增长问题

接口名称接口说明
size获取数据个数
capacity获取容量大小
empty判断是否为空
resize改变vector的size(重点)
reserve改变vector的capacity(重点)

3.1 resize机制:

#include <iostream>
#include <vector>
using namespace std;

int main() {
    vector<int> v(10);
    // 使用iota生成0到9的序列
    iota(v.begin(), v.end(), 0);

    // 使用范围for循环打印元素
    for (int ch : v) 
        cout << ch << ' ';
    cout << endl;

    // 调整v的大小并打印结果
    v.resize(5);
    for (int ch : v) 
        cout << ch << ' ';
    cout << endl;

    v.resize(8, 99);
    for (int ch : v) 
        cout << ch << ' ';
    cout << endl;

    v.resize(12);
    for (int ch : v) 
        cout << ch << ' ';
    cout << endl;

    // 直接在范围for循环中对元素+1并打印
    for (int& ch : v) {
        ch += 1;
        cout << ch << ' ';
    }
    cout << endl;

    // 使用auto和范围for打印元素
    for (auto it = v.begin(); it != v.end(); ++it) 
        cout << *it << ' ';
    cout << endl;

    // 使用reverse_iterator打印元素并增加1
    for (auto rit = v.rbegin(); rit != v.rend(); ++rit) {
        *rit += 1;
        cout << *rit << ' ';
    }
    cout << endl;

    return 0;
}

详解如下: 

缩小容器的容量并删除多余的元素

    // 调整v的大小并打印结果
    v.resize(5);
    for (int ch : v) 
        cout << ch << ' ';
    cout << endl;

在代码中,v.resize(5) 将向量大小从 10 缩小到 5。对于已经存在的元素,只保留前 5 个,而超出部分被舍弃掉了。

指定新元素的初始值

    v.resize(8, 99);
    for (int ch : v) 
        cout << ch << ' ';
    cout << endl;

v.resize(8, 99) 将向量的大小从 5 增加到 8,并用值 99 初始化新增加的三个元素。

新元素根据类型会使用默认构造函数进行初始化

    v.resize(12);
    for (int ch : v) 
        cout << ch << ' ';
    cout << endl;

将向量从大小 8 扩展到 12,而新增的元素未显式提供初始值时,会被默认初始化为 0(对于整型)。

3.2 reserve扩容机制:

int main()
{
// vector的默认扩容机制
// vs:按照1.5倍方式扩容
// linux:按照2倍方式扩容
	vector<int> v;
	//v.reserve(100);预先扩容
	int sz = v.capacity();
	cout << "Inite capacity:" << sz << endl;
	for (int i = 0; i < 100; i++)
	{
		v.push_back(i);
		if (sz != v.capacity())
		{
			sz = v.capacity();
			cout << "Change capacity:i:" << sz << endl;
		}
	}
	return 0;
}

在 C++ 中,std::vector 的大小是动态的。当向向量中添加元素时,如果向量容量不足,它会自动分配更大的内存空间。这个扩容机制为了提高插入效率,通常不会每次只扩展一个元素的容量,而是采用倍增方式,常见的扩容因子有 1.5 倍或者 2 倍(VS中是1.5倍扩容,Linux中是2倍扩容)。

四、vector的常用初始化方法

4.1 不带参数的构造函数初始化

std::vector<int> vec; // 初始化一个size为0的vector

4.2 带参数的构造函数初始化

指定大小与初始值:

std::vector<int> vec(10); // 初始化了10个默认值为0的元素  
std::vector<int> vec(10, 5); // 初始化了10个值为5的元素

通过数组地址和同类型的vector初始化:

//通过数组地址初始化:
int arr[] = {1, 2, 3, 4, 5};  
std::vector<int> vec(arr, arr + 5); 
// 通过数组arr的地址初始化,注意地址是从0到5(左闭右开区间)

//使用等号操作符赋值:
std::vector<int> vec1(5, 1);  
std::vector<int> vec2(vec1); // 通过vec1初始化
// vec2 = vec1; // 将vec1赋值给vec2

//使用assign函数赋值:
vec2.assign(vec1.begin(), vec1.end()); // 将vec1赋值给vec2

//使用swap函数赋值:
std::vector<int> vec1(5, 1);  
std::vector<int> vec2;  
vec2.swap(vec1); // 将vec1赋值给vec2,此时vec1变为空

使用初始化列表(C++11及以后):

std::vector<int> vec = {1, 2, 3, 4, 5}; 
// 或者 std::vector<int> vec{1, 2, 3, 4, 5};

4.3 通过resize函数初始化

std::vector<int> vec;  
vec.resize(5, 0); // 设置大小为5,所有元素初始化为0  
vec.resize(5); // 设置大小为5,元素初始化为int的默认值,即0

4.4 通过insert函数初始化

std::vector<int> vec;  
vec.insert(vec.begin(), 6, 6); // 在vec开始位置处插入6个6

4.5 二维数组初始化(构造函数)

#include <vector>  
  
int main() {  
    int rows = 5; // 行数  
    int cols = 5; // 列数  
    std::vector<std::vector<int>> vec(rows, std::vector<int>(cols, 0));  
  
    // 现在 vec 是一个 5x5 的二维 vector,所有元素都是 0  
    return 0;  
}

五、vector的增删改查 

成员函数功能描述
size()返回当前vector中的元素数量。
capacity()返回vector当前分配的容量大小。
empty()检查vector是否为空(即是否包含任何元素)。
resize(n)调整vector的大小为n,必要时添加或移除元素。
resize(n, value)调整vector的大小为n,新添加的元素初始化为value
shrink_to_fit()请求减少vector的容量以适应其当前大小(C++11及更高版本)。
reserve(n)请求vector容量至少为n,如果必要,会分配更多内存。
void test_vector3()  
{  
    // 使用C++11的初始化列表语法初始化vector  
    vector<int> v{ 1, 2, 3, 4 }; // vector包含:1, 2, 3, 4  
  
    // 查找值为3的元素位置  
    auto pos = find(v.begin(), v.end(), 3); // 使用STL算法find  
  
    // 如果找到了值为3的元素,则在其前面插入30  
    if (pos != v.end())  
    {  
        v.insert(pos, 30); // 在pos位置插入30,vector现在包含:1, 2, 30, 3, 4  
    }  
  
    // 遍历并输出vector  
    vector<int>::iterator it = v.begin();  
    while (it != v.end())  
    {  
        cout << *it << " ";  
        ++it;  
    }  
    cout << endl;  
  
    // 再次查找值为3的元素,并删除它  
    pos = find(v.begin(), v.end(), 3);  
    v.erase(pos); // 删除pos位置的元素,vector现在包含:1, 2, 30, 4  
  
    // 再次遍历并输出vector  
    it = v.begin();  
    while (it != v.end())  
    {  
        cout << *it << " ";  
        ++it;  
    }  
    cout << endl;  
}
// 查找值为3的元素位置  
    auto pos = find(v.begin(), v.end(), 3); // 使用STL算法find 

vector没有提供find方法,使用STL中的find算法,在v中查找值为3的元素。find函数返回指向找到元素的迭代器,如果未找到,则返回v.end()

void test_vector4()  
{  
    vector<int> v{ 1, 2, 3, 4 }; // 使用初始化列表初始化vector  
  
    // 通过下标访问和修改vector中的元素  
    v[0] = 10; // 修改第一个元素为10,vector现在包含:10, 2, 3, 4  
    cout << v[0] << endl; // 输出第一个元素  
  
    // 使用for循环和下标遍历vector  
    for (size_t i = 0; i < v.size(); ++i)  
        cout << v[i] << " "; // 输出每个元素  
    cout << endl;  
  
    vector<int> swapv; // 创建一个空的int类型vector  
    swapv.swap(v); // 交换v和swapv的内容  
  
    // 输出v的数据  
    cout << "v data = ";  
    for (size_t i = 0; i < v.size(); ++i)  
        cout << v[i] << " "; // 此时v为空  
    cout << endl;  
  
    // 使用迭代器遍历swapv  
    cout << "swapv data:";  
    auto it = swapv.begin();  
    while (it != swapv.end())  
    {  
        cout << *it << " "; // 输出swapv中的每个元素  
        ++it;  
    }  
    cout << endl;  
  
    // 使用范围for循环遍历v(此时v为空,不会输出任何内容)  
    for (auto x : v)  
        cout << x << " ";  
    cout << endl;  
}
vector<int> swapv; // 创建一个空的int类型vector  
swapv.swap(v); // 交换v和swapv的内容

创建一个空的vector swapv,并使用swap函数交换vswapv的内容。交换后,v变为空,而swapv包含原本v中的元素:10, 2, 3, 4。 

今天就先到这了!!!

看到这里了还不给博主扣个:
⛳️ 点赞☀️收藏 ⭐️ 关注!

你们的点赞就是博主更新最大的动力!
有问题可以评论或者私信呢秒回哦。


 

标签:std,初始化,修行,int,元素,C++,vector,size
From: https://blog.csdn.net/2301_79558858/article/details/143054799

相关文章

  • C++的RAII原则
    C++的RAII原则内容ResourceAcquisitionIsInitialization(RAII)isacoreprogrammingconceptinC++(andotherresource-managedlanguages).Itensuresthatresources,suchasmemory,filehandles,ornetworkconnections,areacquiredandreleasedproperlyb......
  • C++——继承
    1.概念继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能,这样产生新的类,称派生类。继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。以前我们接触的复用都是函数......
  • 图-C++基础
    图论是计算机科学和数学中非常重要的一个分支,涉及到图的性质、结构以及相关的算法。以下是对图论的基础知识、常用算法及其相关代码的整理,帮助你为CSP备考做好准备。一、图的基本概念1.1图的定义在数学中,图是一个由顶点(或节点)和边组成的集合。图可用以下形式表示:无向图:边......
  • 【C++】原地逆置单链表(不开辟新的储存空间)
    首先看图例:创建一个单链表L,并用指针p指向需要逆置的第一个结点,s指向p的下一个。(这里s的作用是为了防止p后的结点丢失) 第一个结点逆置后成为最后一个,所以其后为NULL,即p->next=NULL(其他结点正常头插)用s指向了的p之后的结点,所以它们不会丢失。第一个结点接上后,p、s重新指向......
  • 【信奥赛·C++基础语法】CSP-J C++ STL 标准模板库 - 算法
    序言标准模板库(STL)的算法部分提供了一系列强大的工具,用于对各种容器中的数据进行操作。这些算法可以大大提高编程效率,减少代码重复,使程序更加简洁、高效和可读。无论是处理简单的数据结构还是复杂的大规模数据,STL算法都能发挥重要作用。一、STL算法的分类排序算法快速......
  • (新!)c++多态
    C++ 多态多态按字面的意思就是多种形态。当类之间存在层次结构,并且类之间是通过继承关联时,就会用到多态。C++多态意味着调用成员函数时,会根据调用函数的对象的类型来执行不同的函数。下面的实例中,基类Shape被派生为两个类,如下所示:实例#include<iostream>usingnames......
  • 洛谷知识点——C++ 11 实现一次性输出多行文本
    完整语法是R"deli(...)deli"。(其中deli并不是固定的,那里其实是一个用户自定义的字符序列,最多16个基本字符,不可含反斜线,空格和小括号。)故P1000超级玛丽游戏解法为#include<iostream>usingnamespacestd;intmain(){cout<<R"(********......
  • C++基础
    1、注释单行注释://这是注释多行注释:/*这是注释*/2、变量 数据类型变量名=变量初始值; 3、常量宏常量:通常在文件开头定义#define常量名常量值const修饰的静态变量,表示一个常量,不可修改。const数据类型常量名=常量值#include<iostream>usingna......
  • 「图::连通」详解并查集并实现对应的功能 / 手撕数据结构(C++)
    目录概述成员变量创建销毁根节点访问路径压缩启发式合并复杂度Code概述并查集,故名思议,能合并、能查询的集合,在图的连通性问题和许多算法优化上着广泛的使用。这是一个什么数据结构呢?一般来讲,并查集是由一系列集合组成的集合群。其中,每个集合都有一个根节点,它的......
  • 学习记录,这该死的c++
    最近还是比较懈怠,除了老师布置的作业其他的也是匆匆过一眼至于代码方面最主要的问题还是不理解该怎么表达。总的来说还是要在多沉淀。为什么会有知道敲代码但是不知道该怎么成功表达这个问题啊?还是不能把代码敲得精简一些可以来个人教我怎么敲关系符号吗?运用的还不是很熟练......