C语言:指针4:一些面试中常见的练习题(一个C语言小白的理解之旅)

一.两段“有趣”的代码

首先,来两个”开胃小菜“,分析一下下面两段代码的含义:

//代码1
(*(void (*)())0)();
//代码2
void (*signal(int,void(*)(int)))(int);

解释1:
在代码1中,先把0这个数字强制类型转成void(*)()这样的函数指针类型。再把转成的函数指针进行解引用,然后再进行函数调用。

(*   (  void (*)() )   0)   ();//拆分成这样看更容易理解

解释2:
在代码2中,我们先声明了一个名为signal的函数,这个函数的参数是int和void(*)(int)这样一个函数指针,返回值类型也是一个函数指针。

void    (  *  signal(int,void(*)(int)  )  )   (int);//拆分成这样看更容易理解

二.回调函数

回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
使用回调函数,模拟实现qsort(采用冒泡的方式)。

#include<stdio.h>
#include<stdlib.h>
	int int_cmp(const void * p1, const void * p2){
		return (*(int *)p1 > *(int *)p2);
	}
	void _swap(void *p1, void * p2, int size){
		int i = 0;
		for (i = 0; i < size; i++){
			char tmp = *((char *)p1 + i);
			*((char *)p1 + i) = *((char *)p2 + i);
			*((char *)p2 + i) = tmp;
		}
	}
	
	void bubble(void *base, int count, int size, int(*cmp)(void *, void *)){
		int i = 0;
		int j = 0;
		for (i = 0; i< count - 1; i++){
			for (j = 0; j<count - i - 1; j++){
				if (cmp((char *)base + j*size, (char *)base + (j + 1)*size) > 0){
				_swap((char *)base + j*size, (char *)base + (j + 1)*size, size);
				}
			}
		}
	}
	int main(){
		int arr[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };
		int i = 0;
		bubble(arr, sizeof(arr) / sizeof(arr[0]), sizeof(int), int_cmp);
		for (i = 0; i< sizeof(arr) / sizeof(arr[0]); i++){
			printf("%d ", arr[i]);
		}
		printf("\n");
		system("pause");
		return 0;
	}

运行结果:在这里插入图片描述

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

sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小。单位:字节bit。

1.一维数组

int arr[] = { 1,2,3,4 };

//arr数组有4个元素,每个元素为int型,故总共大小为4*4个字节。
printf("%d\n", sizeof(arr));

//(arr+0)数组名隐式转换成指针,在32位系统下指针占4个字节。
printf("%d\n", sizeof(arr + 0));

//*arr操作将数组名转换成指针,其指向的是首元素指针,即1个int型,占4个字节。
printf("%d\n", sizeof(*arr));

//(arr+1)操作数组名隐式转换成指针,在32位系统下指针占4个字节。
printf("%d\n", sizeof(arr + 1));

//数组下标为1的元素大小,其实质在求sizeof(int)。占4个字节。
printf("%d\n", sizeof(arr[1]));

//&arr得到的是数组指针int(*)[4],其本质是指针,在32位系统下指针占4个字节。
printf("%d\n", sizeof(&arr));

//&arr得到一个数组指针。*&arr则是进行解引用操作,得到的是一个int[4]数组,故占4*4个字节。
printf("%d\n", sizeof(*&arr));

//arr隐式转换成首元素指针,*arr对指针进行解引用得到int,再进行&*arr取地址操作得到的int*,占4个字节。
printf("%d\n", sizeof(&*arr));
//&arr得到的是数组指针int(*)[4],其本质是指针,&arr+1类型不变,任然是指针,在32位系统下指针占4个字节。
printf("%d\n", sizeof(&arr + 1));

//首元素取地址后是一个int*指针,在32位系统下指针占4个字节。
printf("%d\n", sizeof(&arr[0]));

//首元素取地址后是一个int*指针,+1不影响其类型,在32位系统下指针占4个字节。
printf("%d\n", sizeof(&arr[0] + 1));

