第六节 数组与指针
1.1 数组、数组元素、指针的大小
1. 如程序清单6. 1所示,程序输出什么?
程序清单6. 1 数组、数组元素、指针的大小
#include
int main(int argc, char *argv[])
{
int *p ;
printf( "sizeof(p) = %d \n" , sizeof(p) ) ;
printf( "sizeof(*p) = %d \n" , sizeof(*p) ) ;
int a[100];
printf( "sizeof(a) = %d \n" , sizeof(a) ) ;
printf( "sizeof(a[100]) = %d \n" , sizeof(a[100]) ) ;
printf( "sizeof(&a) = %d \n" , sizeof(&a) ) ;
printf( "sizeof(&a[0]) = %d \n" , sizeof(&a[0]) ) ;
return 0;
}
p是指针,任何数据类型的指针都是占4字节;
*p是一个指针指向的int数据,int型数据占用4字节;
a是数组,除了sizeof(a)和&a之外,当a出现在表达式中时,编译器会将a生成一个指向a[0]的指针,而这里测量的是整个数组的大小。
答案:
sizeof(p) = 4
sizeof(*p) = 4
sizeof(a) = 400
sizeof(a[100]) = 4
sizeof(&a) = 4
sizeof(&a[0]) = 4
请按任意键继续. . .
1.2 广东省的省政府和广州市的市政府都在广州
1. 如程序清单6. 2所示,如果ptr = 0x1000 0000 ,那么剩下三个输出是多少?
程序清单6. 2 数组首地址与数组首元素地址
int iArray[3] = { 1 , 2 , 3 } ;
int *ptr = iArray ;
printf("ptr = %#x\n" , ptr ) ;
printf("iArray = %#x\n" , iArray ) ;
printf("&iArray = %#x\n" , &iArray ) ;
printf("&iArray[0] = %#x\n" , &iArray[0] ) ;
iArray是数组名,由6.1节对a的说明,可iArray知同时也是数组首元素的地址,为0x1000 0000;&iArray是数组的首地址,这是毫无疑问的。&iArray[0]是数组首元素的地址,也是0x1000 0000。也就是说数组的首地址和数组首元素的地址是相等的。因为广东省的省政府和广东省的首号城市广州市的市政府都在广州,两者的地址是相等的。如图6. 1所示。
如程序清单6. 3所示,ptr = 0x1000 0000 ,那么这三个输出会是多少?
程序清单9. 3 数组首地址加1、数组首元素地址加1
int iArray[3] = { 1 , 2 , 3 } ;
int *ptr = iArray ;
printf("&iArray+1 = %#x\n" , &iArray+1 ) ;
printf("iArray+1 = %#x\n" , iArray+1 ) ;
printf("&iArray[0]+1 = %#x\n" , &iArray[0]+1 ) ;
答案是,第一个输出:0x1000 000C;第二个、第三个输出:0x1000 0004。
&iArray是数组的首地址,那么&iArray+1是偏移一个数组单元,因为站在全国的角度报全国各省政府的天气预报,报完广东省省政府之后就为湖南省省政府;如图6. 1所示。&iArray[0]是数组首元素的地址,&iArray[0]+1是偏移一个数组元素单元,好比站在广东的角度报广东各城市的天气预报,报完广东省首号城市广州的天气预报之后就是为广东省第二号城市的天气预报。
1.3 数组作为函数参数,是传什么进去了
1. 如程序清单6. 4所示,程序输出什么?
程序清单6. 4 数组作为函数参数
void text(char cArray[])
{
printf( "sizeof(cArray) = %d \n" , sizeof(cArray) ) ;
}
int main(int argc, char* argv[])
{
char cArray[] = "aBcDeF" ;
printf( "sizeof(cArray) = %d \n" , sizeof(cArray) ) ;
text(cArray) ;
return 0;
}
这里考查两个知识点,其一,sizeof和strlen();其二text(char cArray[])形参到底是什么?
答案是7,4。看到答案我想大家就应该明白上面两个问题了吧。到底是传值还是传址一定要搞明白哦。
1.4 指针相减
1. 如程序清单6. 5程序,输出会是什么?
程序清单6. 5 指针相减
#include
int main(int argc, char *argv[])
{
int a[2] = { 3 , 6 } ;
int *p = a ;
int *q = p + 1 ;
printf( "q - p = %d \n" , q-p ) ;
printf( "(int)q - (int)p = %d \n" , (int)q-(int)p ) ;
return 0;
}
用数学方法到可以做出q-p = 1这个答案,但是(int)q - (int)p 的答案。指针,指针的强大。由于指针加1,内存地址是加sizeof(int),但是int(q)和int(p)就不再是指针,而是一个整形数据。所以(int)q - (int)p = 4 。
1.5 指针加1到底是加什么
1. 如程序清单6. 6所示,请问p1+5=__;p2+5=__;
程序清单6. 6 指针加1
#include
int main(int argc, char *argv[])
{
unsigned char *p1 ;
unsigned long *p2 ;
p1 = (unsigned char *)0x801000 ;
p2 = (unsigned long *)0x810000 ;
printf( "p1+5 = %#x \n" , p1 + 5 ) ;
printf( "p2+5 = %#x \n" , p2 + 5 ) ;
return 0;
}
由于p + n = p + n * sizeof(p的数据类型) ,所以答案为:
p1+5 = 0x801005
p2+5 = 0x810014
请按任意键继续. . .
1.6 数组与指针的概念
1. 如程序清单6. 7所示,解释程序。
程序清单6. 7 数组与指针的概念
a) int a;
b) int *a;
c) int **a;
d) int a[10];
e) int *a[10];
f) int (*a)[10];
g) int (*a)(int);
h) int (*a[10])(int);
答案:
a) 一个整型数 ;
b) 一个指向整型数的指针;
c) 一个指向指针的指针,它指向的指针是指向一个整型数;
d) 一个有10个整型数的数组;
e) 一个有10个指针的数组,该指针是指向一个整型数的;
f) 一个指向有10个整型数数组的指针;
g) 一个指向函数的指针,该函数有一个整型参数并返回一个整型数;
h) 一个有10个指针的数组,该指针指向一个函数,该函数有一个整型参数并返回一个整型数。
这个题目很经典,很多公司的笔试题目都会截取上面部分出来考试。特别是e和f,哪一个是数组指针,哪一个又是指针数组;g和h哪一个是函数指针,哪一个又是指针函数。
1.7 数组与指针的糅合
1. 如程序清单6. 8所示,输出会是什么?
程序清单6. 8 数组与指针的糅合应用1
int arr[] ={6,7,8,9,10};
int *ptr =arr;
*(ptr++) +=123;
printf("%d,%d",*ptr,*(++ptr));
这个题目来源于华为的一道C语言笔试题,答案是:8,8。*ptr = arr ,这里ptr取得是数组arr[]的首元素地址,*(ptr++) +=123 ,这里是ptr++,此时*(ptr)即为:6,那么*(prt++)+123=129,执行完*(ptr++)+=123之后,*(ptr) = 7。跟*(++ptr)之后为8这个值是没有半点关系,由于执行了*(++ptr),所以此刻的*(ptr)为8,所以输出为:8,8。
2. 如程序清单6. 9所示,输出会是什么?
程序清单6. 9 数组与指针的糅合应用2
int main( int argc , char *argv[] )
{
int a[5] = { 1 , 2 , 3 , 4 , 5 };
int *ptr = (int *)( &a + 1 );
printf( "%d,%d" , *(a+1) , *(ptr-1) );
}
这个题目要求对指针的理解要比较透彻。由于*(a+1)和a[1]是等效的,则*(a+1)=a[1] = 2 。&a指的是指向整个数组变量的指针,&a+1不是首地址+1,系统会认为加了一个a数组的偏移量,即偏移了一个数组的大小,因此ptr指向的是a[5],即是*(ptr+5),既然如此,那么*(ptr-1)当然就是a[4] = 5咯。所以这个题目的答案是: 2 , 5 。
其实这个题目还有一个延伸,int *ptr = (int *)( (int) a + 1 ), *ptr是多少。答案是:2 00 00 00。
假设 &a[0] = 0x1000 0000,由于存储方式是小端模式,那么a[0] = 1和a[1] = 2的存储方式如图6. 2所示。
因为a = 0x1000 0000,而(int)a将这个地址强制转化为了int型数据,((int)a + 1) = 0x1000 0001,经过(int *)((int)a + 1)成了地址,ptr = (int *)((int)a + 1),由于ptr是指向int型的指针,*ptr占4个字节,*ptr所占字节即为:0x00,0x00,0x00,0x02,那么*ptr即为0x02000000。
3. 如程序清单6. 10所示,如果ptr1为0x1000 0000,那么三个输出分别为多少?
程序清单9. 10 数组与指针的糅合应用3
int iArray[7] = { 1 , 2 , 3 , 4 , 5 , 6 , 7 } ;
int *ptr1 = iArray ;
int *ptr2 = &iArray[5] ;
printf( " ptr2 = %#x \n" , ptr2 ) ;
printf( " ptr1 = %#x \n" , ptr1 ) ;
printf( "ptr2 - ptr1 = %d \n" , ptr2 - ptr1 ) ;
很明显iArray是整型数据类型数组,那么ptr2 = ptr1 + 5*sizeof(int) = 0x1000 0014。很多同学立马就会脱口而出ptr2 – ptr1 = 20嘛!真的是这样吗?其实答案是:5!
解释之前,我们先来看这个程序:
int iArray[7] = { 1 , 2 , 3 , 4 , 5 , 6 , 7 } ;
char *p1 = (char *) iArray ;
char *p2 = (char *) &iArray[5] ;
printf( "p2 - p1 = %d \n" , p2 - p1 ) ;
这个程序的输出是:20。因为指针类型是char*,char是1个字节;而上面*ptr1和*ptr2是int*,所以答案是:5。
如果是:
short *p1 = (short *) iArray ;
short *p2 = (short *) &iArray[5] ;
则p2 – p1 就是为:10。
这里还有一个延伸:
int iArray[7] = { 1 , 2 , 3 , 4 , 5 , 6 , 7 } ;
int *ptr1 = iArray ;
int *ptr2 = &iArray[5] ;
printf( " ptr2 = %#x \n" , ptr2 ) ;
printf( " ptr1 = %#x \n" , ptr1 ) ;
printf( "ptr2 - ptr1 = %d \n" , (int)ptr2 – (int)ptr1 ) ;
这样输出答案是多少呢?20!
1.8 指针数组
1. 阅读程序,输出什么?
char *cArray[3] = { "abcdef" , "123456" , "jxlgdx" } ;
printf( "sizeof(cArray[0]) = %d \n" , sizeof(cArray[0]) );
我相信有较多的人的答案是:6或者7。原因是忽略了*cArray[3]这是一个指针数组,也就是说cArray[3]数组中存放的是指针,而不是字符串常量。在C语言笔试陷阱与难点第一阶段讲过,只要是指针变量,其大小就是:4。所以这里毋庸置疑,输出应该是:4。
你要是存在怀疑,可以输出cArray[3]数组的各个元素看看是不是指针。
printf( "cArray[0] = %#x \n" , cArray[0] ) ;
printf( "cArray[1] = %#x \n" , cArray[1] ) ;
printf( "cArray[2] = %#x \n" , cArray[2] ) ;
运行程序输出为:
sizeof(cArray[0]) = 4
cArray[0] = 0x415840
cArray[1] = 0x415770
cArray[2] = 0x415768
请按任意键继续. . .
读者亦可输出指针所指向的字符串:
printf( "cArray[0] = %s \n" , cArray[0] ) ;
printf( "cArray[1] = %s \n" , cArray[1] ) ;
printf( "cArray[2] = %s \n" , cArray[2] ) ;
运行输出为:
cArray[0] = abcdef
cArray[1] = 123456
cArray[2] = jxlgdx
请按任意键继续. . .
2. 阅读下列程序,输出什么?
typedef int (init_fnc_t) (void);
extern int arch_cpu_init(void);
extern int board_early_init_f(void);
init_fnc_t *init_sequence[] = {
arch_cpu_init,
board_early_init_f,
NULL,
};
int arch_cpu_init(void)
{
printf("This is arch_cpu_init \n");
return 0;
}
int board_early_init_f(void)
{
printf("This is board_early_init_f \n");
return 0;
}
void hang (void)
{
printf("Error! \n");
while (1) ;
}
int main(int argc, char* argv[])
{
init_fnc_t **init_fnc_ptr;
for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr)
{
if ( (*init_fnc_ptr)() != 0 )
{
hang ();
}
}
return 0;
}
这个题目是我在阅读u-boot-2012.10源码的时候稍作修改从中提取出来的。这个题目将指针数组、函数指针等知识点融为一体。
This is arch_cpu_init
This is board_early_init_f
请按任意键继续. . .
1.9 数组指针
1. 如程序清单6. 11所示,程序输出什么?
程序清单6. 11 数组指针
#include
int main(int argc, char *argv[])
{
int a[][4]={ 1,3,5,7,9,11,13,15,17,19,21,23};
int (*p)[4] , i=2 , j=1 ;
p=a;
printf( "%d\n", *(*(p+i)+j));
return 0;
}
答案是:19。
不能理解?好吧,如果我告诉你**(p+1) = 9, *((*p)+1) = 3!到这里能理解了吗?如果还是不能理解,ok,p是指向一个含有4个整型数据的数组,那么p如果加1,地址是不是得偏移4个整形数据的地址,而p等于数组a的首元素地址,a是二维数组,也就意味着p是双重指针了。
1.10 再论数组指针与数组首地址
1. 如程序清单6. 12所示,已知&a[0][0] = 0x22fe70,想想会是输出什么?
程序清单6. 12 数组指针与数组首地址
int main(int argc, char *argv[])
{
int a[8][8] = {1,2,3,4};
int (*ptr1)[8] = a ;
int (*ptr2)[8][8] = &a;
int *ptr3 = &a[0][0];
printf(" ptr1 = %#x\n" , ptr1);
printf(" &a[0] = %#x\n" , &a[0]);
printf(" ptr1+1 = %#x\n" , ptr1+1);
printf(" &a[0]+1 = %#x\n\n" , &a[0]+1);
printf(" ptr2 = %#x\n" , ptr2);
printf(" &a = %#x\n" , &a);
printf(" ptr2+1 = %#x\n" , ptr2+1);
printf(" &a+1 = %#x\n\n" , &a+1);
printf(" ptr3 = %#x\n" , ptr3);
printf(" &a[0][0] = %#x\n" , &a[0][0]);
printf(" ptr3+1 = %#x\n" , ptr3+1);
printf(" &a[0][0]+1 = %#x\n\n" , &a[0][0]+1);
return 0;
}
这个题目涉及到两个知识点,其一,讲烂了的数组首元素地址和数组的首地址;其二,数组指针和指针数组的区别。
先看第三个指针,int *ptr3 = &a[0][0];这个毫无疑问,是将数组a的首元素地址赋给指针ptr3,由于是int型数组,那么ptr3+1则是偏移一个int型大小,即偏移4个字节,那么ptr3这一组的输出即为:
ptr3 = 0x22fe70
&a[0][0] = 0x22fe70
ptr3+1 = 0x22fe74
&a[0][0]+1 = 0x22fe74
我们再看第二个指针,int (*ptr2)[8][8] = &a;根据之前我们讲过的,这个是取数组a的首地址,ptr2的解释就是:一个指向二维数组[8][8]的指针,那么ptr2+1则是偏移了一个二维数组[8][8]的地址,即为4*8*8=256(0x100)个字节的偏移。那么ptr2这一组的输出即为:
ptr2 = 0x22fe70
&a = 0x22fe70
ptr2+1 = 0x22ff70
&a+1 = 0x22ff70
剩下第一个指针,这个和6.9节差不多,int (*ptr1)[8] = a ;其实它是等价于int (*ptr1)[8] = &a[8] ;那么ptr1则是一个指向一维数组[8]的指针,如果我们这么理解a[8][8] = {a1[8],a2[8],…a8[8]}(当然这个理解是错误的),那么ptr1就是指向a1[8],那么当ptr1+1就是指向a2[8],也就是偏移了一个含有8个int型数据的数组,即4*8=32(0x20)个字节。那么ptr1这一组的输出即为:
ptr1 = 0x22fe70
&a[0] = 0x22fe70
ptr1+1 = 0x22fe90
&a[0]+1 = 0x22fe90
这里再一次重复讲一下数组指针和指针数组。
int (*p)[8] p是指向一个含有8个整型数据的数组的指针(数组指针)
int *p[8] p是一个含有8个指针型变量的数组(指针数组)