MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

C++ 模板类vector用法详解

2023-04-214.4k 阅读

1. 引言

在C++编程中,vector是一个非常强大且常用的模板类,它定义在<vector>头文件中。vector提供了动态数组的功能,与普通数组相比,它能自动管理内存,并且支持各种方便的操作,如插入、删除、遍历等。下面我们将详细介绍vector的用法。

2. 定义与初始化

2.1 基本定义

要使用vector,首先需要包含<vector>头文件。定义一个vector对象的基本语法如下:

#include <vector>
// 定义一个存储int类型的vector
std::vector<int> vec;

这里std::vector<int>表示一个存储int类型元素的vector容器,vec是这个容器的对象名。

2.2 初始化方式

  • 默认初始化

    std::vector<int> v1; // 初始化一个空的vector
    

    此时v1不包含任何元素,其大小为0。

  • 使用初始值列表初始化

    std::vector<int> v2 = {1, 2, 3, 4, 5};
    // C++11起支持这样的初始化方式,v2包含5个元素,分别为1, 2, 3, 4, 5
    
  • 使用构造函数指定大小和初始值

    std::vector<int> v3(10, 5);
    // v3包含10个元素,每个元素的值都是5
    
  • 通过其他容器初始化

    std::vector<int> v4(v2);
    // v4通过v2初始化,v4的内容与v2相同
    std::vector<int> v5(v2.begin(), v2.end());
    // 同样通过v2的迭代器范围初始化v5
    

3. 元素访问

3.1 通过下标访问

vector支持通过下标运算符[]来访问元素,就像普通数组一样。例如:

#include <iostream>
#include <vector>
int main() {
    std::vector<int> vec = {1, 2, 3, 4, 5};
    std::cout << "第一个元素: " << vec[0] << std::endl;
    std::cout << "第三个元素: " << vec[2] << std::endl;
    return 0;
}

在上述代码中,vec[0]访问vector的第一个元素,vec[2]访问第三个元素。需要注意的是,使用[]访问元素时,不会进行越界检查,如果访问越界,程序可能会崩溃或产生未定义行为。

3.2 使用at()成员函数访问

vector提供了at()成员函数来访问元素,它会进行边界检查。如果访问越界,会抛出std::out_of_range异常。示例如下:

#include <iostream>
#include <vector>
#include <exception>
int main() {
    std::vector<int> vec = {1, 2, 3, 4, 5};
    try {
        std::cout << "第一个元素: " << vec.at(0) << std::endl;
        std::cout << "第六个元素: " << vec.at(5) << std::endl;
    } catch (const std::out_of_range& e) {
        std::cerr << "捕获到越界异常: " << e.what() << std::endl;
    }
    return 0;
}

在这个例子中,访问vec.at(5)会抛出异常,程序通过try - catch块捕获并处理这个异常。

3.3 访问第一个和最后一个元素

vector提供了front()back()成员函数来分别访问第一个和最后一个元素。例如:

#include <iostream>
#include <vector>
int main() {
    std::vector<int> vec = {1, 2, 3, 4, 5};
    std::cout << "第一个元素: " << vec.front() << std::endl;
    std::cout << "最后一个元素: " << vec.back() << std::endl;
    return 0;
}

front()返回第一个元素的引用,back()返回最后一个元素的引用。

4. 容量与大小