2.字符数组

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

	//sizeof(char[6]),char类型大小为1个字节,6个元素的大小为6个字节。
	printf("%d\n", sizeof(arr));

	//sizeof(char*),字符数组隐式转换成字符指针,+多少不影响其类型,在32位系统下指针占4个字节。
	printf("%d\n", sizeof(arr + 0));

	//sizeof(char),arr获得的是数组首元素的指针,*arr得到的是数组首元素。占1个字节。
	printf("%d\n", sizeof(*arr));

	//sizeof(char),相当于在求数组下标为1的数组元素的大小。占1个字节。
	printf("%d\n", sizeof(arr[1]));

	//sizeof(char(*)[6]),&arr得到的是一个数组指针,在32位系统下指针占4个字节。
	printf("%d\n", sizeof(&arr));

	//sizeof(char[6]),&arr得到一个数组指针,*&arr对该数组指针进行解引用操作,获得一个数组,占6个字节。
	printf("%d\n", sizeof(*&arr));

	//sizeof(char*),*arr得到数组首元素,&*arr对首元素进行取地址得到一个指针char*,在32位系统下指针占4个字节。
	printf("%d\n", sizeof(&*arr));

	//sizeof(char*),&arr[1]得到的是char*,+几都不影响其类型,在32位系统下指针占4个字节。
	printf("%d\n", sizeof(&arr[1]+1));

	//sizeof(char(*)[6]),&arr得到一个数组指针,+几都不影响其类型,在32位系统下指针占4个字节。
	printf("%d\n", sizeof(&arr + 1));
	

	//未定义行为,strlen的实现是从头开始遍历数组每个字符,count++,直到遇见“\0”停止计数,
	//但是arr是字符数组不是字符串,其后不确定是否存在“\0”,故该操作属于未定义行为。
	printf("%d\n", strlen(arr));

	//未定义行为,原因同上。
	printf("%d\n", strlen(arr + 0));

	//*arr操作得到的是首元素,即字符“a”,其类型为char,strlen的参数类型是const char*类型,故该代码编译错误。
	printf("%d\n", strlen(*arr));

3.字符串指针

char arr[] = "abcdef";

	//此处arr[]是一个字符串,其末尾包含“\0”,故其大小为7个char类型元素,占7个字节。
	printf("%d\n", sizeof(arr));

	//sizeof(char*),对数组名进行运算就会将数组名隐式转换成指针,在32位系统下指针占4个字节。
	printf("%d\n", sizeof(arr + 0));

	//sizeof(char),*arr操作获得的是字符串首字符,其大小是1个char类型,占1个字节。
	printf("%d\n", sizeof(*arr));

	//sizeof(char),arr[1]获得的是下标为1的字符元素,其大小是1个char类型,占1个字节。
	printf("%d\n", sizeof(arr[1]));

	//sizeof(char(*)[7]),&arr操作获得的是一个数组指针,在32位系统下指针占4个字节。
	printf("%d\n", sizeof(&arr));

	//sizeof(char[7]),&arr操作获得的是一个数组指针,*&arr操作获得的是一个字符串数组,共占7个字节。
	printf("%d\n", sizeof(*&arr));

	//sizeof(char*),*arr操作获得字符串首元素,&*arr获得首元素地址,其类型是一个指针,在32位系统下指针占4个字节。
	printf("%d\n", sizeof(&*arr));

	//sizeof(char*),&arr[0]操作获得的是一个字符指针,在32位系统下指针占4个字节。
	printf("%d\n", sizeof(&arr[0] + 1));


	//结果:6,strlen计算的是字符串的长度,不计算“\0”。当算占用的内存空间需要包含“\0”。
	printf("%d\n", strlen(arr));

	//结果:5,arr获得第1个元素地址,arr+1获得第2个元素地址,即从第2个元素开始计算字符串长度。
	printf("%d\n", strlen(arr + 1));
	
	//未定义行为,&arr获得的是一个数组指针,&arr+1则是跳过整个数组,即到“\0”后面了。编译失败。
	printf("%d\n", strlen(&arr + 1));
	
	//类型不匹配,strlen参数类型是const char*类型,而&arr的类型是char(*)[7],编译会失败。
	//但是在实际中,&arr获得的理论上是数组指针,但是实际是arr[0]首元素的地址,其类型是char*,所以实际编译会通过。
	printf("%d\n", strlen(&arr));

