指针的一些笔试题

Posted 正义的伙伴啊

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了指针的一些笔试题相关的知识,希望对你有一定的参考价值。

整型数组

int a[] = { 1,2,3,4 };//4*4=16
	printf("%d\\n", sizeof(a));//16
	printf("%d\\n", sizeof(a + 0));//4/8 a + 0 是第一个元素的地址,sizeof(a + 0)计算的是地址的大小
	printf("%d\\n", sizeof(*a));//4  *a是数组的第一个元素,sizeof(*a)计算的是第一个元素的大小
	printf("%d\\n", sizeof(a + 1));//4/8 a + 1是第二个元素的地址,sizeof(a+1)计算的地址的大小
	printf("%d\\n", sizeof(a[1]));//4 - 计算的是第二个元素的大小

	printf("%d\\n", sizeof(&a)); //4/8 - &a虽然数组的地址,但是也是地址,sizeof(&a)计算的是一个地址的大小
	printf("%d\\n", sizeof(* &a));//16 - 计算的数组的大小
	//&a -- int(*p)[4] = &a;
	printf("%d\\n", sizeof(&a + 1));//4/8 - &a + 1 是数组后面的空间的地址
	printf("%d\\n", sizeof(&a[0]));//4/8
	printf("%d\\n", sizeof(&a[0] + 1));//4/8

数组名只有在两种情况下才代表整个数组:

  1. sizeof(数组名)------这里的数组名代表的是数组的地址,sizeof算的是整个数组的大小(这里注意括号里只有数组名时才表示数组的地址,例如sizeof(a+0)就不代表数组的地址了)
  2. &数组名------这里代表的是数组地址

sizeof后面放的参数无非就三种情况:

  1. 放的是只有单独一个数组名,这里结果时整个数组的大小(特殊情况)
  2. 放的是指针(除了上面的这种情况)返回的结果是4(32位操作系统)或 8(64位操作系统)
  3. 放的是变量,结果是返回变量类型的大小

字符数组

char arr[] = { 'a','b','c','d','e','f' };

	printf("%d\\n", strlen(arr));//随机值
	printf("%d\\n", strlen(arr + 0));//随机值
	//printf("%d\\n", strlen(*arr));//err
	//printf("%d\\n", strlen(arr[1]));//err
	printf("%d\\n", strlen(&arr));//随机值
	printf("%d\\n", strlen(&arr + 1));//随机值-6
	printf("%d\\n", strlen(&arr[0] + 1));//随机值-1

	printf("%d\\n", sizeof(arr));//6
	printf("%d\\n", sizeof(arr + 0));//4/8
	printf("%d\\n", sizeof(*arr));//1
	printf("%d\\n", sizeof(arr[1]));//1
	printf("%d\\n", sizeof(&arr));//4/8
	printf("%d\\n", sizeof(&arr + 1));//4/8
	printf("%d\\n", sizeof(&arr[0] + 1));//4/8

strlen函数传过去的是指针,返回值是’\\0’前面的所有字符个数。但是arr数组并没有’\\0’,顾strlen会访问越界直到遇到’\\0’为止,返回值也就是一个随机值。

char arr[] = "abcdef";
	//[a b c d e f \\0]
	printf("%d\\n", strlen(arr));//6
	printf("%d\\n", strlen(arr + 0));//6
	//printf("%d\\n", strlen(*arr));//err
	//printf("%d\\n", strlen(arr[1]));//err
	printf("%d\\n", strlen(&arr));//6
	printf("%d\\n", strlen(&arr + 1));//随机值
	printf("%d\\n", strlen(&arr[0] + 1));//5


	[a b c d e f \\0] 
	printf("%d\\n", sizeof(arr));//7
	printf("%d\\n", sizeof(arr + 0));//4/8
	printf("%d\\n", sizeof(*arr));//1
	printf("%d\\n", sizeof(arr[1]));//1
	printf("%d\\n", sizeof(&arr));//4/8  char(*)[7]
	printf("%d\\n", sizeof(&arr + 1));//4/8 
	printf("%d\\n", sizeof(&arr[0] + 1));//4/8

常量字符串

