C语言:指针笔试题

这篇文章重点解析了指针和数组相关笔试题,细读可以大大提高自己对指针和数组的理解.

一. 相关知识前瞻回顾

首先复习一下相关重要知识:

  1. 数组名的意义:
  • sizeof(数组名),这里的数组名表示的是整个数组,计算的是整个数组的大小.
  • &数组名,这里的数组名表示的是整个数组,表示的是整个数组的地址.
  • 除此之外所有的数组名都表示数组首元素的地址.
  1. sizeof
  • sizeof是C语言的一个运算符,用于计算数据类型或者变量所占据的内存大小(以字节Byte为单位)
  • sizeof运算符在编译时求值,不会进行真正的计算.它在编译阶段根据类型信息来确定所占用的内存大小,并将结果作为一个常量.

有如下例子:

int a = 5;
short s = 4;
printf("%d\n", sizeof(s = a + 2));
printf("%d\n", s);

sizeof算出值为short类型所占内存大小,即为2;
s最终值为4.

  1. strlen函数
  • strlen是一个C语言标准库函数,用于计算字符串的长度(不包括\0)
  • 函数会从指定地址(传入参数)开始计算连续的字符数量,直到遇到空字符为止,并返回计算出的长度值.
  • 在使用strlen前,必须确保传入的参数是const char *即合法的地址值;必须确保以空字符结尾,否则会导致不可预测的结果.
  1. 本章所有试题都在x86环境下运行,这表示每个指针变量所占内存空间为4个字节,而x64环境下每个指针变量所占内存空间则为8个字节.

二. 指针和数组笔试题解析

一维数组

	int a[] = { 1,2,3,4 };
	printf("%d\n", sizeof(a));				
    //16  int[4]	 sizeof(数组名), a表示整个数组, 得到整个数组所占内存大小 

	printf("%d\n", sizeof(a + 0));			
    //4   int*		 a表示数组首元素地址, 加0后仍然表示数组首元素地址 

	printf("%d\n", sizeof(*a));				
    //4   int		 a表示数组首元素地址, 对该地址使用*解引用得到首元素

	printf("%d\n", sizeof(a + 1));			
    //4   int*		 a表示数组首元素地址, a+1表示第二个元素即a[1]的地址

	printf("%d\n", sizeof(a[1]));			
    //4   int		 a[1]是数组元素, 数组元素类型为整型

	printf("%d\n", sizeof(&a));				
    //4   int(*)[4]  &数组名, a表示整个数组, 得到整个数组的地址

	printf("%d\n", sizeof(*&a));			
    //16  int[4]	 *&a等于没有操作,还是sizeof(a)

	printf("%d\n", sizeof(&a + 1));			
    //4   int(*)[4]  &数组名, a表示整个数组, &a+1得到&a后移一个数组大小后的地址

	printf("%d\n", sizeof(&a[0]));			
    //4   int*		 a表示数组首元素地址, &a[0]取得数组首元素地址

	printf("%d\n", sizeof(&a[0] + 1));		
    //4   int*		 a表示数组首元素地址, &a[0] + 1得到 &a[1]

在这里插入图片描述

结果如下:

在这里插入图片描述

字符数组

	//字符数组
	char arr[] = { 'a','b','c','d','e','f' };
	printf("%d\n", sizeof(arr));				
    //6   char[6]	 sizeof(数组名), arr表示整个数组, 得到整个数组所占内存大小

	printf("%d\n", sizeof(arr + 0));			
    //4	  char*		 arr表示数组首元素地址, arr+0仍然表示数组首元素地址

	printf("%d\n", sizeof(*arr));				
    //1   char	     arr表示数组首元素地址, 对该地址*解引用得到数组首元素'a'

	printf("%d\n", sizeof(arr[1]));				
    //1	  char	     arr[1]表示数组第二个元素'b'

	printf("%d\n", sizeof(&arr));				
    //4   char(*)[6] &数组名, arr表示整个数组, 取到的是整个数组的地址

	printf("%d\n", sizeof(&arr + 1));			
    //4   char(*)[6] &数组名, arr表示整个数组, &arr+1得到&a后移一个数组大小后得到的地址

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

在这里插入图片描述

结果如下:

