不同初始化方式的构造函数

Constructors for different ways of initialisation

我正在 class Matrix 上编写代码。所以我很难理解构造函数的使用方式。其实我对默认构造函数和参数化构造函数特别怀疑。

class 的默认构造函数:Matrix() 将行和列以及矩阵元素初始化为零。

参数化构造函数:Matrix(int rows, int columns) 初始化传递的值,以及默认值为 0 的 2D 矩阵元素。

我不知道这两个构造函数是如何工作的。不要 class 只有一个构造函数,或者它可以有多个构造函数。 我知道如何编写默认构造函数以及如何编写参数化构造函数。当我们在同一个 class 中编写这两个构造函数时,并帮助了解这两个构造函数如何工作。 这行得通吗?

class Matrix{

  private:
  int rows;
  int columns;
  int **mat;

public:
Matrix(int row, int column){
    this->rows = row;
    this->columns = column;
    mat = new int *[row];
    for(int i=0;i<rows;i++){
       mat[i]=new int[column];    
      }
    }
 };

你的代码有很多错误。当 rowscolumns 仅在运行时已知时,您不能将它们用作数组大小。即使您可以使用 rowcolumns 作为数组大小,在为它们赋值之前,您也是将它们用作数组的大小。此外 this->mat[rows][columns]={0}; 试图访问一个超出数组范围的元素,它会调用未定义的行为。对动态大小的数组使用 std::vector

是的,一个 class 可以有多个构造函数。调用哪个构造函数由 overload resolution 决定。在下面的示例中,要调用的构造函数可以简单地由传递的参数数量来确定。一般来说,重载解析更复杂(并且超出了这个答案的范围)。

#include <vector>
#include <iostream>

struct Matrix {
    int rows;
    int columns;
    std::vector<std::vector<int>> data;
    Matrix() : rows(0),columns(0) {}

    Matrix(int rows,int columns) : rows(0),columns(0),data(rows,std::vector<int>(columns)) {}
};

int main() {
    Matrix m1;
    std::cout << m1.rows() << "\n";
    std::cout << m1.columns() << "\n";
    Matrix m2{5,10};
    std::cout << m2.rows() << "\n";
    std::cout << m2.columns() << "\n";
}

请注意 std::vector 也有不止一个构造函数:https://en.cppreference.com/w/cpp/container/vector/vectorMatrix() 使用向量默认构造函数 (1) 创建一个空向量。 data(rows,std::vector<int>(columns)) 通过调用采用大小和值 (3) 的向量构造函数,用向量的向量初始化 data

术语“参数化构造函数”用词不当。 “参数化构造函数”和默认构造函数之间的区别是错误和误导的。构造函数可以同时被参数化和默认构造函数。默认构造函数是可以不带参数调用的构造函数。这可能是因为它没有参数或因为它有默认参数。比如上面两个可以等价写成一个。此外,您不需要将 rowscolumns 存储为成员,因为向量可以通过其 size() 方法告诉您它的大小:

#include <vector>
#include <iostream>

struct Matrix {
    std::vector<std::vector<int>> data;
    Matrix(int rows=0,int columns=0) : data(rows,std::vector<int>(columns)) {}
    size_t rows() { return data.size(); }
    size_t columns() {
        if (data.size()) return data[0].size();
        return 0;
    }
};

int main() {
    Matrix m1;
    std::cout << m1.data.size() << "\n";
    Matrix m2{5,10};
    std::cout << m2.data.size() << "\n";
    std::cout << m2.data[0].size() << "\n";
}

这里Matrix(int rows=0,int columns=0)是一个默认的构造函数,它是参数化的,因为它可以用以下两者之一调用:

Matrix m1;
Matrix m2{5,10};

不过,也可以通过

调用带默认参数的构造函数
Matrix m3{42};

这可能是不可取的。因此,更好的选择可能是(如 Caleth 所述):

struct Matrix {
    std::vector<std::vector<int>> data;
    Matrix(int rows,int columns) : rows(0),columns(0),data(rows,std::vector<int>(columns)) {}
    Matrix() : Matrix(0,0) {}
};

这使用委托构造函数来避免重复某些代码(自 C+11 起可用)。


PS: 向量的向量并不是一个特别好的数据结构。 std::vector 的优势在于其数据的局部性,但在 std::vector<std::vector<int>> 中丢失了。 std::vector<int> 中的 int 存储在连续内存中。但是 std::vector<std::vector<int>> 中的 int 并没有全部存储在连续的内存中。那是因为元素没有存储在向量中。通常最好也对 2D 情况使用平面 std::vector<int> 并通过索引转换模拟第二维。

是的,对于给定的class,我们可以有多个构造函数。下面给出的示例说明了这一点。

其次 请注意,在标准 C++ 中,an 的大小必须是 编译时间常数。所以当你写道:

int mat[rows][columns];//THIS IS NOT STANDARD C++

以上语句不是标准的C++。

