1、我们大都知道可以使用vector或array模板作为线性数组的实现,那么对于需要二维矩阵、三维数组(或者N维数组)时应该怎么解决。
由于N维数组的基本情况中的所有问题都可以用一个二维矩阵举例说明,因此以下的讨论仅限于此,并简单的称为矩阵。
如果矩阵的大小在编译时是已知的,可以很方便的把它实现为数组的数组,这个很简单。这里,我们主要把注意力集中在当矩阵的大小是在运行时计算产生,对于这种复杂的情况,我们可以很方便的使用vector或的vector来实现。事实上,如果不同的行必须必须具有不同的长度,这也是唯一可行的方法。但是,在大多时候,所有的行应该具有相同的长度,此时使用vector的vector是一种效率不高的方法:它需要多次分配内存,这是相对较慢的操作。由于我们使用C++的一个重要原因是追求高效,因此我们尝试一种不同的方法,即只通过一次内存分配来创建一个长方形的矩阵。
示例代码:
//二维长方形矩阵 template <typename T> class matrix { public: typedef unsigned size_type; matrix(size_type num_rows, size_type num_cols) :rows_(num_rows),cols_(num_cols),data_(num_rows*num_cols) { SCPP_TEST_ASSERT(num_rows > 0, "Number of rows in a matrix must be positive"); SCPP_TEST_ASSERT(num_cols > 0, "Number of columns in a matrix must be positive"); } matrix(size_type num_rows, size_type num_cols, const T& init_value) :rows_(num_rows),cols_(num_cols),data_(num_rows*num_cols,init_value) { SCPP_TEST_ASSERT(num_rows > 0, "Number of rows in a matrix must be positive"); SCPP_TEST_ASSERT(num_cols > 0, "Number of columns in a matrix must be positive"); } size_type num_rows() const { return rows_; } size_type num_cols() const { return cols_; } //访问方法:返回由行和列所指定的元素 T& operator() (size_type row, size_type col) { return data_[index(row,col)]; } const T& operator() (size_type row, size_type col) const { return data_[index(row,col)]; } private: size_type rows, cols; std::vector<T> data_; size_type insex(size_type row, size_type col) const { SCPP_TEST_ASSERT(row < rows_, "Row " <<row<<" must be less than "<<rows_); SCPP_TEST_ASSERT(col < cols_, "Column " <<col<<" must be less than "<<cols_); return cols_* row + col; } };
首先,这个类中有2个构造函数。第一个构造函数允许我们用指定的行数和列数创建一个矩阵。第二个构造函数具有一个额外的init_value参数,允许把每个元素初始化为一个指定的值(例如,把一个matrix<double>的每个元素设置为0.0)。注意,对元素的访问时通过()操作符进行的。这是因为C++的[]操作符只接受1个参数,不能接受2个参数或更多的参数。因此,为了访问多维数组,我们或者使用多个[](例如my_matrix[i][j]),或者使用1个()操作符(例如my_matrix(i,j))。
如果我们让[]操作符返回指向第i行第0个元素的T*类型的指针,就可以实现第一种方法。但是,这样就无法诊断列索引越界的问题,违背了在运行时捕捉缺陷的初衷。当然,我们可以创建一些模板类,包括一个指向一个列的智能引用,并使用第一个操作符([i])返回它的一个实例,并在第二个操作符([j])中使用边界检查。在某种程度上,这是因人的习惯。我们看不到仅仅为了保留my_matrix[i][j]的语法而采用这种复杂设计的价值,显然具有多个参数的()操作符看上去更为直观。
对索引越界的检查是在index(row,col)函数中执行的,这两个参数分别表示行数和列数。如果出现了运行时错误,就会导致一个错误处理函数被调用。
最后,在示例代码最后,提供一个matrix<T>模板的<<操作符,是为了输出矩阵:
cout<<"my matrix = \n"<<my_matrix<<endl;
但是,前提是这个矩阵不能太大,并且类型T定义了<<操作符。
总结:避免“索引越界”的规则:
- 不要使用静态或动态分配的数组,可以改用array或者vector模板
- 不要使用带方括号的new和delete操作符,让vector模板为多个元素分配内存
- 使用scpp::vector代替std::vector,使用scpp::array代替静态数组,并打开安全检查
- 对于多维数组,使用scpp::matrix,并通过()操作符访问元素,已提供索引越界检查。