在这里插入图片描述

	char arr[] = { 'a','b','c','d','e','f' };
	printf("%d\n", strlen(arr));				
	//随机值		字符数组中没有存放空字符,不知道空字符在后面具体哪个位置,会一直往后找,结果为随机值

	printf("%d\n", strlen(arr + 0));			
	//随机值		arr + 0表示首元素地址, 一样是随机值

	printf("%d\n", strlen(*arr));				
	//err		*arr 得到的是数组首元素'a', 'a'的ASCII码值是97, 即在内存中存放的是 0x00000061
	//size_t strlen(const char*)接收的是一个地址, 当我们传入'a'的时候, strlen()函数会把'a'表示的内容即 0x00000061 传入函数
	//但0x00000061是不允许访问的,所以会产生非法访问内存的错误

	printf("%d\n", strlen(arr[1]));
	//err		同上,只不过这次传的是第二个元素'b' 即 0x00000062

	printf("%d\n", strlen(&arr));
	//随机值		arr表示整个数组, &arr得到整个数组的地址, 数组的地址和数组首元素地址是一样的, strlen()还是会一直往后找

	printf("%d\n", strlen(&arr + 1));
	//随机值		&arr+1 得到&a后移一个数组大小的地址, 得到的是 随机值-6

	printf("%d\n", strlen(&arr[0] + 1));
	//随机值		&arr[0]+1 是 &arr[1], 得到的是数组第二个元素的地址, 随机值-1

结果如下:

在这里插入图片描述

在这里插入图片描述

char arr[] = "abcdef";					//arr数组存放了{'a', 'b', 'c', 'd', 'e', 'f', '\0'}
	printf("%d\n", sizeof(arr));			
	//7	 char[7]	  sizeof(数组名), arr表示整个数组, 得到的是整个数组占据内存大小

	printf("%d\n", sizeof(arr + 0));		
	//4  char*		  arr表示数组首元素地址, arr+0 仍然表示数组首元素地址

	printf("%d\n", sizeof(*arr));			
	//1  char		  arr表示数组首元素地址, *arr得到数组首元素 'a'

	printf("%d\n", sizeof(arr[1]));			
	//1  char		  arr[1]表示数组第二个元素

	printf("%d\n", sizeof(&arr));			
	//4  char(*)[7]	  &数组名, arr表示整个数组, 得到的是整个数组的地址

	printf("%d\n", sizeof(&arr + 1));		
	//4  char(*)[7]	  &数组名, 数组名表示整个数组的地址, &arr+1 得到&a后移一个数组大小后的地址

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

结果如下:

在这里插入图片描述

    char arr[] = "abcdef";					
    //arr数组存放了{'a', 'b', 'c', 'd', 'e', 'f', '\0'}

	printf("%d\n", strlen(arr));			
    //6		  strlen()取到整个字符串的长度(不包括\0)

	printf("%d\n", strlen(arr + 0));		
    //6		  arr表示数组首元素地址, arr+0 仍然表示数组首元素地址, 得到结果仍然是6

	printf("%d\n", strlen(*arr));			
    //err	  arr表示数组首元素地址, *arr得到数组首元素'a', 传入strlen()的参数 即为 0x00000061, 内存非法访问
    
	printf("%d\n", strlen(arr[1]));			
    //err	  arr[1]得到数组第二个元素'b', 传入strlen()的参数 即为 0x00000062, 内存非法访问

	printf("%d\n", strlen(&arr));			
    //6		  &数组名, arr表示整个数组, 得到数组的整个地址, 这个地址和数组首元素地址是一样的, 结果为6

	printf("%d\n", strlen(&arr + 1));		
    //随机值   &数组名, arr表示整个数组, &arr+1 得到&a后移一个数组大小后的地址, 结果为随机值

	printf("%d\n", strlen(&arr[0] + 1));	
    //5		  &arr[0]+1 即 &arr[1], strlen()从第二个元素开始向后数有几个字符,直到遇到\0, 结果为 6 - 1 = 5

结果如下:

在这里插入图片描述

下面的指针变量p并不是存放了字符串"abcdef",而是指针变量p存放了字符串"abcdef"的地址
在这里插入图片描述

    char* p = "abcdef";						
    //指针变量p 存放了字符串"abcdef"的地址	

	printf("%d\n", sizeof(p));				
    //4		char*	sizeof(p), 计算了指针变量p所占内存大小

	printf("%d\n", sizeof(p + 1));			
    //4		char*	p+1, 指向了'b'所在的内存空间即'b'的地址

	printf("%d\n", sizeof(*p));				
    //1     char	p指向了字符串首元素的地址, 即'a'的地址, 对地址*解引用得到元素 'a'

	printf("%d\n", sizeof(p[0]));			
    //1		char	p[0]即'a'

	printf("%d\n", sizeof(&p));				
    //4		char**	p是一级指针, &p即p的地址

	printf("%d\n", sizeof(&p + 1));			
    //4		char**	&p+1得到&p后移一个指针变量大小后的地址, 仍然是地址

	printf("%d\n", sizeof(&p[0] + 1));		
    //4		char*	&p[0] + 1 即 &p[1], 表示字符串第二个元素即'b'的地址