char* p = "abcdef";

	printf("%d\\n", strlen(p)); //6 
	printf("%d\\n", strlen(p + 1));//5
	printf("%d\\n", strlen(*p));//error
	printf("%d\\n", strlen(p[0]));//error
	printf("%d\\n", strlen(&p));//error &p是char **类型,传入参数应为char*类型
	printf("%d\\n", strlen(&p + 1));//error
	printf("%d\\n", strlen(&p[0] + 1)); //5


	printf("%d\\n", sizeof(p)); // 4/8
	printf("%d\\n", sizeof(p + 1)); // 4/8
	printf("%d\\n", sizeof(*p)); //1
	printf("%d\\n", sizeof(p[0])); // 1
	printf("%d\\n", sizeof(&p)); // 4/8
	printf("%d\\n", sizeof(&p + 1));// 4/8
	printf("%d\\n", sizeof(&p[0] + 1)); //4/8

我们知道常量字符串是把首元素的地址存入指针中。

二维数组

int a[3][4] = { 0 };

	printf("%d\\n", sizeof(a));//48 = 3*4*sizeof(int)
	printf("%d\\n", sizeof(a[0][0]));//4 - a[0][0] - 是第一行第一个元素
	printf("%d\\n", sizeof(a[0]));//16
	printf("%d\\n", sizeof(a[0] + 1));//4 解释:a[0]作为数组名并没有单独放在sizeof内部,
									//也没取地址,所以a[0]就是第一行第一个算的地址
									//a[0]+1,就是第一行第二个元素的地址
	printf("%d\\n", sizeof(*(a[0] + 1)));//4 - 解释:*(a[0] + 1)是第一行第二个元素

	printf("%d\\n", sizeof(a + 1));//4 - 解释:a是二维数组的数组名,并没有取地址
	//也没有单独放在sizeof内部,所以a就表示二维数组首元素的地址,即:第一行的地址
	//a + 1就是二维数组第二行的地址

	printf("%d\\n", sizeof(*(a + 1)));//16 解释:a+1是第二行的地址,所以*(a+1)表示第二行
	//所以计算的就是第2行的大小

	printf("%d\\n", sizeof(&a[0] + 1));//4 解释:a[0]是第一行的数组名,
	//&a[0]取出的就是第一行的地址,&a[0]+1 就是第二行的地址

	printf("%d\\n", sizeof(*(&a[0] + 1)));//&a[0]+1 就是第二行的地址
	//*(&a[0]+1) 就是第二行,所以计算的第二行的地址

	printf("%d\\n", sizeof(*a));//16 解释:a作为二维数组的数组名,没有&,没有单独放在sizeof内部
	//a就是首元素的地址,即第一行的地址,所以*a就是第一行,计算的是第一行的大小

	printf("%d\\n", sizeof(a[3]));//16 解释:a[3]其实是第四行的数组名(如果有的话)
	//所以其实不存在,也能通过类型计算大小的
	printf("%d\\n", sizeof(a[-1]));

这里再次解释一下二维数组的构造:
如果不是很了解的可以参考一下《关于指针的一些总结》——目录:数组指针

int a[3][4]={};  //实际上等价于
int *p[3]={a[0],a[1],a[2]};

a是代表首行数组的地址,由a[i]=*(a+i),可知a[i]实际上等于第i行数组的首元素的地址,而首元素的地址实际上相当于第i行数组的数组名,而a[i]作为数组名单独放在sizeof内结果是第i行的大小,整个二维数组实际上就相当于一个存放数组首元素指针的指针数组。

搞清楚了上面的关于指针的练习,我们来看几道笔试题:

笔试题一:

int main()
{
    int a[5] = { 1, 2, 3, 4, 5 };
    int* ptr = (int*)(&a + 1);
    printf("%d,%d", *(a + 1), *(ptr - 1));

    return 0;
}

很明显&a是数组指针类型,它加一跳过整个数组
在这里插入图片描述

笔试题二:

//由结构体的内存对齐可知,该结构体的内存为20字节
struct Test
{
	int Num;
	char* pcName;
	short sDate;
	char cha[2];
	short sBa[4];
}* p;
//假设p 的值为0x100000。 如下表达式的值分别为多少?
//已知,结构体Test类型的变量大小是20个字节

