8.7 指向数组的指针变量
什么是指向数组的指针呢?在学习一维数组时,已经有所接触。当指针变量保存的数据为数组的首地址,且将此地址解释为数组时,此指针变量被称为数组指针。指向数组元素的指针很简单,只要是指针变量,都可以用于寻址该类型的一维数组中各元素,得到数组中的数据。而指向一维数组的数组指针会有些变化,指向一维数组的数组指针的定义格式如下:
组成部分1 组成部分2 组成部分3
类型名(*指针变量名称)[一维数组大小];
例如,对于二维字符数组“char cArray[3][10]={{"Hello"},{"World"},{"!\r\n"}};”,定义指向这个数组的指针为“char(*pArray)[10]=cArray;”,那么数组指针如何访问数组成员呢?见代码清单8-14。
代码清单8-14 数组指针寻址—Debug版
//C++源码说明:利用数组指针访问二维数组成员
void main(){
char cArray[3][10]={"Hello","World","!\r\n"};
char(*pArray)[10]=cArray;
for(int i=0;i<3;i++){
printf(*pArray);//依次显示二维数组中各一维数组中的字符串信息
pArray++;
}
}
//C++源码与对应汇编代码讲解
void main(){
char cArray[3][10]={"Hello","World","!\r\n"};
;二维字符数组初始化略
char(*pArray)[10]=cArray;
00401119 lea ecx,[ebp-2Ch];取数组首地址存入ecx中
;将数组首地址复制到指针变量pArray
0040111C mov dword ptr[ebp-30h],ecx
for(int i=0;i<3;i++){
printf(*pArray);
;取出指针pArray保存数据到eax中
00401137 mov eax, dword ptr[ebp-30h]
0040113A push eax
0040113B call printf(00401160)
00401140 add esp,4
pArray++;
;取出指针pArray保存数据到ecx中
00401143 mov ecx, dword ptr[ebp-30h]
00401146 add ecx,0Ah;对ecx执行加等于10操作
;重新赋值指针pArray为ecx中数据
00401149 mov dword ptr[ebp-30h],ecx
}
}
代码清单8-14中的数组指针pArray保存了二维字符数组cArray首地址,当对pArray执行加等于1操作后,指针pArray中保存的地址值增加了10字节长。这个数值是如何计算出来的呢?根据指针加法公式:
指针变量+=数值指针变量地址数据+=(sizeof(指针类型)*数值)
代码清单8-14中的数组指针pArray类型为char[10],求得其大小为10字节。对pArray加1操作,实质是对pArray中保存的地址加10。加1后偏移到地址为二维字符数组cArray中的第二个一维数组首地址,即&(cArray[1])。
对指向二维数组的数组指针执行取内容操作后,得到的还是一个地址值,再次执行取内容操作才能寻址到二维字符数组中的单个字符数据。看上去与二级指针相似,实际上并不一样。二级指针的类型为指针类型,其偏移长度在32位下固定为4字节,而数组指针的类型为数组,其偏移长度随数组而定,两者的偏移计算不同,不可混为一谈。
二级指针可用于保存一维指针数组。如对于一维指针数组char*p[3],可用char**pp来保存其数组首地址。通过对二级指针pp使用3次寻址即可得到数据。第3章已经接触过数组指针。在利用VC++6.0生成的控制台工程中,main函数的定义(main(int argc, char*argv[],char*envp[]))中有3个参数:
1)argc:命令行参数个数,整型。
2)argv:命令行信息,保存字符串数组首地址的指针变量,是一个指向数组的指针。
3)envp:环境变量信息,和argv类型相同。
参数argv与envp就是两个指针数组。当数组作为参数时,实际上以指针方式进行数据传递。这里两个参数可转换为char**二级指针类型,修改为:main(int argc, char**argv, char**envp)。
通过编译器工程选项传入3个命令行参数,查看数组指针argv的寻址过程。命令行参数的传入方式有多种,本节通过修改编译器工程选项,加入3个命令行参数:“Hello”、“World”、“!”。(命令行设置:单击菜单选项Project→Settings,设置如图8-10所示。)
图 8-10 设置命令行参数
在默认情况下,使用VC编译器生成的控制台会有一个命令行参数,这个参数信息为当前程序所在路径,因此在命令行字符串数组argv的第0项中保存着路径字符串首地址。通过如图8-10所示的命令行设置后,数组argv的第1项为字符串“Hello”的首地址。通过编写代码,显示命令行中的信息,并分析指针数组如何寻址。见代码清单8-15。
代码清单8-15 通过指针数组获取命令行参数—Debug版
//C++源码说明:指针数组寻址访问命令行参数
void main(int argc, char**argv, char**envp){
for(int i=1;i<argc;i++){//跳过第一个命令行参数
printf(argv[i]);//获取命令行参数信息
}
}
//C++源码与对应汇编代码讲解
void main(int argc, char**argv, char**envp){
for(int i=1;i<argc;i++){
;for循环讲解略
printf(argv[i]);
00401112 mov edx, dword ptr[ebp-4];取下标值i并将其保存到edx中
;对指针变量取内容,得到数组首地址
00401115 mov eax, dword ptr[ebp+0Ch]
;一维数组寻址,将得到的数组数据保存到ecx中
00401118 mov ecx, dword ptr[eax+edx*4]
0040111B push ecx;压入ecx作为参数
0040111C call printf(00401160);调用printf函数
00401121 add esp,4
}
}
代码清单8-15中的字符串指针数组寻址过程和一维数组相同,都是取下标、取数组首地址。利用相对比例因子寻址方式,访问内存得到数据。需要注意的是,代码清单8-15中的argv是一个参数,它保存着字符串数组的首地址,因此需要使用“mov eax, dword ptr[ebp+0Ch]”指令对其取内容,得到数组首地址。
在使用数组指针的过程中,经常在定义数组指针中出现类型匹配错误。有没有什么方法可以根据多维数组的类型,快速匹配出对应的数组指针类型呢?可以通过指定数组下标达到这一目标,如三维数组int nArray[2][3][4];,其数组指针的定义如下:
int(*pnArray)[3][4]=nArray;
三维数组指针变量名称为*pnArray,替换掉原三维数组中的数组名称及三维下标nArray[2]。数组转换数组指针的规则总结如下:
数组 数组指针
类型 数组名称[最高维数][X][Y]……类型(*数组指针名称)[X][Y]……
在定义数组指针时,为什么只有最高维数可以省去?先来看看普通的指针变量寻址过程:
假设:整型指针变量*p中保存的地址为0x0012FF00,对其执行加等于1操作
p+=1;
p=0x0012FF00+sizeof(int);
p=0x0012FF04
指针在地址偏移过程中需要计算出偏移量,因此需要所指向的数据类型来配合计算偏移长度。在多维数组中,可以将最高维看做是一维数组,其后数据为这个一维数组中各元素的数据类型。例如:int nArray[3][4][5]同int[4][5]nArray[3]一样,可将int[4][5]看做是一个整体的数据类型,记作int[4][5]*p=nArray;。由于C++语法中没有此种语法格式,故无法使用,正确的语法格式为:int(*p)[4][5]=nArray;,括号的使用是为了与指针数组进行区分。
虽然指针与数组间的关系千变万化,错综复杂,但只要掌握了它们的寻址过程,就可通过偏移量获得其类型以及它们之间的关系。