对于 C++ 中的简单多项式类,重载标量乘法的值不正确



简化:我正在尝试编写一个简单的Polynomial类,当我尝试重载乘法运算符时,我得到不正确的值:

#include <iostream>
class Polynomial {
public:
unsigned int degree;
int *coefficients;
Polynomial(unsigned int deg, int* arr) {
degree = deg;
coefficients = arr;
}
~Polynomial() {}
int& operator[](int index) const {
return coefficients[index];  
}
};
std::ostream& operator<<(std::ostream &os, const Polynomial& P){
for (unsigned int i=0; i < P.degree; i++) os << P[i] << ",";
os << P.coefficients[P.degree];
return os;
}
Polynomial operator*(const Polynomial &P, const int &x) {
int arr[P.degree];
for (unsigned int i=0; i <= P.degree; i++) arr[i] = P[i];
Polynomial p(P.degree, arr);
std::cout << p << std:: endl; // just for debugging
return p;
}

我正在main中测试代码:

int g[] = {-1, 0, 1, 1, 0, 1, 0, 0, -1, 0, -1};
Polynomial P_g = Polynomial(10, g);
std::cout << P_g << std::endl;
Polynomial P_g_3 = P_g*3;
std::cout << P_g_3[0] << std::endl;
std::cout << P_g_3[1] << std::endl;
std::cout << P_g_3[2] << std::endl;
std::cout << P_g_3[3] << std::endl;
std::cout << P_g_3[4] << std::endl;
std::cout << P_g_3[5] << std::endl;
std::cout << P_g_3[6] << std::endl;
std::cout << P_g_3[7] << std::endl;
std::cout << P_g_3[8] << std::endl;
std::cout << P_g_3[9] << std::endl;
std::cout << P_g_3[10] << std::endl;
std::cout << P_g_3 << std::endl;

但是控制台中的输出与我预期的完全不同:

-1,0,1,1,0,1,0,0,-1,0,-1
-1,0,1,1,0,1,0,0,-1,0,-1
-1
0
0
0
0
0
-2002329776
32767
-516177072
32766
539561192
0,32766,539581697,32767,-2002329776,32767,1,0,243560063,1,-2002329776

但请注意,重载运算符中的内部cout语句返回正确的多项式。只有当程序退出该函数时,系数才会被搞砸......而且这两种印刷策略甚至与自身不一致。这是怎么回事?

分析

您的Polynomial类相当不寻常,因为它不拥有自己的数据。虽然这本质上并没有错,但它几乎总是一种不明智的方法。

作为说明,请查看主函数中的变量。多项式P_g的数据存储在数组g中。变量g除了存储该数据外没有任何用途,因此它的名称是混乱的。这里还有一个一致性问题,因为如果有人要更改g元素,那么P_g也会更改。更糟糕的是,如果gP_g还存在时就不复存在,那么你将拥有一个多项式而无法访问它的数据!

幸运的是,局部变量以相反的创建顺序被销毁,因此P_g将在g之前销毁。但是,当您调用operator*时,没有这样的运气会干预。在该运算符中,多项式p将其数据存储在局部变量arr中。到目前为止,情况与main()相同.直到你p回来.此时,p被复制/移动到调用作用域,并且该副本使用与原始副本相同的数据。数组arr被销毁,但返回的对象仍尝试访问其数据的arr。返回的对象P_g_3有一个悬空的指针。

当您尝试访问P_g_3的数据时,会发生未定义的行为。在某些情况下,您可能会看到预期的行为。在这种情况下,产生了垃圾值。从一个角度来看,您的结果是更理想的结果,因为您能够检测到存在问题,从而允许您尝试修复它。更阴险的是未定义的行为,当您运行程序时,它会按预期执行,但在其他人运行时则不然。

溶液

通常的方法是让对象拥有自己的数据。通常,这是独占所有权,因此数据无法在不通过对象的情况下更改。

第一个改进是将数据保存数组移动到对象中。主要障碍是您事先不知道数组有多大。这需要动态内存分配。您可以在不更改类的数据成员的情况下开始这条路;第一步可能是将coefficients初始化为分配了new的内存,而不是将其分配给arr。这导致需要遵循三法则。不幸的是,这需要做一些正确的工作,特别是因为您选择了跟踪多项式的次数而不是数组的大小。

幸运的是,存储未知长度的数据是一个常见的问题,标准库提供了自动化大多数细节的工具。一个这样的工具是std::vector.如果将coefficients类型从int*更改为std::vector<int>,则Polynomial对象将能够拥有自己的数据,并且三法则变为零法则。(也就是说,编译器生成的析构函数和复制方法就足够了。您的operator*可以简单地复制传入的Polynomial,然后迭代副本的向量进行更改。

另一个好处是,您不再需要手动跟踪degree,因为非零多项式的次数将coefficients.size() - 1。(好吧,如果前导系数为零,则存在复杂性,但对于原始实现来说,这也是一个未处理的问题。这说明了类经常将其数据成员保持private的一个原因。如果已将数据成员设为私有,而是定义了degree()方法,则可以更改度数的确定方式,而无需修改任何使用Polynomial的代码。

注意:
如果对向量使用基于范围的for循环,则示例代码无需查看多项式的次数。您将提供成员函数,以便为类外部的代码提供帮助。

最新更新