4.1 大小(size

size()成员函数返回vector中当前元素的数量。例如:

#include <iostream>
#include <vector>
int main() {
    std::vector<int> vec = {1, 2, 3, 4, 5};
    std::cout << "vector的大小: " << vec.size() << std::endl;
    return 0;
}

上述代码输出vector中元素的个数,即5。

4.2 容量(capacity

capacity()成员函数返回vector在不重新分配内存的情况下可以容纳的元素数量。vector的容量通常大于或等于其大小。当vector的大小达到容量时,再添加新元素,vector会重新分配内存,将容量扩大。例如:

#include <iostream>
#include <vector>
int main() {
    std::vector<int> vec;
    std::cout << "初始容量: " << vec.capacity() << std::endl;
    for (int i = 0; i < 10; ++i) {
        vec.push_back(i);
        std::cout << "添加元素 " << i << " 后,容量: " << vec.capacity() << std::endl;
    }
    return 0;
}

在这个例子中,我们可以观察到随着元素的添加,vector的容量如何变化。通常,vector在重新分配内存时,会将容量增加为原来的两倍(具体实现可能因编译器而异)。

4.3 调整大小(resize

resize()成员函数用于改变vector的大小。如果新的大小大于当前大小,会在vector末尾添加默认值(对于内置类型为0)或指定值;如果新的大小小于当前大小,会删除超出部分的元素。例如:

#include <iostream>
#include <vector>
int main() {
    std::vector<int> vec = {1, 2, 3, 4, 5};
    vec.resize(8);
    // 此时vec的大小变为8,新增的3个元素值为0
    for (int i : vec) {
        std::cout << i << " ";
    }
    std::cout << std::endl;
    vec.resize(3);
    // 此时vec的大小变为3,删除后面5个元素
    for (int i : vec) {
        std::cout << i << " ";
    }
    std::cout << std::endl;
    return 0;
}

4.4 预留空间(reserve

reserve()成员函数用于预先分配一定的容量,以避免多次重新分配内存。例如:

#include <iostream>
#include <vector>
int main() {
    std::vector<int> vec;
    vec.reserve(100);
    // 预先分配能容纳100个元素的空间
    for (int i = 0; i < 50; ++i) {
        vec.push_back(i);
    }
    std::cout << "当前容量: " << vec.capacity() << std::endl;
    return 0;
}

在上述代码中,我们预先为vec预留了100个元素的空间,这样在添加50个元素的过程中,不会发生重新分配内存的操作。

5. 元素插入与删除

5.1 尾部插入(push_back

push_back()成员函数用于在vector的尾部添加一个元素。例如:

#include <iostream>
#include <vector>
int main() {
    std::vector<int> vec = {1, 2, 3};
    vec.push_back(4);
    for (int i : vec) {
        std::cout << i << " ";
    }
    std::cout << std::endl;
    return 0;
}

上述代码在vec的尾部添加了元素4,输出结果为1 2 3 4

5.2 任意位置插入(insert

insert()成员函数可以在vector的任意位置插入元素。其基本语法有多种形式:

  • 在指定位置插入单个元素:

    #include <iostream>
    #include <vector>
    int main() {
        std::vector<int> vec = {1, 2, 4, 5};
        auto it = vec.begin() + 2;
        vec.insert(it, 3);
        for (int i : vec) {
            std::cout << i << " ";
        }
        std::cout << std::endl;
        return 0;
    }
    

    在这个例子中,我们使用insertvec的第三个位置(索引为2)插入元素3。

  • 在指定位置插入多个相同元素:

    #include <iostream>
    #include <vector>
    int main() {
        std::vector<int> vec = {1, 2, 4, 5};
        auto it = vec.begin() + 2;
        vec.insert(it, 3, 10);
        // 在it位置插入3个值为10的元素
        for (int i : vec) {
            std::cout << i << " ";
        }
        std::cout << std::endl;
        return 0;
    }
    
  • 在指定位置插入另一个容器的元素范围:

    #include <iostream>
    #include <vector>
    int main() {
        std::vector<int> vec1 = {1, 2, 3};
        std::vector<int> vec2 = {4, 5, 6};
        auto it = vec1.begin() + 1;
        vec1.insert(it, vec2.begin(), vec2.end());
        for (int i : vec1) {
            std::cout << i << " ";
        }
        std::cout << std::endl;
        return 0;
    }
    

    上述代码将vec2的所有元素插入到vec1的第二个位置(索引为1)。

5.3 尾部删除(pop_back

pop_back()成员函数用于删除vector的最后一个元素。例如:

#include <iostream>
#include <vector>
int main() {
    std::vector<int> vec = {1, 2, 3, 4, 5};
    vec.pop_back();
    for (int i : vec) {
        std::cout << i << " ";
    }
    std::cout << std::endl;
    return 0;
}

执行pop_back()后,vec的最后一个元素5被删除,输出结果为1 2 3 4

5.4 任意位置删除(erase

erase()成员函数可以删除vector中指定位置或指定范围的元素。

  • 删除单个元素:

    #include <iostream>
    #include <vector>
    int main() {
        std::vector<int> vec = {1, 2, 3, 4, 5};
        auto it = vec.begin() + 2;
        vec.erase(it);
        for (int i : vec) {
            std::cout << i << " ";
        }
        std::cout << std::endl;
        return 0;
    }
    

    上述代码删除了vec中索引为2的元素,即3。

  • 删除元素范围:

    #include <iostream>
    #include <vector>
    int main() {
        std::vector<int> vec = {1, 2, 3, 4, 5};
        auto first = vec.begin() + 1;
        auto last = vec.begin() + 3;
        vec.erase(first, last);
        for (int i : vec) {
            std::cout << i << " ";
        }
        std::cout << std::endl;
        return 0;
    }
    

    这里删除了从索引1到索引3(不包括索引3)的元素,即2和3。

5.5 清空vectorclear

clear()成员函数用于删除vector中的所有元素,将其大小变为0,但容量不变。例如:

#include <iostream>
#include <vector>
int main() {
    std::vector<int> vec = {1, 2, 3, 4, 5};
    vec.clear();
    std::cout << "vector的大小: " << vec.size() << std::endl;
    std::cout << "vector的容量: " << vec.capacity() << std::endl;
    return 0;
}

执行clear()后,vec的大小变为0,容量保持不变。

6. 迭代器

6.1 迭代器的概念

迭代器是一种类似指针的对象,用于遍历容器中的元素。vector提供了多种迭代器类型,包括正向迭代器、反向迭代器等。

6.2 正向迭代器

begin()成员函数返回一个指向vector第一个元素的正向迭代器,end()成员函数返回一个指向vector最后一个元素之后位置的正向迭代器。例如:

#include <iostream>
#include <vector>
int main() {
    std::vector<int> vec = {1, 2, 3, 4, 5};
    for (auto it = vec.begin(); it != vec.end(); ++it) {
        std::cout << *it << " ";
    }
    std::cout << std::endl;
    return 0;
}

在上述代码中,it是一个正向迭代器,通过*it可以访问迭代器指向的元素。++it使迭代器指向下一个元素。

6.3 反向迭代器

rbegin()成员函数返回一个指向vector最后一个元素的反向迭代器,rend()成员函数返回一个指向vector第一个元素之前位置的反向迭代器。例如:

#include <iostream>
#include <vector>
int main() {
    std::vector<int> vec = {1, 2, 3, 4, 5};
    for (auto it = vec.rbegin(); it != vec.rend(); ++it) {
        std::cout << *it << " ";
    }
    std::cout << std::endl;
    return 0;
}

这段代码使用反向迭代器从后往前遍历vector,输出结果为5 4 3 2 1

6.4 常量迭代器

cbegin()cend()返回常量正向迭代器,crbegin()crend()返回常量反向迭代器。常量迭代器不能用于修改元素值,常用于只读遍历。例如:

#include <iostream>
#include <vector>
int main() {
    const std::vector<int> vec = {1, 2, 3, 4, 5};
    for (auto it = vec.cbegin(); it != vec.cend(); ++it) {
        std::cout << *it << " ";
    }
    std::cout << std::endl;
    return 0;
}

在这个例子中,vec是一个常量vector,只能使用常量迭代器进行遍历。

7. 遍历vector

7.1 使用for循环和下标

这是最基本的遍历方式,如前面示例中多次使用的:

#include <iostream>
#include <vector>
int main() {
    std::vector<int> vec = {1, 2, 3, 4, 5};
    for (size_t i = 0; i < vec.size(); ++i) {
        std::cout << vec[i] << " ";
    }
    std::cout << std::endl;
    return 0;
}

7.2 使用迭代器

如前面迭代器部分示例所示,使用正向或反向迭代器遍历vector

#include <iostream>
#include <vector>
int main() {
    std::vector<int> vec = {1, 2, 3, 4, 5};
    for (auto it = vec.begin(); it != vec.end(); ++it) {
        std::cout << *it << " ";
    }
    std::cout << std::endl;
    return 0;
}

7.3 使用范围for循环(C++11起)

范围for循环是C++11引入的一种简洁的遍历容器的方式:

#include <iostream>
#include <vector>
int main() {
    std::vector<int> vec = {1, 2, 3, 4, 5};
    for (int num : vec) {
        std::cout << num << " ";
    }
    std::cout << std::endl;
    return 0;
}

在范围for循环中,num依次绑定到vec中的每个元素。如果需要修改元素,可以使用引用:

#include <iostream>
#include <vector>
int main() {
    std::vector<int> vec = {1, 2, 3, 4, 5};
    for (int& num : vec) {
        num *= 2;
    }
    for (int num : vec) {
        std::cout << num << " ";
    }
    std::cout << std::endl;
    return 0;
}

在这个例子中,num是元素的引用,通过num *= 2修改了vector中的每个元素。

8. vector与算法

C++标准库提供了丰富的算法,这些算法可以与vector结合使用。例如,使用<algorithm>头文件中的sort算法对vector进行排序:

#include <iostream>
#include <vector>
#include <algorithm>
int main() {
    std::vector<int> vec = {5, 3, 1, 4, 2};
    std::sort(vec.begin(), vec.end());
    for (int i : vec) {
        std::cout << i << " ";
    }
    std::cout << std::endl;
    return 0;
}

上述代码使用std::sortvec进行升序排序。除了sort,还有很多其他算法,如find用于查找元素,count用于统计元素个数等,都可以与vector配合使用。

9. vector的内存管理

vector在内部使用动态内存来存储元素。当添加元素导致大小超过容量时,vector会重新分配内存,将旧元素复制到新的内存位置。这一过程可能会导致性能开销,特别是在频繁插入元素时。为了减少这种开销,可以预先使用reserve函数分配足够的容量。

vector对象被销毁时,它会自动释放所占用的内存。这使得vector在内存管理方面比普通数组更加安全和方便。

10. 总结

vector作为C++中常用的模板类,提供了动态数组的功能,具有自动内存管理、方便的元素访问和操作方法。通过掌握vector的各种用法,包括定义与初始化、元素访问、容量与大小管理、元素插入与删除、迭代器使用、遍历方式以及与算法的结合等,开发者可以更高效地编写C++程序,处理各种数据存储和操作需求。在实际应用中,需要根据具体情况合理使用vector的特性,以优化程序性能和代码的可读性。