C语言---指针的应用以及一些面试题

转移表

假设现在要实现一个计算器,功能是完成整数的加,减,乘,除。

原始版本:

以下是最好写也是最好想的初代计算机的实现版本,初步观察一下就会发现问题,每个case语句下边的重复代码过多,有点没必要,而且使用switch语句的话,如果这个计算机还要完成更多其他的运算功能,就需要写更多的case分支语句,会很麻烦,那有没有什么方法可以改进一下呢?

cpp 复制代码
#include<stdio.h>

int Add(int x, int y)
{
	return x + y;
}

int Sub(int x, int y)
{
	return x - y;
}

int Mul(int x, int y)
{
	return x * y;
}

int Div(int x, int y)
{
	return x / y;
}

int main()
{
	int input = 0;
	int x = 0, y = 0;
	int r = 0;//存结果
	do {
		printf("---------  计算器 ------------\n");
		printf("------1.加法----2.减法--------\n");
		printf("------3.乘法----4.除法--------\n");
		printf("----------0.退出--------------\n");
		printf("请输入你想要的功能: ");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			printf("请输入两个操作数: ");
			scanf("%d%d", &x, &y);
			r = Add(x, y);
			printf("%d\n", r);
			break;
		case 2:
			printf("请输入两个操作数: ");
			scanf("%d%d", &x, &y);
			r = Sub(x, y);
			printf("%d\n", r);
			break;
		case 3:
			printf("请输入两个操作数: ");
			scanf("%d%d", &x, &y);
			r = Mul(x, y);
			printf("%d\n", r);
			break;
		case 4:
			printf("请输入两个操作数: ");
			scanf("%d%d", &x, &y);
			r = Div(x, y);
			printf("%d\n", r);
			break;
		case 0:
			printf("程序退出\n");
			break;
		default:
			printf("输入错误,请重新输入\n");
			break;
		}
	} while (input);

	return 0;
}

改进版本:

利用指针数组,通过下标找到对应函数的地址之后调用该函数起到简化代码的作用。而这个函数指针数组就被称为转移表。

cpp 复制代码
#include<stdio.h>

void menu()
{
	printf("---------  计算器 ------------\n");
	printf("------1.加法----2.减法--------\n");
	printf("------3.乘法----4.除法--------\n");
	printf("----------0.退出--------------\n");
}

int Add(int x, int y)
{
	return x + y;
}

int Sub(int x, int y)
{
	return x - y;
}

int Mul(int x, int y)
{
	return x * y;
}

int Div(int x, int y)
{
	return x / y;
}

int main()
{
	int input = 0;
	int x = 0, y = 0;
	int r = 0;//存结果

	//函数指针数组
	//转移表
	//增加一个NULL充当下标为0的元素,方便下标和menu里的选项一一对应
	int (*pf[5])(int, int) = { NULL, Add, Sub, Mul, Div };
	                          //0     1    2    3    4

	do {
		menu();
		printf("请输入你想要的功能: ");
		scanf("%d", &input);
		if (input >= 1 && input <= 4)
		{
			printf("请输入两个操作数: ");
			scanf("%d%d", &x, &y);
			//pf[input]获取到下标对应函数的地址,之后调用
			r = pf[input](x, y);
			printf("%d\n", r);
		}
		else if (input == 0)
		{
			printf("退出计算器\n");
		}
		else
		{
			printf("输入错误,请重新输入\n");
		}
	} while (input);

	return 0;
}

回调函数

将函数指针作为参数传给一个函数,在该函数内部用这个指针去调用其指向的函数,这个被调用的函数叫做回调函数。

cpp 复制代码
#include<stdio.h>

void menu()
{
	printf("---------  计算器 ------------\n");
	printf("------1.加法----2.减法--------\n");
	printf("------3.乘法----4.除法--------\n");
	printf("----------0.退出--------------\n");
}

int Add(int x, int y)
{
	return x + y;
}

int Sub(int x, int y)
{
	return x - y;
}

int Mul(int x, int y)
{
	return x * y;
}

int Div(int x, int y)
{
	return x / y;
}

void calc(int (*pf)(int, int))
{
	int x = 0, y = 0;
	printf("请输入两个操作数: ");
	scanf("%d%d", &x, &y);
	printf("%d\n", pf(x, y));
}

int main()
{
	int input = 0;

	do {
		menu();
		printf("请输入你想要的功能: ");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			calc(Add);
			break;
		case 2:
			calc(Sub);
			break;
		case 3:
			calc(Mul);
			break;
		case 4:
			calc(Div);
			break;
		case 0:
			printf("退出计算器\n");
			break;
		default:
			printf("输入错误,请重新输入\n");
			break;
		}
	} while (input);

	return 0;
}

