分类: C/C++
2013-06-05 14:45:56
原文地址:二维数组指针和函数参数 作者:g_programming
二维数组指针和函数参数
前几天看到一段代码,是在函数参数中传递二维数组的例子,弄得很糊涂,最近看了一下书,总结一下。
1.指针和二维数组
我们这里先定义一个二维数组:int zippo[4][2];/*整数数组的数组*/
(1)zippo为二维数组的首地址,zippo[0]即是其第一个整型数组的首地址,我们知道,
如果定义一个 int myint[2],那么myint为其数组名,而且也是首地址,由此类推,那么我们可以知道zippo[0]是zippo[0][...]数组的首地址也是其第一个元素的地址;
(2)zippo为二维数组的首地址,我们也从一维的数组推,如果定义一个 int myint[2],那么myint+1为第二个元素的地址,很明显zippo+1也是其第二个元素的地址,不过这里第二个元素特殊一点,它的第二个元素的一个一维的整型数组,所以zippo+1指向的是第二个一维数组。
(3)关于二维数组的存储,如果是以行主序的存储的,那么其存储结构如下:
图(1)二维数组存储结构
先前我进入了一个误区,由于二维数组zippo是地址的地址,那么我想定义一个int **pint = zippo;那么就能够完整且能准确的使用二维数组,使用如下代码:
int zippo[4][2]={2,1,3,45,3};
代码段1:int **pint = zippo;
for (i=0;i<4;i++)
{
for (j=0;j<2;j++)
{
printf(" %d ", pint[i][j]);
}
printf("\n");
}
注:我在使用如下代码成功执行了。
for (i=0;i<4;i++)
{
for (j=0;j<2;j++)
{
printf(" %d ", zippo[i][j]);
}
printf("\n");
}
程序结果:
2 1
3 45
3 0
0 0
使用代码段1会在VC中会出现
很明显出现了段错误,由于数据在内存中的存储是是线性的存储的,如图(1)二维数组存储结构,为了我用使用 zippo[i][j] 可以呢??根据我的分析,应该是在声明一个二维数组的时候,编译的时候zippo成为二维数组的数组名使用的特殊用法,而我们声明int **pint = zippo;会让zippo二维数组名退化成一个指针,那么我们就只能当成一个指向整型的一个指针,我们看代码段2.
那么我换为代码段2,如下:
代码段2:
int **pint = zippo;
for (i=0;i<4;i++)
{
for (j=0;j<2;j++)
{
printf(" %d ", pint[i*2+j]);
}
printf("\n");
}
程序结果如下:
2 1
3 45
3 0
0 0
成功执行了,这里我们可能会奇怪,这里明明使用的是一个指针的指针,为什么能正常工作呢,我的理解是因为指针大小跟int型大小一致,这样可以正常使用,那我们来试试换成一个二维字符数组,
临时代码段1:char char_array[4][20] = {"a.txt", "b.txt"};
char **pchar1 = char_array;
for (i=0;i<4;i++)
{
for (j=0;pchar1[i*20+j];j++)
{
printf(" %c ", pchar1[i*20+j]);
}
printf("\n");
}
编译通过,但是运行的时候结果不正确,结果如下:
a t
? -
? ? ? ? ? ? ? ? ? ? ? p ?
? ? ? x
不正确的原因就如刚刚我们分析的一样,因为 pchar1[i*20+j]等同于*(pchar1+i*20+j),那么取的是*(pchar1+i*20+j)的内容,但是定义的时候char **pchar1,所以*(pchar1+i*20+j)还是一个指针,所以不正确;
那我们换一下代码,试试我们刚刚的分析:
临时代码段2:char char_array[4][20] = {"a.txt", "b.txt"};
char *pchar = char_array;
for (i=0;i<4;i++)
{
for (j=0;pchar[i*20+j];j++)
{
printf(" %c ", pchar[i*20+j]);
}
printf("\n");
}
运行结果:
a . t x t
b . t x t
正确运行,这样证明我们的猜想是正确的,我们仅仅使用一个指向字符的指针就可以成功遍历了,这是由于数据的存储模型决定的。
看到这里你可能会想到上面临时代码段1,不是由于是指向的一个指针出错的,pchar1[i*20+j]还是一个指针,那么我使用**(pchar1+i*20+j)是不是就正确了呢,那么你想错了,因为数据在内存中仅仅是用了一个地址而已,而不是将数据的地址存储在另一个地方再使用,就是我们使用的zippo的地址也就是其数据的首地址了,而不是指针的指针,所以**(pchar1+i*20+j)这样用会指向一个未知区域,这样做事禁止的。
那我们再换一次代码段3:
代码段3:
int *pint = zippo;//这里换成了指向整型的指针
for (i=0;i<4;i++)
{
for (j=0;j<2;j++)
{
printf(" %d ", pint[i*2+j]);
}
printf("\n");
}
程序结果:
2 1
3 45
3 0
0 0
程序结果正确,原因我们已经在前面的分析中说明了,我们不嫌麻烦,在一次换代码段4:
int *pint = *zippo;
for (i=0;i<4;i++)
{
for (j=0;j<2;j++)
{
printf(" %d ", pint[i*2+j]);
}
printf("\n");
}
程序结果:
2 1
3 45
3 0
0 0
程序也能运行正确,因为我们知道,其实zippo的值和*zippo,还有&zippo[0][0],zippo[0]的值都是一样的,所以这样使用是正确的,我们来在程序中看看这几个的值:
printf("zippo:%0x\n",zippo);
printf("zippo[0]:%0x\n",zippo[0]);
printf("*zippo:%0x\n",*zippo);
printf("&zippo[0][0]:%0x\n",&zippo[0][0]);
程序结果:
zippo:12ff0c
zippo[0]:12ff0c
*zippo:12ff0c
&zippo[0][0]:12ff0c
2.函数和二维数组
如果在要编写处理二维数组的函数,必须很好的理解指针与数组的关系,正确的声明数组的参数,我们就在这里详细的讲解一下。
若是我们要处理一个二维字符数组 char char_array[4][20] = {"a.txt", "b.txt"};使它输出,一种办法是使用在函数参数中使用一维数组,如下:
void Print(char *p)//void Print(char p[])也行
//在传递数组指针的时候,相当于做了char *p = char_array[i]的操作,p不会成为 //类似的数组名,而是退化成一个指向字符的指针
{
int i = 0;
while(p[i])
printf("%c",p[i++]);
printf("\n");
}
for (i=0; i< 4; i++)
{
Print(char_array[i]);
}
当然这么编写肯定很麻烦,而且不这么规范,所以我当然就想到二维数组的指针传递了,如果要做二维数组的指针传递我们可以如下声明:
void Print1(char (*p)[20], int n)//n为二维数组中一维数组的个数
//void Print1(char p[][20], int n) 两种声明都行,{
int i;
for(i = 0; i < n; i++)
{
printf("array[%d]= %s\n", i,*(p+i));
}
}
为什么要这么声明呢??因为传递二维数组指针的时候,编译器不知道传递进来的函数参数是什么特殊的指针,我们使用char (*p)[20]来做限定,限定传递的参数为一个第二维为20个元素的二维数组指针,而且p指针还不是数组名,所以不是常量,能进行自加等操作,
char (*p)[20]将(*p)加括号是因为间接运送符的优先级低于[]运算符,所以必须加上才能等同于char p[][20];下面我们再来看看几个可能让你不好理解的地方:
(1)void Print1(char p[][], int n) //错误的声明
我在《c primer plus》中知道,编译器会把数组符号转换成指针符号,例如ar[1]会被转换为ar+1,编译器这样转换需要知道ar所执行对象的大小,下面的声明:
void Print1(char ar[][20], int n) //合法的声明
就表示ar指向的是由20个char型构成的数组,所以ar+1表示“在这个地址上加20个字节的大小”,如果是空括号,那么编译器将不能正确处理。
(2)我们经过第一节的学习,我们可以知道可以使用一个指向字符的指针来处理多维数组,其具体使用如下:但是这样使用会处理一维和二维数组之间的转换,不到万不得已最好不用。
char char_array[4][20] = {"a.txt", "b.txt"};
Print2(char_array, 4, 20);
void Print2(char *p, int row, int col)
{
int i,j;
for(i = 0; i < row; i++)
{
j = 0;
while (p[i*col+j])
{
printf(" %c ", p[i*col+j]);
j++;
}
printf("\n");
}
}
(3)void Print1(char **p, int n) //错误的声明 编译能通过
这种类型的声明跟第一种情况是一样的,编译器不能正确识别为二维数组,注意
char **p和char p[2][3]中的p在有些情况下能相互通用,比如我们直接在函数内部使用的使用,两者是通用的,但是如果作为函数的参数传递,那么是不可以的,一维数组也是一样的。
(4)void Print1(char *str[20], int n)//错误的声明 编译能通过
我们知道“*”的优先级低于“[]”,那么是20个一维字符数组,但是编译器同样不能断定每个一维数组的大小,那么就不能正常使用。
(5)通过上面的学习我们知道
int (*pz) [2];//pz指向的一个包含两个int型的数组
而 int * pz [2];//是定义了2个一维的int型数组,记住这两者是不一样的,其运算符的优先级关系上面已经说了,但是下面有一种特殊用法,我也一种不知道原因:
编程中,需要向某个函数传递一个字符串数组。测试代码如下:
1 #include
2 #define M 2
3 #define N 100
4
5 void test(const char** pstr)
6 {
7 int i = 0;
8 for(i = 0; i < 2; i++)
9 {
10 printf("array[%d]= %s\n", i,*(pstr+i));
11 }
12 return;
13 }
14
15 int main()
16 {
17 char char_array[M][N] = {"a.txt", "b.txt"};
18
19 test((const char**) char_array);
20
21 return 0;
22 }
当然这么编写会出现段错误,原因我在前面已经说明了,是因为数组指针在编译的时候已经退化为符号指针了,其pstr就是符号指针,
1. 传递给test的紧紧是二维字符数组的首地址;
2. test 参数中pstr的类型是char**, *(pstr+i)的类型是 char*,四字节的指针, 而**pstr的类型是char;
printf(" %d %d %d \n",sizeof(pstr),sizeof(*pstr),sizeof(**pstr));
但是
for (i=0; i<7;i++)
{
printf("%c",**(pstr+i));
}
会报错,虽然**(pstr+i)指向的是char类型,但是实际上指向的地址是实际上没有使用的,这样使用会造成错误。
如果我们把程序改为如下:
23 #include
24 #define M 2
25 #define N 100
26
27 void test( char** pstr)
28 {
29 int i = 0;
30 for(i = 0; i < 2; i++)
31 {
32 printf("array[%d]= %s\n", i,*(pstr+i));
33 }
34 return;
35 }
36
37 int main()
38 {
39 char * str_array[M] = {"a.txt", "b.txt"};
40
41 test(str_array);
42
43 return 0;
44 }
能正确运行,网上有一篇帖子说明了原因,但是我没怎么看懂,因为照理说编译器将不会正确识别pstr为二维数组指针的,但是不知道为啥??
参考:http://blog.csdn.net/fantasy666666/archive/2009/08/07/4422110.aspx