int main()
{
	printf("%p\\n", p + 0x1);         // 1
	printf("%p\\n", (unsigned long)p + 0x1);     //2
	printf("%p\\n", (unsigned int*)p + 0x1);     //3
	return 0;
}

这个笔试题实际上考察的是指针的类型的意义,指针类型决定指针的“步伐”有多大。
1 已知p是一个结构体指针,所以它加一内存会跳过20个字节,故结果为:0x10000014
2 指针p被强制类型转换为无符号长整型,整型加一,结果加一(注意这里已经不是指针了!)顾结果为:0x10000001.
3 指针p被强制类型转换为整型指针,顾加一内存会跳过4个字节,所以结果为:0x10000004

笔试题三:

#include<stdio.h>
int main()
{
    int a[4] = { 1, 2, 3, 4 };
    int* ptr1 = (int*)(&a + 1);      //1
    int* ptr2 = (int*)((int)a + 1);//2

    printf("%x,%x", ptr1[-1], *ptr2);
    return 0;
}

1 就不用多解释和前面的题一样
2 这里指针a被强制转换成int类型加一后又被强制转换成int类型指针。实际上指针a向后移动了一字节,而ptr2指向的内容是什么这就涉及到字节序——大小端存储,可以参考文章《数据在内存中的存储——具体在目录第二个》
在这里插入图片描述

笔试题四

#include<stdio.h>
#include<string>

int main()
{
    int a[5][5];
    int(*p)[4];
    p =(int (*)[4]) a;   //1
    printf("%p,%d\\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);
    return 0;
}

二维数组一直可以被看成一个指针数组(存放数组指针),//1实际上将a地址的值赋给了p,但p指针类型又是一个指向4个元素的指针数组,所以p每加一跳过一个四个元素的数组。整个a[5][5]实际上被重新划分了
在这里插入图片描述
从图中很直观的看出&p[4][2]和&a[4][2]之间差了四个元素,而指针-指针结果是之间元素的个数,所以答案是: FFFFFFFC -4

笔试题五:

#include <stdio.h>
int main()
{
	const char* a[] = { "work","at","alibaba" };
	const char** pa = a;
	pa++;
	printf("%s\\n", *pa);
	return 0;
}

这里a是一个指针数组,数组存的是常量字符串第一个字符的地址。而a代表的 是首元素的地址,故为一个二级指针。pa++指向了数组的下个元素也就是常量字符串“at”首元素地址的地址,顾打印的结果为at

笔试题六:

#include<stdio.h>
int main()
{
	char* c[] = { "ENTER","NEW","POINT","FIRST" };
	char** cp[] = { c + 3,c + 2,c + 1,c };
	char*** cpp = cp;
}

和笔试题五一样我们知道指针c是一个char **类型存放的是常量字符串首字符的地址的地址。
两个数组的结构如下图:
在这里插入图片描述

printf("%s\\n", **++cpp);

在这里插入图片描述
指针先自增再两次解引用,最后得到的是p3的地址,顾打印结果为: POINT


printf("%s\\n", *-- * ++cpp + 3);

在这里插入图片描述
指针cpp先自增再解引用再自减再解引用得到p1,p1+3指向的是E的地址,所以字符串从‘E’开始打印,最后结果为:ER

printf("%s\\n", *cpp[-2] + 3);

在插入图片描述
cpp[-2]等价于*(cpp-2),顾表达式也可以写成 * *(cpp-2)+3,和上面一样可以得到指向‘S’的指针,结果为:ST

printf("%s\\n", cpp[-1][-1] + 1);

在这里插入图片描述
cpp[-1][-1]等价于*(*(cpp-1)-1),思路和上面一样,结果为:EW

总结:

关于指针我们一定要注意的是一定要搞清楚指针的类型,因为指针的类型决定了指针的定义还决定了指针加减整数,其次要搞清楚指针的指向。弄清楚这两点指针的题就可以迎刃而解了。

以上是关于指针的一些笔试题的主要内容,如果未能解决你的问题,请参考以下文章

深入理解C指针经典笔试题——指针和数组

#yyds干货盘点# C语言数组与指针常考笔试题(原题+解析+原码)

指针中容易混淆的概念以及常见笔试题

指针进阶—指针和数组笔试题解析[建议收藏]

70道指针数组笔试题

指针进阶—指针和数组笔试题解析[建议收藏]