转移表
假设现在要实现一个计算器,功能是完成整数的加,减,乘,除。
原始版本:
以下是最好写也是最好想的初代计算机的实现版本,初步观察一下就会发现问题,每个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: 