qsort的使用以及模拟实现

介绍以及使用:

之前学过的冒泡排序只能排整型数据,但是qsort可以排任意类型的数据,qsort的函数声明如下。

void qsort (void* base, size_t num, size_t size, int (*compar)(const void*,const void*));

qsort排序的对象为base指针指向的数组,这个数组有num个数据,数组里的每个数据size个字节大小,compar这个函数指针指向的函数表示的是排序的比较方式,并且这个函数的返回值为int,两个参数类型均为const void*,只有参数为const void*才能去比较任意类型的数据。(具体可以去看看cplusplus文档)

cpp 复制代码
//只要涉及到排序,就会有元素的比较
void qsort(void* base, //指向待排序的数组的第一个元素
           size_t num, //base指向的数组的元素个数
           size_t size,//每个元素的大小,单位是字节
          int (*compar)(const void* p1 , const void* p2)//函数指针,指向的函数可以比较数组中的两个元素
          //p1 指向的元素排在p2指向的元素前,返回<0的数字
          //p1 指向的元素和p2指向的元素一样大,返回0
          //p1 指向的元素排在p2指向的元素后,返回>0的数字
          );
cpp 复制代码
#include<stdio.h>
#include<stdlib.h>

int cmp_int(const void* p1, const void* p2)
{
    //假设*p1是1,*p2是2
	//如果排升序,*(int*)p1 - *(int*)p2,即1-2=-1<0,p1排p2前边
	//如果排降序,*(int*)p2 - *(int*)p1,即2-1=1>0,p1排p2后边
	return *(int*)p1 - *(int*)p2;
}

int cmp_float(const void* p1, const void* p2)
{
	//浮点数直接做差之后还是float类型,但返回值是int类型,会发生截断
	if (*(float*)p1 < *(float*)p2)
	{
		return -1;
	}
	else if (*(float*)p1 == *(float*)p2)
	{
		return 0;
	}
	else
	{
		return 1;
	}
}

struct stu
{
	char name[20];
	int age;
};

//按年龄升序排
int cmp_stu_age(const void* p1, const void* p2)
{
	if (((struct stu*)p1)->age < ((struct stu*)p2)->age)
	{
		return -1;
	}
	else if (((struct stu*)p1)->age == ((struct stu*)p2)->age)
	{
		return 0;
	}
	else
	{
		return 1;
	}
}

//两个字符串比较大小的方法:strcmp
//strcmp(str1, str2)
//str1 > str2, 返回 >0
//str1 == str2, 返回 0
//str1 < str2, 返回 <0
#include <string.h>

int cmp_stu_name(const void* p1, const void* p2)
{
	return strcmp(((struct stu*)p1)->name, ((struct stu*)p2)->name);
}

int main()
{
	int arr1[] = { 3,1,8,6,0,9,4,2,7,5 };
	int sz1 = sizeof(arr1) / sizeof(int);
	float arr2[] = { 2.0, 1.0, 5.0, 7.0,3.0 };
	int sz2 = sizeof(arr2) / sizeof(float);

	qsort(arr1, sz1, sizeof(int), cmp_int);
	qsort(arr2, sz2, sizeof(float), cmp_float);

	for (int i = 0; i < sz1; i++)
	{
		printf("%d ", arr1[i]);
	}
	printf("\n");

	for (int i = 0; i < sz2; i++)
	{
		printf("%f ", arr2[i]);
	}
	printf("\n");

	//排结构体
	struct stu arr3[] = { {"lisi", 10}, {"zhangsan", 11}, {"wangwu", 12} };
    qsort(arr3, 3, sizeof(arr3[0]), cmp_stu_name);
	for (int i = 0; i < 3; i++)
	{
		printf("%s %d ", arr3[i].name, arr3[i].age);
	}
	printf("\n");


	return 0;
}

模拟实现:(借助冒泡排序)

要改造冒泡排序,首先得知道哪里需要改,以及qsort函数为什么需要4个参数?(如下图)。其中第3个参数代表的是base指向数组里每一个数据的大小,由于base是void*类型,因此根本无法去知道每个数据所占字节大小是多少,就不方便解引用访问数据,也不方便操控指针加减,因为这些操作都是需要知道指针指向的数据类型是什么的,因此需要第3个参数。

修改第一处:if(arr[j] > arr[j + 1])判断成立就交换,我们知道,arr[j]等价于*(arr + j),但是现在base是void*类型的指针,不能直接base[j]这么写,得通过计算找到j和j+1对应元素的起始地址去传给compar函数,如下图。

