1.什么是vector
在C++中,vector
是一个动态数组,它会自动扩容以适应需要存储的元素数量。vector
的实现方式是通过使用连续的内存块来存储元素。支持随机访问和索引。
当向vector
中添加元素时,如果当前的内存空间不足以容纳新的元素,vector
会自动进行扩容。扩容的过程包括以下几个步骤:
vector
会检查当前的容量(capacity)是否足够,如果不够,则需要分配更大的内存空间。- 分配新的内存空间,通常是当前容量的两倍大小。
- 将原有的元素从旧的内存空间复制到新的内存空间。
- 释放旧的内存空间。
- 更新
vector
的容量和大小信息。
这种扩容策略可以保证向vector
中添加元素的平均时间复杂度为常数时间O(1),最坏是O(n)。但是,在进行扩容时,可能会涉及到内存的重新分配和元素的复制,这可能会导致一些性能开销。
需要注意的是,由于vector
使用连续的内存块存储元素,当进行扩容时,如果内存空间不足,可能需要重新分配一块更大的内存空间,这可能导致之前获取的指向vector
中元素的迭代器、引用或指针失效。因此,在进行扩容操作后,需要重新获取这些迭代器、引用或指针,以确保它们仍然指向有效的元素。
2.Vector的主要功能
在C++中,`vector`是一种动态数组容器,提供了以下主要操作:
1. 插入和删除操作:
- `push_back(value)`:在数组末尾插入一个元素。
- `pop_back()`:删除数组末尾的元素。
- `insert(position, value)`:在指定位置之前插入一个元素。
- `erase(position)`:删除指定位置的元素。
- `clear()`:清空数组中的所有元素。
2. 访问操作:
- `at(index)`:返回指定索引位置的元素的引用。
- `front()`:返回数组第一个元素的引用。
- `back()`:返回数组最后一个元素的引用。
- `data()`:返回指向数组内存中第一个元素的指针。
- `operator[]`:使用索引访问元素。
3. 大小和容量操作:
- `size()`:返回数组中元素的个数。
- `empty()`:判断数组是否为空。
- `capacity()`:返回数组当前的容量。
- `reserve(newCapacity)`:设置数组的最小容量为`newCapacity`,可能会导致内存重新分配。
4. 其他操作:
- `resize(newSize)`:改变数组的大小为`newSize`,可能会导致元素的添加或删除。
- `swap(otherVector)`:交换当前数组和另一个数组的内容。
在C++中,vector
容器没有提供直接的push_front()
函数用于在数组头部插入元素。由于vector
是基于动态数组实现的,插入元素到数组头部会导致所有元素的后移,这将导致较高的时间复杂度,退化为O(n)。如果需要频繁在头部插入元素,可以使用deque容器。
用一段程序体现vector的主要功能
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
//这行代码用来创建一个名为`container`的`std::vector`容器,并初始化它的元素为1、2、3、4、5
std::vector<int> container = { 1, 2, 3, 4, 5 };
// 获取容器的起始和结束迭代器
std::vector<int>::iterator it_begin = container.begin();
auto it_end = container.end();
在C++中,容器类(如std::vector、std::list等)提供了成员函数begin(),用于获取指向容器中第一个元素的迭代器。通过调用container.begin(),我们可以获取一个指向容器起始位置的迭代器。
auto关键字是C++11引入的一种类型推断机制,它可以根据变量的初始化表达式自动推断出变量的类型。而::iterator声明可以更明确地表达迭代器的类型,使代码更易于理解和维护。
因此,用于获取起始和结束迭代器的写法不同,但是结果一样,可以根据实际情况选取。
// 将迭代器移动到第三个位置
std::advance(it_begin, 2);
这行代码的作用是将迭代器 it_begin 向前移动 2 个位置。
在 C++ 中,std::advance() 是一个算法函数,用于将迭代器向前或向后移动指定的步数。它接受两个参数:一个迭代器和一个表示移动的步数的整数值。
在这个例子中,it_begin 是一个迭代器,我们希望将它向前移动 2 个位置。通过调用 std::advance(it_begin, 2),我们将迭代器 it_begin 向前移动了 2 个位置。
请注意,如果尝试将迭代器移动超出容器范围,将会导致未定义行为。因此,在使用 std::advance() 之前,确保要移动的位置在有效范围内。
// 将第三个元素乘以2
*it_begin *= 2;
在 C++ 中,通过使用 * 运算符,可以从迭代器中获取其指向的元素。在这个例子中,it_begin 是一个迭代器,指向容器中的某个元素。通过 *it_begin,我们可以获取该元素的值,并对其进行操作。
而 *= 运算符是一个复合赋值运算符,用于将右侧的值与左侧的值相乘,并将结果赋值给左侧的值。因此,*it_begin *= 2; 的作用是将迭代器 it_begin 所指向的元素乘以2,并将结果更新到容器中。
// 打印修改后的容器
for (auto it = container.begin(); it != container.end(); ++it) {
std::cout << *it << " ";
}
std::cout << std::endl;
return 0;
}
声明一个新的迭代器,用来打印。for循环的判断条件也可以使用C++11引入的一种更简洁的写法:for(auto it : container),它可以遍历一个容器中的所有元素,将每个元素赋值给迭代变量it
。这两种写法等价。
std::cout 是一个输出流对象,可以通过 << 运算符将数据插入到输出流中。<< 运算符可以连续使用,用于串联多个输出操作。输出的数据可以是字符串、变量、表达式等。在例子里,先输出*it(迭代器对应的元素值),再输出空格分隔开各个元素。
在 C++ 中,`std::endl` 是一个特殊的操纵符,表示插入一个换行符并刷新输出流。它类似于使用 `"\n"` 插入一个换行符,但它还会刷新输出流,确保之前的输出内容被立即输出到目标设备。
需要注意的是,使用 `std::endl` 会导致输出流的性能略有下降,因为它会刷新输出缓冲区。如果只需要插入换行符而不需要刷新输出流,可以使用 `"\n"` 字符串代替 `std::endl`。
3.迭代器失效
之前提到,在进行增删操作时,之前的迭代器可能会失效,举一个例子
#include <iostream>
#include <vector>
int main() {
std::vector<int> myVector = { 1, 2, 3, 4, 5 };
auto it = myVector.begin() + 2; // 获取迭代器指向第三个元素
myVector.insert(it, 10); // 在第三个元素前插入10
std::cout << *it << std::endl; // 使用失效的迭代器访问元素
return 0;
}
运行这个程序会报错,针对这个问题,我们只需在删改操作后更新迭代器即可
#include <iostream>
#include <vector>
int main() {
std::vector<int> myVector = { 1, 2, 3, 4, 5 };
auto it = myVector.begin() + 2; // 获取迭代器指向第三个元素
it=myVector.insert(it, 10); // 在第三个元素前插入10
std::cout << *it << std::endl; // 使用失效的迭代器访问元素
return 0;
}
此时代码可以正常运行,因为vector
的插入和删除操作通常会返回一个指向被插入或删除元素的迭代器。可以使用这个返回值来更新迭代器,确保它们指向有效的元素。
除了利用删改时的操作更新迭代器,我们也可以重新获取一个迭代器。对于vector这种支持索引和随机访问的容器,我们也可以使用索引来替代迭代器的功能,避免出现迭代器失效的问题。
#include <iostream>
#include <vector>
int main() {
std::vector<int> myVector = {1, 2, 3, 4, 5};
auto it = myVector.begin() + 2; // 获取迭代器指向第三个元素
int index = it-myVector.begin(); // 获取迭代器对应的索引
myVector.insert(myVector.begin() + index, 10); // 在索引位置插入10
std::cout << myVector[index] << std::endl; // 使用索引访问元素
return 0;
}
在这段代码里,我们没有更新迭代器,也没有重新定义迭代器,而是创建一个索引,使用vector[index]的方式访问容器里的元素。
需要注意,当你尝试将一个迭代器直接赋值给一个整型变量时,比如int index = it ,会导致编译错误。这是因为迭代器是一个复杂的对象,它不是一个简单的整数值。
如果你想获得迭代器在容器中的索引值,你需要使用适当的方法来计算迭代器与容器起始位置之间的距离,而不是简单地将迭代器赋值给一个整型变量。也可以使用int index = std::distance(myVector.begin(), it);来计算。
4.迭代器的替代
在2的例子里,我们进行了利用迭代器把vector第三项乘以2的操作。但是除了使用迭代器,还有其他常见的方法可以实现“把容器里的第三项乘二”这个需求。
1.使用索引
这是因为vector支持索引和随机访问
#include <iostream>
#include <vector>
int main() {
std::vector<int> container = { 1, 2, 3, 4, 5 };
int length = container.size(); // 获取容器的长度
int count = 0; // 计数器变量
while (count < length) { // 使用while循环遍历容器
if (count == 2) { // 判断当前遍历的元素位置是否为第三个元素
container[count] *= 2; // 将第三个元素乘以2
}
count++; // 计数器自增
}
for (auto element : container) { // 输出结果
std::cout << element << " ";
}
std::cout << std::endl;
return 0;
}
2.使用函数
#include <iostream>
#include <vector>
#include <algorithm>
// 定义一个函数,接受一个元素作为参数,并返回该元素乘以2的结果
int multiplyByTwo(int num) {
return num * 2;
}
int main() {
std::vector<int> container = { 1, 2, 3, 4, 5 }; // 假设容器是一个vector
std::transform(container.begin() + 1, container.begin() + 3, container.begin() + 2, multiplyByTwo);
for (int it : container) { // 输出结果
std::cout << it << " ";
}
std::cout << "\n";
return 0;
}
使用std::transform函数将multiplyByTwo函数应用于容器中的第x个元素。
以下是 `std::transform` 函数在只有一个容器的情况下的用法:
std::transform(first, last, aim, unary_op);
参数说明:
- `first`:容器的起始迭代器,表示要进行操作的起始位置。
- `last`:容器的结束迭代器,表示要进行操作的结束位置。(操作不包括last)
- `unary_op`:一个一元函数对象(或函数指针),用于执行对容器中元素的操作。该函数接受一个参数,表示容器中的元素,并返回操作的结果。
`std::transform` 函数会遍历容器中的每个元素,并将它们作为参数传递给 `unary_op` 函数执行操作,然后将结果存储回aim中,如果first和last之间有多个元素,则会从aim开始储存。从本例子具体来说,它将容器中从索引1到索引3(不包括索引3,即索引1和索引2)的元素作为输入,然后将转换的结果存储到容器中从索引2开始的位置。也就是说,这行代码将容器中索引为1和2的元素乘以2,并将结果存储到索引为2和3的位置上。