指针篇之五 指针与数组 剪不断理还乱
来源:互联网 发布:直接翻译软件界面 编辑:程序博客网 时间:2024/06/08 11:45
该文章转载于http://blog.csdn.net/ipmux/article/details/17206775,感谢博主悉心讲解。
数组和指针可以说是一对纠缠不清的冤家,下面是一些常见的关于数组和指针的说法:“一维数组是一级指针”;“二维数组是二级指针”;“数组名可以作指针用”;“数组名是常量指针”。
很遗憾,这些说法全部错误。数组名不是指针,也不存在什么常量指针!数组名代表一个地址,它可以作为右值赋给指针变量。至于指针与地址的关系,在第一篇最后已有说明。忘记了?回去看。
存放数组的区域是一块内存,数组名这个符号代表了这块内存的首地址。注意:不是数组名这个符号的值代表内存首地址(只有变量才能被赋值,数组名不是变量),而是数组名符号本身代表了首地址。换言之,数组名是符号常量,是右值。与此对应,指针是变量,是左值。左右值的不同代表二者根本属性的差别,任何混淆数组名和指针的表述都是错误的。
但实际应用中,数组又的确经常和指针有所关联。下面列举几种容易误解和混淆的场合:
1)作为形参的数组,自动被编译器转换为指针
C编译器会把作为形参的数组自动按指针编译,但这并不说明数组名就是指针。这种转换是因为制定ANSI C标准时,从执行效率出发,不主张参数传递时复制整个数组,而用传递数组首地址代替,被调函数再根据这个首地址处理数组内容,以避免数据在内存间搬运(结构体参数也有类似转换)。谁能容纳数组首地址呢?自然非指针莫属。但这种“转换”是编译器在不影响功能前提下的一种智能替代和优化,用传递藏宝图代替直接搬运宝藏,例如:
a) Bool XXX_ReadKey(char *aCert, char PublicKey[KEY_SIZE]);
b) Bool XXX_ReadKey(char *aCert, char *PublicKey);
如果代码中出现a),编译器分析后认为b)效率更高,且效果相同,就会自动替换为b)。这类似于编译器有时用移位替换乘除法一样,只是特定场合的优化,不代表双方随时能互换。
2)函数形参是指针时,数组名可以作为实参传递,这又是怎么回事?
函数 int fun(char *pch),调用时可以把数组char ctab[10]的名字ctab做实参传递,即fun(ctab),可以证明这里数组等于指针?
我们知道,函数参数按值传递,即:形参期望得到的,并不是实参本身,而是实参的值!例如void fun(int i);可以用整型变量int n作实参,即fun(n);也能用整数常量做实参,如fun(10);那按这种逻辑,形参是整型变量,10可以充当实参,难道说10是一个整型变量?
其实这里形参类型int,只是限定传递一个整数类型,并非要求一定是整型变量,所以变量n和常量10都满足。指针情形类似,指针形参并不要求实参必须是指针,它需要的是地址值,谁能给予它一个地址值?指针变量、地址常量都行,数组名作为地址常量自然也满足,所以这里数组名是赋给指针形参的地址实参!数组名等于指针,仍然是一个错觉。
3)指针与数组可以在访问形式上相互代替
a.使用地址访问符号*号访问数组,比如:
int array[10]={0,1,2,3,4,5,6,7,8,9},
int value;
value=*array; //相当于value=array[0];
value=*(array+3); //相当于value=array[3];
肯定有人会说,这里数组和指针不是混用了么!形式上array的确既代表数组名又似乎被当指针用,但盲点在于*号并不是指针变量专用,继续看:
array++; //错,数组名代表地址,为右值,不能改变自身。所以数组终归不是指针。
……
不要一看到*号就认为后面是指针,实际上*号又称地址访问符,它后面可以跟任何形式的地址,绝不仅仅是指针变量(这个误解在很多人心里根深蒂固),作为地址常量的数组名当然也可以被*号访问。数组名和指针的本质区别体现在array++这种操作:左值的指针变量可变,而右值的数组名地址常量不行。
b引用符号[]访问指针
[]号称为数组引用,但并不是只能用于数组定义和数组索引求值,C89规定:进行数组引用时,[]号左边除了数组名,还可以是一个表达式,但表达式类型须为”指针”。因此类似:
int a[10], *p = a;
int x = p[1];
int y=(p+1)[2];
这样是合法的,因为p和p+1都是指针,可以用数组引用符[]求值,不能一看到p[i]就把p当成数组。编译器只负责把p[i]计算式统统转换成*(p+i),无论p是数组还是指针。可很多人常忽略这一点,总把[]跟数组划等号,这样在下面情况下就容易犯错:
int tab[4] = {10,20,30,40};
int * pstart = tab;
int a = *pstart++;
int b = pstart[1];
此时b值为多少?
关键:pstart[1]和tab[1]性质不同,pstart是指针而不是数组,pstart[1]相当于*(pstart+1),只是借用[]号的形式完成指针偏移访问。为a赋值后,指针pstart自增1,当计算b=pstart[1]时,b值为指针pstart再次增1后指向的值,即b=tab[2]=30,而不是tab[1]的10。
4)静态数组与动态指针内存各自适合的应用场合
前面讲数组和指针语义上的区别和联系,应用中更深层的问题是什么时候适合用静态数组,而什么时候该用指针指向的动态堆内存。可从两个角度分析:
首先二者占用的内存区域不同,局部数组位于栈内存,全局数组位于数据区,而malloc返回的指针指向动态分配的堆内存。选择前要考虑不同资源的占用情况,后续内存篇将详述。
从数据结构角度看,数组是定义时就指定长度的静态分配,用于存储数目固定且类型相同的元素。而以指针为纽带的各种动态数据结构,如链表,常用于存储动态变化的数据。举例说明它们各自的擅长:
例1)用库函数strcpy()和strcat()来把两个字符串s和t连接成一个新字符串r,首先尝试用数组r,用来容纳s和t,即:
char r[100];
strcpy(r, s);
strcat(r, t);
这里定义数组长度为100,可一旦字符串s,t长度之和超过100,内存访问就越界。很明显,这种情况不适合用数组,因为数组定长,无法应对长度变化的外部输入字符串s和t,这里s和t就是动态数据,适合用动态内存解决问题,即:
char *r;
r = malloc(strlen(s) + strlen(t) + 1);
strcpy(r, s);
strcat(r, t);
注意字符串尾隐含一个null。strlen()只返回实际字符数,不包括这个结尾空字符。所以strlen(s)为n,却需n+1字符的空间,要为null额外分配1字节。
例2)要保存一个班级所有成员期中考试的成绩,班级成员数为30,则可以用数组记录:int score[30]={100, 99, ……, 60}
而如果是某班级招生,事先不知道能招到多少人,又要记录所有生源信息,对这种动态变化的数据,最好方式是用指针和动态内存构建链表,来1人分配一块内存保存其信息,并用指针把这些内存块连在一起,以便统计和查询。(具体链表实现?查书)
以上就是指针和数组的关联,一刀两断,不要继续纠缠不清哦
- 指针篇之五 指针与数组 剪不断理还乱
- 指针篇之五 指针与数组 剪不断理还乱
- 五 数组与指针
- 指针与数组【重学C之五】
- 指针:指针与数组
- 指针与指针数组
- 指针之指针数组
- C 语言之五 指针 数组 函数
- C++ 之数组与指针
- 指针数组与数组指针
- 指针数组与数组指针
- 指针数组与数组指针
- 指针数组与数组指针
- 指针数组与数组指针
- 指针数组与数组指针
- 指针数组与数组指针
- 数组指针与指针数组
- 指针数组与数组指针
- FMS Flex 视频应用开发
- Hive CSV Support(csv-serde)
- 第十五周-(项目三)带姓名的成绩单。
- 网页中返回顶部代码(多种方法)另附注释说明
- Mysql常用函数
- 指针篇之五 指针与数组 剪不断理还乱
- 爱情之线
- ifconfig使用及其配置文件
- Spring Security 3.1.3最小入门配置及实例下载
- WebStrom快捷键
- Oracle Process Architecture - Oracle 进程结构篇5
- Yii文件上传 添加了, 视图实现用widget
- 2013-12-11调用系统API的ChooseFont字体设置
- 严重: null, httl: 1.0.11, jvm: 1.6.0_37, os: Windows 7 6.1 amd64