修改第二处:如果if判断成立的话就要去交换两个元素,这时候普通的交换方式显然是不行的,又为了满足能够交换所有类型的数据,处理方式就是一个个字节交换,循环len次,每次交换一个字节,循环结束就实现了两个元素的交换。

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>

void Swap(char* buf1, char* buf2, int len)
{
	for (int i = 0; i < len; i++)
	{
		char tmp = *buf1;
		*buf1 = *buf2;
		*buf2 = tmp;
		++buf1;
		++buf2;
	}
}

int com(const void* p1, const void* p2)
{
	return *(int*)p1 - *(int*)p2;
}

//默认排升序
void bubble_sort(void* base, int sz, int len, int (*compar)(const void* p1, const void* p2))
{
	for (int i = 0; i < sz - 1; i++)
	{
		int flag = 1;
		for (int j = 0; j < sz - 1 - i; j++)
		{
			//if(arr[j] > arr[j + 1])---判断成立就交换
			//注意这里比较使用了compar函数,其参数是两个指针
			//compar函数返回一个>0的数字表示p1要排在p2的后边
			if (compar((char*)base + (len * j), (char*)base + (len * (j + 1))) > 0)
			{
				//交换两个元素
				Swap((char*)base + (len * j), (char*)base + (len * (j + 1)), len);
				flag = 0;
			}
		}
		if (flag == 1)
		{
			break;
		}
	}
}

int main()
{
	int a[] = { 1,3,5,7,9,2,4,6,8,0 };
	int sz = sizeof(a) / sizeof(int);
	bubble_sort(a, sz, sizeof(int), com);
	for (int i = 0; i < sz; i++)
	{
		printf("%d ", a[i]);
	}
	printf("\n");
	return 0;
}

数组和指针常见的面试题目

一维数组

cpp 复制代码
//数组名的理解:
//1. 数组名是数组首元素的地址
//但是有2个例外:
//1. sizeof(数组名),数组名单独放在sizeof内部,这里的数组名表示整个数组,计算的是整个数组的大小,单位是字节
//2. &数组名,这里的数组名表示整个数组,取出的是整个数组的地址

int main()
{
	int a[] = { 1,2,3,4 };
	printf("%zd\n", sizeof(a));//sizeof(数组名), 4*4 = 16
	printf("%zd\n", sizeof(a + 0));//a就是首元素的地址,a+0还是首元素的地址,计算的是地址的大小: 4 / 8
	printf("%zd\n", sizeof(*a));//a就是首元素的地址,*a就是首元素.计算的是首元素的大小:4个字节
	printf("%zd\n", sizeof(a + 1));//a就是首元素的地址,a+1是第二个元素的地址,计算的是地址的大小: 4 / 8
	printf("%zd\n", sizeof(a[1]));//计算的是第二个元素的大小,4
	printf("%zd\n", sizeof(&a));//&a - 取出的是数组的地址. 是地址都是4/8个字节
	//&a + 1 跳过多少个字节: 16
	//*&a 这里的解引用访问到16个字节
	//int (*)[4]
	printf("%zd\n", sizeof(*&a));//16
	//printf("%zd\n", sizeof(a));//16
	//*&a, * 和 &抵消了,sizeof(a)
	printf("%zd\n", sizeof(&a + 1));//&a+1是跳过整个数组,指向4的后面,其实也是地址,是地址就是4/8个字节
	printf("%zd\n", sizeof(&a[0]));//&a[0]就是首元素,4 / 8 个字节
	printf("%zd\n", sizeof(&a[0] + 1));//&a[0] + 1是第二个元素的地址. 4/8
	//                     int*
	return 0;
}

字符数组

cpp 复制代码
#include<string.h>

