函数指针
1.函数地址的理解
函数地址较为特殊,函数名和&函数名都是函数的地址
int Add(int x, int y);
- Add / &Add均表示函数的地址
// 代码测试
int Add(int x, int y)
{
return x + y;
}
int main()
{
printf("%p\n", Add);
printf("%p\n", &Add);
}
2.函数指针的用法
函数指针指向函数的地址,可以通过指针直接调用该函数
int (*pf)(int, int)
,分析*pf指向函数地址,int为返回值类型,(int, int)是参数的类型调用的特殊性:
int (*pf)(int, int) = Add;
,利用指针调用是可以用int ret = (*pf)(3, 4);
或者int ret = pf(3, 4);
来调用,函数指针指向函数的地址(函数名 / &函数名),通过指针可以调用函数
重点(记忆):对于函数指针的理解,
void (*pf)(int, int)
,*pf表明这是一个函数指针,指向的函数参数为(int ,int),返回类型为void,pf中存放的是Add函数的地址,其pf类型为void (*)(int, int)
,类比int* pi = &a;
,*pi表明pi是一个指针,pi中存放的是a的地址,其pi的类型为int*
所以函数指针中的*表明pf是一个指针,其类型为
void (*pf)(int, int)
重点:函数指针的typedef的特性:
typedef void (*pf)(); - pf就是函数指针
typedef void(*ptr_t)(int); // 函数指针typedef特性
int main()
{
// 强制类型转换 - 函数指针类型的强转 - 一个常数被函数指针强转类型转换就是将这个函数变量存放在这个常数对应的地址上
// 当这个常数被强转后 - 调用解引用*就会调用此地址处的函数
// 0x0012ff40 - int
// 强转:void(*)() - 在这个int类型对应的地址(0x0012ff40)上存放一个函数变量
// 调用0地址处的函数
// 1.将0强制类型转换成void(*)()类型的指针
// 2.调用0地址处的这个函数 - 最后一个()表示传参
(*(void(*)()) 0)();
// 本程序用户无法使用(崩溃),用户不能访问0地址 - 系统程序可以使用
// void(*pf)() = Test();
// 调用 pf() / (*pf)()
void(*signal(int, void(*)(int)))(int);
// ptr_t后面有参数就替换掉signal,没有就直接*
ptr_t signal(int, ptr_t);
}
3.函数指针数组的用法
函数指针经常配合函数指针数组使用
int(*pf[])(int ,int) = {nulluptr};
表示
重点:函数指针数组拆分理解,
int(*pf[])
,pf[ ]表明这是一个数组,去掉pf[ ]后是int (*)(int, int)
,表明数组中存放的类型是int (*)(int, int)
,即存放的是函数指针总结,若为指向函数的函数指针则是pf,类型为
void (*)(int, int)
,若是存放函数指针的函数指针数组则是pf[ ],类型同为`void (*)(int, 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");
}
int main()
{
int input;
do
{
menu();
int x, y, ret;
cin >> input;
// []的结合性大于*
// int(*pf)(int, int) - pf,表明是一个函数指针,去掉pf,对应类型为函数指针 - int(*)(int, int)
// int(*pfArr[])(int, int) - pfArr[]表明是一个函数指针数组 - 去掉pfArr[],对应类型为函数指针 - int(*)(int, int)
// 类型是数组 - *表示存储的类型
// int(*(*pf)[])(int, int) - *pf表明这是一个指针,[]表明*pf是一个指向数组的指针 - 去掉(*pf)[],对应类型为函数指针 - int(*)(int, int)
// 类型是指针 - *表示指向的类型是一个数组(&数组名)
// 所以此类型对应的是指向函数指针数组的指针
// Error:int((*pf)[])(int, int) - *pf表明是一个指针,[]表示指向数组的指针,但去除(*pf)[],类型是int()(int, int),不再能表示函数指针数组的类型
// 转移表
int (*pfArr[5])(int, int) = { nullptr, Add, Sub, Mul, Div };
if (input >= 1 && input <= 4)
{
printf("请输入两个数:");
cin >> x >> y;
ret = pfArr[input](x, y);
cout << ret << endl;
}
else if (input == 0)
{
printf("退出程序\n");
}
else
{
printf("输入有误,请重新输入\n");
}
} while (input);
}
4.指向函数指针数组的指针
指向函数指针数组的指针:
int(*(*pf)[])(int, int)
,拆分理解,因为[ ]的结合性比*要高,所以要加( ),*pf先表明这是一个指针,若是指向函数地址,则直接pf跟函数类型,这是指向函数指针数组,则[ ]表明这个指针指向的是一个数组,去除(*pf)[]
,剩下的是int (*)(int, int)
,表明数组的类型int (*)(int ,int)
,因为数组中存放的数据类型是函数指针,所以数组的类型也是函数指针类型
误区:Error:
int((*pf)[])(int, int)
- *pf表明是一个指针,[]表示指向数组的指针,但去除(*pf)[ ],类型是int()(int, 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;
}
int main()
{
int (*pfArr[5])(int, int) = {nulluptr, Add, Sub, Mul, Div};
// 首先写类型 int(*)(int, int)
// 指向函数数组 - 指针类型:int(* (*pf))(int, int)
// 指向数组表示 - int (*(*pf)[])(int, int)
// 注:第一个*表示的是函数指针类型
int (*(*pft_arr)[])(int, int) = &pfArr;
}
总结:方法总结: []的结合性大于*
1.int(*pf)(int, int)
- pf,表明是一个函数指针,去掉pf,对应类型为函数指针 -int(*)(int, int)
2.int(*pfArr[])(int, int)
- pfArr[]表明是一个函数指针数组 - 去掉pfArr[],对应类型为函数指针 -int(*)(int, int)
,类型是数组 - *表示存储的类型
3.int(*(*pf)[])(int, int)
- pf表明这是一个指针,[]表明pf是一个指向数组的指针 - 去掉(*pf)[],对应类型为函数指针 -int(*)(int, int)
,类型是指针 - *表示指向的类型是一个数组(&数组名,所以此类型对应的是指向函数指针数组的指针Error:
int((*pf)[])(int, int)
- *pf表明是一个指针,[]表示指向数组的指针,但去除(*pf)[],类型是int()(int, int)
,不再能表示函数指针
5.回调函数(重点)
回调函数是函数指针的特性,利用函数指针可以使一个函数支持多种模板,int / char* / struct等参数,C++是使用仿函数来实现的(模板 + 结构体 + 运算符重载)
>void*作用补充:void*的指针 - 无具体类型的指针
> void*类型的指针可以接收任意类型的地址(特性)
> 这种类型的指针不能直接解引用操作
> void*类型的指针不能直接进行指针运算
回调函数的作用,初始的
void Bububble_sort(int arr, int size);
,仅能只能int类型,改造版的是void*
类型,可以传任意类型,利用函数指针进行回调,传相应的比较函数进行比较
Bubble _sort改造
1.传入数组的类型不确定,所以用void*(
void*类型的指针可以接收任意指针,在函数中再char*找起始位置
) / 2.要传入数组数据的大小,便于确定起始地址(回调函数的作用是确定从起始位置向后找多少位字节的数据)/ 3.(char*)找起始位置,因为char*
的步长是一个字节,例如6间隔可以char*
+ 6 / 4.注意cmp函数的返回类型,参数要和传参保持一致
回调函数要将指针的类型强转,作用是确定从起始位置访问数据的步长,解引用*取出相应数据,进行比较
C++中采用模板的偏特化 + 结构体 + 运算符重载实现的仿函数实现,仿函数能在模板的作用下适应不同类型,
int
就调用Int的运算符重载,string
就调用sting的
// 模拟实现库中的qsort() - 回调
// int类型 / 结构体封装类型
struct People
{
const char* name;
int age;
};
void Swap(char* buf1, char* buf2, int length)
{
char tmp = 0;
for (int i = 0; i < length; ++i)
{
// 一个字节一个字节交换
tmp = *buf1;
*buf1 = *buf2;
*buf2 = tmp;
++buf1;
++buf2;
}
}
// size - 数组大小 / length - 每个元素的大小
// char*找到要比较数据的起始位置
// 回调函数中有强转,确定取数据的步长
void Bubble_sort(void* base, int size, int length, int(*cmp)(const void*, const void*))
{
// 排升序
for (int i = 0; i < size - 1; ++i)
{
for (int j = 0; j < size - 1 - i; ++j)
{
if (cmp((char*)base + j * length, (char*)base + (j + 1) * length) > 0)
{
Swap((char*)base + j * length, (char*)base + (j + 1) * length, length);
}
}
}
}
// int类型的cmp_int
int cmp_int(const void* base1, const void* base2)
{
// int*的意义:从给定位置向后访问4个字节
return (*(int*)base1) - (*(int*)base2);
}
// 结构体
// name
int cmp_string(const void* base1, const void* base2)
{
// struct People*的意义:从给定位置向后访问sizeof(struct People*)个字节
return strcmp(((struct People*)base1)->name, ((struct People*)base2)->name);
}
// age
int cmp_age(const void* base1, const void* base2)
{
return ((struct People*)base1)->age - ((struct People*)base2)->age;
}
// arr的含义:sizeof(arr) / &arr - 此时arr指的是整个给数组的地址
void Test_Arrint()
{
// 升序
// int arr[] = { 9, 8, 7, 6, 5, 4, 3, 2, 1 };
int arr[] = { 3, 5, 6, 4, 2, 1, 9, 0};
Bubble_sort(arr, sizeof(arr) / sizeof(arr[0]), sizeof(arr[0]), cmp_int);
for (size_t i = 0; i < sizeof(arr) / sizeof(int); ++i)
{
cout << arr[i] << " ";
}
}
void Test_String()
{
struct People s[] = { {"zhangsan", 15}, {"lisi", 10}, {"wangwu", 20}};
// Bubble_sort(s, sizeof(s) / sizeof(s[0]), sizeof(s[0]), cmp_string);
Bubble_sort(s, sizeof(s) / sizeof(s[0]), sizeof(s[0]), cmp_age);
}
int main()
{
Test_Arrint();
Test_String();
}
6.面试常考函数指针(重点)
当面试提问到函数指针时的回答思路
1.函数指针的typedef与其它不同,组成及类型,且在调用方式上的特性
2.函数指针常配合函数指针数组使用,将同类型的函数封在一起,返回类型,参数类型必须相同
3.函数指针的特性:回调函数,库中的qsort,回调函数的作用是能够使函数适应多种类型的参数,也能够接收多种传参(void*)
接收数组 / 结构体
4.从函数指针引导到C++中的仿函数,说明仿函数的作用就是替代函数指针,实现一个函数在模板的加持下能够适应多种类型参数
本章代码汇总
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");
}
int main()
{
int input;
do
{
menu();
int x, y, ret;
cin >> input;
// []的结合性大于*
// int(*pf)(int, int) - pf,表明是一个函数指针,去掉pf,对应类型为函数指针 - int(*)(int, int)
// int(*pfArr[])(int, int) - pfArr[]表明是一个函数指针数组 - 去掉pfArr[],对应类型为函数指针 - int(*)(int, int)
// 类型是数组 - *表示存储的类型
// int(*(*pf)[])(int, int) - *pf表明这是一个指针,[]表明*pf是一个指向数组的指针 - 去掉(*pf)[],对应类型为函数指针 - int(*)(int, int)
// 类型是指针 - *表示指向的类型是一个数组(&数组名)
// 所以此类型对应的是指向函数指针数组的指针
// Error:int((*pf)[])(int, int) - *pf表明是一个指针,[]表示指向数组的指针,但去除(*pf)[],类型是int()(int, int),不再能表示函数指针数组的类型
// 转移表 - 函数指针数组
int (*pfArr[5])(int, int) = { nullptr, Add, Sub, Mul, Div };
if (input >= 1 && input <= 4)
{
printf("请输入两个数:");
cin >> x >> y;
ret = pfArr[input](x, y);
cout << ret << endl;
}
else if (input == 0)
{
printf("退出程序\n");
}
else
{
printf("输入有误,请重新输入\n");
}
} while (input);
}
/*******************************************************************************************/ ///
//回调函数 - 函数指针的应用
//模拟实现qsort(Bubble_sort)
//void*的指针 - 无具体类型的指针
//void*类型的指针可以接收任意类型的地址(特性)
//这种类型的指针不能直接解引用操作
//void*类型的指针不能直接进行指针运算
struct People
{
const char* name;
int age;
};
void Swap(char* buf1, char* buf2, int length)
{
// 一个字节一个字节交换
char tmp = 0;
for (int i = 0; i < length; ++i)
{
tmp = *buf1;
*buf1 = *buf2;
*buf2 = tmp;
++buf1;
++buf2;
}
}
void Bubble_sort(void* base, int size, int length, int(*cmp)(const void*, const void*))
{
// 排升序
for (int i = 0; i < size - 1; ++i)
{
for (int j = 0; j < size - 1 - i; ++j)
{
if (cmp((char*)base + j * length, (char*)base + (j + 1) * length) > 0)
{
Swap((char*)base + j * length, (char*)base + (j + 1) * length, length);
}
}
}
}
// int类型的cmp_int
int cmp_int(const void* base1, const void* base2)
{
// int*的意义:从给定位置向后访问4个字节
return (*(int*)base1) - (*(int*)base2);
}
int cmp_string(const void* base1, const void* base2)
{
return strcmp(((struct People*)base1)->name, ((struct People*)base2)->name);
}
int cmp_age(const void* base1, const void* base2)
{
return ((struct People*)base1)->age - ((struct People*)base2)->age;
}
// arr的含义:sizeof(arr) / &arr - 此时arr指的是整个给数组的地址
int main()
{
int arr[] = { 9, 8, 7, 6, 5, 4, 3, 2, 1 };
int arr[] = { 3, 5, 6, 4, 2, 1, 9, 0};
Bubble_sort(arr, sizeof(arr) / sizeof(arr[0]), sizeof(arr[0]), cmp_int);
for (size_t i = 0; i < sizeof(arr) / sizeof(int); ++i)
{
cout << arr[i] << " ";
}
struct People s[] = { {"zhangsan", 15}, {"lisi", 10}, {"wangwu", 20}};
Bubble_sort(s, sizeof(s) / sizeof(s[0]), sizeof(s[0]), cmp_string);
Bubble_sort(s, sizeof(s) / sizeof(s[0]), sizeof(s[0]), cmp_age);
}
End!!!