内容
1.函数指针
2.函数指针的功能
3.函数指针的应用场景
*1.函数指针
(1)函数指针
- 函数指针:定义一个指针变量,指向函数的首地址;
#include<stdio.h>
#include<stdlib.h>
void test() {
printf("funcPointer\n");
}
int main() {
printf("%p\n", test);
printf("%p\n", &test);
system("pause");
return 0;
}
- %p打印变量地址;代码中函数名test / &函数名 &test 都可以得到函数的地址;而指向函数地址的指针 就称为 函数指针(保存的是函数地址);
- 函数名 <=> &函数名;
(2)函数指针怎么定义?
例:
void func(){
printf("haha\n");
}
int add(int x,inty){
return x+y;
}
int main(){
void (*P)() = func;
int (*pp)(int a,int b)=add;
//p=pp; //报错,虽然都是指针,但是类型不同
}
如上所示:
- 定义一个函数指针 p 变量 指向函数 func ,函数指针类型为 void (*)();
- 定义了一个函数指针变量 pp 指向函数 add,函数指针类型为 int(*)(int,int);
- 这是两种不同类型的函数指针变量;
- 两个函数指针变量必须 参数个数 / 返回值类型都完全相同,才可认为两个指针类型相同、才能相互 赋值;(在C语言编译器中检查不出来,在C++编译器中可以检查出错误)
该错误列表中 ,明显看出两个指针类型不匹配,不能转换; 其中 _ _ cdecl 表示调用约定,这不是程序员需要关注的事情;
- 调用约定 :是操作系统和编译器需要关注的事情,站在机器语言的角度上,看待函数调用(把函数参数入栈,把函数返回值地址入栈)的过程;
- 入栈操作:通过esp/edp 寄存器 寄存调用栈、入栈顺序,不同的操作系统或者编译器存在差异;
(3)函数在内存中怎么存储(怎么进入内存)和使用
- 首先我们在C语言.c文件中编写了函数代码,编译后,将函数转化为二进制指令、并且写入exe 可执行程序中 / 文件中去 / 磁盘;
- 当双击 / 运行 exe程序/文件时,操作系统就会把exe 文件中的内容(二进制指令和数据)加载到内存中、同时创建一个进程,开始执行里面的指令;
*2.函数指针的功能
函数指针与其他指针的区别:
- 其他指针的功能很多;链接:指针的类型和使用
- 函数指针支持的功能非常少,主要功能是 ( )运算 ——函数调用;
*3.函数指针的应用场景
*3.1 回调函数-模拟实现qsort函数
回调函数:就是通过函数指针调用的函数;当把该函数的指针(地址)作为参数传递给另一个函数时;函数指针通过调用指向的该函数完成对某一事件的相应;
- 应用:针对结构体数组中不同成员/属性进行不同排序方式实现:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
typedef struct Student {
int id;
char name[100];
int score;
}Student;
//打印输出排序后的结构体
void print(Student a[], int len) {
for (int i = 0; i < len; i++) {
printf("%d %s %d\t", a[i].id,a[i].name ,a[i].score );
printf("\n");
}
}
//结构体传参无脑传指针
typedef int(*Stu)(Student* x, Student* y);
void bubbleSort(Student a[], int len,Stu stu) {
for (int bound = 0; bound < len; bound++) {
for (int cur = len - 1; cur > bound; cur--) {
// 因为函数指针指向的函数的形参是指针,故实际调用的实参为:&变量名
if (stu(&a[cur - 1], &a[cur]) == 1) {
Student temp = a[cur - 1];
a[cur - 1] = a[cur];
a[cur] = temp;
}
}
}
}
//按照id排列设置回调函数
int idDesc(Student* a, Student* b) { //降序
return a->id < b->id ? 1 : 0;
}
int idAsce(Student* a, Student* b) { //升序
return a->id > b->id ? 1 : 0;
}
//按照分数设置回调函数
int scoreDesc(Student* a, Student* b) {
return a->score < b->score ? 1 : 0;
}
int scoreAscec(Student* a, Student* b) {
return a->score > b->score ? 1 : 0;
}
//按照成绩升序排,如果成绩相同按照id 升序排
int scorAsce_idAsce(Student* a, Student* b) {
if (a->score == b->score) {
return a->id > b->id ? 1 : 0;
}
return a->score > b->score ? 1 : 0;
}
int main() {
Student arr[] = {
{1,"张三",78},
{3,"王五",89},
{4,"赵六",97},
{2,"李四",78}
};
int len = sizeof(arr) / sizeof(arr[0]);
bubbleSort(arr, len, scorAsce_idAsce);
print(arr, len);
bubbleSort(arr, len,idAsce);
print(arr, len);
system("pause");
return 0;
}
泛型编程:通过void *实现,针对数组/结构体数组的排序函数,能支持多种类型;
- void* 不能进行解引用,获取指针指向的数据时,将强制转换为 (char*),原因是因为该指针指向的内存是一个字节(内存最小单位),后续不管比较什么类型数据,指针移动的空间可以根据unitLen控制;
- unitLen 表示数组每一个元素的大小;
- 不同类型进行交换,通过创建临时变量进行数据拷贝)memcpy(参数1,参数2,拷贝len):表示将从2位置拷贝len个内存数据长度给1;
- 结构体传参——参数为指针类型(提高内存利用效率);
- 在定义不同的回调函数时,强制转换指针类型,因为指针都占4个字节的内存空间,故可以进行强制转换而不丢失数据;
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
typedef struct Student {
int id;
char name[100];
int score;
}Student;
void print1(int a[], int len) {
for (int i = 0; i < len; i++) {
printf("%d ", a[i]);
}
printf("\n");
}
void print2(Student a[], int len) {
for (int i = 0; i < len; i++) {
printf("%d %s %d\t", a[i].id,a[i].name ,a[i].score );
printf("\n");
}
}
typedef int(*Cmp)(void* a, void* b);
void bubbleSort(void* a, int len, int unitLen, Cmp judge) {
for (int bound = 0; bound < len; bound++) {
for (int cur = len - 1; cur > bound; cur--) {
char* p = (char*)a;
char* p1 = p + (cur - 1)*unitLen;
char* p2 = p + cur * unitLen;
if (judge(p1, p2) == 1) {
void* temp[1024] = { 0 }; //创建临时变量存储拷贝的数据
memcpy(temp, p1, unitLen);
memcpy(p1, p2, unitLen);
memcpy(p2, temp, unitLen);
}
}
}
}
//按照int型数据进行升序排列
int intAsce(void* a, void* b) {
int* x = (int*)a;
int* y = (int*)b;
return *x > *y ? 1 : 0;
}
//按照结构体数据进行降序排列
int strDesc(void* a, void* b) {
Student* x = (Student*)a;
Student* y = (Student*)b;
return x->id < y->id ? 1 : 0;
}
int main() {
int arr[] = { 2,4,5,3,6,1 };
int len1 = sizeof(arr) / sizeof(arr[0]);
bubbleSort(arr, len1, sizeof(arr[0]), intAsce);
print1(arr, len1);
Student stu[] = {
{2,"张三",68},
{1,"李四",87},
{4,"王五",90},
{3,"赵六",79}
};
int len2 = sizeof(stu) / sizeof(stu[0]);
bubbleSort(stu, len2, sizeof(stu[0]), strDesc);
print2(stu, len2);
system("pause");
return 0;
}
- C语言中类似库函数 qsort函数(泛型编程),不建议使用,真正进行多类型多功能实现时,利用C++中的模板代表任何类型,更为简洁和直观;链接:C++模板实现
*3.2 转移表-计算器的实现
- 圈复杂度:衡量一个代码的可读性,没有条件选择 / 循环的代码圈复杂度为1; 有条件+1 / 有循环圈复杂度加1; 圈复杂度越高,代码可读性越差;一般一个函数圈复杂度应该 <10;
- 降低代码圈复杂度的方法:
- 抽取新的函数;
- 通过函数指针数组—转移表(表驱动的方式);
(1)没用转移表的实现
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
//实现计算器功能
int add(int a, int b) {
return a + b;
}
int sub(int a, int b) {
return a - b;
}
int mult(int a, int b) {
return a * b;
}
int division(int a, int b) {
return a / b;
}
int main() {
int a = 0;
int b = 0;
int ret = 0;
printf("欢迎使用加减乘除计算器,请输入两个整数:");
scanf("%d %d", &a, &b);
int choice = 0;
again:
printf("请选择功能:1.加法;2.减法;3.乘法;4.除法\n");
scanf("%d", & choice);
if (choice == 1) {
ret = add(a, b);
}
else if (choice == 2) {
ret = sub(a, b);
}
else if (choice == 3) {
ret = mult(a, b);
}
else if (choice == 4) {
ret = division(a, b);
}
else{
printf("输入错误!!!\n");
goto again;
}
printf("结果为:%d\n", ret);
system("pause");
return 0;
}
- 虽然本程序圈复杂度没有大于10,但是随着计算器功能的增加,不但函数越来越多,而且可读性也低;
(2)用转移表的方式实现
- 利用函数指针数组(转移表/表驱动的方式)降低圈复杂度;
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
//实现计算器功能
int add(int a, int b) {
return a + b;
}
int sub(int a, int b) {
return a - b;
}
int mult(int a, int b) {
return a * b;
}
int division(int a, int b) {
return a / b;
}
//定义一个函数指针(两个整型参数)返回值也为整型
typedef (*func)(int, int);
int main() {
func arr[]={ //定义一个数组,数组内储存的是函数指针
NULL,
add,
sub,
mult,
division
};
int a = 0;
int b = 0;
printf("欢迎使用加减乘除计算器,请输入两个整数:");
scanf("%d %d", &a, &b);
int choice = 0;
printf("请选择功能:1.加法;2.减法;3.乘法;4.除法\n");
scanf("%d",& choice);
int ret = arr[choice](a, b);
printf("结果为:%d\n", ret);
system("pause");
return 0;
}