int main()
{
	char arr[] = { 'a','b','c','d','e','f' };
	printf("%zd\n", sizeof(arr));//arr是数组名,单独放在sizeof内部.计算的是数组的大小 - 6
	printf("%zd\n", sizeof(arr + 0));//arr是数组名,是数组首元素的地址,arr+0也是数组首元素的地址,是地址就是4/8
	printf("%zd\n", sizeof(*arr));//arr是数组首元素的地址,*arr就是首元素.大小是1个字节
	printf("%zd\n", sizeof(arr[1]));//arr[1]是第二个元素,大小是1个字节
	printf("%zd\n", sizeof(&arr));//&arr是数组的地址,数组的地址也是地址,大小是4/8个字节
	printf("%zd\n", sizeof(&arr + 1));//&arr是数组的地址,&arr+1就是跳过整个数组,指向了f的后面. 4/8个字节
	printf("%zd\n", sizeof(&arr[0] + 1));//&arr[0] + 1是第二个元素的地址,大小就是4/8


	char arr[] = { 'a','b','c','d','e','f' };
	printf("%zd\n", strlen(arr));//随机值;arr是首元素的地址,数组中没有\0,一直往后找,什么时候遇到\0不清楚
	printf("%zd\n", strlen(arr + 0));//随机值;arr是首元素的地址,arr+0依然是首元素的地址
	//printf("%zd\n", strlen(*arr));//*arr是首元素,是'a' - 97,传给strlen函数,97会被当做地址.
	//以97作为地址,会形成非法访问,程序会崩溃
	//printf("%zd\n", strlen(arr[1]));//arr[1]是第2个元素,就是'b' - 98,传给strlen函数,98会被当做地址.同上,程序崩溃
	printf("%zd\n", strlen(&arr));//&arr是数组的地址,虽然是数组的地址,值和首元素的地址一样.strlen依然是从第一个
	//字符的位置开始向后找\0,会得到随机值.
	printf("%zd\n", strlen(&arr + 1));//&arr + 1是f后面的地址,什么时候遇到\0,依然不知道.随机值.
	printf("%zd\n", strlen(&arr[0] + 1));//&arr[0]+1就是第二个元素的地址,得到的也是随机值


	char arr[] = "abcdef";
	printf("%zd\n", sizeof(arr));//arr表示整个数,计算的是整个数组的大小单位字节,7*1 = 7
	printf("%zd\n", sizeof(arr + 0));//arr就是数组首元素的地址,arr+0还是数组首元素的地址 4 / 8
	printf("%zd\n", sizeof(*arr));//1,arr就是数组首元素的地址,*arr是首元素,大小就是1个字节
	printf("%zd\n", sizeof(arr[1]));//arr[1]数组的第二个元素,计算的就是第二个元素的大小,单位是字节 - 1
	printf("%zd\n", sizeof(&arr));//arr表示整个数组,&arr取出的是整个数组的地址,是地址大小就是4/8个字节
	printf("%zd\n", sizeof(&arr + 1));//&arr + 1是跳过这个数组后的地址,是地址大小就是4/8个字节
	printf("%zd\n", sizeof(&arr[0] + 1));//&arr[0]是数组首元素的地址,&arr[0]+1是数组第二个元素的地址:4 / 8


	char arr[] = "abcdef";
	printf("%zd\n", strlen(arr));//6: arr是数组首元素的地址,从第一个元素开始,统计\0之前字符的个数
	printf("%zd\n", strlen(arr + 0));//6:arr是数组首元素的地址,arr+0还是数组首元素的地址,结果同上
	//printf("%zd\n", strlen(*arr));//arr是数组首元素的地址,*arr就是首元素了,*arr == 'a' == 97
	//非法访问内存,导致程序崩溃
	//printf("%zd\n", strlen(arr[1]));//arr[1]是第二个元素 == 'b' == 98, 道理同上
	printf("%zd\n", strlen(&arr));//6:&arr取出的是数组的地址,数组的地址和首元素的地址是同一个值
	//strlen也是从第一个字符开始向后统计\0之前的字符个数。
	printf("%zd\n", strlen(&arr + 1));//随机值
	printf("%zd\n", strlen(&arr[0] + 1)); //5:&arr[0] + 1是第二个元素的地址,\0之前有5个元素


	const char* p = "abcdef";
	printf("%zd\n", sizeof(p));//4/8
	//p是一个指针变量,大小就是4/8个字节
	printf("%zd\n", sizeof(p + 1));//4/8
	//p中存放的是'a'的地址,p+1是'b'的地址
	//大小就是4/8个字节
	printf("%zd\n", sizeof(*p));//1: *p=='a'
	printf("%zd\n", sizeof(p[0]));//1:p[0]==*(p+0)==*p
	//结果就同上
	printf("%zd\n", sizeof(&p));//&p是指针变量p的地址
	//是地址大小就是4/8个字节
	printf("%zd\n", sizeof(&p + 1));//&p + 1还是地址大小是4/8个字节
	//&p+1是指向p变量的后边
	printf("%zd\n", sizeof(&p[0] + 1));//&p[0] + 1是'b'的地址
	//大小是4/8个字节


	char* p = "abcdef";
	printf("%zd\n", strlen(p));//6: p里边存放是'a'的地址
	printf("%zd\n", strlen(p + 1));//5: p+1是'b'的地址
	//printf("%zd\n", strlen(*p));//非法访问, *p是'a'
	//printf("%zd\n", strlen(p[0]));//非法访问,效果同上
	printf("%zd\n", strlen(&p));//随机值
	//&p是p这个变量的地址,strlen就是从p这块空间的起始地址开始向后找\0的
	//p中存放的地址是不确定的,所有有没有\0,什么时候会遇到\0都不确定
	printf("%zd\n", strlen(&p + 1));//随机值
	//&p+1是p变量后边的地址,从这个位置向后的内存数据不知道
	//什么时候会遇到\0都不确定
	printf("%zd\n", strlen(&p[0] + 1));//5

	return 0;
}

