指针进阶2
1. 函数指针数组
数组:
是一个存放相同数据类型的存储空间
在指针进阶1中我们已经了解了:
指针数组:
char* arr[5];//字符指针数组-数组-存放的是字符指针
int* arr[10];//整形指针数组-数组-存放的是整形指针
//数组的每个元素是char* 或者int*
函数指针数组与此类似:
函数指针数组,顾名思义-数组-存放的是函数指针,存放的是函数的地址
函数指针:
- 函数指针数组的定义:
int (*parr1[10])();//与函数指针非常相似
//函数指针的定义: int(*parr1)()
parr1
先与[]组合,说明了parr1
是数组
是int(*)()
类型的函数指针
- 函数指针的用途: 转移表
例子:(计算器)
//加减乘除函数的定义
//加法
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 menu()
{
printf("****************************\n");
printf("*** 1. add 2. sub ***\n");
printf("*** 3. mul 4. div ***\n");
printf("*** 0. exit ***\n");
printf("****************************\n");
}
int main()
{
int input = 0;
int x = 0;
int y = 0;
int ret = 0;
do
{
menu();
printf("请选择>");
scanf("%d", &input);
switch (input)
{
case 1:
printf("请输出两个操作数:");
scanf("%d %d", &x, &y);
ret = Add(x, y);
printf("ret=%d\n", ret);
break;
case 2:
printf("请输出两个操作数:");
scanf("%d %d", &x, &y);
ret = Sub(x, y);
printf("ret=%d\n", ret);
break;
case 3:
printf("请输出两个操作数:");
scanf("%d %d", &x, &y);
ret = Mul(x, y);
printf("ret=%d\n", ret);
break;
case 4:
printf("请输出两个操作数:");
scanf("%d %d", &x, &y);
ret = Div(x, y);
printf("ret=%d\n", ret);
break;
case 0:
printf("退出计算器\n");
break;
default:
printf("选择错误,请重新选择\n");
break;
}
} while (input);
return 0;
}
利用函数指针数组实现:
int main()
{
//模式的选择
int input = 0;
//输入的两个数
int x = 0;
int y = 0;
int ret = 0;
do
{
menu();
printf("请选择:>");
scanf("%d %d", &input);
//函数指针数组 - 转移表
int(*pfArr[])(int, int) = {NULL, Add, Sub, Mul, Div};
//因为只是只是声明了函数指针的类型,并不使用参数int后面不加参数
if (0 == input)
{
printf("退出计算机\n");
}
else if (input >= 1 && input <= 4)
{
printf("请输入2个操作数: ");
scanf("%d %d", &x, &y);
ret = pfArr[input](x, y);
//(*pfarr)[input](x, y)
printf("ret = %d\n", ret);
}
else
{
printf("选择错误,重新选择!\n");
}
} while (input != 0);
return 0;
}
函数指针可以通过两种方式进行调用:
使用函数指针名称后面跟随参数列表,或者使用解引用操作符(*)来调用指针所指向的函数并传递参数列表。
因此,ret = pfArr[input](x, y);
和ret = (*pfArr[input])(x, y);
都会调用指针所指向的函数,并传递参数x和y,然后将返回值赋给ret变量。
函数:Add(int x, int y) 函数名就是该函数的地址,即Add<->&Add
两种方式的效果是相同的。
2. 指向函数指针数组的指针
int a = 10;
int b = 20;
int c = 30;
int* arr[] = {&a, &b, &c};//整形指针数组
int* (* p)[3] = &arr;//p是指针,是指向整形指针数组的指针
//函数指针数组-数组-存放的是函数的地址
int (*pfArr[5])(int, int) = {NULL, Add, Sub, Mul, Div};//pfArr是函数指针数组
p = &pfArr;
int (*(*p)[5])(int, int) = &pfArr;
3. 回调函数
依赖函数指针,有了函数指针才能实现回调函数
回调函数就是一个通过函数指针调用的函数,如果把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由改函数的实现方直接调用,而是在特定的事件或者条件发生时由另外的一方调用的,用于对该事件或者条件进行响应.
利用回调函数优化计算器代码
void calc(int (*pf)(int,int))
{
int x = 0;
int y = 0;
int ret = 0;
printf("请输入2个操作数:");
scanf("%d %d", &x, &y);
ret = pf(x, y);
printf("ret = %d\n", ret);
}
int main()
{
int input = 0;
do
{
menu();
printf("请选择:>");
scanf("%d", &input);
switch (input)
{
case 1:
calc(Add); //把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);
return 0;
}
回调函数案例: qsort
(qsort
是一个库函数)
- 函数可以直接使用
- 函数可以用来排序任意类型的数据
- 底层使用的快速排序的方法,对数据进行排序
对数据排序的一些方法:
- 冒泡排序
- 选择排序
- 插入排序
- 快速排序
- 冒泡排序
- 冒泡排序思想:两两相邻的元素进行比较
- 冒泡排序的具体实现思路:
3. **冒泡排序的弊端**:只能排序一类数据
4. **冒泡算法的代码实现**:
```c
void print_arr(int* pc, int sz)//int arr[],本质上是int* arr,如果写成int arr[],只是便于初学者理解
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", pc[i]);
}
printf("\n");
}
void bubble_sort(int* pc, int sz)
{
//趟数
int i = 0;
for (i = 0; i < sz - 1; i++)
{
//每一趟冒泡排序的过程
int j = 0;
for (j = 0; j < sz - 1 - i; j++)
{
if (pc[j] > pc[j + 1])
{
int tmp = pc[j];
pc[j] = pc[j + 1];
pc[j + 1] = tmp;
}
}
}
}
int main()
{
//数据
int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
int sz = sizeof(arr) / sizeof(arr[0]);
print_arr(arr, sz);
bubble_sort(arr, sz);//冒泡排序
print_arr(arr, sz);
return 0;
}
```
**注意**:
在函数定义中,**形参是用来接收函数调用时传递进来的实参的变量。形参和实参可以同名,但是它们是两个不同的变量,分别在不同的作用域中**。
当形参和实参同名时,函数内部的代码会优先使用形参的值,而不是外部传入的实参的值。这意味着在函数内部,同名的形参会屏蔽外部的同名实参。
示例代码如下:
```c
def add(x, y):
result = x + y
return result
x = 2
y = 3
print(add(x, y)) # 输出 5
```
在这个例子中,函数`add`的形参`x`和`y`与外部的同名变量`x`和`y`重名,但它们是两个不同的变量。在函数内部,`x`和`y`分别代表函数调用时传入的实参值,而外部的`x`和`y`保持不变。函数内部的计算结果会使用形参的值,所以最终输出结果为5。
需要注意的是,虽然形参和实参可以同名,但这样的命名方式会增加代码的可读性和维护性的难度,容易引起混淆和错误,不建议在实际开发中使用。最好避免同名的形参和实参,或者使用更具描述性的命名方式来避免混淆。
**关于void***
void* 类型的指针 - 不能进行解引用操作符,也不能进行±整数的操作
void* 类型的指针是用来存放任意类型数组的地址
void* 无具体类型的指针
int main()
{
char c = 'w';
char* pc = &c;
int a = 100;
//int* p = &c;
// char*
void* pv = &c;//char*
pv = &a;//int*
return 0;
}
- 使用回调函数,模拟实现
qsort
(采用冒泡的方式)
#include<stdio.h>
#include <stdlib.h>
#include <string.h>
void print_arr(int arr[], int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
printf("\n");
}
int cmp_int(const void* e1, const void* e2)
{
return *(int*)e1 - *(int*)e2;
}
//测试qsort排序整型数据
void test1()
{
int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
int sz = sizeof(arr) / sizeof(arr[0]);
print_arr(arr, sz);
qsort(arr, sz, sizeof(arr[0]), cmp_int);
print_arr(arr, sz);
}
//测试qsort排序结构体数据
struct Stu
{
char name[20];
int age;
};
//结构体数据怎么比较呢?
//1. 按照年龄比较
//2. 按照名字比较
int cmp_stu_by_age(const void* e1, const void* e2)
{
return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
}
void test2()
{
struct Stu arr[] = { {"zhanhsan", 20}, {"lisi", 30}, {"wangwu", 12} };
int sz = sizeof(arr) / sizeof(arr[0]);
qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_age);
}
//2. 按照名字比较
int cmp_stu_by_name(const void* e1, const void* e2)
{
return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);
}
void test3()
{
struct Stu arr[] = { {"zhanhsan", 20}, {"lisi", 30}, {"wangwu", 12} };
int sz = sizeof(arr) / sizeof(arr[0]);
qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_name);
}
int main()
{
//数据
//test1();
//test2();
test3();
return 0;
}