涉及指针和手动实现的矩阵类的问题



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

我首先开始编写 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

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

我也忘了补充一点,类和类函数在单独的文件(header 和 cpp(中,尽管我不知道这会如何影响事情。

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

Matrix& operator=(const Matrix&);

void operator=(const Matrix&);

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

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

如果一个类不需要用户定义的构造函数、用户定义的赋值运算符和用户定义的析构函数,

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

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

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;
};

复制操作应该是深入的,即它们应该复制数据,而不仅仅是底层指针。让我们从复制构造函数开始。它应该分配存储,然后将数据从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的正确实现,你就可以确保这些函数得到正确的实现(包括自赋值处理(。欣赏复制和交换成语的优雅。

现在让我们谈谈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->data_添加到副本中并返回该副本。无需引入另一个局部Matrix变量。

完整演示

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

Matrix operator=(Matrix &M);

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

return M;

我检查了这个并工作了。 在运算符+(和 *(中,也考虑此注释。

最新更新