在此,我们将讲述C语言的精髓——指针的八道经典题目,这里我们主要以画图的方式解答。
提前说明:二维数组我们可以想象成由几个简单的一维数组组成。
题目一:
int main()
{
int a[5] = { 1, 2, 3, 4, 5 };
int *ptr = (int *)(&a + 1);
printf("%d,%d", *(a + 1), *(ptr - 1));
//2,5
system("pause");
return 0;
}
题目分析:数组名a表示数组首元素的地址,(&a+1)表示将整个数组的地址加一,(int*)(&a+1)表示将(&a+1)强制类型转换为int*类型。
所以本题的结果为:
题目二:
struct Test
{
int mun;
char *pcNmae;
short sDate;
char cha[2];
short sBba[4];
}*p;
//假设p的值为0x100000
int main()
{
p = (struct Test *)0x100000;
printf("%p\n", p + 0x1);
printf("%p\n", (unsigned long)p + 0x1);
printf("%p\n", (unsigned int *)p + 0x1);
printf("%p\n", (unsigned char*)p + 0x1);
printf("%p\n", (unsigned char**)p + 0x1);
system("pause");
return 0;
}
题目分析:首先结构体为20个字节;
printf("%p\n", p + 0x1);//0x00 10 00 14
//p是(struct Test *)类型,是指针,该结构体大小为20个字节
//p:0x00 10 00 00
//0x1:在十进制中为1
//p+0x1=p+1=p+20个字节=0x00 10 00 14
printf("%p\n", (unsigned long)p + 0x1);//0x00 10 00 01
//p是(unsigned long)类型
//p:0x00 10 00 00
//0x1:0x00 00 00 01
//p+0x1=0x00 10 00 01
printf("%p\n", (unsigned int *)p + 0x1);//0x00 10 00 04
//p是(unsigned int *)类型,是指针,32位平台下是4个字节,指向int类型的数据
//p:0x00 10 00 00
//0x1:在十进制中为1
//p+0x1=p+1=p+4个字节=0x00 10 00 04
printf("%p\n", (unsigned char*)p + 0x1);//0x00 10 00 01
//p是(unsigned char*)类型,是指针,32位平台下是4个字节,指向char类型的数据
//p:0x00 10 00 00
//0x1:在十进制中为1
//p+0x1=p+1=p+1个字节=0x00 10 00 01
printf("%p\n", (unsigned char**)p + 0x1);//0x00 10 00 04
//p是(unsigned char**)类型,是指针,32位平台下是4个字节,指向char*类型的数据
//p:0x00 10 00 00
//0x1:在十进制中为1
//p+0x1=p+1=p+4个字节=0x00 10 00 04
所以,题目的运行结果为:
题目三:
int main()
{
int a[4] = { 1, 2, 3, 4 };
int *ptr1 = (int *)(&a + 1);
int *ptr2 = (int *)((int)a + 1);
printf("%x %x\n", ptr1[-1], *ptr2);
system("pause");
return 0;
题目分析:首先我们先介绍一下什么是大小端存储,大端:是指数据的高字节保存在内存的低地址中,而数据的低字节保存在内存的高地址中;小端:是指数据的高字节保存在内存的高地址中,而数据的低字节保存在内存的低地址中。而数组是在栈中存储的。题目中(int)a是把地址强制类型转换成数值,则+1加1个字节;(int *)((int)a+1)则把上一步转换得到数值强制类型转换成(int *)类型。小段存储,大端打印。
数组的存储方式:
题目中的指针指向:
数据的小端存储:
所以,题目的结果为:
题目四:
int main()
{
int a[3][2] = { (0, 1 ), ( 2, 3 ), (4, 5 ) };
int *p;
p = a[0];
printf("%d\n", p[0]);
system("pause");
return 0;
}
题目分析:本题二维数组中蕴含着逗号表达式,在逗号表达式中,取每一个表达式中最后一个数据。
所以,指针P的指向以及数组a的存储我们可以想象成是这样的:
题目中的p[0]=*(p+0),所以本题的结果为:
题目五:
int main()
{
int a[5][5];
int(*p)[4];
p = (int(*)[4])a;
printf("%p,%d\n", &p[4][2] - &a[4][2],&p[4][2] - &a[4][2]);
system("pause");
return 0;
}
题目分析:在做本题之前首先需要搞清楚p[4][2]以及a[4][2]分别代表什么,题目中二维数组a为5行5列,而数组指针p指向一个为5行4列的二维数组,p[4][2]=*(*(p+4)+2),a[4][2]=*(*(a+4))。我们在开篇已经讲过,二维数组我们可以想象成由几个简单的一维数组组成,所以数组指针p指向的地址就是5行4列数组的第一行的地址,所以p+4加16个字节。如图所示:
由图可知:p[4][2]与a[4][2]之间差了4个字节
所以&p[4][2] - &a[4][2]以%d形式打印-4
而-4的二进制表示为:
原码:1000 0000 0000 0000 0000 0000 0000 0100
反码:1111 1111 1111 1111 1111 1111 1111 1011
在内存中是以补码存储:
补码:1111 1111 1111 1111 1111 1111 1111 1100
而地址是以16进制打印:
16进制:FF FF FF FC
所以&p[4][2] - &a[4][2]以%p形式打印FF FF FF FC
所以本题的结果为:
题目六:
int main()
{
int a[2][5] = { 1, 2, 3, 4, 5, 6, 7, 8, 9,10 };
int *ptr1 = (int *)(&a + 1);
int *ptr2 = (int *)(*(a + 1));
printf("%d %d\n", *(ptr1 - 1), *(ptr2 - 1));
system("pause");
return 0;
}
题目分析:首先,需要明确&(a+1)表示整个数组+1,(int*)(*(a+1))表示将*(a+1)强制类型转换成为(int*)类型,其次在二维数组中数组名可以理解为第0行的地址,所以a+1表示第一行的地址。画图解题即可,如图所示:
所以,本题的结果为:
题目七:
int main()
{
char *a[] = { "work", "at", "alibaba" };
char **pa = a;
pa++;
printf("%s\n", *pa);
system("pause");
return 0;
}
题目分析:本题难度不大,只需找到指针pa初始指向的位置以及指针变量pa++以后所指向的字符串的首地址即可,如图所示:
所以,本题的结果为:
题目八:
int main()
{
char *c[] = { "ENTER", "NEW", "POINT", "FIRST" };
char **cp[] = { c + 3, c + 2, c + 1, c };
char ***cpp = cp;
printf("%s\n", **++cpp);
printf("%s\n", *--*++cpp+3);
printf("%s\n", *cpp[-2]+3);
printf("%s\n", cpp[-1][-1]+1);
system("pause");
return 0;
}
题目分析:在做题目之前,首先需要明确题目中出现的操作符的优先级以及结合性,题目中出现的操作符优先级以及结合性从高到低依次为下标引用[ ]、从左向右;前置++、从右向左;前置--、从右向左;解引用*、从右向左;加法+、从左向右。还要注意前置++与前置--运算后,cpp将会被改变,如果上一个语句中使用前置++或前置--运算后,下一条语句将使用改变后的cpp。
初始时指针数组c、cp以及指针cpp所指向的位置如图所示:
printf("%s\n", **++cpp):图示:
所以*(*++cpp)后得到POINT。
printf("%s\n", *--*++cpp+3):图示:
所以*--*++cpp运算后得到ENTER,+3得到ER。
printf("%s\n", *cpp[-2]+3):*cpp[-2]+3=*(*(p-2)+3)图示:
所以*cpp[-2]运算完后得到FIRST,+3得到ST。
printf("%s\n", cpp[-1][-1]+1):cpp[-1][-1]+1=*(*(cpp-1)-1)+1。图示:
所以cpp[-1][-1]运算完以后得到NEW,+1得到EW。
所以本题的运行结果为:
总结:这8道题考查了二维数组的访问、数组的存储、大端模式、小端模式、数组名与&数组名+1所代表的含义、操作符结束运算后变量是否发生改变以及指针的运算,画图更加便于理解数组与指针相结合的题目,希望大家可以加深理解。