【C++】容器的迭代器失效问题

文章目录

  • 1. 了解哪些操作可能导致迭代器失效:
  • 2. 管理迭代器:
    • 编写改变容器的循环程序
    • 不要保存`end` 返回的选代器
  • 3. 编写安全的循环程序:
  • 下面是一些关于迭代器失效问题的建议和注意事项,并附上一些示例:

迭代器失效问题是 C++ 程序设计中一个常见的问题。在使用容器进行添加/删除元素的操作时,指向容器元素的指针、引用或迭代器可能会失效,这将导致严重的程序错误。

为了避免迭代器失效,我们需要了解哪些操作可能会导致迭代器失效,以及如何管理迭代器。下面是一些关于迭代器失效问题的建议和注意事项:

1. 了解哪些操作可能导致迭代器失效:

向容器添加元素和从容器删除元素的操作都可能导致迭代器失效。具体来说,对于不同的容器类型,失效的情况也有所不同,比如:

  • 对于 vectorstring 容器,存储空间被重新分配时,所有指向容器元素的指针、引用或迭代器都会失效;如果存储空间未重新分配,则指向插入位置之后的元素的迭代器、指针和引用将会失效。
  • 对于 deque 容器,插入到除首尾位置之外的任何位置都会导致迭代器、指针和引用失效;如果在首尾位置添加元素,迭代器会失效,但指向存在的元素的引用和指针不会失效。
  • 对于 listforward_list 容器,指向容器的迭代器、指针和引用仍然有效。

2. 管理迭代器:

为了避免使用失效的迭代器,我们需要尽量减少要求迭代器必须保持有效的程序片段。具体来说,每次改变容器的操作之后都应该重新定位迭代器。在编写改变容器的循环程序时,需要特别注意。比如:

  • 在循环中调用 inserterase 时,需要及时更新迭代器。
  • 不要保存 end 返回的迭代器,而是应该在每次添加/删除元素后重新计算 end。

编写改变容器的循环程序

添加/删除vectorstringdeque 元素的循环程序必须考虑迭代器、引用和指针可能失效的问题。程序必须保证每个循环步中都更新迭代器、引用或指针。如果循环中调用的是insert或erase,那么更新迭代器很容易。这些操作都返回迭代器,我们可以用来更新:

	//傻瓜循环,删除偶数元素,复制每个奇数元素
	vector<int> vi = { 0,1,2,3,4,4,4,5,6,7,8 };
	auto iter = vi.begin();	// 调用 begin 而不是 cbegin,因为我们要改变 vi
	while (iter != vi.end()) {
		if (*iter % 2) {
			iter = vi.insert(iter, *iter);	// 复制当前元素,并记录返回有效的迭代器
			iter += 2;	// 向前移动迭代器,跳过当前元素以及插入到它之前的元素
			iter++;
		}	  
		else {
			iter = vi.erase(iter);		//删除偶数元素,并记录返回有效的迭代器
			// 不应该向前移动迭代器,iter指向我们删除的元素之后的元素
		}
	}

此程序删除vector中的偶数值元素,并复制每个奇数值元素。我们在调用inserterase 后都更新迭代器,因为两者都会使迭代器失效。
在调用erase后,不必递增迭代器,因为erase返回的迭代器已经指向序列中下、个元素。调用insert后,需要递增迭代器两次。记住,insert在给定位置之前插入新元素,然后返回指向新插入元素的迭代器。因此,在调用insert后,iter指向新插入元素,位于我们正在处理的元素之前。我们将迭代器递增两次,恰好越过了新添加的元素和正在处理的元素,指向下一个未处理的元素。

不要保存end 返回的选代器

当我们添加/删除 vectorstring 的元素后,或在 deque 中首元素之外任何位添加/删除元素后,原来 end 返回的迭代器总是会失效。因此,添加或删除元素的循环程序必须反复调用 end,而不能在循环之前保存 end 返回的选代器,一直当作容器末用。通常 C++标准库的实现中 end()操作都很快,部分就是因为这个原因。
例如,考虑这样一个循环,它处理容器中 的每个元素,在其后添加一个新元素。我们希望循环能跳过新添加的元素,只处理原有元素。在每步循环之后,我们将定位选代器使其指向下一个原有元素。如果我们试图“优化”这个循环,在循环之前保存 end()返的迭代器,一直用作容器末尾,就会导致一场灾难:

