C语言中数组与指针的透彻分析

来源:互联网 发布:淘宝秒杀器手机版下载 编辑:程序博客网 时间:2024/05/19 13:28

一. 一维数组与指针的关系分析

1.指针与一维数组的定义

指针定义: 指针是包含内存地址的变量,一般的指针变量直接包含特定的值,指针变量包含的是某一特定数据类型的内存地址。

 一维数组的定义: 数组是一组连续的内存位置,它们都具有相同的名称和类型。数组名表示数组的首地址。

定义上的区别: 指针是个变量,可以进行加或减运算,可以改变其指向的内存地址值,而数组是个常量,其内存地址的值不可改变。

可见,指针变量里面存放的是地址,数组名表示数组的首地址。因此,既然都有地址,那么它们之间就有着密切的联系。

 

2. 指针与数组的输出实例

 


#include <stdio.h>
#include <stdlib.h>
int main(){
char *p;

char a[]={"abcde"};
p=a; 
printf("%s/n",p);   
printf("%s/n",a);
printf("%c/n",*p);

printf("%s/n",(p+1));
p=&a[1];
printf("%s/n",p);
printf("%c/n",*p+10);
printf("%x/n",a);
printf("%x/n",&a);
printf("*a=%c/n",*a);
printf("%s/n",a+1);
int i;
for(i=0;i<4;i++){
printf("%c/n",*(p+i));
}
return 0;
}

输出值:

abcde

abcde

a

bcde

b

bcde

l

bf92c016

bf92c016

*a=a

bcde

b

c

d

e

 

 

 

我们从第1行开始理解一下程序:

 

char *p;  -定义一个字符型 的指针变量

char a[]={"abcde"}; -定义一个字符型 数组

 p=a;  -这条语句是很有讲究的,为什么不是p=&a? ,首先等式的左边是一个指针型变量,其类型为char*即字符型变量,那么等式的右边应该也是字符型变量,在C语言中,当数组出现在表达式中,就退化为指针了。  这一点,一定要清楚,它是理解C语言中数组与指针关系的精华。所以,表达式的右边退化为指针,即为char*类型,指向数组的首地址。因此,类型就匹配了。那么p=&a为什么不对呢? 如上分析,表达式左边是char*类型,而表达式右边是char(*)[5]类型,是一个指向数组长度为5的数组指针类型。很明显类型不匹配,所以不能是p=&a.

 

 printf("%s/n",p); -打印出字符串abcde,为什么不是abcde的首地址了? 当a的首地址给了p,那么p里面存放的是a的首地址,按理说,应该打印出 的是地址值,但在C语言中,printf一遇到地址,就打印出地址里面的内容值,直到/0为止。这一点也是非常重要的。把%s改成%x就能打印出地址值了。

 printf("%s/n",a);-与上面一样,打印出地址里面的内容,从这也可以看出,数组名已经退化为指针了,但数组与指针有着本质的不同。

 printf("%c/n",*p); -输出字符串中的第一个字符,由于p指向的是字符数组的首地址,自然*p就输出字符数组的首字符了。

 printf("%s/n",(p+1));  -p指向数组的首地址,那么p+1指向的是数组的第二个元素的地址,即a[1]的地址,那么p+1打印了是从第二个字符一直到/0为止的所有字符。即bcde.

p=&a[1]; -这条语句也是很有讲究的。等式左边是一个char型 指针变量,那么等式右边也应该是char型 指针变量,a[1]是一个字符值,&a[1]就是a[1]的地址,即是一个字符指针。

printf("%s/n",p); -由于p指向a[1]的地址,那么输出的应该是bcde.

printf("%c/n",*p+10); -*p的值是b, b+10也是一个字符,由于字符与整数是相对应的,所以b+10为字符l.

printf("%x/n",a); -打印出数组a的首地址.

printf("%x/n",&a); -也是数组的首地址。但我们多次强调,a与&a的类型是不一样的。

printf("*a=%c/n",*a);-打印出数组的第一个元素,即a.

printf("%s/n",a+1); -打印出bcde.

 

for(i=0;i<4;i++){
printf("11%c/n",*(p+i));
}

 

 p指向地址a[1],那么打印出的应该为bcde. *(p+i)=a[i].

 

小结