结果如下:
在这里插入图片描述

	char* p = "abcdef";							
	//指针变量p 存放了字符串"abcdef"的地址	

	printf("%d\n", strlen(p));					
	//6		  指针变量p 存放了字符串首元素的地址, 使用strlen(), 即得到整个字符串的长度

	printf("%d\n", strlen(p + 1));				
	//5		  指针变量p 存放了字符串首元素的地址, p+1则存放了第二个元素的地址, strlen()从第二的元素开始向后数有5个字符

	printf("%d\n", strlen(*p));					
	//err	  指针变量p 存放了字符串首元素的地址, 使用*解引用得到首元素'a', ASCII码值即 0x00000061, 作为参数传入strlen(), 内存非法访问

	printf("%d\n", strlen(p[0]));				
	//err	  p[0]即字符串首元素'a', 一样是内存非法访问

	printf("%d\n", strlen(&p));
	//随机值	  &p得到指针变量p的地址, 该位置后什么位置出现'\0'是未知的, 得到随机值

	printf("%d\n", strlen(&p + 1));
	//随机值	  &p得到指针变量的地址, &p+1指向&p sizeof(&p)后(即4字节)后的地址, 仍然是随机值

	printf("%d\n", strlen(&p[0] + 1));
	//5		  &p[0] + 1 即 &p[1], strlen()从字符串第二个元素开始向后数有多少个'\0', 得到5

结果如下:

在这里插入图片描述

二维数组

int a[3][4] = { 0 };					
	//二维数组		3行4列

	printf("%d\n", sizeof(a));
	//48	int[3][4]	  sizeof(数组名), a表示整个数组, 得到整个数组占据内存的大小

	printf("%d\n", sizeof(a[0][0]));
	//4		int			  a[0][0]指数组第一个元素, 得到该元素占据内存的大小

	printf("%d\n", sizeof(a[0]));
	//16	int[4]		  a[0]表示数组第一个元素, 该数组是二维数组, 第一个元素即"第一行"的一维数组, sizeof(数组名)得到整个数组占据的空间

	printf("%d\n", sizeof(a[0] + 1));
	//4     int*	      a[0] 表示二维数组"第一行"一维数组名,即"第一行"首元素数组地址, a[0]+1表示"第一行"第二个元素的地址

	printf("%d\n", sizeof(*(a[0] + 1)));
	//4     int			 a[0]+1表示"第一行"第二个元素的地址, *解引用, 得到元素即a[0][1]

	printf("%d\n", sizeof(a + 1));
	//4		int(*)[4]	  a表示数组首元素地址, 即"第一行"一维数组的地址, a+1 即"第二行"一维数组的地址

	printf("%d\n", sizeof(*(a + 1)));
	//16	int[4]		  a表示数组首元素地址, a+1即"第二行"数组的地址, *解引用得到"第二行"一维数组占据内存的大小
	//					  sizeof(*(a + 1)) -> sizeof(a[1])

	printf("%d\n", sizeof(&a[0] + 1));
	//4		int(*)[4]	  &a[0]指"第一行"整个一维数组的地址, &a[0]+1 则指"第二行"整个一维数组的地址

	printf("%d\n", sizeof(*(&a[0] + 1)));
	//16	int[4]		  &a[0]+1 指"第二行"整个一维数组的地址, 对该地址*解引用得到"第二行"整个一维数组, 即sizeof(a[1])

	printf("%d\n", sizeof(*a));
	//16	int[4]		  a表示二维数组首元素 即"第一行"一维数组, sizeof(a[0])是sizeof(数组名), 得到整行一维数组占据的空间 

	printf("%d\n", sizeof(a[3]));
	//16	int[4]		  a[3]表示"第三行"一维数组, 但不会造成越界访问, sizeof只关心a[3]的类型, 在编译的时候就已经将整个表达式转换成了16
	//					  运行该程序的时候, sizeof(a[3]) 已经被替换成了16, 并不会真正访问到a[3]的位置

运行结果如下:

在这里插入图片描述
在这里插入图片描述

三. 指针笔试题

题目1

#include <stdio.h>

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. &a,a代表整个数组, &a+1则得到了&a后移一个数组大小后的地址即&a[5].原本类型是int(*)[5],将该地址强制类型转换为int*,存放到ptr这个指针变量内.

  2. a+1a代表数组首元素地址,那么a+1指向了数组第二个元素,通过*解引用得到第二个元素的值2

  3. ptrint*类型的, 步长为sizeof(int), 那么ptr-1正好指向了数组最后一个元素的地址,*解引用得到5

  4. 在这里插入图片描述

  5. 程序运行如下:
    在这里插入图片描述

题目2

#include <stdio.h>
//x86环境下, 该结构体的大小是20个字节
struct Test
{
	int Num;
	char* pcName;
	short sDate;
	char cha[2];
	short sBa[4];
}*p = (struct Test*)0x100000;

