大家好啊,这几天憋了个大的,现在我们就来聊聊C++中的vector的那些重要知识点,那么开始发车喽!
1.1 vector的介绍
在C++标准库中,std::vector
是一个非常常用的动态数组容器,它提供了动态大小的数组功能,可以根据需要动态增加或减少数组的大小。std::vector
是一个模板类,可以存储任意类型的元素,类似于数组,但具有更多的功能和灵活性。
特点:
- 动态大小:
std::vector
可以根据需要动态调整大小,无需在编译时指定数组大小。 - 随机访问:通过下标可以快速访问
std::vector
中的元素,支持常数时间复杂度的随机访问。 - 尾部插入和删除:可以在
std::vector
的尾部高效地插入和删除元素。 - 迭代器支持:提供了迭代器接口,支持遍历容器中的元素。
- 内存自动管理:
std::vector
会自动管理动态内存分配和释放,避免了手动管理内存的麻烦。
下面我们通过演示代码来展示std::vector
的基本用法:
#include <iostream>
#include <vector>
int main() {
// 创建一个空的vector
std::vector<int> vec;
// 向vector中添加元素
vec.push_back(10);
vec.push_back(20);
vec.push_back(30);
// 遍历vector并输出元素
for (size_t i = 0; i < vec.size(); ++i) {
std::cout << vec[i] << " ";
}
std::cout << std::endl;
return 0;
}
在上面的示例中,我们首先创建了一个空的std::vector<int>
对象vec
,然后使用push_back
方法向vec
中添加了三个整数元素,最后使用循环遍历vec
并输出元素。这也是std::vector
的基本用法。
1.2.1 vector的定义
在本节中,我们将重点讨论std::vector的构造函数和拷贝构造函数。
vector的构造函数
vector()
- 功能:默认构造函数,创建一个空的
std::vector
对象。 - 示例代码:
#include <iostream>
#include <vector>
int main() {
// 使用默认构造函数创建一个空的vector
std::vector<int> vec;
// 判断vector是否为空
if (vec.empty()) {
std::cout << "Vector is empty" << std::endl;
}
return 0;
}
- 注释:使用默认构造函数可以创建一个空的
std::vector
对象,然后可以通过其他方法向其中添加元素。
vector的拷贝构造函数
vector (const vector& x)
- 功能:拷贝构造函数,创建一个新的
std::vector
对象,其内容与参数x
相同。 - 示例代码:
#include <iostream>
#include <vector>
int main() {
// 创建一个包含元素的vector
std::vector<int> vec = {1, 2, 3, 4, 5};
// 使用拷贝构造函数创建一个新的vector
std::vector<int> newVec(vec);
// 输出新的vector内容
for (int num : newVec) {
std::cout << num << " ";
}
std::cout << std::endl;
return 0;
}
- 注释:拷贝构造函数会创建一个新的
std::vector
对象,其内容与参数x
相同,但是是独立的对象,对其中一个对象的修改不会影响另一个对象。
1.2.2 vector iterator 的使用
在C++中,std::vector
提供了一组迭代器(iterator)来访问容器中的元素。迭代器是一种类似指针的对象,可以用于遍历容器中的元素。在本节中,我们将重点讨论begin()
和end()
函数,这两个函数返回指向第一个元素和最后一个元素之后位置的迭代器。
使用begin()
和end()
函数
begin()
- 功能:返回指向容器中第一个元素的迭代器。
- 示例代码:
#include <iostream>
#include <vector>
int main() {
std::vector<int> vec = {1, 2, 3, 4, 5};
// 使用begin()函数获取第一个元素的迭代器
std::vector<int>::iterator it = vec.begin();
// 输出第一个元素
std::cout << "First element: " << *it << std::endl;
return 0;
}
- 注释:
begin()
函数返回一个迭代器,指向容器中的第一个元素。
end()
- 功能:返回指向容器中最后一个元素之后位置的迭代器。
- 示例代码:
#include <iostream>
#include <vector>
int main() {
std::vector<int> vec = {1, 2, 3, 4, 5};
// 使用end()函数获取最后一个元素之后位置的迭代器
std::vector<int>::iterator it = vec.end();
// 输出最后一个元素之后位置的值
std::cout << "Value after the last element: " << *it << std::endl;
return 0;
}
- 注释:
end()
函数返回一个迭代器,指向容器中最后一个元素之后的位置。
1.2.3 vector 空间增长问题
我们知道,在C++中,std::vector
是一个动态数组,它可以自动调整存储空间的大小以容纳新元素。在本节中,我们将重点讨论resize()
和reserve()
函数,这两个函数用于管理std::vector
的存储空间。
使用resize()
和reserve()
函数
resize()
- 功能:改变
std::vector
的大小,如果新大小大于当前大小,则在末尾插入默认构造的元素;如果新大小小于当前大小,则删除多余的元素。 - 示例代码:
#include <iostream>
#include <vector>
int main() {
std::vector<int> vec = {1, 2, 3};
// 改变vector的大小为5,多出的元素用0填充
vec.resize(5);
// 输出所有元素
for (int num : vec) {
std::cout << num << " ";
}
std::cout << std::endl;
return 0;
}
- 注释:
resize()
函数可以用来调整std::vector
的大小,并在需要时填充默认值。
reserve()
- 功能:为
std::vector
预留存储空间,但不改变std::vector
的大小。 - 示例代码:
#include <iostream>
#include <vector>
int main() {
std::vector<int> vec = {1, 2, 3};
// 预留存储空间为10个元素
vec.reserve(10);
// 输出当前存储空间大小
std::cout << "Capacity after reserving: " << vec.capacity() << std::endl;
return 0;
}
- 注释:
reserve()
函数可以用来预留存储空间,避免频繁重新分配内存。
1.2.4 vector 增删查改
我们知道,在C++中,std::vector
是一个动态数组,提供了丰富的操作函数来实现元素的增删查改。本节将重点讨论push_back
、pop_back
和operator[]
这三个函数的用法。
push_back()
- 功能:在
std::vector
的末尾添加一个元素。 - 示例代码:
#include <iostream>
#include <vector>
int main() {
std::vector<int> vec = {1, 2, 3};
// 在末尾添加元素4
vec.push_back(4);
// 输出所有元素
for (int num : vec) {
std::cout << num << " ";
}
std::cout << std::endl;
return 0;
}
- 注释:
push_back()
函数可以用来向std::vector
中添加新元素。
pop_back()
- 功能:删除
std::vector
的末尾元素。 - 示例代码:
#include <iostream>
#include <vector>
int main() {
std::vector<int> vec = {1, 2, 3};
// 删除末尾元素
vec.pop_back();
// 输出所有元素
for (int num : vec) {
std::cout << num << " ";
}
std::cout << std::endl;
return 0;
}
- 注释:
pop_back()
函数可以用来删除std::vector
的末尾元素。
operator[]
- 功能:通过下标访问
std::vector
中的元素。 - 示例代码:
#include <iostream>
#include <vector>
int main() {
std::vector<int> vec = {1, 2, 3};
// 访问第二个元素
std::cout << "Element at index 1: " << vec[1] << std::endl;
return 0;
}
- 注释:
operator[]
可以用来通过下标直接访问std::vector
中的元素。
1.2.5 vector 迭代器失效问题
在使用C++中的std::vector
时,需要注意迭代器失效的问题。当对std::vector
进行插入或删除操作时,可能会导致迭代器失效,进而引发未定义行为。本节将重点讨论vector迭代器失效问题以及如何避免这种情况。
迭代器失效问题
在std::vector
中,当进行插入或删除操作时,会导致内存重新分配,原有的迭代器可能会指向无效的内存位置,从而造成程序崩溃或产生不可预测的结果。
避免迭代器失效的方法
-
使用索引代替迭代器:在插入或删除元素后,尽量避免继续使用之前的迭代器,可以通过索引来访问元素,因为索引不会失效。
-
重新获取迭代器:在每次插入或删除操作后,重新获取需要使用的迭代器,确保迭代器指向有效的元素。
-
使用
erase
函数:在删除元素时,可以使用erase
函数返回下一个有效迭代器,避免使用失效的迭代器。
示例代码
#include <iostream>
#include <vector>
int main() {
std::vector<int> vec = {1, 2, 3, 4};
// 插入元素
vec.insert(vec.begin() + 2, 5);
// 删除元素
vec.erase(vec.begin() + 1);
// 使用迭代器遍历
for (auto it = vec.begin(); it != vec.end(); ++it) {
std::cout << *it << " ";
}
std::cout << std::endl;
return 0;
}
1.2.6 vector 在OJ中的使用
在我们平时做的OJ题中,std::vector
是一个常用的数据结构,可以帮助我们解决各种算法问题。本节将介绍如何在OJ中正确地使用std::vector
,并提供一些示例代码和习题。
使用示例
下面是一个简单的OJ题目:给定一个整数数组,将数组中的所有偶数移到数组的末尾,保持奇数和偶数的相对顺序不变。
#include <iostream>
#include <vector>
void moveEvenToEnd(std::vector<int>& nums) {
std::vector<int> result;
for (int num : nums) {
if (num % 2 != 0) {
result.push_back(num);
}
}
for (int num : nums) {
if (num % 2 == 0) {
result.push_back(num);
}
}
nums = result; // 将结果拷贝回原数组
}
int main() {
std::vector<int> nums = {1, 2, 3, 4, 5, 6, 7, 8};
moveEvenToEnd(nums);
for (int num : nums) {
std::cout << num << " ";
}
std::cout << std::endl;
return 0;
}
习题:
-
题目:给定一个整数数组,找出数组中出现次数超过一半的元素。
-
题目:给定一个字符串,统计字符串中每个字符出现的次数,并按字符顺序输出。
-
题目:给定两个有序整数数组,合并两个数组并保持有序。
习题详细解题思路和步骤:
1. 找出数组中出现次数超过一半的元素
解题思路:
- 使用
std::vector
记录每个元素出现的次数。 - 找出出现次数超过一半的元素。
解题步骤:
- 遍历数组,使用
std::vector<int>
记录每个元素出现的次数。 - 找出出现次数超过一半的元素。
2. 统计字符串中每个字符出现的次数,并按字符顺序输出
解题思路:
- 使用
std::vector<int>
记录每个字符出现的次数。 - 按字符顺序输出统计结果。
解题步骤:
- 遍历字符串,使用
std::vector<int>
记录每个字符出现的次数。 - 按字符顺序输出统计结果。
3. 合并两个有序整数数组并保持有序
解题思路:
- 使用两个指针分别指向两个数组的开头。
- 比较大小后依次将较小的元素添加到结果数组中。
解题步骤:
- 初始化两个指针分别指向两个数组的开头。
- 比较指针所指元素的大小,将较小的元素添加到结果数组中,并移动对应指针。
- 直到其中一个数组遍历完毕,将剩余数组的元素依次添加到结果数组中。
#include <iostream>
#include <vector>
// 找出数组中出现次数超过一半的元素
int findMajorityElement(std::vector<int>& nums) {
int majorityElement = 0;
int count = 0;
for (int num : nums) {
if (count == 0) {
majorityElement = num;
count = 1;
} else if (num == majorityElement) {
count++;
} else {
count--;
}
}
return majorityElement;
}
// 统计字符串中每个字符出现的次数,并按字符顺序输出
void countAndOutputCharacters(const std::string& str) {
std::vector<int> charCount(256, 0); // 假设为ASCII字符集
for (char c : str) {
charCount[static_cast<int>(c)]++;
}
for (int i = 0; i < 256; i++) {
if (charCount[i] > 0) {
std::cout << static_cast<char>(i) << ": " << charCount[i] << std::endl;
}
}
}
// 合并两个有序整数数组并保持有序
std::vector<int> mergeSortedArrays(const std::vector<int>& arr1, const std::vector<int>& arr2) {
std::vector<int> mergedArray;
int i = 0, j = 0;
while (i < arr1.size() && j < arr2.size()) {
if (arr1[i] < arr2[j]) {
mergedArray.push_back(arr1[i]);
i++;
} else {
mergedArray.push_back(arr2[j]);
j++;
}
}
while (i < arr1.size()) {
mergedArray.push_back(arr1[i]);
i++;
}
while (j < arr2.size()) {
mergedArray.push_back(arr2[j]);
j++;
}
return mergedArray;
}
int main() {
// 示例用法
std::vector<int> nums = {1, 2, 2, 2, 3, 2, 2, 2, 5};
int majorityElement = findMajorityElement(nums);
std::cout << "Majority Element: " << majorityElement << std::endl;
std::string str = "hello";
countAndOutputCharacters(str);
std::vector<int> arr1 = {1, 3, 5, 7};
std::vector<int> arr2 = {2, 4, 6, 8};
std::vector<int> mergedArray = mergeSortedArrays(arr1, arr2);
std::cout << "Merged Sorted Array: ";
for (int num : mergedArray) {
std::cout << num << " ";
}
std::cout << std::endl;
return 0;
}
2.1 std::vector的核心框架接口的模拟实现bit::vector
在本节中,我们将模拟实现bit::vector
,一个简化版的std::vector
,以便更好地理解std::vector
的核心框架接口。
#include <iostream>
#include <algorithm>
template <typename T>
class bit_vector {
private:
T* data;
size_t capacity;
size_t size;
public:
// 默认构造函数
bit_vector() : data(nullptr), capacity(0), size(0) {}
// 复制构造函数
bit_vector(const bit_vector& other) : capacity(other.capacity), size(other.size) {
data = new T[capacity];
std::copy(other.data, other.data + size, data);
}
// 析构函数
~bit_vector() {
delete[] data;
}
// 返回指向第一个元素的迭代器
T* begin() {
return data;
}
// 返回指向最后一个元素之后的位置的迭代器
T* end() {
return data + size;
}
// 在vector末尾添加元素
void push_back(const T& value) {
if (size == capacity) {
reserve(capacity == 0 ? 1 : capacity * 2);
}
data[size++] = value;
}
// 移除vector末尾的元素
void pop_back() {
if (size > 0) {
--size;
}
}
// 预留空间
void reserve(size_t new_capacity) {
if (new_capacity > capacity) {
T* new_data = new T[new_capacity];
std::copy(data, data + size, new_data);
delete[] data;
data = new_data;
capacity = new_capacity;
}
}
// 访问元素
T& operator[](size_t index) {
return data[index];
}
};
int main() {
bit_vector<int> vec;
vec.push_back(1);
vec.push_back(2);
for (int& num : vec) {
std::cout << num << " ";
}
return 0;
}
在上面的代码中,我们定义了一个简化版的bit_vector
类,模拟实现了std::vector
的核心框架接口,包括构造函数、析构函数、迭代器的使用、增加元素、删除元素、访问元素等功能。
2.2 使用memcpy拷贝问题
在C++中,使用memcpy
函数进行内存拷贝时,对于涉及到动态内存分配的对象(如std::vector
),需要格外小心,因为memcpy
只是简单的按字节拷贝,无法正确处理对象内部的指针等成员变量。
下面我们来演示一个使用memcpy
拷贝std::vector
的错误示例,并说明问题所在:
#include <iostream>
#include <vector>
#include <cstring>
int main() {
std::vector<int> vec1 = {1, 2, 3, 4, 5};
// 使用memcpy进行拷贝
std::vector<int> vec2;
vec2.resize(vec1.size());
memcpy(vec2.data(), vec1.data(), vec1.size() * sizeof(int));
// 输出vec2的内容
for (int num : vec2) {
std::cout << num << " ";
}
return 0;
}
在上面的代码中,我们试图通过memcpy
将vec1
拷贝到vec2
,但这样做是错误的。因为std::vector
内部不仅仅包含元素数据,还包含了指向动态分配的内存空间的指针,这些指针在拷贝时无法正确处理,会导致潜在的内存错误。
正确的方式是使用std::vector
提供的拷贝构造函数或赋值运算符来进行拷贝:
std::vector<int> vec3(vec1); // 使用拷贝构造函数
// 或
std::vector<int> vec4;
vec4 = vec1; // 使用赋值运算符
这样可以确保std::vector
内部的指针等成员变量得到正确的处理,避免潜在的问题。
2.3 动态二维数组理解
动态二维数组是指在运行时动态分配内存以创建二维数组。在C++中,可以使用std::vector
嵌套来实现动态二维数组,具有灵活性和便利性。
下面是一个演示如何创建和操作动态二维数组的示例代码:
#include <iostream>
#include <vector>
int main() {
int rows = 3;
int cols = 4;
// 创建动态二维数组
std::vector<std::vector<int>> matrix(rows, std::vector<int>(cols));
// 初始化动态二维数组
for (int i = 0; i < rows; ++i) {
for (int j = 0; j < cols; ++j) {
matrix[i][j] = i * cols + j;
}
}
// 输出动态二维数组
for (int i = 0; i < rows; ++i) {
for (int j = 0; j < cols; ++j) {
std::cout << matrix[i][j] << " ";
}
std::cout << std::endl;
}
return 0;
}
在上面的代码中,我们首先定义了行数和列数,然后使用std::vector
嵌套创建了一个3行4列的动态二维数组matrix
。接着我们对数组进行了初始化,并输出了数组的内容。
使用std::vector
嵌套可以方便地创建和操作动态二维数组,同时避免了手动管理内存的复杂性,提高了代码的可读性和可维护性。动态二维数组在处理需要动态大小的二维数据时非常实用。
好了,感谢大家看到这,如果觉得本篇文章对你有帮助的话,还请点个赞支持一下,有什么问题也可以评论区留言,那么我们下次再见了,Peace~