valarray如何对其元素进行除法运算?

4

当我将一个valarray数组除以其第一个元素时,只有第一个元素变为1,其他元素保持原始值。

#include <iostream>
#include <valarray>
using namespace std;

int main() {
    valarray<double> arr({5,10,15,20,25});
    arr=arr/arr[0]; // or arr/=arr[0];

    for(int value:arr)cout << value << ' ';
    return 0;
}

实际输出结果为:
1 10 15 20 25

预期输出为:
1 2 3 4 5

为什么实际输出结果与预期不符?

我使用的是带有-std=c++11选项的g++(4.8.1)编译器。


1
似乎 operator/ 使用了对 arr[0] 的引用。 - 463035818_is_not_a_number
如果我理解了它,我会这样做,但是operator/的签名是template <class T> std::valarray<T> operator/ (const std::valarray<T>& lhs, const std::valarray<T>& rhs);(请注意const),因此不应修改输入,而应创建一个临时变量来存储其结果(然后返回它)。也许这是一些valarray的魔法...你真的可以用//=得到相同的结果吗? - 463035818_is_not_a_number
现在我很好奇,一旦有时间,我会把它作为练习来实现这个运算符,也许那时我就能理解了。 - 463035818_is_not_a_number
@tobi303 哦!我错过了那个点!这真的很奇怪。我第一次遇到这个问题是使用/=版本,然后我尝试了/版本... 然后就感到困惑了。 - apple apple
如果我使用VC++,operator/版本会按预期运行。也许这是g++的一个错误。 - apple apple
显示剩余4条评论
2个回答

3

这个可以正常工作:

#include <iostream>
#include <valarray>
using namespace std;

int main() {
    valarray<double> arr({5,10,15,20,25});
    auto v = arr[0];
    arr=arr/v; // or arr/=arr[0];

    for(int value:arr)cout << value << ' ';
    return 0;
}

问题在于您正在尝试同时修改数组(arr)并使用其值(arr [0])。
直觉上,一旦您通过执行arr [0] / arr [0]来更新arr [0],它包含什么值?
好吧,这就是从现在开始用于除以其他值的值...
请注意,对于arr /= arr [0]也适用相同的规则(首先,在for循环或类似内容中发生arr [0] / arr [0],然后才进行所有其他操作)。
还请注意,从文档中可以看出,std :: valarray的operator []返回T&。 这证实了上述假设:它在迭代的第一步中转换为1,然后所有其他操作都无用。
通过简单地复制即可解决此问题,例如示例代码。

我知道我可以做到这一点,但我想知道为什么 arr/=arr[0] 不起作用。 - apple apple
1
@appleapple 我已经在答案中写了。我强烈怀疑这是实现相关的,但如果确实被要求开发该方法,那么直觉上我会选择这种方式。 - skypjack
arr = arr/arr[0] 中,不是先计算 arr/arr[0] 的结果,然后再将其赋值给 arr 吗? - 463035818_is_not_a_number
@tobi303 请参见此处将复合赋值运算符应用于数值数组中的每个元素。由于arr [0]以引用返回,因此其余部分如答案所述。 - skypjack
1
使用 arr.operator=(arr.operator/(arr[0]));,首先会计算 arr.operator/(arr[0]) 的结果,然后将其传递给 operator= 吗? - 463035818_is_not_a_number
对于大多数类型,是的,但valarray是特殊的。请参见[valarray.syn] p3,它允许arr/arr[0]返回一个表达式模板,延迟评估直到需要结果。有关更多详细信息,请参见我的答案。 - Jonathan Wakely

1
这种情况发生的详细原因是由于在valarray中使用的实现技巧来提高性能。libstdc++和libc++都使用表达式模板来表示valarray操作的结果,而不是立即执行操作。这在C++标准的[valarray.syn] p3中明确允许:任何返回valarray<T>的函数都可以返回另一种类型的对象,只要valarray<T>的所有const成员函数也适用于该类型。
在你的例子中发生的是,arr/arr[0]不会立即执行除法运算,而是返回一个对象,比如_Expr<__divide, _Valarray, _Constant, valarray<double>, double>,它引用了arrarr[0]。当该对象被分配到另一个valarray时,除法运算将被执行,并且结果直接存储到赋值的左侧(这避免了创建一个临时的valarray来存储结果,然后将其复制到左侧)。
因为在你的例子中左侧是同一个对象arr,这意味着表达式模板中存储的对arr[0]的引用,在更新arr的第一个元素后会引用不同的值。
换句话说,最终结果类似于:
valarray<double> arr{5, 10, 15, 20, 25};
struct DivisionExpr {
  const std::valarray<double>& lhs;
  const double& rhs;
};
DivisionExpr divexpr = { arr, arr[0] };
for (int i = 0; i < size(); ++i)
  arr[i] = divexpr.lhs[i] / divexpr.rhs;

for循环的第一次迭代将把arr[0]设置为arr[0] / arr[0](即arr[0]=1),然后所有后续迭代都会将arr[i]=arr[i]/1,这意味着值不会改变。
我正在考虑对libstdc++实现进行更改,使表达式模板直接存储double而不是保持引用。这意味着arr[i] / divexpr.rhs将始终求值为arr[i] / 5,而不使用更新后的arr[i]的值。

标准是否也明确允许在右手边是表达式模板时进行就地 operator= 操作? - apple apple
这就是使用表达式模板的目的,避免创建任何临时valarray来存储结果。 - Jonathan Wakely
你想解释一下这段代码吗?它看起来好奇怪...我试过arr = arr / arr[0] + arr,结果输出了6 11 17 23 29!(如果需要开一个新问题,请告诉我。谢谢) - apple apple
是的,完全一样。我可能没有考虑够。谢谢。 - apple apple
我会接受这个答案,因为它给出了原因,但我仍然好奇编译器在赋值时是否允许这样做。(至少我认为它没有禁止。) - apple apple
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接