//假设p 的值为0x100000。 如下表表达式的值分别为多少?
//已知,结构体Test类型的变量大小是20个字节
int main()
{
	printf("%p\n", p + 0x1);
	printf("%p\n", (unsigned long)p + 0x1);
	printf("%p\n", (unsigned int*)p + 0x1);
	return 0;
}
  1. p + 0x1中, p是结构体指针变量, 加减步长为p指向变量占据内存空间的大小即20字节, p的值为0x100000. 结果为0x100000 + 20 = 0x100000 + 0x14 = 0x100014.按%p占位符,打印出00100014.
  2. (unsigned long)p + 0x1,将p强制类型转换为unsigned long, 此时数字直接加减.结果为0x100000 + 0x1 = 0x100001.按%p占位符,打印出00100001.
  3. (unsigned int*)p + 0x1,将p强制类型转换为unsigned int*, 步长为sizeof(int)4个字节;结果为0x100000 + 0x4 = 0x100004.按%p占位符,打印出00100004.
  4. 程序运行如下:
    在这里插入图片描述

题目3

#include <stdio.h>

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;
}
  1. (int*)(&a + 1)中,&a表示整个数组的地址, &a+1则是&a后移一个数组大小得到的地址,将这个int(*)[4]类型的指针强制类型转换为int*类型,int*类型的ptr1存放了这个地址.
  2. (int*)((int)a + 1)中,a表示数组首元素即1的地址, 将该地址强制类型转换为int类型, 整数类型直接相加, (int)a + 1等于将数组首元素地址直接加1.int*类型的ptr2存放了这个地址.
  3. printf("%x,%x", ptr1[-1], *ptr2);
    • ptr1[-1],即*(ptr-1),即a[4],按%x打印得到4.
    • *ptr2,本机器小端存储,a所指内存空间内容为01000000,则int(a) + 1ptr2指向了00000002.按%x打印得到20000000.
  4. 程序运行结果如下:
    在这里插入图片描述

题目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;
}

  1. ()表达式, 括号内的表达式依次被执行,整个括号表达式的结果取最右边的表达式的结果.则(0, 1), (2, 3), (4, 5)结果为1,3,5
  2. int a[3][2] = { (0, 1), (2, 3), (4, 5) };实质为int a[3][2] = {1, 3, 5};
  3. p = a[0];, int*类型的指针变量p,指向了二维数组第一个元素即"第一行"一维数组.
  4. p[0]*(p + 0) = *(a[0] + 0) = a[0][0], 最终结果为1.
  5. 程序运行结果如下:
    在这里插入图片描述

题目5

#include <stdio.h>

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;
}
  1. 二维数组在内存中真实是连续排列的

  2. int(*p)[4],p指向的是int[4]的空间

  3. p = a, p存放了数组a的首元素地址

  4. &p[4][2]&a[4][2]所指位置如下图所示:
    在这里插入图片描述

  5. &p[4][2] - &a[4][2]的真值为-4,-4的补码为0xFFFFFFFC.

  6. 最终打印FFFFFFFC,-4

  7. 程序运行结果如下:
    在这里插入图片描述

题目6

#include <stdio.h>

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;
}
  1. int* ptr1 = (int*)(&aa + 1)中, &aa取出整个数组的地址,&aa+1&aa后移一个数组大小的地址.将这个int(*)[5]类型的指针变量强制类型转换为int*类型,存放到int*类型的ptr1中.
  2. int* ptr2 = (int*)(*(aa + 1))中,aa表示数组首元素地址即数组第一行的地址,aa+1则是数组第二行的地址,使用*解引用操作符 *(aa+1)aa[1]表示第二行数组的数组名,也是第二行首元素地址,将这个地址存放到int*类型的ptr2中.
  3. *(ptr1 - 1)5,*(ptr2 - 1)10
  4. 程序运行结果如下:
    在这里插入图片描述

题目7

#include <stdio.h>

int main()
{
	char* a[] = { "work","at","alibaba" };
	char** pa = a;
	pa++;
	printf("%s\n", *pa);
	return 0;
}
  1. char* a[] = { "work","at","alibaba" }中,a是一个指针数组
  2. char** pa = a中,pa是一个二级指针变量,存放了a的首元素地址
  3. pa++,pa指向的位置后移一个步长(sizeof(char*),pa++pa指向了数组第二个元素的地址
  4. *pa对第二个元素的地址解引用,得到第二个元素"at"的首元素地址,即'a'的地址
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CaR62c2y-1689317984122)(image/指针笔试题/1689309608848.png)]
  5. printf("%s\n", *pa)%s打印,依次打印该地址后的每个字节,直至遇到\0,最后打印at
  6. 程序运行结果如下:
    在这里插入图片描述

题目8

#include<stdio.h>

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;
}

具体解析如图:
在这里插入图片描述

程序运行结果如下:

在这里插入图片描述
本章完.

  • 11
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 12
    评论
评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值