目录
先回顾一下上一节的部分内容
数组指针:指向数组的指针
函数指针:指向函数的指针
再加深一下上节课讲过的代码的理解
函数指针数组
实现一个计算器
回调函数
qsort
1.测试qsort排序整型数据
2.测试qsort排列结构体数据
int main()
{
int arr[10] = { 0,1,2,3,4,5,6,7,8,9 };
int(*parr)[10] = &arr;
//*号告诉我们parr存放了数组的地址的指针,这个数组的元素个数是10,数组中每个元素的类型是int
//parr存放了数组的地址,而数组的地址是以首元素的地址为起点
int i = 0;
int sz = sizeof(arr) / sizeof(int);
for (i = 0; i < sz; i++)
{
printf("%d ", *((*parr)+i));
//*((*parr) + i)先解引用parr找到数组(以首元素的地址为起点),
//然后再+i找到下标为i的地址,再解引用找到下标为i的元素的值
}
return 0;
}
//先创建一个函数
int test(char*pc, int a)
{
//......
return 0;
}
//接下来创建一个指针指向test()这个函数
int main()
{
int(*pf)(char*,int) = &test;
//*号告诉我们pf存放了函数test的地址,这个函数的第一个参数类型是char*,第二个参数的类型是int,
//这个函数的返回值类型是int
//通过函数指针去调用这个函数
(*pf)("name", 26);
//pf存放了test函数的地址,解引用之后找到这个函数,然后给这个函数传参"name", 26
//其实pf前面的*号可以不用写,因为pf本身存放的就是test函数的地址,可以不用解引用
//由于函数名就是函数的地址
//之前我们常规的写法是test("name", 26),也是直接通过函数名来调用函数,所以不用解引用
//可以直接写成pf("name", 26)
return 0;
}
void(* signal( int,void(*)(int) ))(int)
signal(int, void(*)(int))是一个函数声明,
这个函数的第一个参数类型是int,第二个函数类型是函数指针类型,
也就是说第二个参数是存放了一个函数地址的指针(它的函数参数类型是int,返回值类型是void)
void(* )(int) 这个是signal函数的返回值类型(这个返回值是存放了一个函数地址的指针(它的函数参数类型是int,返回值类型是void))
总的来说,上面这整段代码是一个函数声明
如果写成这样的话就比较容易看出来:void(* )(int) signal( int, void(*)(int) )
但是对于返回值为函数指针类型的函数来说,这样写声明是错误的
所以必须将函数写进返回值类型中*后面,于是就成了这样:void(* signal( int,void(*)(int) ))(int)
补充一点
函数的声明,以下两种写法都是对的
int Add(int x,int y)
int Add(int, int)只指定参数类型,没有定义形参
如果要简化上述的void(* )(int) 指针类型,可以用typedef重命名
typedef void(* )(int) pf_t;//但是这种写法是错误的
对于函数指针类型的重命名,应该这样写
typedef void(* pf_t )(int);将重命名写进原名中*后面
重命名后,void(* signal( int,void(*)(int) ))(int)就可以这样写了:
pf_t signal(int,pf_t);
注意重命名时typedef void(* pf_t )(int)里面的pf_t不是函数指针变量的名字,而是重命名后函数指针类型的别名
但是如果将typedef去掉,则void(* pf_t )(int)里面的pf_t就变成函数指针变量名了
复习一下:extern是用来声明外部符号的,来自其他源文件的,要加extern,来自本文件的话可以不用加
新知识:
//先类比一下:
//char* arr[5];//字符指针数组
//int* arr[6];//整型指针数组
//把指针可以放在数组中
//函数指针也是指针,是否也能放在数组中呢?yes!!!
//这种数组就是函数指针数组
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(*pf)(int,int) = Add;//pf是函数指针
int (*pfarr[4])(int, int) = {Add,Sub,Mul,Div};//存放函数指针的数组-函数指针数组
//在函数指针的指针变量后面加上[数组元素的个数]就变成了函数指针数组
//函数个数不写也可以int (*pfarr[])(int, int)
//int (*)(int, int)这是函数指针类型
//以上就是一个存放了四个函数地址的数组
//如何访问这个函数指针数组?
int i = 0;
for (i = 0; i < 4; i++)
{
//int ret =*pfarr[i](6, 2);
//也可以把*去掉,函数指针去调用函数的时候,*号是个摆设,
//比如我们平时写到调用函数是:函数名(),
//而函数名存放的就是函数的地址,
//那指针变量名pfarr存放的也是函数的地址,
//我们通过函数名或者指针变量名里面存放的函数地址找到函数,然后传参
//也可以通过pfarr解引用后找到函数,传参
int ret=pfarr[i](6,2);//访问i为下标的元素
//第一个元素(函数)是做加法
//第二个元素(函数)是做减法
//第三个元素(函数)是做乘法
//第四个元素(函数)是做除法
printf("%d\n", ret);//8 4 12 3
}
return 0;
}
//这个计算器能够计算整数的:加法,减法,乘法,除法
void menu()
{
printf("********1.Add*********\n");
printf("********2.Sub*********\n");
printf("********3.Mul*********\n");
printf("********4.Dev*********\n");
printf("********0.Exit********\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;
int y = 0;
int ret = 0;
do
{
menu();
printf("请输入运算类型:\n");
scanf("%d", &input);
switch (input)//以input作为执行条件
{
case 1:
printf("请输入两个操作数:");
scanf("%d %d", &x, &y);
ret = Add(x, y);
printf("%d\n", ret);
break;
case 2:
printf("请输入两个操作数:");
scanf("%d %d", &x, &y);
ret = Sub(x, y);
printf("%d\n", ret);
break;
case 3:
printf("请输入两个操作数:");
scanf("%d %d", &x, &y);
ret = Mul(x, y);
printf("%d\n", ret);
break;
case 4:
printf("请输入两个操作数:");
scanf("%d %d", &x, &y);
ret = Div(x, y);
printf("%d\n", ret);
break;
case 0:
printf("退出计算器\n");
break;
default:
printf("输入错误,请重新输入\n");
break;
}
} while (input);//intput不是0时,进入循环,input为0时,退出循环
return 0;
}
//以上代码可以优化以下,用函数指针数组来实现
//这里的函数指针数组,我们称为转移表
void menu()
{
printf("********1.Add*********\n");
printf("********2.Sub*********\n");
printf("********3.Mul*********\n");
printf("********4.Dev*********\n");
printf("********0.Exit********\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;
do
{
menu();
printf("请输入运算类型:");
scanf("%d", &input);
if (input==0)
{
printf("退出计算器\n");
}
else if (input <=4)
{
int x = 0;
int y = 0;
printf("请输入两个操作数:");
scanf("%d %d", &x, &y);//补充:scanf其实是有返回值,这里它的返回值是int,但是我们不关心它的返回值,编译器就会报警,不过不用管
int (*pfarr[5])(int, int) = { NULL,Add,Sub,Mul,Div };
int ret = pfarr[input](x, y);
printf("%d\n", ret);
}
else
{
printf("输入错误,请重新输入\n");
}
} while (input);
return 0;
}
//这样优化之后,如果还想添加其他的运算类型的话,需要改变的代码比较少也比较简便
//函数指针到底有什么用呢?
//函数指针可以用来实现回调函数:在一个函数里面通过一个函数指针去调用另一个函数,则被调用的函数就叫回调函数
//这种调用函数的方式是间接的,不是直接的
//以上代码还可以用回调函数优化:
void menu()
{
printf("********1.Add*********\n");
printf("********2.Sub*********\n");
printf("********3.Mul*********\n");
printf("********4.Dev*********\n");
printf("********0.Exit********\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))//函数的返回值没有在主函数中赋值给谁的话,
//我们就不用返回值,直接写void,只让它执行这段代码,不需要返回值
{
int x = 0;
int y = 0;
printf("请输入两个操作数:");
scanf("%d %d", &x, &y);
int ret = pf(x, y);//只有调用这里不同,可以用回调函数解决,将函数的地址作为参数传给另一个函数
//pf存放了函数的地址,可以通过函数地址找到函数,传参x,y过去进行计算
//如果传过来的是Add函数的地址,然后通过指针pf在这里调用Add函数,则Add就被称为回调函数
printf("%d\n", ret);
}
int main()
{
int input = 0;
do
{
menu();
printf("请输入运算类型:\n");
scanf("%d", &input);
switch (input)//以input作为执行条件
{
case 1:
calc(Add);//把加法函数的地址(函数名即函数地址)传给calc函数,让它计算结果
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);//intput不是0时,进入循环,input为0时,退出循环
return 0;
}
//函数指针的使用:qsort
//qsort是库函数,这个函数可以完成任意类型数据的排序
怎么使用qsort进行排序呢?
//先复习一下冒泡排序
void Bubble_arr(int arr[], int sz)//这段冒泡排序的缺陷是:只能排序整型数据
{
int i = 0;//趟数
for (i = 0; i < sz - 1; i++)
{
//一趟冒泡排序的过程
//两两相邻元素的比较
int j = 0;//下标
for (j = 0; j < sz - 1 - i; j++)
{
if (arr[j] > arr[j + 1])
{
int tmp = 0;
tmp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = tmp;
}
}
}
}
void Print_arr(int arr[], int sz)
{
int i = 0;//下标
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
printf("\n");
}
int main()
{
int arr[10] = { 1,4,7,8,5,2,3,6,9,0 };
int sz = sizeof(arr) / sizeof(int);
Print_arr(arr, sz);//打印
Bubble_arr(arr, sz);//升序
Print_arr(arr, sz);//打印
return 0;
}
//前面说过由于冒泡排序Bubble_arr这个函数是参数写死了是int类型
//所以这段冒泡排序的缺陷是:只能排序整型数据
//但是qsort函数就能实现任意数据的排序
//先认识一个qsort函数的参数
void qsort
(
void* base,//base指向了要排序的数组的第一个元素
size_t num,//base指向的数组中的元素个数(待排序的数组的元素的个数)
size_t size,//bese指向的数组中元素的大小(单位是字节)
int(*compar)(const void*, const void*)//函数指针-指针指向的函数是用来比较数组中的2个元素的,
//比如该函数两个参数是(const void*p1, const void*p2),
//如果p1大于p2,那函数就返回一个>0的数字,
//如果p1小于p2,那函数就返回一个<0的数字,
//如果p1等于p2,那函数就返回0
)
注意:使用qsort函数,要加上#include
//补充:两个整型元素比较大小直接可以用> < ==比较
//但是两个结构体的数据不能直接用> < ==来比较
//所以qsort函数参数中compar指针就是专门抽离出来实现比较的函数,类似于前面讲过的calc函数中的pf
//qsort使用举例
//1.使用qsort函数排列整型数据
//2.使用qsort函数排列结构体数据
void print_arr(int arr[], int sz)
{
int i = 0;//下标
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
printf("\n");
}
int cmpr_int(const void* p1,const void* p2)
//p1 p2存的是arr数组中的某两个元素的地址,比较时,要解引用
{
return *(int*)p1 - *(int*)p2;
//void*指针不能直接解引用,比如void* p1中的p1不能直接解引用,要先强制转换类型再解引用
//这里要解引用为数组里面元素的类型,即和数组元素的类型保持一致
// 如果p1大于p2,那函数就返回一个>0的数字,
// 如果p1小于p2,那函数就返回一个<0的数字,
// 如果p1等于p2,那函数就返回0
//那如果想要降序排列怎么办?
//补充qsort函数里面的逻辑关系
//void qsort
//(
// void* base,//base指向了要排序的数组的第一个元素
// size_t num,//base指向的数组中的元素个数(待排序的数组的元素的个数)
// size_t size,//bese指向的数组中元素的大小(单位是字节)
// int(*compar)(const void*, const void*)//函数指针-指针指向的函数是用来比较数组中的2个元素的
//)
//{
// if (compar(x, y) > 0)//实现升序
// {
// //交换
// }
//}
//只需要对调位置,让返回值的值的逻辑是相反的就可以了:
//return *(int*)p2 - *(int*)p1;
//原先只要p1>p2,就调换位置,反过来就是只要p2>p1就调换位置
}
void test1()
{
int arr[10] = { 1,4,7,8,5,2,3,6,9,0 };
int sz = sizeof(arr) / sizeof(int);
Print_arr(arr, sz);//打印
qsort(arr, sz,sizeof(int),cmpr_int);//升序
Print_arr(arr, sz);//打印
}
int main()
{
//将一组数据排序为升序
test1();//整型排序
return 0;
}
//先复习一下结构体:
struct stu //描述一个学生的信息可以用students来做结构体的标签名,struct stu这个东西就像做月饼的模具
{
char name[20];//字符串的长度为20
int age;
float score;
}s3 = { "wangwu",33,66.0f }, s4 = { "翠花",18,100.0f };//要有分号,就算不在这里定义结构体变量和初始化结构体成员也要分号结束
//这里的s3和s4(是结构体变量)它们就像是用struct stu模具做出来的两个月饼,变量中成员的初始值相当于是月饼(结构体变量)的馅
//当然定义结构体变量和初始化结构体成员可以不用在这里进行,可以在主函数中进行:
int main()
{
struct stu s1 = { "zhangsan",20,95.5f};
struct stu s2 = { "lisi",18,87.5f };
// 注意:在结构体后面定义结构体变量和初始化结构体成员时,结构体变量属于全局变量;
//而在主函数中定义结构体变量和初始化结构体成员时,结构体变量属于局部变量
//注意:一般初始化的时候要严格按照创建结构体中的成员列表中的顺序
//打印结构体:
printf("%-12s %-8d %-8f\n", s1.name, s1.age, s1.score);
printf("%-12s %-8d %-8f\n", s2.name, s2.age, s2.score);
printf("%-12s %-8d %-8f\n", s3.name, s3.age, s3.score);
printf("%-12s %-8d %-8f\n", s4.name, s4.age, s4.score);
//使打印结果对齐时,可以在占位符中间输入对齐的个数,左对齐就负号,右对齐正数(不用输)
return 0;
}
//怎么比较2个结构体数据?-不能直接使用> < ==来比较
//1.可以按照名字比较
//2.可以按照年龄比较
//创建结构体:
struct stu
{
char name;
int age;
};
//通过年龄比较
int cmpr_stu_by_age(const void* p1, const void* p2)
{
return ((struct stu*)p1)->age - ((struct stu*)p2)->age;
//*(struct Stu*)p1.age 强制类型转换为结构体指针,p1存放的结构体的地址,解引用后找到结构体,用.号来直接访问这个结构体的某个成员
//但是结构体指针我们一般不用解引用的方式,我们可以强制类型转换成结构体指针,由于p1存放的就是结构体的地址,我们可以用->号来间接访问结构体的某个成员
}
void test2()
{
//结构体数组-初始化
struct stu arr[] = { {"zhangsan",12},{"lisi",34},{"wangwu",23} };
int sz=sizeof(arr) / sizeof(arr[0]);
qsort(arr, sz, sizeof(arr[0]), cmpr_stu_by_age);//通过年龄比较
}
//通过名字比较
int cmpr_stu_by_name(const void* p1, const void* p2)
{
return strcmp(((struct stu*)p1)->name, ((struct stu*)p2)->name);
//但是名字是字符串,那两个字符串是怎么比较大小的?
//两个字符串不能直接使用> < ==来比较
//而是使用库函数strcmp-string compare,使用方法只需要将p1和p2指向的值传给strcmp函数就可以了
//#include//strcmp的头文件
//这个函数比较字符串时比较的不是字符串的长度,而是比较内容,
//也就是比较对应位置上的字符的ASCII码值
//比如:
//abcdef
//abq
//由于c的ASCII码值小于q,所以"abq"大于"abcdef"
// 使用库函数strcmp比较时
// 如果p1大于p2,那函数就返回一个>0的数字,
// 如果p1小于p2,那函数就返回一个<0的数字,
// 如果p1等于p2,那函数就返回0
}
void test3()
{
//结构体数组-初始化
struct stu arr[] = { {"zhangsan",12},{"lisi",34},{"wangwu",23} };
int sz = sizeof(arr) / sizeof(arr[0]);
qsort(arr, sz, sizeof(arr[0]), cmpr_stu_by_name);//通过名字比较
}
int main()
{
//将一组数据排序为升序
test2();//通过年龄比较
test3();//通过名字比较
return 0;
}
//qsort是C语言定义好的库函数(用来排序任意类型的数据),
//作为C语言的使用者,我们只需要遵守语法规则去使用就OK了;
//qsort的底层用的是快速排序
预知后事如何,请听下回分解......