实用经验 32 多维数组和指针

多维数组是对一维数组的扩展,如果1维数组的元素也是1维数组,便构成了2维数组。以此类比如果2维数组的元素类型是一维数组,那便构成3维数组了。一般地,n维数组的格式为:

类型 <数组名称>[1维元素数] [2维元素数] ……[第n维元素数];

一维数组名是一个指针常量,它的类型是“指向元素类型的指针”,它指向数组的第一个元素。那对于多维数组呢?其实也差不多,唯一的区别是多维数组的第1维元素是一个数组,而不是一维数组的普通元素了。例如下面这个声明:

int  arrMatrix[3][10] = {0};

arrMatrix可看做一个一维数组,它包含3个元素。只是每个元素是包含10个整型元素的数组。arrMatrix的类型是指向它第1个元素的指针。所以arrMatrix是一个指向包含10个整型元素数组的指针。

现在晓得了,计算机内存不存在二维数组。二维数组是一种特殊的一维数组:它的元素是一维数组。例如:

int a[3][4];     // 定义一个3*4的数组
const int alen = sizeof(a)/sizeof(a[0]);  // alen的值为3

我们可将a看成一维数组,它的元素是a[0]、a[1]、a[2];而每个元素又包含4个元素的一维数组,数组分布和内存布局如图4-7所示。a实质上可视作一个指针字面值,指向包含3个整型元素数组的首元素。它并不是一个指向整型量的指针。这也许会造成一些出人意料的结果。

在这里插入图片描述

图4-7 二维数组内存布局