一维数组与指针的关系始终要注意三点:

 (1)当数组名出现在表达式中,此时数组已经退化为指针了。

   (2)  在输出数组名或者指针指向的数组地址时,其输出的是内容值,直到/0结束为止。

   (3) *(p+i)=a[i] , a与&a类型是不一样的,前者是char型 指针,后者是char(*)[5]数组指针类型。

另外在传参数的时候,char* strcpy(char* s1, const char* s2);-第一个参数直接可以传入字符数组名,因为字符数组名已经退化为指针了。

因此,可以这样认为,C语言中没有真正意义上的数组,只在定义层面上,它是一个数组,而在应用的时候都退化为指针了。

 

 

二.二维数组与指针的关系

 

前面分析了一维数组与指针的关系,二维数组与指针的关系较为复杂。

 

首先,我们认清两个概念,指针数组与数组指针。

 

指针数组:指针数组是一种特殊的数组,这类数组里面存放的全是指针。

如const char*c[4]={""China","USA","Rassia",""Japan"};

这是一个指针数组,里面包含4个指针分别是c[0],c[1],c[2],c[3].这四个指针分别指向字符串的首地址。

数组指针: 数组指针是指向一个数组的指针。 如 char(*c)[4]([]的优先级比*高,因此要有括号). 定义了一个指向长度为4的数组的指针。

指针数组本质上是数组,数组指针本质上是指针。

 

同样,我们看一段程序:

#include <stdio.h>

#include <string.h>

#include <stdlib.h>

