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



我在类矩阵上编写代码。所以我在理解如何使用构造函数方面遇到了一个小困难。实际上我对默认构造函数和参数化构造函数特别怀疑。

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

参数化构造函数:矩阵(整数行、整数列)初始化传递的值,以及默认值为 0 的二维矩阵元素。

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

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

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

#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/vector。Matrix()使用向量默认构造函数 (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>并通过索引转换来模拟第二维。

我想向你指出任何C++书。

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

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

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

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

而且,你必须意识到:C++不是解释性语言。它在一天编译,在另一天运行。编译器根本不知道哪些值将用于rowscolumns,因此在成员声明中使用这些维度将不起作用。大多数时候,编译器会抱怨数组的非常量维度。

您有几种可能性:在运行时确定要使用的维度或在编译时确定。每种都有其优点和缺点。我想对于绝对的初学者来说,运行时方法看起来更容易,但编译时方法要快得多。

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

如果您在编译时知道要使用哪些维度,我强烈建议您使用编译时版本。具有不同维度的矩阵将是不同的类型(这就是矩阵的重点,不是吗?),因此,如果您尝试,编译器将通过给您错误来帮助您避免错误。

以下是我使用的标准库内容的一些文档:

  • https://en.cppreference.com/w/cpp/memory/unique_ptr
  • https://en.cppreference.com/w/cpp/memory/unique_ptr/make_unique
  • https://en.cppreference.com/w/cpp/container/array
  • https://en.cppreference.com/w/cpp/language/templates

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

是的,我们可以为给定类使用多个构造函数。下面给出的示例对此进行了说明。

第二要注意的是,在标准C++中,的大小必须是编译时常量。所以当你写:

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

上述声明不是标准C++。

更好的方法是使用 2Dstd::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

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

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

是 C++ 中动态数组的非法定义。数组大小需要是C++的编译时常量,但rowscolumns不是(如果你同时const,这甚至不会改变,因为类的不同实例可能使用不同的值。

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

mat[rows][columns] = ...

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

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

一个非常简单的方法是将数据维护为std::vectorsstd::vector

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

向量隐式存储有关其内部大小的信息,因此以前的rowscolumns成员(无论如何都会size_t正确的类型,而不是int)是多余的,可以并且应该放弃,以支持使用来自向量的信息的函数:

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

我假设这里所有行的大小都相同,如果不是(即锯齿状数组),我们就无法将其应用于列。

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

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

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

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

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

现在关于您的实际问题:

你总是可以重载构造函数,即提供多个在数量和/或参数类型上不同的构造函数;然后将根据你提供的参数选择最合适的一个(尽管在某些特定情况下可能会出现歧义)。

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

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

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

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

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

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

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

就是这样。。。

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

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

你现在可能会停在这里,但实际上我们还没有结束。一旦你开始了上面的工作(请尝试自己实现,不要只是复制/粘贴,你不会从中学到任何东西),你可以回到这里进行下一步:

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 一个只返回一个值,这样就不可能对矩阵进行(非法)修改,而非 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类的m[x][y]语法感兴趣,请发表评论,我可能会添加一些额外的行;)

最新更新