从零开始的Cpp(1)STL容器:vector(向量)与迭代器

1.什么是vector

在C++中,vector是一个动态数组,它会自动扩容以适应需要存储的元素数量。vector实现方式通过使用连续的内存块来存储元素支持随机访问和索引

当向vector中添加元素时,如果当前的内存空间不足以容纳新的元素,vector会自动进行扩容。扩容的过程包括以下几个步骤

  1. vector会检查当前的容量(capacity)是否足够,如果不够,则需要分配更大的内存空间。
  2. 分配新的内存空间,通常是当前容量的两倍大小。
  3. 将原有的元素从旧的内存空间复制到新的内存空间。
  4. 释放旧的内存空间。
  5. 更新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的位置上。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值