我正在参加C ++的自学课程,学习标准库的工作方式,我想了解这个使用for_each
的代码是如何工作的,特别是关于改变对象 (与原生数据类型相对)。我意识到you shouldn't use for_each
this way,但这是为了学习。
我原以为这段代码会改变集合中的所有元素,但事实并非如此。
我的问题是: 1.为什么这段代码不会改变集合?
2.如何更改代码以便 修改集合?澄清:有没有办法保留for_each
并让它操纵集合,或者这是不可能的,还必须使用其他一些方法(例如transform
)?
#include <iostream>
#include <algorithm>
#include <set>
using namespace std;
class A {
int a;
public:
A(int a) : a(a) {}
int getA() const { return a; }
void setA(int a) { this->a = a; }
bool operator<(const A & b) const { return a<b.a; }
};
struct myprinter {
void operator()(const A & a) { cout << a.getA() << ", "; }
};
struct doubler {
void operator()(A a) { a.setA(a.getA()*2); }
};
int main() {
int mynumbers[] = {8, 9, 7, 6, 4, 1};
set<A> s1(mynumbers, mynumbers+6);
for_each(s1.begin(), s1.end(), doubler()); //<-- Line in question
for_each(s1.begin(), s1.end(), myprinter());
return 0;
}
//Expected output: 2, 8, 12, 14, 16, 18
//Actual output: 1, 4, 6, 7, 8, 9,
起初我认为问题是倍增器是按值而不是通过引用获取参数,因此它没有将更改保存到集合中。但是当我将签名更改为void operator()(A & a)
时,我收到错误:
error: no match for call to '(doubler) (const A&)
' __f(*__first);
~~~^~~~~~~~~~
error: binding 'const A' to reference of type 'A&' discards qualifiers
我扣除了指出的那一行来自for_each
的内部实现。我无法将参数设为const参考,因为我尝试使用a
方法更改setA()
值,因此它不能是const
。
修改: moooeeeep与另一个问题that shows how to edit each element of a set相关联。这是我的问题的实用解决方案,但我的问题更具理论性 - 为什么你不能使用for_each
编辑集合,你可以在哪里编辑向量和其他stl容器?
答案 0 :(得分:4)
因为std::set
管理它的元素的顺序,它禁止用户通过它的迭代器更改它的元素。这意味着它的begin()
和end()
方法返回const_iterator
。你只能读取迭代器指向的元素,而不是修改它(它是const),这是doubler()
试图做的。
A solution只需使用std::vector
和std::sort
自行订购:
#include <iostream>
#include <algorithm>
#include <vector>
class A {
int a;
public:
A(int a) : a(a) {}
int getA() const { return a; }
void setA(int a) { this->a = a; }
bool operator<(const A & b) const { return a<b.a; }
};
struct myprinter {
void operator()(const A & a) { cout << a.getA() << ", "; }
};
struct doubler {
void operator()(A& a) { a.setA(a.getA()*2); } // by reference!
};
int main() {
int mynumbers[] = {8, 9, 7, 6, 4, 1};
std::vector<A> s1(mynumbers, mynumbers+6);
std::sort(s1.begin(), s1.end());
std::for_each(s1.begin(), s1.end(), doubler());
std::for_each(s1.begin(), s1.end(), myprinter());
return 0;
}
答案 1 :(得分:1)
问题是您不能修改std::set
中的元素。如果有可能,那么它将如何处理这样的事情:
std::set<int> my_set { 1, 2, 3 };
int& foo = *(my_set.begin());
foo = 2;
现在有两个值为2的元素。由于
,在set
中没有意义
std :: set是一个关联容器,包含一组排序为Key的唯一对象。
(强调我的)
答案 2 :(得分:1)
根据评论,到目前为止,我已经了解了以下内容:
<强> 1。为什么这段代码不会改变集合?
因为传递给doubler方法的参数是通过值(C ++默认值)传递的,而不是通过引用传递的。因此,每个a
参数的值都会被修改,但这不会保存到容器中的元素中。
<强> 2。如何修改代码以便修改集合?
不容易做到。集合(与向量,deques,列表和数组等其他容器相对)维护其元素的顺序。 (相反,假设for_each
方法中使用的函数否定了每个元素。然后该集合将向后排序,并且集合尝试维护的顺序将丢失)。因此,如果你想修改集合you have to do so one at a time的元素(谢谢你@NathanOliver和@moooeeeep的评论)
注意:如果容器不是一个集合(而不是vector或deque),则可以将doubler方法修改为以下内容以改变元素:
void operator()(A & a) { a.setA(a.getA()*2); }
答案 3 :(得分:0)
因为std::set
中对象的内容决定了它的位置。这就是为什么std::set
迭代器总是const
。
当然,通常情况下并非对象的所有成员都对其位置产生影响。然后可能的解决方法是声明那些成员mutable
。然后,您可以使用const
引用修改它们。
要修改影响对象在集合中的位置的成员,请从集合中删除对象,修改对象并将其重新添加。
答案 4 :(得分:0)
std::set
(http://www.cplusplus.com/reference/set/set/)的文档将std::set::iterator
和begin
的非常量版本返回的end
定义为:
的双向迭代器
const value_type
因此,即使是非const std::set::iterator
也会返回const
个对象。
答案 5 :(得分:0)
您不能简单地更改数据结构的键值存储类型中元素的键。 (如果是一组,它只是键,没有价值。)
如果愿意,可能需要调整底层容器中元素的位置,基于重新计算的哈希值(如果是无序哈希表)或二元搜索树中的不同排序位置/有序列表。
因此std::set
的API要求您在这种情况下擦除并重新插入项目。
在这个方向上现有问题的答案已经说明了:
答案 6 :(得分:0)