【问题标题】:Problem involving pointers and manually implemented matrix classes涉及指针和手动实现的矩阵类的问题
【发布时间】:2020-10-03 18:10:08
【问题描述】:

我目前正在从事一个更大的项目,其中涉及实现线性代数计算器。我决定不使用任何其他可能帮助我实现它的现有库,因为我认为这太容易了。

我首先开始编写 Matrix 类,现在看起来像这样:

class Matrix{
private:
    int rows; // no. rows
    int columns; // no. columns
    double** matVal; //values of the matrix
    char name; //only used when printing it out or by outside programs.
public:
    //constructors and destructor
    Matrix();
    Matrix(int r,int c,char _name);
    ~Matrix();

    //basic get functions for private access
    int getNrRows();
    int getNrCols();
    char getName();
    double getVal(int row,int col);

    //basic set functions for private variables
    void setVal(int row,int col,double value);
    void setName(char _name);

    //basic matrix operations
    Matrix operator=(Matrix M);
    Matrix operator+(Matrix M);
    Matrix operator-(Matrix M);
    Matrix operator*(Matrix M);

    //Printing out the matrix
    void Print();
};

一开始很顺利,但后来我偶然发现了一个致命的错误,这不会让我进一步进步。有关更多信息,这里是我的函数(+ 一些试图找出问题所在的代码)以及我在 main() 中执行的内容:

#define cout std::cout

Matrix::Matrix(){
    rows = 0;
    columns = 0;
    matVal = nullptr;
}

Matrix::Matrix(int r,int c,char _name){
    rows = r;
    columns = c;
    name = _name;
    matVal = new double*[r];
    for(int i = 0; i < r; i++){
        matVal[i] = new double[c];
    }
    for(int i = 0; i < r; i++){
        for(int j = 0; j < c; j++){
            matVal[i][j] = 0;
        }
    }
}

Matrix::~Matrix(){
    for (int i = 0; i < rows; i++)
        delete[] matVal[i];
    delete[] matVal;
}

int Matrix::getNrRows(){
    return rows;
}

int Matrix::getNrCols(){
    return columns;
}

char Matrix::getName(){
    return name;
}

double Matrix::getVal(int row, int col){
    return matVal[row-1][col-1];
}

void Matrix::setVal(int row,int col,double value){
    matVal[row-1][col-1] = value;
}

void Matrix::setName(char _name){
    name = _name;
}

Matrix Matrix::operator=(Matrix M){
    for (int i = 0; i < rows; i++)
        delete[] matVal[i];
    delete[] matVal;

    rows = M.rows;
    columns = M.columns;

    matVal = new double*[rows];

    for(int i = 0; i < rows; i++){
        matVal[i] = new double[M.columns];
    }

    for(int i = 0; i < M.rows; i++){
        for(int j = 0; j < M.columns; j++){
            matVal[i][j] = M.matVal[i][j];
            cout<<matVal[i][j]<<' ';
        }
        cout<<'\n';
    }
    cout<<this<<std::endl;
    return *this;
}

Matrix Matrix::operator+(Matrix M){
    Matrix Rez;
    Rez.rows = rows;
    Rez.columns = columns;
    for(int i = 0; i < rows; i++){
        for(int j = 0; j < columns; j++){
            Rez.matVal[i][j] = matVal[i][j] + M.matVal[i][j];
        }
    }
    return Rez;
}

void Matrix::Print(){
    cout<<'\n';
    cout<<name<<": "<<"\n";
    for(int i = 0; i < rows; i++){
        for(int j = 0; j < columns; j++){
            cout<<matVal[i][j]<<' ';
        }
        cout<<'\n';
    }
    cout<<'\n';
    return;
}

主要:

Matrix M(4,3,'A');
M.setVal(1,1,2);
M.setVal(1,3,-1.1);
M.Print();
Matrix A(4,3,'B');
A.setVal(3,2,5);
A.Print();
Matrix C(4,3,'C');
C = A;
cout<<C.getVal(3,2)<<'\n';
cout<<C.getNrCols()<<" "<<C.getNrRows()<<endl;
C.Print();
cout<<"S"<<endl;

打印前 2 个矩阵工作正常,当我在给它分配了正确的值之后在 operator= 函数中打印 C 的每个元素时,它再次工作正常,但是当我在 C 上使用 Print() 函数时它崩溃.这是上面代码的控制台输出:

A:
2 0 -1.1
0 0 0
0 0 0
0 0 0


B:
0 0 0
0 0 0
0 5 0
0 0 0

0 0 0
0 0 0
0 5 0
0 0 0
0x69fed0
5
3 4

C:

一开始我完全不知道为什么会这样,但后来我打印了指向每个变量的指针(它全部打印出来,这次返回 0):

A:
0x850e38 0x850e40 0x850e48
0x851318 0x851320 0x851328
0x851338 0x851340 0x851348
0x851358 0x851360 0x851368


B:
0x851390 0x851398 0x8513a0
0x8513b0 0x8513b8 0x8513c0
0x8513d0 0x8513d8 0x8513e0
0x855b08 0x855b10 0x855b18

0x855b40 0x855b48 0x855b50
0x855b60 0x855b68 0x855b70
0x855b80 0x855b88 0x855b90
0x855ba0 0x855ba8 0x855bb0
0x69fed0
5
3 4

