指针与数组的话题可谓是老生常谈了,大多数的书箱也都是把这两个东东放在一块来讲述的,此篇文章不是我个人总结, 算是篇读《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)基于这种实际情况,根据需要选择自己需要的效果。