按键盘上方向键 ← 或 → 可快速上下翻页,按键盘上的 Enter 键可回到本书目录页,按键盘上方向键 ↑ 可回到本页顶部!
————未阅读完?加入书签已便下次继续阅读!
2中声明它为指针。这有什么问题吗?平时不是总说数
组与指针相似,甚至可以通用吗?但是,很不幸,这是错误的。通过上面的分析我们也能
明白一些,但是“革命尚未成功,同志仍需努力”。你或许还记得我上面说过的话:数组就
是数组,指针就是指针,它们是完全不同的两码事!他们之间没有任何关系,只是经常穿
着相似的衣服来迷惑你罢了。下面就来分析分析这个问题:
在第一章的开始,我就强调了定义和声明之间的区别,定义分配的内存,而声明没有。
定义只能出现一次,而声明可以出现多次。这里
extern告诉编译器
a这个名字已经在别的文
件中被定义了,下面的代码使用的名字
a是别的文件定义的。再回顾到前面对于左值和右值
的讨论,我们知道如果编译器需要某个地址(可能还需要加上偏移量)来执行某种操作的
话,它就可以直接通过开锁动作(使用“*”这把钥匙)来读或者写这个地址上的内存,并不
需要先去找到储存这个地址的地方。相反,对于指针而言,必须先去找到储存这个地址的
地方,取出这个地址值然后对这个地址进行开锁(使用“*”这把钥匙)。如下图:
这就是为什么externchara''与externchara'100'等价的原因。因为这只是声明,不分配这就是为什么externchara''与externchara'100'等价的原因。因为这只是声明,不分配
空间,所以编译器无需知道这个数组有多少个元素。这两个声明都告诉编译器
a是在别的文
件中被定义的一个数组,
a同时代表着数组
a的首元素的首地址,也就是这块内存的起始地
址。数组内地任何元素的的地址都只需要知道这个地址就可以计算出来。
但是,当你声明为
externchar*a时,编译器理所当然的认为
a是一个指针变量,在
32位系
统下,占
4个
byte。这
4个
byte里保存了一个地址,这个地址上存的是字符类型数据。虽
然在文件
1中,编译器知道
a是一个数组,但是在文件
2中,编译器并不知道这点。大多数
编译器是按文件分别编译的,编译器只按照本文件中声明的类型来处理。所以,虽然
a实际
大小为
100个
byte,但是在文件
2中,编译器认为
a只占
4个
byte。
我们说过,编译器会把存在指针变量中的任何数据当作地址来处理。所以,如果需要
访问这些字符类型数据,我们必须先从指针变量
a中取出其保存的地址。如下图:
4。3。3。2,定义为指针,声明为数组
显然,按照上面的分析,我们把文件
1中定义的数组在文件
2中声明为指针会发生错误。
同样的,如果在文件
1中定义为指针,而在文件中声明为数组也会发生错误:
文件
1
char*p
=
“abcdefg”;
文件
2
externcharp'';
在文件
1中,编译器分配
4个
byte空间,并命名为
p。同时
p里保存了字符串常量“abcdefg”
的首字符的首地址。这个字符串常量本身保存在内存的静态区,其内容不可更改。在文件
2
中,编译器认为
p是一个数组,其大小为
4个
byte,数组内保存的是
char类型的数据。在
文件
2中使用
p的过程如下图:
p
0x0000FF00
p
0x000x000x000xFFpcharp'0'
p'1'p'2'p'3'
4char0x000x000xFF0x00p'i'p
4。3。4,指针和数组的对比
通过上面的分析,相信你已经知道数组与指针的的确确是两码事了。他们之间是不可
以混淆的,但是我们可以“以
XXXX的形式”访问数组的元素或指针指向的内容。以后一
定要确认你的代码在一个地方定义为指针,在别的地方也只能声明为指针;在一个的地方
定义为数组,在别的地方也只能声明为数组。切记不可混淆。下面再用一个表来总结一下
指针和数组的特性:
指针数组
保存数据的地址,任何存入指针变量
p的数
据都会被当作地址来处理。p本身的地址由
编译器另外存储,存储在哪里,我们并不知
保存数据,数组名
a代表的是数组首元素的
首地址而不是数组的首地址。&a才是整个数
组的首地址。a本身的地址由编译器另外存
道。储,存储在哪里,我们并不知道。
间接访问数据,首先取得指针变量
p的内容,
把它作为地址,然后从这个地址提取数据或
向这个地址写入数据。指针可以以指针的形
式访问*(p+i);也可以以下标的形式访问
p'i'。
但其本质都是先取
p的内容然后加上
i*sizeof(类型)个
byte作为数据的真正地址。
直接访问数据,数组名
a是整个数组的名字,
数组内每个元素并没有名字。只能通过“具
名+匿名”的方式来访问其某个元素,不能把
数组当一个整体来进行读写操作。数组可以
以指针的形式访问*(a+i);也可以以下标的形
式访问
a'i'。但其本质都是
a所代表的数组首
元素的首地址加上
i*sizeof(类型)个
byte作为
数据的真正地址。
通常用于动态数据结构通常用于存储固定数目且数据类型相同的元
素。
相关的函数为
malloc和
free。隐式分配和删除
通常指向匿名数据(当然也可指向具名数据)自身即为数组名
4。4,指针数组和数组指针
4。4。1,指针数组和数组指针的内存布局
初学者总是分不出指针数组与数组指针的区别。其实很好理解:
指针数组:首先它是一个数组,数组的元素都是指针,数组占多少个字节由数组本身
决定。它是“储存指针的数组”的简称。
数组指针:首先它是一个指针,它指向一个数组。在
32位系统下永远是占
4个字节,
至于它指向的数组占多少字节,不知道。它是“指向数组的指针”的简称。
下面到底哪个是数组指针,哪个是指针数组呢:
A),int
*p1'10';
B),int
(*p2)'10';
每次上课问这个问题,总有弄不清楚的。这里需要明白一个符号之间的优先级问题。
“''”的优先级比“
*”要高。
p1先与“
''”结合,构成一个数组的定义,数组名为
p1,int*
修饰的是数组的内容,即数组的每个元素。那现在我们清楚,这是一个数组,其包含
10个
指向
int类型数据的指针,即指针数组。至于
p2就更好理解了,在这里“()”的优先级比
“''”高,“*”号和
p2构成一个指针的定义,指针变量名为
p2,int修饰的是数组的内容,
即数组的每个元素。数组在这里并没有名字,是个匿名数组。那现在我们清楚
p2是一个指
针,它指向一个包含
10个
int类型数据的数组,即数组指针。我们可以借助下面的图加深
理解:
4。4。2,int
(*)'10'p2…也许应该这么定义数组指针
这里有个有意思的话题值得探讨一下:平时我们定义指针不都是在数据类型后面加上
指针变量名么?这个指针
p2的定义怎么不是按照这个语法来定义的呢?也许我们应该这样
来定义
p2:
int
(*)'10'
p2;
int
(*)'10'是指针类型,p2是指针变量。这样看起来的确不错,不过就是样子有些别
扭。其实数组指针的原型确实就是这样子的,只不过为了方便与好看把指针变量
p2前移了
而已。你私下完全可以这么理解这点。虽然编译器不这么想。^_^
4。4。3,再论
a和&a之间的区别
既然这样,那问题就来了。前面我们讲过
a和&a之间的区别,现在再来看看下面的代
码:
int
main()
{
chara'5'={'A';'B';'C';'D'};
char(*p3)'5'=&a;
char(*p4)'5'=
a;
return0;
}
上面对
p3和
p4的使用,哪个正确呢?p3+1的值会是什么?p4+1的值又会是什么?
毫无疑问,p3和
p4都是数组指针,指向的是整个数组。&a是整个数组的首地址,a
是数组首元素的首地址,其值相同但意义不同。在
C语言里,赋值符号“
=”号两边的数据
类型必须是相同的,如果不同需要显示或隐式的类型转换。
p3这个定义的“
=”号两边的数
据类型完全一致,而
p4这个定义的“=”号两边的数据类型就不一致了。左边的类型是指
向整个数组的指针,右边的数据类型是指向单个字符的指针。在
VisualC++6。0上给出如下
警告:warningC4047:
'initializing':
'char(*)'5''differsinlevelsof
indirectionfrom
'char*'。还好,
这里虽然给出了警告,但由于
&a和
a的值一样,而变量作为右值时编译器只是取变量的值,
所以运行并没有什么问题。不过我仍然警告你别这么用。
既然现在清楚了
p3和
p4都是指向整个数组的,那
p3+1和
p4+1的值就很好理解了。
但是如果修改一下代码,会有什么问题?p3+1和
p4+1的值又是多少呢?
int
main()
{
chara'5'={'A';'B';'C';'D'};
char(*p3)'3'=&a;
char(*p4)'3'=
a;
return0;
}
甚至还可以把代码再修改:
int
main()
{
chara'5'={'A';'B';'C';'D'};
char(*p3)'10'=
&a;
char(*p4)'10'=
a;
return0;
}
这个时候又会有什么样的问题?p3+1和
p4+1的值又是多少?
上述几个问题,希望读者能仔细考虑考虑。
4。4。4,地址的强制转换
先看下面这个例子:
structTest
{
int
Num;
char
*pcName;
short
sDate;
char
cha'2';
short
sBa'4';
}*p;
假设
p的值为
0x100000。如下表表达式的值分别为多少?
p