更好的方法是使用 2D std::vector,如下所示。您可以将此示例作为参考。

#include <iostream>
#include <vector>
class Matrix{

    private:
    
    std::size_t rows;
    std::size_t columns;
  
    //use a std::vector instead of array
    std::vector<std::vector<int>> mat;

    public:
        //default constructor
        Matrix(): rows(0), columns(0), mat()  //this uses constructor initiailzer list
        { 
            
        }
        //parameterized constuctor
        Matrix(std::size_t pRows, std::size_t pColumns): rows(pRows), columns(pColumns), mat(rows, std::vector<int>(columns))//this also uses constructor initiailzer list
        {
            
        }
        
        //member function to display columns and rows of the matrix
        void display()
        {
            
            for(auto &r: mat)
            {
                for(auto &element: r)
                {
                    std::cout<<element<<" ";
                }
                std::cout<<std::endl;
            }
            std::cout<<"--------"<<std::endl;
        }
};
int main()
{
    Matrix m1; //this uses default constructor 
    m1.display();
    
    Matrix m2(5,7);//this uses parameterized constructor
    m2.display();
    return 0;
}

我所做的一些修改包括:

  1. 添加了默认构造函数,它使用构造函数初始化列表。
  2. 添加了参数化构造函数,它使用构造函数初始化列表。
  3. 添加了一个 display() 函数,打印出矩阵中的所有元素(行和列)。
  4. 创建了一个对象 m1,它使用默认构造函数初始化其数据成员 rowscolumnsmat
  5. 创建了一个对象 m2,它使用参数化构造函数初始化其数据成员 rowscolumnsmat

我想向您推荐任何 C++ 书籍。

一个 class 可以有任意数量的构造函数,只要它们的签名(参数的数量、类型和顺序)都不同。

你的class有一个构造函数,它是默认构造函数。它只能创建零维矩阵。

使用 this-> 访问数据成员是可选的,我认为实际使用它的开发人员并不多。

我假设,您不想(希望如此)动态更改矩阵的维度,所以我建议让成员 rowscolumns const .

而且,您必须认识到:C++ 不是解释型语言。它在一天编译,运行 在另一天编译。编译器根本不知道 rowscolumns 将使用哪些值,因此在成员声明中使用这些维度是行不通的。大多数时候,编译器会抱怨数组的 non-const 维度。

您有几种可能性:在 run-time 确定要使用的维度或在 compile-time 确定。每个都有其优点和缺点。我想对于一个绝对的初学者来说,run-time 方法看起来更简单,但 compile-time 方法要快得多。

class matrix_runtime
{
public:
    matrix_runtime(int r, int c)
        : rows(r), columns(c)
    {
        mat = std::make_unique<int[]>(rows*columns);
    }
private:
    int const rows{};
    int const columns{};
    std::unique_ptr<int[]> mat;
};

template<size_t Rows, size_t Columns>
class matrix_compiletime
{
public:
    matrix_compiletime() = default;
public:
    std::array<int, Rows * Colums> mat;
};

如果您在 compile-time 知道要使用哪些尺寸,我强烈建议使用 compile-time 版本。具有不同维度的矩阵将是不同的类型(这就是矩阵的意义,不是吗?),因此如果您尝试编译器会给出错误,从而帮助您避免错误。

这里有一些关于我使用的标准库的文档:

如果您想了解 C++ 初始化:https://en.cppreference.com/w/cpp/language/initialization

您的代码中存在很多更基本的问题:

int rows;
int columns;
int mat[rows][columns];

