先来回顾一下通常动态二维数组的创建过程,这里以 m×n int数组为例
int **pp=new int*[m]; for(int i=0;i<m;++i) *(pp+i)=new int[n]; //通过*(*(pp+i)+j)操作数组元素(i,j) for(i=0;i<m;++i) delete [] *(pp+i); delete [] pp;
这样的方式称为离散动态数组,主要存在两个缺点:
1. 数组储存区不连续;
2. 堆上空间释放比较麻烦。
图中以m=4,n=3为例。
所以我们寻求更好的代码:
int *p=new int[m*n]; int **pp=new int*[m]; for(int i=0;i<m;++i) *(pp+i)=p+i*n; delete [] pp; delete [] p;
同样通过 *(*(pp+i)+j) 访问数组成员(i,j),先申请一段连续的空间,然后让指针数组保存它们的地址。
图中,m=4,n=3,这样做有两个好处:
1. 空间的释放更方便了;
2. 可以重新操作这段空间组成新数组。相较于第一种方式,其额外的开销仅是一个int*变量。
比如要把m×n个int所组成的空间段重分配为k×j的数组:
delete [] pp; pp=new int*[k]; for(int i=0;i<k;++i) *(pp+i)=p+i*j;
图中,以k=2,j=6为例。
通过观察代码,不难发现对于任意的i,都有
*(pp+i)=p+i*n;
那么此时对于二维数组的操作事实上不需要二级指针的参与,只需一句表达式即可以用元素(i,j):
int *p=new int[m*n]; //*(p+i*n+j)——(i,j) delete [] p;
其实矩阵这个概念本来就是抽象的,把一个串拆分下并列着放就是矩阵了。对于这样的存储方式,可以很轻松的完成其转置,只要换一种引用方式:
*(p+i+j*n)
即可完成矩阵的“转置”操作。事实上根本没有矩阵,存在的只是在内存中的一块连续空间而已,不同的访问方式显示了这块内存段在我们大脑中不同的映射。
这里并不是按常规的思维方式,先形成一个二维数组,如A(m,n),然后填充,而是创建一个串,按某种规则写入数据,按某种规则读取数据。对于普通的矩阵操作,其读写规则为同一种。
而对于一个矩阵转置问题,其本质是,按某种规则写入数据,按另一种规则读取数据,而这两种规则的不同点反映出矩阵转置的概念。内存段没变,变的是它在我们大脑中映射的矩阵,或者更准确的说是映射的规则变了。