C:
0 0x8 0x10
0 0x8 0x10
0 0x8 0x10
0 0x8 0x10

S

现在我认为 Print 函数有问题(否则为什么我能够在 main 中打印出 5?)。我仍然不知道到底发生了什么,所以我请求你的帮助。如果这是菜鸟的错误,我很抱歉,我还很缺乏经验。

我还忘了补充一点,类和类函数位于不同的文件(头文件和 cpp)中,虽然我不知道这会如何影响事情。

【问题讨论】:

  • 您应该检查是否 (this != &M) in operator= 重载,因为您将使用同一个对象执行所有操作
  • 另外,查找“三规则”。要点是,如果需要手动定义赋值运算符、复制构造函数或析构函数之一,则有必要实现所有这三个。您已经实现了赋值运算符和析构函数,但没有实现复制构造函数。在 C++11 及更高版本中,三法则变为五法则。当复制管理资源的对象(例如,在您的情况下动态分配的内存)时,不遵循这些规则最终会产生未定义的行为。
  • @Marius,如果您在向其添加 *this 后返回相同的值,则按值传递就可以了。另请注意,operator= 应该通过引用返回 *this,或者如果不需要链接分配,则只是 void
  • 当您通过值传递Matrix 时,副本包含与原始相同的指针。当副本超出范围时,析构函数将delete[] 那些指针。因此,您会在原始 Matrix 中获得悬空指针,并且这些指针是双倍的。

标签: c++ class pointers matrix dynamic-memory-allocation


【解决方案1】:

在另一个答案中提出的签名Matrix operator=(Matrix&amp;); 完全错误。正确的签名应该是

Matrix& operator=(const Matrix&);

void operator=(const Matrix&);

如果您不需要链接分配 (a = b = c)。

您必须实现的最低限度:复制构造函数、复制赋值和析构函数。对于矩阵类,实现移动操作也是合理的。这被称为the rule of zero/three/five(在我们的例子中是五个):

如果一个类不需要用户定义的构造函数、没有用户定义的赋值运算符和没有用户定义的析构函数,不要定义它们;如果一个类需要用户定义的析构函数、用户定义的复制(和移动)构造函数或用户定义的复制(和移动)赋值运算符,那么它几乎肯定需要全部三(五)个。

假设矩阵内部表示为一维数组。这种方法避免了矩阵元素访问的不必要的间接访问并简化了代码。

class Matrix {
public:
    Matrix(const Matrix&);
    Matrix(Matrix&&);

    Matrix& operator=(const Matrix&);
    Matrix& operator=(Matrix&&);

    ~Matrix();

private:
    double* data_        = nullptr;
    std::ptrdiff_t rows_ = 0;
    std::ptrdiff_t cols_ = 0;
};

复制操作应该是deep,即它们应该复制数据,而不仅仅是底层指针。让我们从复制构造函数开始。它应该分配存储空间,然后将数据从other 复制到这个存储空间:

Matrix(const Matrix& other) 
: rows_(other.rows_), cols_(other.cols_) {
    const auto n = other.rows_ * other.cols_;
    data_ = new double[n];
    std::copy(other.data_, other.data_ + n, data_);
}

现在让我们实现swap

void swap(Matrix& other) {
    std::swap(rows_, other.rows_);
    std::swap(cols_, other.cols_);
    std::swap(data_, other.data_);
}

这个函数非常有用,因为它让我们几乎不用代码就可以实现移动构造函数、复制赋值和移动赋值:

Matrix(Matrix&& other) {
    swap(other);
}

Matrix& operator=(const Matrix& other) {
    Matrix(other).swap(*this);
    return *this;
}

Matrix& operator=(Matrix&& other) {
    Matrix(std::move(other)).swap(*this);
    return *this;
}

有了这样的规范实现,一旦你正确实现了复制构造函数和swap,就可以确保这些函数被正确实现(包括自赋值处理)。欣赏copy-and-swap idiom 的优雅。

现在让我们谈谈operator+。看看这个实现:

Matrix operator+(Matrix other) const {
    assert(rows_ == other.rows_);
    assert(cols_ == other.cols_);

    const auto n = rows_ * cols_;
    for (std::ptrdiff_t i = 0; i < n; ++i)
        other.data_[i] += data_[i];
    return other;
}

在这里,我们按值获取参数并获取它的(深层)副本。然后我们将this-&gt;data_ 添加到副本并返回该副本。不需要再引入另一个本地的Matrix变量。

Complete demo

【讨论】:

    【解决方案2】:

    您可以将 operator= 函数更改为这种形式:

    Matrix operator=(Matrix &M);
    

    在定义中,您应该将其返回更改为:

    return M;
    

    我检查了这个并且工作了。 在 operator+(and *) 中也要考虑这个注释。

    【讨论】:

    • 对我来说,只需将返回 *this 更改为返回 M。非常感谢!
    • 通常这是一个移动分配,它不应该复制矩阵,而是将资源所有权从 M 移动到当前实例。返回 M 将中断运算符链接。按值返回是错误的,因为您返回了M 的副本,这可能会导致双重删除和再次悬空指针。我想它似乎有效,因为未使用的返回值已被优化掉。查看 EVG 的答案以获得规范和正确的实施。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-05-25
    • 1970-01-01
    • 2017-04-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-05-29
    相关资源
    最近更新 更多