4.指针数组

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

	//结果:48。sizeof(int[3][4])。arr数组有3*4个元素,每个元素都是in类型,占3*4*4个字节。
	printf("%d\n", sizeof(arr));

	//结果:4。sizeof(int)。arr[0][0]得到二维数组的第一个元素,类型为int。
	printf("%d\n", sizeof(arr[0][0]));

	//结果:16。sizeof(int[4])。arr[0]获得的是长度为4的一维数组,其中每个元素
	printf("%d\n", sizeof(arr[0]));

	//结果:4。sizeof(int*)。arr[0] + 1将数组隐式转换成指针,在32位系统下指针占4个字节。
	printf("%d\n", sizeof(arr[0] + 1));

	//结果:48。sizeof(int)。arr[0]获得一个数组,由于前面是运用了*,
	//故该处数组隐式转换成指针,*为解引用,又变成了数组元素。

	//结果:16。sizeof(int[4])。arr从二维数组隐式转换成指针int(*)[4],在对其解引用操作又将其变成一个数组int[4]。
	printf("%d\n", sizeof(*arr));

	//结果:4。sizeof(int*)。*arr先将arr隐式转换成一个数组指针int(*)[4],然后解引用成数组int[4],
	//*arr+1又将其转换成指针,在32位系统下指针占4个字节。
	printf("%d\n", sizeof(*arr + 1));

	//结果:16。sizeof(int[4])。arr+1将数组隐式转换成数组指针nt(*)[4],在对其解引用操作又将其变成一个数组int[4]。
	printf("%d\n", sizeof(*(arr + 1)));

	//结果:4。sizeof(int(*)[4])。arr[0]得到一个数组,&arr[0]得到一个数组指针,
	//对指针进行运算不改变其类型,在32位系统下指针占4个字节。
	printf("%d\n", sizeof(&arr[0] + 1));

	//结果:4。sizeof(int(*)[4])。arr[0]获得一个数组,&arr[4]获得一个int(*)[4]数组指针
	//运算无法改变指针类型。在32位系统下指针占4个字节。
	printf("%d\n", sizeof(&arr[0] + 1));

	
	//结果:4。sizeof(int(*)[4])。int[3][4]是一个长度为3的二维数组,其下标取值只能是0,1,2
	//所以取值10已经超过了下标,但是代码可以运行,叫做编译器求值,不是未定义行为,
	//代码编译完了之后生成的二进制指令相当于printf("%d\n",4),在运行过程中不涉及内存访问,更没有数组下标越界。
	printf("%d\n", sizeof(arr[10]));

四.代码分析笔试题

笔试题1:

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

运行结果:在这里插入图片描述
解析:
arr是一个数组,arr+1将数组隐式准换成指针,并且指向数组下标为1的元素,*(arr+1)对其进行解引用,获得数组下标为1的元素,即2。
&arr获得的是一个数组指针,&arr+1则是跳过了整个数组,指向了数组后面的位置,接着又用(int * ) 对数组指针类型进行类型强制转换成(int *)类型,此时ptr指向的位置是整个数组末尾,(ptr-1)则指向了元素5这个位置, *(ptr-1)解引用操作获得元素5。

笔试题2:

//这里的结构体Test类型的变量大小是20个字节。
struct Test{
	int Num;
	char *pcName;
	shortsDate;
	char cha[2];
	shortsBa[4];
}*p;

假设p 的值为0x100000。 如下表表达式的值分别为多少?

p + 0x1 = 0x___ ?

解析:
*p=>(struct Test *),这个结构体指针的地址是0x100000,(p+0X1)是指在指针的基础上进行+1则要跳过整个结构体,也就是要在该结构体指针的地址上再加上20个字节,此出需要注意的是20是十进制的表示,而我们打印的时候需要输出16进制的表示,所以先要将十进制20转换成十六进制14表示,然后进行相加,得到最后结果:0X100014。

(unsigned long)p + 0x1 = 0x___ ?

解析:
这个结构体指针的地址是0x100000,(unsigned long)p对其进行强制类型转换,将其从指针类型转换成长整型,(unsigned long)p + 0X1就是在长整型基础上+1,得到最后结果:0X100001。

(unsigned int*)p + 0x1 = 0x___ ?

解析:
这个结构体指针的地址是0x100000,(unsigned int*)p 将其强制类型转换,转换成int *指针,接下来,(unsigned int *)p + 0x1进行的还是指针运算,表示跳过一个int型元素,即在原始地址上+4。得到最后结果:0X100004。

笔试题3:

int main(){
	int arr[4] = { 1, 2, 3, 4 };
	int *ptr1 = (int *)(&arr + 1);
	int *ptr2 = (int *)((int)arr + 1);
	printf( "%x,%x", ptr1[-1], *ptr2);
	return 0;
}

解析:
&arr将数组阴识类型转换成数组指针,(&arr+1)跳过整个数组,(int *)(&arr + 1)强制类型转换,将指针强制转换成(int *)指针,所以,ptr1是一个int *型指针,指向的位置是在arr数组后面位置,现在对其进行ptr1[-1],其含义是对ptr1先进行-1左移操作后再解引用,结果是:4。
(int)arr将数组强制类型转换成一个整数,(int)arr+1则是按照整数进行加法运算,此时内存地址按照int类型往后移动一位,(int *)((int)arr + 1),最后又进行了强制类型转换,将整数转换成了int *指针类型,*ptr2是对该指针进行了解引用,结果为:0X2000000。

  • 4
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值