首页 > 解决方案 > 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重载运算符中的内部语句返回正确的多项式。只有当程序退出该函数时,系数才会被搞砸……而且这两种打印策略甚至与它们本身不一致。到底是怎么回事?

标签: c++arraysclass

解决方案


分析

您的Polynomial班级很不寻常,因为它不拥有自己的数据。虽然这本身并不是错误的,但它几乎总是一种不明智的做法。

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

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

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

解决方案

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

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

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

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

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


推荐阅读