int main(int argc,char*argv[]){

char a[2][6]={"abcde","ABCDE"};

char *p;

char (*p1)[6];

printf("%x/n",a);       //输出bfd2fc08

printf("%x/n",&a);    //bfd2fc08

printf("%x/n",*a);    //bfd2fc08

 

printf("a[0]=%x/n",a[0]);    //bfd2fc08

printf("a[1]=%x/n",a[1]);    //bfd2fc0e

printf("a[0]+1=%x/n",a[0]+1);    //bfd2fc09

printf("*(a[0]+1)=%c/n",*(a[0]+1));   //b

printf("&a[0]=%x/n",&a[0]); //bfd2fc08

printf("%s/n",&a[0]);        //abcde

printf("&a[0][0]=%x/n",&a[0][0]);//bfd2fc08

printf("%s/n",&a[0][0]+1); // bcde

printf("&a[0]+1=%x/n",&a[0]+1); //bfd2fc08

printf("%s/n",&a[0]+1); //ABCDE

printf("%s/n",a); //abcde

printf("%s/n",a+1); //ABCDE

printf("%s/n",*a+1); //bcde

printf("%s/n",*(a+1)); //ABCDE

printf("%c/n",**(a+1)); //A

printf("%c/n",*(*(a+1)+1)); //B

 

p1=a;

p1=&a[0];

printf("p1=%s/n",p1); //abcde

printf("p1+1=%s/n",p1+1); //ABCDE

printf("%x/n",p1); //bfd2fc08

printf("%x/n",p1+1); //bfd2fc0e

printf("%x/n",*p1); //bfd2fc08

printf("%x/n",*(p1+1)); //bfd2fc0e

printf("%c/n",**(p1+1)); //A

printf("%c/n",*p1[0]); //a

printf("%c/n",*(p1[0]+1)); //b

 

int j;

p=a[0];

p=&a[0][0];

for(j=0;j<11;j++){

printf("%c/n",*(p+j));

}

//输出 abcde ABCDE

char *v[]={"hello",""world"};

int k;

for(k=0;k<2;k++){

printf("%s/n",v[k]);

printf("%s/n",*(v+k));

printf("%c/n",*v[k]);

printf("%c/n",**(v+k));

 

}

//输出hello hello h h world world w w

return 0;

}

 

估计看到这,肯定有点迷糊。下面一行一行解释上面的程序:

 

char a[2][6]={"abcde","ABCDE"}; -定义了一个二维字符数组

char *p; -定义了字符指针变量

char (*p1)[6]; -定义了数组指针,一个指向数组长度为6的数组指针p1,里面存放的是数组的首地址

 

printf("%x/n",a);
printf("%x/n",&a);
printf("%x/n",*a);

 

 先不着急,看它们的输出内容,分析一下它们的数据类型。a是一个二维数组名,它指向的是二维数组的首地址。那么a的类型应该是char(*)[6]即一个指向二维数组中每一维数组的数组指针。这一点,不好理解,其实二维数组可以看成是数组的数组,那么a就指向了二维数组中的内层数组,在上面的程序中,二维数组a包含两个一维数组,那么a就是指向了第一个一维数组的数组指针。因此,它是一个数组指针类型。

&a是数组名a的地址,它是一个指向二维数组的指针,所以它的类型是 char(*)[2][6],是指向整个二维数组的二维数组指针。

*a相当于a[0],即指向二维数组中第一个数组的char型 指针变量。由于二维数组可以看成是数组的数组,那么二维数组中的a[0]就相当于一维数组中的数组名,所以它是一个char型指针变量。

所以,数组名a的类型是char(*)[6]的数组指针,&a是char(*)[2][6]二维数组指针,*a是一个char型指针变量。它们的类型有着本质的不同。这是理解二维数组的精华所在。强调一句,二维数组与一维数组一样,在表达式中,退化为指针类型,不过不是简单的指针类型,也不是二重指针,它与二重指针有着本质的区别。

上述三条语名的输出值是一样的,即二维数组的首地址值。

 

printf("a[0]=%x/n",a[0]); //a[0]是一个地址值,即二维数组中第一维数组的首地址。其值与a相同,类型不同,a[0]退化为char型 指针。

printf("a[1]=%x/n",a[1]); //a[1]的值是二维数组中第二维数组的首地址,类型与a[0]相同。

printf("a[0]+1=%x/n",a[0]+1); //a[0]是一维数组的首地址,那么a[0]+1当前是a[0][1]的地址了。

printf("*(a[0]+1)=%c/n",*(a[0]+1));//a[0]+1是a[0][1]的地址,那么*(a[0]+1)就是a[0][1]的内容了。

printf("&a[0]=%x/n",&a[0]); //与a一样,数组的首地址值,a[0]是一个char*型指针,&a[0]就是一个char(*)[6]指向一维数组的数组指针与a的s类型相同了。

printf("%s/n",&a[0]);  //&a[0]与a一样,指向的是二维数组中第一维维的首地址,前面说过,一遇到地址输出内容,直到/0为止,所以输出的应该为 abcde.

printf("&a[0][0]=%x/n",&a[0][0]); //输出的是a[0][0]的地址,&a[0][0]与a[0]的类型相同,是一个char型指针。

printf("%s/n",&a[0][0]+1); //&a[0][0]相当于a[0],a[0]+1从二维数组的第一维数组中的第2个字符一直输出到/0,即bcde

printf("&a[0]+1=%x/n",&a[0]+1); //&a[0]相当于a,是一个字符指针数组类型,a+1输出的是二维数组中的第二个一维数组的元素值,直到/0为止。

 printf("%s/n",a); //输出二维数组中的第一个数组,直到/0为止。即abcde

 printf("%s/n",a+1); //输出二维数组中的第二个数组,直到/0为止,即ABCDE

 printf("%s/n",*a+1); //*a相当于a[0],a[0]+1,从二维数组中的第一个数组中的第二个字符一直输出到最后,即bcde.

 printf("%c/n",**(a+1)); //a+1是第二个数组的地址,*(a+1)为a[1],即为字符指针,所以**(a+1),应该为第二个数组的首字符,即A。

 printf("%c/n",*(*(a+1)+1));  //a+1的类型为数组指针

 

p1=a;//等号左边是数组指针类型,即为char(*)[6], a也是一个数组指针。所以类型匹配。

p1=&a[0]; //a[0]是一个指针变量,&a[0]是一个数组指针,相当于a.

printf("p1=%s/n",p1); //输出第一个数组的字符值,直到遇到/0

printf("p1+1=%s/n",p1+1); //p1+1相当于a+1,输出第二个数组的字符,直到遇到/0

 

printf("%x/n",p1);

printf("%x/n",p1+1);

前一个输出第一个数组的首地址,后一个输出第二个数组的首地址。

 

printf("%x/n",*p1);  //*p1相当于a[0],是一个字符指针,a[0]的首地址。
printf("%x/n",*(p1+1)); //*(p1+1)相当于a[1],即a[1]的首地址
printf("%c/n",**(p1+1)); //**(p1+1)即a[1][0]的值
printf("%c/n",*p1[0]); //[]的优先级比*高,p1[0]代表a[0]的首地址,*p1[0]为第一个数组的第一个字符。
printf("%c/n",*(p1[0]+1)); //第一个数组的第二个字符

 

 

 

char *v[]={"hello",""world"};

int k;

for(k=0;k<2;k++){

printf("%s/n",v[k]);

printf("%s/n",*(v+k));

printf("%c/n",*v[k]);

printf("%c/n",**(v+k));

 

这段代码是一个指针数组类型, v[k]是一个地址,一遇到地址输出字符值,直到/0结束。

*(v+k) 相当于v[k]. (为了好理解,可以把v看成是二维数组中的a,那么a+k类型是数组指针类型,*(v+k)就是字符指针类型,所以是值是个地址)

*v[k]第一个字符。

**(v+k)同上。

 

小结: 要深刻理解二维数组退化为指针这一概念。二维数组与数组指针的关系。

 

1.二维数组名是一个数组指针类型。a,a+1分别表示第一个数组与第二个数组的首地址。其类型为数组指针类型,在本例中为char(*)[n]

2. *a退化为a[0]是一个指针类型,即char型 指针。 &a升级为二维数组的指针,指向二维数组,不是指向一维数组的,类型为 char(*)[m][n].

对于这们的表达式 char**p=a,显示是不对了,前面是二重指针类型,后面是一个数组指针类型。数组指针不是二重指针,是一个一重指针,是指向这个数组的指针,而数组是一个复合概念。但数组不是指针。所以二重指针不=一重指针+数组

3. 对于a+1,a[0]+1, &a[0][0]+1,&a[0]+1要理解好。 a+1指向的是二维数组的第二个数组的首地址。 a[0]+1是二维数组的第一个数组中的第二个r元素的地址。 &a[0]+1 相当于a[0]+1, &a[0][0]+1,相当于a[0]+1.

4. 当p=a时,就可以把p看成是a.这样运算就会十分方便。

 

总结:

要透彻理解C语言中数组与指针的关系不是一件容易的事。我感觉应该注意以下几点:

 

(1)一维数组a,二维数组a,要理解数据类型。在一维数组中a是一个指针变量。在二维数组中,a是一个数组指针类型。它指向二维数组中的第一个一维数组。因此是不相同的。 一维数组中的a=二维数组中的a[0]. a[0]也是一个指针变量。

 

一维数组 a ----基本类型指针变量

二维数组 *a -----基本类型的指针变量

                 a -----数组指针类型的指针变量 基本类型(*)[n]

                &a-----指向二维数组的指针 基本类型(*)[m][n]

                &a[0]-----相当于a

                &a[0][0]----相当于a[0]

                a+1---指向第二个数组首地址

                a[0]+1指向第一个数组的第二个元素地址

 

 

(2)二维数组与数组指针是对应的。 char(*)p[n]--数组指针类型,对应二维数组中的a

 

(3)当%s输出时,会输出地址中的内容值,直到遇到/0为止。这一点很重要。

 

(4) a[k] 相当于*(a+k). a[i][j]相当于*(*(a+i)+j)

可以这样理解,一维数组中a+k表示a[k]的地址,那么*(a+k)自然就是a[k]了。

                          二维数组中a+i不是a[i],*(a+i)才是a[i],因为a+i的类型是数组指针类型,a[i]是一个基本类型指针,*(a+i)=a[i],那么*(a+i)+j就

                          是a[i][j]的地址了,所以*(*(a+i)+j)就是a[i][j]了。

但a绝不是二重批针。只是个数组指针类型。

 

(5)指针数组,里面放的全是指针。char *c[m]={"hello","world"};, c[0],c[1]都是指针,即是hello和world字符串的首地址。

 但在输出的时候输出c[0]就可以了,因为一遇到地址就输出内容。

 

(6) *与&是一个相反的运算符。取值与取地址。*p是指针,对p取地址就变为二重指针了。

 

所以,除去定义,从某种意义上说,C语言中不存在数组,所谓数组,在表达式中就退化为指针。二维数组也只是一维数组的演化。记住一点,在表达式中数组与指针是一样的。另外,特别强调一点,二重指针与二维数组是两个概念,绝不一样。因此,char**p=a必然是错的,类型不匹配呀。