根据上面的论述。二维数组的内存映射可描述如下:假设二维数组A[m][n]是一个m行,n列的二维数组。假设M为数组的第一个元素。按“行优先顺序”(C/C++语言采用这种存储形式)存储时,元素a[i][[j]的地址为:

LOC(a[i][j]) = LOC(M)+(i*n+j)*t;

上式中t为数组元素的字节数。可以看出二维数组分配的一块连续的内存。

总结:二维数组声明时要求给定第二维下标。(同样,多维数组声明时要求给定除第一维外其他所有维的下标)。因为不管几维数组在内存中的存储和一维数组没有本质区别,都是线性存储的,长度是各个维下标之积。

多维数组元素的操作是多维数组存在的原因。如果要标识一个多维数组的某一个元素,我们必须按照与数组声明时相同的顺序为每一维提供一个下标,并且每个下标都单独位于一个方括号内。例如,有这样一个数组声明:

int  arrMatrix[3][4] = {0};

表达式arrMatrix[1][2]表示访问的元素是:
在这里插入图片描述
一维数组中,下标引用只是间接访问的一种伪装形式,在多维数组中也是一样。下面我们考虑多维数组的指针访问形式。考虑表达式arrMatrix,他是一个“指向包含4个元素数组的指针”。它的值是:
在这里插入图片描述

但*(arrMatrix+1)+2表示什么呢?其和arrMatrix[1][2]是什么关系呢?按照上面的说法arrMatrix+1是“指向第2个包含4个元素数组的指针”,(arrMatrix+1)应该是“包含4个元素的数组”,(arrMatrix+1)+2是“指向第2个包含4个元素的数组的第3个元素”。 所以*(*(arrMatrix+1)+2) 实际上就是arrMatrix[1][2]。

指向数组的指针。对于一维数组,我们声明指向一维数组的指针形式如下:

int  arrIMatrix[10];
int  *parrIMatrix = arrIMatrix;

在这组声明中,arrIMatrix声明为一个数组,parrIMatrix声明为指向一个整型的指针。同时把arrIMatrix初始化指向arrIMatrix第1个元素。arrIMatrix和parrIMatrix具有相同的类型:指向整型的指针。

对于多维数组的指针。现在我们看下面这组声明:

int   arrMatrix[2][10];
int  * parrMatrix = arrMatrix;

这个声明是错误的,我们看这个声明存在什么问题?因为arrMatrix的类型是指向整型数组的指针,而不是指向整型的指针。所以可看出上述声明是存在问题的。

现在,我们看如何声明一个指向数组的指针,先看这个声明int (*p)[10];,也就是实用经验28所述的数组指针,专门指向一个数组。对于二维数组arrMatrix,它的每个元素都是一维数组。所以上述声明应修改成下述方式,才是正确的声明:

int   arrMatrix[2][10];
int  *parrMatrix[10] =  arrMatrix;

但是,如果你需要一个指针逐个的访问整型元素,而不是逐行在数组中移动,你可以采用下面两种形式,声明创建一个简单的整型指针,并初始化指向arrMatrix的第1个整型元素。

int   *pi = &arrMatrix[0][0];
int   *pi = arrMatrix[0];

警告:如果你打算在指针上执行任何指针运算,应避免如下这种类型的声明。

int (*p)[] = arrMtrix;

这种声明中,p依然是指向整型数组的指针,但是数组的长度不可见。当某个整数与这种类型的指针执行指针运算时。它的值将根据空数组的长度进行调整,我想这不是你想要的结果。有些编译器可以捕捉到这类错误,但是有些编译器却不能。

多维数组作为函数参数是常见的一种应用方式,作为函数参数的多维数组名的传递方式和一维数组名相同——实际传递的是个指向数组第0个元素的指针。但不同的是多维数组的第1个元素是另外一个数组,编译器需要知道他的维数,以便为函数形参的形参的下标表达式求值。下面是二维数组的应用实例,此实例完成数组元素的遍历工作,实例中展示了两种多维数组的声明方式,可看出不论哪种方式在函数声明是都必须包含第2个及以后各维的长度,否则编译无法通过编译。

#include <stdio.h>
#include <stdlib.h>

// 数组遍历函数,实现二维数组元素的遍历,声明方式一
void trans1(int a[][3], int b[]);
// 数组遍历函数,实现二维数组元素的遍历,声明方式二
void trans2(int (*a)[3], int b[]);


int main(void)
{
    int a[3][3] = { {1, 2, 3}, {4, 5, 6}, {7, 8, 9} };
    int b[9] = {0};
    int i = 0;

    trans(a, b);

    for (i = 0; i < 9; i++)
    {
        printf("%d\t", b[i]);
    }
	printf("\n");
    exit(0);
}

//数组遍历函数,实现二维数组元素的遍历, 声明方式一
void trans1(int a[][3], int b[])
{      
    int i, j, k;
    for (i = 0; i < 3; i++) 
	{
        for (j = 0; j < 3; j++)
        {
            b[k++] = a[i][j];
        }
    }
}

//数组遍历函数,实现二维数组元素的遍历, 声明方式二
void trans2(int (*a)[3], int b[]);
{      
    int i, j, k;
    for (i = 0; i < 3; i++) 
	{
        for (j = 0; j < 3; j++)
        {
            b[k++] = a[i][j];
        }
    }
}

在编写一个数组形参的函数原型时,你可以采用数组的形式,也可以采用指针的形式。但对于多维数组,只有第1维可如此选择。所以如果你把多维数组写成多重指针,那将是一件愚蠢的事情。也就是说指向整型指针的指针和指向整型数组的指针不是一回事。这一点是你必须明确的。

小心陷阱:多维数组作为形参时,第1维的长度其实并不重要,因为在计算下标值时用不到它。但第2维及以后各维必须声明。且各维的声明长度必须和实参传值时数组对应各维长度保持一致。否则会导致不可预知的行为。

请谨记

  • 多维数组名和多重指针在任何时候都不等价。在C++中多维数组就是一维数组,只是数组中的每个元素又是一个数组。多重指针指的是指向另一个指针的指针。
  • 多维数组的数组名的类型是数组指针,而不是多重指针。这是你应该深刻理解的。
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值