二维数组

cpp 复制代码
#include<stdio.h>
int main()
{
	int a[3][4] = { 0 };
	printf("%zd\n", sizeof(a));//48: a是数组名,单独放在sizeof内部,表示整个数组,计算的是整个数组的大小,单位是字节3*4*4 = 48
	printf("%zd\n", sizeof(a[0][0]));//4: a[0][0]是第一行第一个元素
	printf("%zd\n", sizeof(a[0]));//16: a[0]是第一行这个一维数组的数组名,数组名单独放在sizeof内部,计算的是第一行这个一维数组的大小
	printf("%zd\n", sizeof(a[0] + 1));//4/8
	//a[0]是数组名,这里表示数组首元素的地址,是第一行第一个元素的地址
	//a[0] + 1就是第一行第二个元素的地址,是地址大小就是4/8
	printf("%zd\n", sizeof(*(a[0] + 1)));//*(a[0] + 1))是第一行第二个元素-大小是4个字节
	printf("%zd\n", sizeof(a + 1));//a+1就是第二行的地址,是地址就是4/8个字节
	//a是二维数组的数组名,在这里表示首元素的地址,也就是第一行的地址
	//a+1就是第二行的地址
	printf("%zd\n", sizeof(*(a + 1)));//16
	//a + 1是第二行的地址,*(a+1)得到的就是第二行
	//int(*)[4]         对数组指针解引用,放一个数组,就是一行的一维数组
	//*(a+1) == a[1], a[1]是第二行的数组名,sizeof(arr[1])计算的是第二行的大小
	//
	printf("%zd\n", sizeof(&a[0] + 1));//4/8:
	//a[0]是第一行的数组名,&a[0]取出的是第一行这个一维数组的地址
	//&a[0]+1就是第二行的地址
	printf("%zd\n", sizeof(*(&a[0] + 1)));//*(&a[0] + 1)是第二行,计算的是第二行的大小,16个字节
	printf("%zd\n", sizeof(*a));//16: a是二维数组的数组名,a是首元素的地址,就是第一行的地址,*a就是第一行
	//计算的是第一行的大小,16个字节
	//*a == *(a+0) == a[0]
	printf("%zd\n", sizeof(a[3]));//16: 没有越界访问,sizeof内部的表达式是不计算的.
	//sizeof(int) - 4
	//sizeof(4+3);--4
	return 0;
}

指针笔试题1

指针笔试题2

指针笔试题3

指针笔试题4

指针笔试题5

指针笔试题6

指针笔试题7

初始状态:

第一个printf结果:

第二个printf结果:(注意此时的c,cp,cpp三个指针的移动是基于上一个printf移动的结果,因为修改指针变量就相当于修改了变量本身)

第三个printf:

第四个printf:

相关推荐
浅时光_c2 小时前
3 shell脚本编程
linux·开发语言·bash
Evand J2 小时前
【三维轨迹目标定位,CKF+RTS,MATLAB程序】基于CKF与RTS平滑的三维非线性目标跟踪(距离+方位角+俯仰角)
开发语言·matlab·目标跟踪
_深海凉_3 小时前
LeetCode热题100-有效的括号
linux·算法·leetcode
今天又在写代码3 小时前
java-v2
java·开发语言
competes4 小时前
慈善基金投资底层逻辑应用 顶层代码低代码配置平台开发结构方式数据存储模块
java·开发语言·数据库·windows·sql
Ulyanov4 小时前
用Pyglet打造AI数字猎人:从零开始的Python游戏开发与强化学习实践
开发语言·人工智能·python
独自归家的兔4 小时前
OCPP 1.6 协议详解:StatusNotification 状态通知指令
开发语言·数据库·spring boot·物联网
希望永不加班5 小时前
Spring AOP 代理模式:CGLIB 与 JDK 动态代理区别
java·开发语言·后端·spring·代理模式
RNEA ESIO5 小时前
PHP进阶-在Ubuntu上搭建LAMP环境教程
开发语言·ubuntu·php