是C++中动态数组的非法定义。数组大小需要是 C++ 中的编译时常量,但是 rowscolumns 而不是 (如果你同时制作 const,因为 class 的不同实例可能使用不同的值。

即使它是合法的,在您的默认构造函数中,您也会在构造函数中越界访问数组:

mat[rows][columns] = ...

数组大小为 0(这在 C++ 中也是非法的),数组中没有可访问的索引 0。

因此,在我们继续之前,我们首先需要解决这些问题。根据您的需要,有多种选择。

一种非常简单的方法是将数据维护在 std::vector of std::vectors:

class Matrix
{
    std::vector<std::vector<int>> mat;
public:
    // ...
};

向量隐式存储有关其内部大小的信息,因此前 rowscolumns 成员(正确的类型应该是 size_t not int) 是多余的,可以而且应该放弃使用向量信息的函数:

size_t rows() { return mat.size(); }
size_t columns() { return mat.size() == 0 ? 0 : mat[0].size(); }

我假设所有行的大小都相同,否则(即锯齿状数组)我们无法将其应用于列。

vector of vector 是实现动态矩阵的一种非常方便的方法,但是伴随着矩阵元素访问的双重间接成本,需要一个额外的数组(对你来说是透明的,但你需要它来存储行)并且必须单独分配列(同样对您透明)。

如果基于一维数组实现矩阵,效率会更高:

size_t m_columns;
std::vector<int> mat;

现在您需要单独存储列,并且您需要显式计算矩阵中的正确偏移量:

size_t rows() { return mat.size() / m_columns; }
size_t columns() { return m_columns; }
int& at(size_t x, size_t y) { return mat[x * m_columns + y]; }

请注意,您仍然可以提供 m[x][y] 语法,替换 at 函数,但这会变得相当复杂,所以我暂时不考虑它。但是,优点是单一间接访问,因此访问速度更快,不需要额外的内存和一次分配所有内存,因此再次更快。

另一种方法是以模板参数的形式提供常量数组大小:

template<size_t Rows, size_t Columns>
class Matrix
{
   int mat[Rows][Columns];
};

这带来了对数组元素的最快访问和最高的类型安全性,因为您可以提供用于转置、加法、乘法...的运算符,这样您就无法将错误维度的矩阵传递给。另一方面,你失去了很多灵活性,例如你不能将不同维度的矩阵直接放入同一个容器(向量、列表或其他)中,因为具有不同参数的模板形成不同的类型(虽然有一些方法,但不太方便,基于多态性)并且需要在已经在代码中键入,i。 e.您无法动态计算矩阵大小。

这两种方法都有用例(基于动态分配和静态类型矩阵),您需要根据您的具体要求select。对于第一次尝试,我建议使用双向量方法,因为它很简单,尽管它有其他缺点。一旦它起作用,您可以切换到一维数组方法...

现在谈谈你的实际问题:

您总是可以 重载 构造函数,即提供多个参数数量 and/or 类型不同的构造函数;然后将根据您提供的论点 select 编辑最合适的(尽管在某些特定情况下可能会出现歧义)。

在您的情况下,您需要提供:

Matrix();
Matrix(size_t rows, size_t columns); // not needed in the template variant,
                                     // dimensions are given via
                                     // template parameters

不过,您应该使用构造函数的初始化列表(不要与 std::initialiser 列表混淆),它可能如下所示:

Matrix() : /*rows(0), columns(0),*/ mat() { }

请记住,我删除了行和列的成员,因此它们也不会再出现在构造函数中,我将它们留在评论中以说明如果我不这样做的话它会是什么样子。请注意,mat() 调用向量的默认构造函数,该构造函数创建一个空构造函数。显式调用它是可选的,您可以将其省略,然后它会被隐式调用。

使用 std::vector 你很幸运:它提供了一个构造函数,其中包含要初始化的元素数量以及将用于所有元素的可选默认值;所以我们可以简单地写:

Matrix(size_t rows, size_t columns) : mat(rows, std::vector<int>(columns)) { }

就是这样...

您完整的 class 可能如下所示(结合上面的所有信息):

class Matrix
{
    std::vector<std::vector<int>> mat;
public:
    Matrix() = default; // short hand syntax...
    Matrix(size_t rows, size_t columns) : mat(rows, std::vector<int>(columns)) { }

    size_t rows() { return mat.size(); }
    size_t columns() { return mat.size() == 0 ? 0 : mat[0].size(); }

    // still providing `at` function as returning a reference to
    // a row vector would allow to modify that one e. g. in size
    // and thus create an invalid matrix!
    int& at(size_t x, size_t y) { return mat[x][y]; }
};

你可能会到此为止,但实际上我们还没有到最后。一旦你完成以上工作(请尝试自己实现,不要只是copy/paste,你不会从中学到任何东西),你可以回到这里下一步:

class Matrix
{
    std::vector<std::vector<int>> mat;
public:
    Matrix() = default; // short hand syntax...
    Matrix(size_t rows, size_t columns) : mat(rows, std::vector<int>(columns)) { }

    size_t rows() const { return mat.size(); }
    size_t columns() const { return mat.size() == 0 ? 0 : mat[0].size(); }

    // still providing `at` function as returning a reference to
    // a row vector would allow to modify that one e. g. in size
    // and thus create an invalid matrix!
    int at(size_t x, size_t y) const { return mat[x][y]; }
    int& at(size_t x, size_t y) { return mat[x][y]; }
};

您注意到 const 关键字了吗?它们允许访问声明为 const 的实例,即不可修改的实例。您应该声明所有不修改对象的成员函数const

对于at函数,我提供了两个版本——const版本只是returns一个值,这样就不会(非法)修改矩阵是可能的,而 non-const 一个引用使得矩阵元素可以改变。

在这种情况下,过载解析非常简单:constconst 矩阵上过载,非 const 在非 const 矩阵上过载:

Matrix m(1, 1);
Matrix const& mr = m; // const reference!
m.at(0, 0);  // non-const at
mr.at(0, 0); // const at
m.rows();    // can call const functions on non-const objects, if there's
             // no non-const overload – but not the other way round

到目前为止是基础知识。如果您对 Matrix class 的 m[x][y] 语法感兴趣,请发表评论,我可能会添加一些额外的行;)