《[免费下载 c语言深度解剖[1]》

下载本书

添加书签

[免费下载 c语言深度解剖[1]- 第19节


按键盘上方向键 ← 或 → 可快速上下翻页,按键盘上的 Enter 键可回到本书目录页,按键盘上方向键 ↑ 可回到本页顶部!
决定。它是“储存指针的数组”的简称。

数组指针:首先它是一个指针,它指向一个数组。在 
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 
+0x1= 
0x___? 
(unsignedlong)p 
+0x1 
=0x___? 
(unsignedint*)p+0x1 
= 
0x___?

我相信会有很多人一开始没看明白这个问题是什么意思。其实我们再仔细看看,这个知识点
似曾相识。一个指针变量与一个整数相加减,到底该怎么解析呢?

还记得前面我们的表达式“ 
a+1”与“ 
&a+1”之间的区别吗?其实这里也一样。指针变
量与一个整数相加减并不是用指针变量里的地址直接加减这个整数。这个整数的单位不是 
byte而是元素的个数。所以: 


p+0x1的值为 
0x100000+sizof(Test)*0x1。至于此结构体的大小为 
20byte,前面的章
节已经详细讲解过。所以 
p+0x1的值为:0x100014。 


(unsignedlong)p+0x1的值呢?这里涉及到强制转换,将指针变量 
p保存的值强制转换
成无符号的长整型数。任何数值一旦被强制转换,其类型就改变了。所以这个表达式其实就
是一个无符号的长整型数加上另一个整数。所以其值为:0x100001。 


(unsignedint*)p+0x1的值呢?这里的 
p被强制转换成一个指向无符号整型的指针。所

以其值为:0x100000+sizof(unsigned 
int)*0x1,等于 
0x100004。
上面这个问题似乎还没啥技术含量,下面就来个有技术含量的:
在 
x86系统下,其值为多少? 
intmain() 
{ 


inta'4'={1;2;3;4}; 
int*ptr1=(int*)(&a+1); 
int*ptr2=(int*)((int)a+1); 


printf(〃%x;%x〃;ptr1'…1';*ptr2); 


return0; 



这是我讲课时一个学生问我的题,他在网上看到的,据说难倒了 
n个人。我看题之后告诉他,
这些人肯定不懂汇编,一个懂汇编的人,这种题实在是小 
case。下面就来分析分析这个问题:

根据上面的讲解,&a+1与 
a+1的区别已经清楚。 


ptr1:将&a+1的值强制转换成 
int*类型,赋值给 
int*类型的变量 
ptr,ptr1肯定指到数
组 
a的下一个 
int类型数据了。 
ptr1'…1'被解析成 
*(ptr1…1),即 
ptr1往后退 
4个 
byte。所以其
值为 
0x4。 


ptr2:按照上面的讲解, 
(int)a+1的值是元素 
a'0'的第二个字节的地址。然后把这个地址
强制转换成 
int*类型的值赋给 
ptr2,也就是说 
*ptr2的值应该为元素 
a'0'的第二个字节开始的
连续 
4个 
byte的内容。

其内存布局如下图:


好,问题就来了,这连续 
4个 
byte里到底存了什么东西呢?也就是说元素 
a'0';a'1'里面
的值到底怎么存储的。这就涉及到系统的大小端模式了,如果懂汇编的话,这根本就不是问
题。既然不知道当前系统是什么模式,那就得想办法测试。大小端模式与测试的方法在第一
章讲解 
union关键字时已经详细讨论过了,请翻到彼处参看,这里就不再详述。我们可以用
下面这个函数来测试当前系统的模式。 


intcheckSystem( 
) 


{ 


unioncheck 


{ 


int 
i; 


charch; 


}c; 


c。i 
=1; 
return(c。ch 
1); 

如果当前系统为大端模式这个函数返回 
0;如果为小端模式,函数返回 
1。
也就是说如果此函数的返回值为 
1的话,*ptr2的值为 
0x2000000。
如果此函数的返回值为 
0的话,*ptr2的值为 
0x100。


4。5,多维数组与多级指针
多维数组与多级指针也是初学者感觉迷糊的一个地方。超过二维的数组和超过二级的
指针其实并不多用。如果能弄明白二维数组与二级指针,那二维以上的也不是什么问题了。
所以本节重点讨论二维数组与二级指针。


4。5。1,二维数组
4。5。1。1,假想中的二维数组布局
我们前面讨论过,数组里面可以存任何数据,除了函数。下面就详细讨论讨论数组里
面存数组的情况。 
Excel表,我相信大家都见过。我们平时就可以把二维数组假想成一个 
excel
表,比如: 


chara'3''4';


4。5。1。2,内存与尺子的对比
实际上内存不是表状的,而是线性的。见过尺子吧?尺子和我们的内存非常相似。一
般尺子上最小刻度为毫米,而内存的最小单位为 
1个 
byte。平时我们说 
32毫米,是指以零
开始偏移 
32毫米;平时我们说内存地址为 
0x0000FF00也是指从内存零地址开始偏移 
0x0000FF00个 
byte。既然内存是线性的,那二维数组在内存里面肯定也是线性存储的。实
际上其内存布局如下图:

以数组下标的方式来访问其中的某个元素:a'i''j'。编译器总是将二维数组看成是一个
一维数组,而一维数组的每一个元素又都是一个数组。a'3'这个一维数组的三个元素分别为:
a'0';a'1';a'2'。每个元素的大小为 
sizeof(a'0');即 
sizof(char)*4。由此可以计算出 
a'0';a'1';a'2'
三个元素的首地址分别为&a'0',& 
a'0'+1*sizof(char)*4,& 
a'0'+2*sizof(char)*4。亦即 
a'i'
的首地址为& 
a'0'+i*sizof(char)*4。这时候再考虑 
a'i'里面的内容。就本例而言,a'i'内有 
4
个 
char类型的元素,其每个元素的首地址分别为&a'i',&a'i'+1*sizof(char), 
&a'i'+2*sizof(char),&a'i'+3*sizof(char),即 
a'i''j'的首地址为&a'i'+j*sizof(char)。再把 
&a'i' 



的值用 
a表示,得到 
a'i''j'元素的首地址为:a+i*sizof(char)*4+j*sizof(char)。同样,可以换
算成以指针的形式表示:*(*(a+i)+j)。

经过上面的讲解,相信你已经掌握了二维数组在内存里面的布局了。下面就看一个题: 


#include 


intmain(intargc;char* 
argv'') 


{ 


inta 
'3''2'={(0;1);(2;3);(4;5)}; 


int*p; 


p=a'0'; 


printf(〃%d〃;p'0'); 




问打印出来的结果是多少?

很多人都觉得这太简单了,很快就能把答案告诉我:0。不过很可惜,错了。答案应该
是 
1。如果你也认为是 
0,那你实在应该好好看看这个题。花括号里面嵌套的是小括号,而
不是花括号!这里是花括号里面嵌套了逗号表达式!其实这个赋值就相当于 
inta 
'3''2'={ 
1;3; 
5};

所以,在初始化二维数组的时候一定要注意,别不小心把应该用的花括号写成小括号
了。

4。5。1。3,&p'4''2' 
…&a'4''2'的值为多少?
上面的问题似乎还比较好理解,下面再看一个例子: 


int 
a'5''5'; 


int 
(*p)'4'; 


p=a;

问&p'4''2'…&a'4''2'的值为多少?
这个问题似乎非常简单,但是几乎没有人答对了。我们可以先写代码测试一下其值,然后分
析一下到底是为什么。在 
VisualC++6。0里,测试代码如下: 


intmain() 


{ 


inta'5''5'; 


int(*p
小提示:按 回车 [Enter] 键 返回书目,按 ← 键 返回上一页, 按 → 键 进入下一页。 赞一下 添加书签加入书架