前面我们学习了指针相关知识,本篇博客就带着大家一起梳理之前学过的知识,强化对指针的了解,本文主以指针相关面试题为主。
目录
1.一维数组
观察下列代码,揣测结果。
//一维数组
int a[] = {1,2,3,4};
printf("%d\n",sizeof(a));
printf("%d\n",sizeof(a+0));
printf("%d\n",sizeof(*a));
printf("%d\n",sizeof(a+1));
printf("%d\n",sizeof(a[1]));
printf("%d\n",sizeof(&a));
printf("%d\n",sizeof(*&a));
printf("%d\n",sizeof(&a+1));
printf("%d\n",sizeof(&a[0]));
printf("%d\n",sizeof(&a[0]+1));
//一维数组
int a[] = {1,2,3,4};
printf("%d\n",sizeof(a));
当sizeof中单独放数组名时,数组名代表整个数组,数组里有4个元素且都为整型,故输出16。
printf("%d\n",sizeof(a+0));
sizeof中并没有单独存放数组名,故这里数组名代表首元素地址,加0依旧是地址,地址的大小为4字节(32位机器)或者8字节(64位机器)。a -- int*
printf("%d\n",sizeof(*a));
这里a也没单独存在sizeof中,故为首元素地址,解引用后为整型,故为4个字节。
printf("%d\n",sizeof(a+1));
这里a并未单独存在于sizeof中,故a代表首元素地址,a+1后指向数组下一个整型,依然是地址,故输出 4/8
printf("%d\n",sizeof(a[1]));
这里访问的是数组中第二个元素,是整型,故输出4
printf("%d\n",sizeof(&a));
&a是取出整个数组的地址,地址的大小为4/8个字节,其中&a -- int(*)[4]
printf("%d\n",sizeof(*&a));
&a取出的是整个数组的地址,接着对数组的地址解引用,访问的是整个数组,故这里输出16,&a ---- int (*)[4] *&a ---- int [4]
printf("%d\n",sizeof(&a+1));
&a是取出整个数组的地址,对其加1则是跳过整个数组,也就是16字节,但其本身还是地址,地址的大小就为 4/8。
printf("%d\n",sizeof(&a[0]));
此处是取出数组中第一个元素的地址,故为4/8 字节。
printf("%d\n",sizeof(&a[0]+1));
此处是取出数组第一个元素的地址并对其加1,得到的是第二个元素的地址,地址的大小还是4/8。
//字符数组
char arr[] = { 'a','b','c','d','e','f' };
printf("%d\n", sizeof(arr));
printf("%d\n", sizeof(arr + 0));
printf("%d\n", sizeof(*arr));
printf("%d\n", sizeof(arr[1]));
printf("%d\n", sizeof(&arr));
printf("%d\n", sizeof(&arr + 1));
printf("%d\n", sizeof(&arr[0] + 1));
printf("%d\n", strlen(arr));
printf("%d\n", strlen(arr + 0));
printf("%d\n", strlen(*arr));
printf("%d\n", strlen(arr[1]));
printf("%d\n", strlen(&arr));
printf("%d\n", strlen(&arr + 1));
printf("%d\n", strlen(&arr[0] + 1));
//字符数组
char arr[] = { 'a','b','c','d','e','f' };
printf("%d\n", sizeof(arr));数组名在sizeof中单独出现,表示整个数组,故输出为6
printf("%d\n", sizeof(arr + 0));数组名没有在sizeof中单独出现代表首元素地址,加0还是首元素地址,地址的大小为4/8,故输出4/8
printf("%d\n", sizeof(*arr));数组名在sizeof中没有单独出现代表数组首元素地址,对其解引用得到一个字符,字符的大小为1,故输出1
printf("%d\n", sizeof(arr[1]));直接获取数组第二个元素,输出1
printf("%d\n", sizeof(&arr));&数组名表示取整个数组的地址,数组的地址也是地址,故输出4/8
printf("%d\n", sizeof(&arr + 1));&数组名获取整个数组的地址,再加1即跳过一个数组,还是一个地址,故输出4/8
printf("%d\n", sizeof(&arr[0] + 1));取第一个数组中第一个元素加1,跳过一个元素,取下个元素地址,故输出4/8
printf("%d\n", strlen(arr));数组名代表首元素地址,strlen函数传过去首元素地址,由于strlen只有碰到\0会停止,并返回\0前的字符个数,由于题中数组并没有\0,故会越界直至碰到\0,所以最终生成随机值。
printf("%d\n", strlen(arr + 0));数组名代表首元素地址,加0同样代表首元素地址,传给strlen后依然如上,输出随机值。
printf("%d\n", strlen(*arr));数组名代表首元素地址,对其解引用,得到数组首元素a。将a的ASCII当作地址传给strlen函数,系统不允许访问这块内存,所以该代码会报错。
printf("%d\n", strlen(arr[1]));获取第二个元素b,并将其ASCII当作地址传给strlen依然会报错。
printf("%d\n", strlen(&arr));&数组名取得是整个数组的地址,然后传给strlen后,由于数组中没有\0,故最终产生随机值。
printf("%d\n", strlen(&arr + 1));&数组名取得整个数组的地址,再加一跳过一个数组,传入strlen后依旧是随机值,对比以上随机值会-6。
printf("%d\n", strlen(&arr[0] + 1));首元素的地址然后加1,跳过一个元素,传给strlen后依旧是随机值,不过会-1。
char arr[] = "abcdef";
printf("%d\n", sizeof(arr));
printf("%d\n", sizeof(arr+0));
printf("%d\n", sizeof(*arr));
printf("%d\n", sizeof(arr[1]));
printf("%d\n", sizeof(&arr));
printf("%d\n", sizeof(&arr+1));
printf("%d\n", sizeof(&arr[0]+1));
printf("%d\n", strlen(arr));
printf("%d\n", strlen(arr+0));
printf("%d\n", strlen(*arr));
printf("%d\n", strlen(arr[1]));
printf("%d\n", strlen(&arr));
printf("%d\n", strlen(&arr+1));
printf("%d\n", strlen(&arr[0]+1));
char arr[] = "abcdef";
printf("%d\n", sizeof(arr));数组名代表整个数组,因为数组中除了保存字符abcdef还保存了字符串的结束字符\0,故数组大小为7,输出7。
printf("%d\n", sizeof(arr+0));数组名没有单独出现在sizeof中,故代表首元素地址,加0后,依然是首元素地址,地址的大小为4/8。
printf("%d\n", sizeof(*arr));数组名没有单独出现在sizeof中,故代表首元素地址,对其解引用后,得到字符a,字符的大小为1。
printf("%d\n", sizeof(arr[1]));取到的是第二个元素,故输出1。
printf("%d\n", sizeof(&arr));&数组名取到的是整个数组的地址,故输出4/8。
printf("%d\n", sizeof(&arr+1));&数组名取到的是整个数组的地址,对其加1跳过整个数组,但依然是地址,输出4/8。
printf("%d\n", sizeof(&arr[0]+1));取出数组第一个元素的地址,加1得到第二个元素的地址,输出4/8。
printf("%d\n", strlen(arr));数组名代表首元素地址,传给strlen后,碰到\0后返回\0前的字符数,输出6。
printf("%d\n", strlen(arr+0));数组名代表首元素地址,加0后依然是首元素地址,传给strlen后,碰到\0后返回\0前的字符数,输出6。
printf("%d\n", strlen(*arr));数组名代表首元素地址,解引用后得到字符a,传给strlen后会报错。
printf("%d\n", strlen(arr[1]));字符b传给strlen后依然会报错。
printf("%d\n", strlen(&arr));&数组名代表取出整个数组的地址,传给strlen后,算出字符长度依旧为6。
printf("%d\n", strlen(&arr+1));&数组名代表取出整个数组的地址,对其加一后,跳过整个数组,即跳到\0后,无法预知什么时候能碰到\0。因此此处输出为随机值。
printf("%d\n", strlen(&arr[0]+1));取出第一个元素的地址加1后,得到第二个元素的地址,传给strlen后,算出字符长度为5。
2.一级指针
char *p = "abcdef";
printf("%d\n", sizeof(p));
printf("%d\n", sizeof(p+1));
printf("%d\n", sizeof(*p));
printf("%d\n", sizeof(p[0]));
printf("%d\n", sizeof(&p));
printf("%d\n", sizeof(&p+1));
printf("%d\n", sizeof(&p[0]+1));
printf("%d\n", strlen(p));
printf("%d\n", strlen(p+1));
printf("%d\n", strlen(*p));
printf("%d\n", strlen(p[0]));
printf("%d\n", strlen(&p));
printf("%d\n", strlen(&p+1));
printf("%d\n", strlen(&p[0]+1));
char *p = "abcdef";
printf("%d\n", sizeof(p));p是一个字符指针,指针的大小是4/8,故输出4/8。
printf("%d\n", sizeof(p+1));p是一个字符指针,加一后,跳过一个字符,依旧是一个指针,故输出4/8。
printf("%d\n", sizeof(*p));p是一个字符指针,解引用后得到字符a,故输出1。
printf("%d\n", sizeof(p[0]));p[0]与*(p+0)相似,取得是数组第一个元素,故输出1。
printf("%d\n", sizeof(&p));p是一个字符指针,取地址后,得到的是一个二级指针,二级指针也是指针,故输出4/8。
printf("%d\n", sizeof(&p+1));&p得到二级指针后,再进行加1,则是跳过一个字符指针,依旧是一个指针,故输出4/8。
printf("%d\n", sizeof(&p[0]+1));取出第一个元素的地址值后加一,得到第二个元素地址,故输出4/8。
printf("%d\n", strlen(p));p中储存的是字符串中第一个字符的地址,传给strlen后,计算\0前的字符个数并返回,故输出6。
printf("%d\n", strlen(p+1));p是一个字符指针,加1后跳过一个字符,因此输出5。
printf("%d\n", strlen(*p));对p进行解引用后得到字符a,把字符a当作地址传给strlen,程序报错。
printf("%d\n", strlen(p[0]));与上面相同,把字符a当作地址传给strlen,程序报错。
printf("%d\n", strlen(&p));&p得到的是指针p的地址,即二级指针,该二级指针变量储存的是p的地址,故何时碰到\0不确定,输出随机值。
printf("%d\n", strlen(&p+1));&p+1后,跳过的是一个字符指针的大小,何时碰到\0也不确定,故输出随机值。
printf("%d\n", strlen(&p[0]+1));取到a的地址后加1,得到b的地址,传给strlen后,输出5。
3.二维数组
//二维数组
int a[3][4] = {0};
printf("%d\n",sizeof(a));
printf("%d\n",sizeof(a[0][0]));
printf("%d\n",sizeof(a[0]));
printf("%d\n",sizeof(a[0]+1));
printf("%d\n",sizeof(*(a[0]+1)));
printf("%d\n",sizeof(a+1));
printf("%d\n",sizeof(*(a+1)));
printf("%d\n",sizeof(&a[0]+1));
printf("%d\n",sizeof(*(&a[0]+1)));
printf("%d\n",sizeof(*a));
printf("%d\n",sizeof(a[3]));
//二维数组
int a[3][4] = {0};a是一个三行四列二维数组,我们可以将这个二维数组看作一维数组,这个一维数组中的每个元素都是一个一维数组,而这些一维数组的数组名分别为a[0]a[1]a[2],这样理解后面的题目会好做很多。
printf("%d\n",sizeof(a));sizeof中单独存放数组名,读取整个二维数组的大小,故输出48。
printf("%d\n",sizeof(a[0][0]));a[0][0]是二维数组中第一个元素,故输出4
printf("%d\n",sizeof(a[0]));a[0]可看作数组名,数组名单独放在sizeof中读取整个数组的大小,故输出16。
printf("%d\n",sizeof(a[0]+1));a[0]没有单独存在于sizeof中,代表a[0]数组中的第一元素的地址,加一后,跳过一个整型,故是第一行第二个元素的地址,故输出4/8。
printf("%d\n",sizeof(*(a[0]+1)));(a[0] + 1)是第二行的地址,那么解引用后,取到的是整行,因此输出16。
printf("%d\n",sizeof(a+1));a没有单独放在sizeof中,故代表首元素地址,即第一个子数组a[0]的地址,对其加一后,来到第二个子数组,也是地址,故输出4/8。
printf("%d\n",sizeof(*(a+1)));(a+1)代表第二个子数组的地址,对其解引用后,则访问整个数组,故输出16。
printf("%d\n",sizeof(&a[0]+1));取出第一个子数组的地址后加一,得到第二个子数组的地址,还是地址,故输出4/8。
printf("%d\n",sizeof(*(&a[0]+1)));(&a[0]+1)代表第二个子数组的地址,再对其解引用,访问整个数组,故输出16。
printf("%d\n",sizeof(*a));a没有单独放在sizeof中,故代表首元素地址,对其解引用后,得到第一个子数组,故输出16。
printf("%d\n",sizeof(a[3]));a[3]是一个不存在的子数组,可将其理解为*(a+3),访问的还是整个数组,故输出16。
总结:数组名的意义
1. sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小。
2. &数组名,这里的数组名表示整个数组,取出的是整个数组的地址。
3. 除此之外所有的数组名都表示首元素的地址。
4.强化笔试题
笔试题1
int main()
{
int a[5] = { 1, 2, 3, 4, 5 };
int *ptr = (int *)(&a + 1);
printf( "%d,%d", *(a + 1), *(ptr - 1));
return 0;
}
//程序的结果是什么?
数组代表首元素地址,即1的地址,而&数组名代表整个数组的地址,他们指向同一块空间,但是由于他们的类型不同,加1有很大的差距。
&a取出整个数组的地址后,加1,是跳过整个数组,即&a的类型为int(*)[5],加1则跳到5后面的那块地址,再强制类型转换为int*类型,最后赋值给整型指针ptr。
而a代表首元素地址,是int*类型,对其加1即跳过一个整型,指向元素2。
输出时,第一输出2,第二个时ptr指针-1,即减去一个整型,输出5
笔试题2
//这里告知结构体的大小是20个字节
struct Test
{
int Num;
char *pcName;
short sDate;
char cha[2];
short sBa[4];
}*p;
//假设p 的值为0x100000。 如下表表达式的值分别为多少?
//已知,结构体Test类型的变量大小是20个字节
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);
return 0;
}
分析题目,我们可以看到有一个结构体类型的指针p,结构体的大小为20字节,此时p的值为0x100000,当我们输出p + 0x1时,即也就是指针加1,指针的类型决定指针加1的步长,故跳过一个结构体,输出0x100014;第二输出语句p转换为了无符号长整形,即数字与数字相加,直接加上就好了,即输出0x100001;第三个输出语句将p转换了无符号整型指针,并对其加1,指针的类型决定了的步长,故加4,输出0x100004;
笔试题3
int main()
{
int a[4] = { 1, 2, 3, 4 };
int *ptr1 = (int *)(&a + 1);
int *ptr2 = (int *)((int)a + 1);
printf( "%x,%x", ptr1[-1], *ptr2);
return 0;
}
ptr1:&是取整个数组的地址取整个数组地址加1,则跳过整个数组,故ptr1指向该数组最后一个元素的后面。
prt2:a代表首元素地址,将首元素地址转换为整型,对整型加1即数字直接加1,再将这个给加1以后的整型转换为整型指针赋值给ptr2。以下指针如下图所示。
之前我们学过,指针的类型可以决定步长,也可以决定解引用访问的字节数,输出中,我们输出ptr[-1]即输出*(ptr-1),ptr是整型指针,因此减1后是将ptr向前挪动四个字节,然后对其解引用打印,也是访问四个字节,即输出00000004,而ptr2也是如此解引用访问四个字节,即输出20000000。
笔试题4
#include <stdio.h>
int main()
{
int a[3][2] = { (0, 1), (2, 3), (4, 5) };
int *p;
p = a[0];
printf( "%d", p[0]);
return 0;
}
首先我们仔细惯出,a是一个3行两列的数组,仔细观察储存的数据,里面放的是3个逗号表达式,即实际储存如下图所示;
清楚数组中的数据以后,我们再来p指针中存放的数据,p是一个整型指针,a[0]我们可以理解为整个二维数组的第一个子数组名,即第一行数组名,而数组名代表首元素地址,即是元素1的地址,将这个给地址赋值给p,p[0]即*(p+0),最终输出结果即为1。
笔试题5
int main()
{
int a[5][5];
int(*p)[4];
p = a;
printf( "%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);
return 0;
}
指针p为数组指针且指针的类型为一个有四个元素的整型数组,接着把a即二维数组的首地址赋值给p。&p[4][2]和&a[4][2]的位置如下图所示。
指针减指针即得到他们中间元素个数,因此结果为-4,而-4以地址的形式打印为0xFFFFFFFC,以整型打印的结果为-4是。
笔试题6
int main()
{
int aa[2][5] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
int *ptr1 = (int *)(&aa + 1);
int *ptr2 = (int *)(*(aa + 1));
printf( "%d,%d", *(ptr1 - 1), *(ptr2 - 1));
return 0;
}
ptr1:&aa是取得整个二维数组的地址,故对其加1则跳过整个二维数组,即指向10后面的地址。
prt2:aa代表首元素地址,即第一个子数组的地址,加1后跳过一个子数组,指向第二个子数组,再对其解引用,即得到第二个子数组的数组名,子数字名代表首元素地址,即指向子元素元素6。
在输出的时候分别对两个指针减1解引用,因此分别输出10,5。
笔试题7
#include <stdio.h>
int main()
{
char *a[] = {"work","at","alibaba"};
char**pa = a;
pa++;
printf("%s\n", *pa);
return 0;
}
a是一个指针数组,数组中存放了三个字符指针,a代表首元素地址,即如下图所示
此时对pa++即跳过一个字符指针,指向第二个字符指针,最后输出at。
笔试题8
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);
return 0;
}
首先根据代码的三个变量画出如下关系:
执行完第一个printf语句后,输出POINT,上图发生如下改变:
执行完第二个printf语句后,输出ER,上图发生如下改变:
执行完第三个printf语句后,输出ST,且并未改变任何指针的指向;
执行完第三个printf语句后,输出EW,且未改变任何指针指向。最终输出如下图
本文小编就带着大家把题目做到这了,掌握上述题目,大家对指针的了解应该很透彻了,指针是C语言的灵魂,都在流传不学指针的C语言就不叫C语言,希望本文可以给大家带来收获,非常感谢各位粉丝不断地支持,看到这给个免费的关注呗。