指针与数组的话题可谓是老生常谈了,大多数的书箱也都是把这两个东东放在一块来讲述的,此篇文章不是我个人总结, 算是篇读《C语言深度解剖》的读书笔记吧,因为看这它里面的第四章指针与数组后,感觉有很多以前搞不清楚的地方有了种顿悟的感觉,所以有把这种感觉写下来的冲动。若是感觉此文给你带来不便,请拜读原文。

归入正题,

1、指针概念:

如上图所示,是int *ptr = (int *)0x0000FF00的示意图,指针p的身份特点:

a.首先是一个变量,p就代表它的内容 0x0000FF00

b. 它的内容是一个地址(另一块内存的地址,也称作指向该地址的内存块)

c.通过*操作符(可以看作是防盗门的钥匙)它直接获取它指向的内存块的数据内容。

例如: int *p2 = NULL

它表达式的用意就是声明一个指针变量p2,并将该指针指NULL (这句是有用意的,声明后即将该指针栓在位置0即NULL位置上,以防止野指针)

           *p2 = NULL

就是将p2指针的内容置NULL。

注:指针变量是一个数据类型,int可看作是用来修饰指针所指向数据的类型的。每个指针变量占4个字节的。

2、数组:

如上图,显示了一个int数组的示意图。表达式为int a[5]

注意分清几个表达式的确切含义:

a:代表整个数组,类型是整形数组,但是作为右值时,仅代表首元素的地址。

&a:代表整个数组的起始地址,类型是int (*)[5],例如:(int (*)[5]) 0xbffff878

&a[0]:代表首元素的起始地址,类型是int * ,例如:(int *) 0xbffff878

其中&a与&a[0]的值虽然一样,但用意不同,如同省政府与市政府同处一个城市,但身份不同。

 3、指针与数组之间的恩恩怨怨

指针与数组虽然经常拿到一块来说,但这两个东西是没有任何关系的,只是它们经常穿着相似的衣服来逗你玩罢了,呵呵,一定要把握这个本质。

例如,分别定义一个数组和指针:

char *p = "tianjin";
char array[]="tianjin";

 

1)数组和指针都支持“以指针的形式访问”和“以数组的形式访问”

接上例,用*(p+2)指针形式,代表访问该指针所指向字符串的第2个字符;用p[1]访问该指针所指向字符串的第2个。

用*(array+2)访问字符数组的第2个元素。用array[1]来同样访问该字符数组的第2个元素。

注意:指针变量的计算,指针的移动是以该指针所指向数据的大小为单位的,

例如

*(p+1),由于p指向的是字符数组,基本单位就是一个字符,此表达式就代表指针向后移动一个字符的距离,即下一个字符。

&array+1 搞清&a代表的是整个数组,基本单位就是数组大小,此表达式就代表指针向后移动此数组大小的距离,即移动7个字符的距离。

 

2)数组和指针的对比

其实对于程序员来说,指针与数组最大的不同之处在于内存分配,如果声明数组,则拥有连续的存储空间,若是声明指针,则只分配一个字,只存储一个地址,并且注意该变量不与任何内存存储空间相关联,直到对它初始化为止。

示例代码:

 1 /* myarray.c --- 
 2  * 
 3  * Filename: myarray.c
 4  * Description: 
 5  * Author: magc
 6  * Maintainer: 
 7  * Created: 二  7月 17 06:16:03 2012 (+0800)
 8  * Version: 
 9  * Last-Updated: 四  7月 19 07:01:13 2012 (+0800)
10  *           By: magc
11  *     Update #: 48
12  * URL: 
13  * Keywords: 
14  * Compatibility: 
15  * 
16  */
17 
18 /* Commentary: 
19  * 
20  * 
21  * 
22  */
23 
24 /* Change Log:
25  * 
26  * 
27  */
28 
44 
45 /* Code: */
46 
47 #include <assert.h>
48 #include <ctype.h>
49 #include <errno.h>
50 #include <limits.h>
51 #include <string.h>
52 #include <stdarg.h>
53 #include <stdlib.h>
54 #include <stdio.h>
55 
56 int main(int argc, char * argv[])
57 {
58     int a[5] = {1,2,3,4,5};
59     int *ptr = (int *)(&a+1);
60 
61     printf("%d , %d\n",*(a+1),*(ptr-1));
62 
63     char c[5] = { 'A','B','C','D','E' };
64     char *p2 = c;//指针p2指向了具名数组c的空间
65     char (*p3)[5] = &c;//声明一个数组指针p3指向具名数组c的空间
66     char (*p4)[10] = &c;
67     
68     //char (*p3)[5] = c;
69     // p3+1  与p2+1 各为多少
70 
71     int i;
72 
73     for(i=0;i<5;i++)
74         {
75             printf("c[%d]=%c;",i,c[i]); //以数组形式访问数组(即下标方式)
76             printf("*(c+%d)=%c;",i,*(c+i));//以指针形式访问数组
77             printf("p2[%d]=%c;",i,p2[i]);//以数组形式访问指针(即下标方式)
78             printf("*(p2+%d)=%c\n",i,*(p2+i));//以指针形式访问指针
79             
80         }
81 
82     return 0;
83     
84     
85 }
86 
87 
88 /* myarray.c ends here */

 经GCC编译运行结果:

