在C ++中设置操作(更新现有值)


21

这是我的代码:

 while (it!=s.end())  //here 's' is a set of stl and 'it' is iterator of set
    {   
        *it=*it-sub;    //'sub' is an int value
        it++;
    }

我无法更新迭代器的set值。我想从集合的所有元素中减去一个整数值“ sub”。

谁能帮助我实际的问题所在,实际的解决方案是什么?

这是错误消息:

error: assignment of read-only location it.std::_Rb_tree_const_iterator<int>::operator*()’
   28 |             *it=*it-sub;
      |             ~~~^~~~~~~~


9
集合中的元素只能读取。通过修改一项,您可以对集合中的其他项重新排序。
rafix07

3
解决方案是擦除迭代器,并使用key插入一个新的迭代器*it - sub。请注意,std::set::erase()返回一个新的迭代器,在您的情况下必须使用该迭代器以使while循环正常工作。
谢夫

2
@Scheff可以在设置集合时这样做吗?难道它不会陷入无限循环吗?尤其是在执行与当前迭代集合相关的操作时,该迭代集合将访问的元素放置在将再次访问的位置?
Yunnosch

1
Imtiaz出于好奇,如果需要对该作业进行跟进,您能否在评论中进行报告(我认为这是一项作业,没有任何不好的意思,您的问题很好)?正如您在对Scheff答案的评论中所看到的,我正在推测与此有关的教师的更大计划。好奇而已。
Yunnosch

Answers:


22

a std::set中元素的键值是const有充分理由的。修改它们可能会破坏顺序,这对于至关重要std::set

因此,解决方案是擦除迭代器并使用key插入一个新的迭代器*it - sub。请注意,std::set::erase()返回一个新的迭代器,在您的情况下必须使用该迭代器以保持while循环正常运行。

#include<iostream>
#include<set>

template <typename T>
std::ostream& operator<<(std::ostream &out, const std::set<T> &values)
{
  const char *sep = "{ ";
  for (const T &value : values) { out << sep << value; sep = ", "; }
  return out << " }";
}

int main()
{
  std::set<int> test{ 11, 12, 13, 14, 15 };
  std::cout << "test: " << test << '\n';
  const int sub = 10;
  std::set<int>::iterator iter = test.begin();
  while (iter != test.end()) {
    const int value = *iter;
    iter = test.erase(iter);
    test.insert(value - sub);
  }
  std::cout << "test: " << test << '\n';
}

输出:

test: { 11, 12, 13, 14, 15 }
test: { 1, 2, 3, 4, 5 }

在coliru上进行现场演示


std::set通常,对它进行迭代时的更改不是问题,但可能导致细微的问题。

最重要的事实是所有使用过的迭代器必须保持完整或不再使用。(这就是为什么将擦除元素的当前迭代器分配为返回值std::set::erase()是完整迭代器或set结束的原因。)

当然,也可以在当前迭代器后面插入元素。虽然这不是一个问题,但std::set它可能会破坏我上面的示例的循环。

为了演示它,我对上面的示例进行了一些更改。请注意,我添加了一个额外的计数器来授予循环终止:

#include<iostream>
#include<set>

template <typename T>
std::ostream& operator<<(std::ostream &out, const std::set<T> &values)
{
  const char *sep = "{ ";
  for (const T &value : values) { out << sep << value; sep = ", "; }
  return out << " }";
}

int main()
{
  std::set<int> test{ 11, 12, 13, 14, 15 };
  std::cout << "test: " << test << '\n';
  const int add = 10;
  std::set<int>::iterator iter = test.begin();
  int n = 7;
  while (iter != test.end()) {
    if (n-- > 0) {
      const int value = *iter;
      iter = test.erase(iter);
      test.insert(value + add);
    } else ++iter;
  }
  std::cout << "test: " << test << '\n';
}

输出:

test: { 11, 12, 13, 14, 15 }
test: { 23, 24, 25, 31, 32 }

在coliru上进行现场演示


1
这可能仅适用于减去某些东西,但是如果该操作导致在以后将要访问的地方重新插入,则可能会陷入无限循环。
Yunnosch

@Yunnosch除了无限循环的危险,将迭代器插入当前迭代器的后面没有问题。迭代器在中是稳定的std::set。可能有必要考虑边界情况,即新的迭代器直接插入到已擦除的后面。-插入循环后将被跳过。
谢夫

3
在C ++ 17中,您可以extract节点,修改其键,然后将其返回设置。这样会更有效率,因为它避免了不必要的分配。
Daniel Langr

您能否详细说明“插入循环后将跳过它。”。我想我不明白你的意思。
Yunnosch

2
另一个问题是,相减元素的值可能与元素中尚未处理的值相同 std::set。由于您不能两次拥有相同的元素,因此插入将std::set保持不变,并且稍后您将丢失该元素。例如,考虑输入集:{10, 20, 30}with add = 10
ComicSansMS

6

只需更换另一套即可简单

std::set<int> copy;

for (auto i : s)
    copy.insert(i - sub);

s.swap(copy);

5

您不能通过std::set设计来突变其元素。看到

https://en.cppreference.com/w/cpp/container/set/begin

因为迭代器和const_iterator都是常量迭代器(实际上可能是相同的类型),所以不可能通过这些成员函数中的任何一个返回的迭代器来对容器的元素进行突变。

那是因为set是排序的。如果您对已排序集合中的元素进行突变,则必须再次对集合进行排序,这当然是可能的,但不是C ++方式。

您的选择是:

  1. 使用其他类型的集合(未排序)。
  2. 创建一个新集合,并用修改后的元素填充它。
  3. 从中删除元素std::set,对其进行修改,然后再次插入。(如果要修改每个元素,这不是一个好主意)

4

A std::set通常在STL中实现为自平衡二叉树。*it是用于对树进行排序的元素的值。如果可以对其进行修改,则该订单将无效,因此无法执行此操作。

如果要更新元素,则必须在集合中找到该元素,将其删除并插入该元素的更新值。但是,由于必须更新所有元素的值,因此必须一一删除并插入所有元素。

提供一个for循环可以做到这一点sub > 0S.erase(pos)移除位置处的迭代器pos并返回以下位置。如果sub > 0,您要插入sub <= 0的更新值将在树中新迭代器的值之前,但如果,则更新值将在树中新迭代器的值之后,因此您将以无限循环。

for (auto itr = S.begin(); itr != S.end(); )
{
    int val = *itr;
    itr = S.erase(itr);
    S.insert(val - sub);
}

这是一个好方法...我认为这是唯一的方法。只需删除然后再次插入即可。
Imtiaz Mehedi

3

该错误几乎可以解释问题

std::set容器的成员是const。更改它们会使它们各自的顺序无效。

要更改中的元素std::set,您将必须删除该项目,然后在更改后将其重新插入。

或者,您可以std::map用来克服这种情况。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.