vector<int> v = { 0,1,2,3,4,4,4,5,6,7,8 };
//灾难:此循环的行为是未定义的
auto begin = v.begin(), 
	end = v.end();	//保存尾迭代器的值是个坏注意
while (begin != end) {
	//做一些处理
	//插入新的值,对begin重新赋值,否则的话他就会失效
	++begin;	// 向前移动begin重新赋值,否则的话它就会失效
	begin = v.insert(begin, 42); // 插入新值
	++begin;	//向前移动begin跳过我们刚刚加入的元素
}

此代码行为是未定义的。在很多标准库实现上,此代码会导致无限循环。问题在于我们将end操作返回的迭代器保存在一个名为end的局部变量中。在循环体中,我们向容器添加了一个元素,这个操作使保存end迭代器中的迭代器失效了。这个迭代器不再指向v中任何元素,或是v中元素之后的值。

如果在一个循环中插入/删除deque、stringvector中的元素,不要缓存end返回的迭代器。

必须每插入操作后重新调用end(),而不能在循环开始保存它返回的迭代器:

//更安全的方法:在每个循环步添加/删除元素后都重新计算end
while (begin != v.end()) {
	//做一些处理
	++begin; // 向前移动begin,因为我们想要在此元素后插入元素
	begin = v.insert(begin, 42);// 插入新值
	++begin; //向前移动begin,跳过我们刚刚加入的元素
}

3. 编写安全的循环程序:

当我们编写添加/删除容器元素的循环程序时,需要特别小心迭代器失效问题。在循环中应该尽量减少对失效的迭代器的使用,并及时更新迭代器。比如:

  • 当我们在 vectorstring 中添加/删除元素时,不要保存 end 返回的迭代器。
  • 在添加/删除 dequevector 中的元素时,需要在每个循环步骤之后重新计算迭代器。

下面是一些关于迭代器失效问题的建议和注意事项,并附上一些示例:

  1. 向容器添加元素时,尽量使用插入操作而不是直接赋值操作。直接赋值操作可能导致容器重新分配内存空间,从而使得原本有效的迭代器失效。
std::vector<int> vec;

// 错误示例:直接赋值操作会导致迭代器失效
for (int i = 0; i < 10; ++i) {
    vec[i] = i;
}

// 正确示例:使用插入操作保证迭代器的有效性
for (int i = 0; i < 10; ++i) {
    vec.push_back(i);
}
  1. 当对容器进行删除操作时,要小心迭代器的失效。在删除元素后,迭代器会指向被删除元素之后的位置,因此需要注意更新迭代器。
std::list<int> lst{1, 2, 3, 4, 5};

// 错误示例:未更新迭代器导致失效
for (auto it = lst.begin(); it != lst.end(); ++it) {
    if (*it % 2 == 0) {
        lst.erase(it);  // 删除偶数元素
    }
}

// 正确示例:正确更新迭代器
for (auto it = lst.begin(); it != lst.end(); ) {
    if (*it % 2 == 0) {
        it = lst.erase(it);  // 删除偶数元素,并更新迭代器
    } else {
        ++it;
    }
}
  1. 在使用迭代器进行循环遍历容器时,要尽量避免修改容器的结构。如果需要修改容器的结构,可以先将需要修改的元素保存到临时变量中,然后再进行修改。
std::vector<int> vec{1, 2, 3, 4, 5};

// 错误示例:在循环中直接删除元素导致迭代器失效
for (auto it = vec.begin(); it != vec.end(); ++it) {
    if (*it % 2 == 0) {
        vec.erase(it);  // 直接删除偶数元素,导致迭代器失效
    }
}

// 正确示例:先保存需要删除的元素,再进行删除操作
std::vector<int> vec{1, 2, 3, 4, 5};
std::vector<int> toRemove;

for (auto it = vec.begin(); it != vec.end(); ++it) {
    if (*it % 2 == 0) {
        toRemove.push_back(*it);  // 保存需要删除的元素
    }
}

for (const auto& elem : toRemove) {
    vec.erase(std::remove(vec.begin(), vec.end(), elem), vec.end());  // 删除元素
}

总之,迭代器失效问题是 C++ 程序设计中一个非常重要的问题,需要特别小心,才能避免程序错误。在编写代码时,要时刻注意迭代器的有效性,并且尽量减少对失效迭代器的使用,这是一个良好的编程习惯。

你可能感兴趣的:(C++,c++,开发语言)