通过 GDB调试打印其中的变量值,如下图所示:

3、一维数组和指针作为形参和实参

在C语言中是无法为一个函数传递一个数组过去的,因为在C世界里有一个潜规则:

C语言中,当一维数组作为函数参数的时候,编译器就是把它解析为一个指向其首元素地址的指针变量

同样作变返回值时,也是不能如愿的,仍是以指针形式返回的,当然数组变身为一个指针后,其长度就需要单独的一个参考来传递了。

以下面代码为证:

 

  1 /* myparameter.c --- 
  2  * 
  3  * Filename: myparameter.c
  4  * Description: 指针和数组作形参
  5  * Author: magc
  6  * Maintainer: 
  7  * Created: 六  7月 21 05:45:58 2012 (+0800)
  8  * Version: 
  9  * Last-Updated: 六  7月 21 06:30:38 2012 (+0800)
 10  *           By: magc
 11  *     Update #: 47
 12  * URL: 
 13  * Keywords: 
 14  * Compatibility: 
 15  * 
 16  */
 17 
 18 /* Commentary: 
 19  * 
 20  * 
 21  * 
 22  */
 23 
 24 /* Change Log:
 25  * 
 26  * 
 27  */
 28 /* Code: */
 29 
 30 #include <assert.h>
 31 #include <ctype.h>
 32 #include <errno.h>
 33 #include <limits.h>
 34 #include <string.h>
 35 #include <stdarg.h>
 36 #include <stdlib.h>
 37 #include <stdio.h>
 38 
 39 void output(int a[],int len);
 40 void output2(int *a,int len);
 41 int *add10(int a[],int len);
 42 
 43 
 44 /**************************************************************************
 45 函数名称:
 46 功能描述:
 47 输入参数:
 48 返   回:
 49 **************************************************************************/
 50 int main(int argc, char * argv[])
 51 {
 52 
 53     int a[5] = {1,3,5,6,7 };
 54     printf("main:sizeof(a) = %d\n",sizeof(a));
 55     output(a,5);
 56     int * b = add10(a,5);
 57     output2(b,5);
 58     output2(a,5);//原数组a也发生了变化
 59     
 60     
 61     
 62 }
 63 /**************************************************************************
 64 函数名称:
 65 功能描述:输出一个数组内容
 66 输入参数:a 数组名,len 数组长度
 67 返   回:
 68 **************************************************************************/
 69 void output(int a[],int len)
 70 {
 71     printf("output:sizeof(a) = %d\n",sizeof(a));//变量名作形参后,会变身为一个指针变量,并指向数组的首元素地址
 72     
 73     int i;
 74     for (i = 0; i < len; i++) {
 75         printf("a[%d] = %d; ",i,a[i]);
 76     }
 77     printf("\noutput end\n");
 78     
 79 }
 80 
 81 /**************************************************************************
 82 函数名称:
 83 功能描述:输出一个数组的内容
 84 输入参数:a int形指针变量,len 数组长度
 85 返   回:
 86 **************************************************************************/
 87 void output2(int *a,int len)
 88 {
 89     printf("output2:sizeof(a) = %d\n",sizeof(a));
 90     int i;
 91 
 92     for (i = 0; i < len; i++) {
 93         printf("a[%d] = %d; ",i,a[i]);
 94         
 95     }
 96     printf("\noutput end\n");
 97     
 98 }
 99 
100 int * add10(int a[],int len)
101 {
102 
103     int i;
104 
105     for (i = 0; i < len; i++) {
106         a[i] += 10;
107         
108     }
109     return a; //数组仍需要以指针形式来返回
110     
111 }
112 
113 
114 
115 /* myparameter.c ends here */

在GCC下编译运行的结果如下所示:

注:

1)output和output2两个函数的形参一个是数组形式,一个是指针形式,结果都是一样的,在函数内容都变身为一个指针变量,大小为4字节了。

2)57行:数组在经过add10函数更新后,原数组的值也同时发生了变化,这其中的原因就是在主函数的局部的数组空间地址,经过指针变量b传递给了add10函数,在add10函数内部操作的数组与main函数中的a数组的地址相同了,所以会影响到main中的局部数组。(这就需要结合内存分配,函数调用,局部变量等知识了)

3)56行:add10的返回值其实是和参数a的地址是一样的,也可以不需要这个返回值。

4)有时,为了避免一个参数在函数内部被修改,常用const来修饰形参,如上output和output2的第一个参数都可以加上const,而add10的第一个参数加上const后, 编译时就会报错,因为在此函数内需要操作原数组地址上的内容,与const冲突了。

5)基于这种实际情况,根据需要选择自己需要的效果。