二次指针与二维数组
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
二次指针与二维数组
首先我们需要知道,什么是二次指针?下面的代码就定义了一个二次指针变量p2。
char **p2;
还是通过图来说明变量、指针变量、二次指针变量的关系吧。
对于代码:
char c = …h‟;
char *p1 = &c;
char **p2 = &p1;
其中c、p1、p2的关系如下图所示:
由于“指针生成”规则(参见教材P176)的存在,我们无法将一个数组传递给一个函数。一个折中的解决办法是传递这个数组的第一个元素的地址,以及数组的各个维数,然后在函数中计算得到要操作的数组元素的地址来操作该数组元素。例如:
void fun(char *p, int n, int m)//从传入指针指向元素开始,将m*n个元素赋值为5
{
for (int i = 0; i < m; i++)
for (int j = 0; j < n; j++)
*(p + i*n + j) = 5;
}
main()
{
char a[2][3];
fun(&a[0][0], 2, 3);
return 0;
}
显然,其中的fun(&a[0][0], 2, 3);对数组的操作相当于:
for (int i = 0; i < m; i++)
for (int j = 0; j < n; j++)
a[i][j] = 5;
在这段代码中,a[i][j]的形式显然更为浅显易懂,也*(p + i*n + j) = 5更为安全(不容易写错)。接下来我们要做的,就是希望实现这样的功能:函数能够以a[i][j]这样的下标处理一个维度变化的二维数组。
通过指针数组,我们可以实现一个一维长度不同的二维数组。当然,这也就意味着该二维数
组的元素在内存中的位置不一定连续了。
什么在指针数组呢?看如下代码:
char *pp[7];
这就是一个指针数组,它表示一个长度为7的数组pp,而数组pp的每一个元素的类型,都是char *。也就是说,我们可以将数组pp的元素赋值给另一个指针变量,就像这样:
char *p = pp[3];
而联系到第一部分指针与一位数组之间的关系,我们可以有让数组pp的每个元素都指向不同的指针,比如说这样:
char a[13] = "ustc", b[17] = "1958";
char *t = a + 2;
char *p[5] = {a, b, t};//VC 6.0中可以用变量对数组的值进行初始化
执行以上代码之后,p[0]就指向了a数组,p[1]就指向了b数组。而p[2]在这里的含义,也是指向一个数组。我们可以看到,通过指针是无法获知数组的长度的。这也就是为什么在C 语言中,字符数组后面会跟一个‟\0‟来作为结束标致。而在操作其他类型的数组的时候,也是需要显示在代码中指定数组的长度。
printf(“%s\n”, p[2]);
上述代码执行后将输出
tc
因为printf函数在输出字符串的时候,会从传入的参数开始一直向外输出,直到遇到字符串结束标志‟\0‟。
好。接下来我们要问了,char a[5]中,a的数据类型可以看做是一个字符指针(char *),那么char *pp[7]中,pp又是什么数据类型呢?这里pp的数据类型,就是我们本部分开始时提到的二次指针了。我们可以通过如下代码来声明一个二次指针变量,并且并给它赋值:char *pp[7];
char **p2p = pp;
而就像我们可以对一个指针变量char *p进行指针运算p[2]或*(p + 2)获取对应的值一样,我们也可以通过如下代码获取一个指针。
char *p1, *p2;
p1 = p2p[3];
p2 = p2p[5];
而对于上述定义的p1、p2,我们还是仍然使用类似p[2]、*(p + 2)的指针运算。于是,我们就能得到类似二维数组的使用方法了。
char a[13] = "ustc", b[17] = "1958";
char *p[5] = {a, b};//VC 6.0中可以用变量对数组的值进行初始化
char **p2p = pp;
printf(“%c\n”, pp[0][2]);
上述代码将输出一个字符‟t‟,以及换行。
好了,到此为止我们完成了对二次指针的分析。在文章动态分配二维数组(或动态分配二维数组)当中,给出了利用二次指针动态分配二维数组的代码样例。以及内存分配的示意图。通过分析该文章的代码和配图,有助于我们更深入的理解二次指针。
而我们也需要在心中牢记的,是二次指针与二维数组名之间的区别。它们是完全不同的两种数据类型。在编程时,都可以通过类似*(*(p + 2) + 3)、p[2][3]的方式来访问一个具体的数组元素。但是他们在计算上述表达式以获取数据时,进行的操作是不一样的。总之,这种表达
形式上的一致性有利于我们进行编程,但同时也是让我们对他们的区别感到迷惑的根源所在。
C语言中,二维数组名与二次指针之间的关系着实令人头疼。在进入下一步的讨论之前,先举例说明一下什么是二维数组名和二次指针。
char a2[5][8];
上述代码中,a2是一个二维数组名,而p2则是一个二次指针(指向指针的指针)
这两个概念之间错综复杂的关系之所以令人头疼,我以为主要来自以下两个方面。
其一是一维数组名与指针之间的对应关系,多少让人对二维数组与二次指针之间的关系产生亦真亦幻的遐想。
事实上,二维数组与二次指针之间没有关系,尝试编译以下代码你就知道这个想法不靠谱了:char a[5][8];
char **p;
p = a; //cannot convert from 'char [5][8]' to 'char ** '
编译这段代码,将得到一个提示为“cannot convert from 'char [5][8]' to 'char ** '”的错误。意思是a和p的类型不一样,而且无法实现隐式转换。在这句提示中,最让人头疼和摸不着头脑的,莫过于char [5][8]了,这是什么鬼东西?
另一个让人对二维数组与二次指针之间对应关系感到迷糊的,还在于我们确实可以通过二次指针来实现一个二维数组,也能像访问二维数组元素一样通过下标索引的方式访问二次指针指向的指向的内存空间。
具体的方法可参考这篇文章:动态分配二维数组(或动态分配二维数组)。从这篇文章的代码极其配图讲解中,能够大致了解到二次指针与二维数组之间的关系并没有我们想想的那么简单。至少用二次指针模拟二维数组涉及到复杂的malloc操作。(幸好在C++中,定义了一个数组指针char (*p1)[8]之后,可以利用new操作符写出简洁明晰的表达式new int[5][8])。