(五)函数指针
1.定义:
指向函数的指针:
#include<stdio.h>
int Add(int a, int b)
{
int z = 0;
z = a + b;
return z;
}
int main()
{
int a = 10;
int b = 20;
printf("%d\n",Add(a, b));
printf("%p\n", &Add);
printf("%p\n", Add);
}
可以看到Add也有地址,且Add和&Add都是函数的地址
那怎么储存呢?
2.储存方式:
#include<stdio.h>
int Add(int a, int b)
{
int z = 0;
z = a + b;
return z;
}
int main()
{
int a = 10;
int b = 20;
printf("%d\n",Add(a, b));
printf("%p\n", &Add);
printf("%p\n", Add);
//int *pa(int,int)=Add;这样写不对,pa先和()结合,是函数,函数的参数是int,返回值是int*;
int(*pa)(int,int)=Add;
printf("%d\n",(*pa)(2,3));
return 0;
}
如果括号里的*,对结果有影响吗?
#include<stdio.h>
int Add(int a, int b)
{
int z = 0;
z = a + b;
return z;
}
int main()
{
int a = 10;
int b = 20;
int(*pa)(int, int) = Add;
printf("%d\n",(pa)(2,3));
printf("%d\n",(*pa)(2,3));
printf("%d\n", (**pa)(2, 3));
printf("%d\n", (***pa)(2, 3));
printf("%d\n", (****pa)(2, 3));
printf("%d\n", (*****pa)(2, 3));
return 0;
}
我们发现增加*不影响,但如果要用*,一定要扩起来。
3.小试牛刀:
#include<stdio.h>
void Print(char* str)
{
printf("%s\n", str);
}
int main()
{
void(*p)(char*) = Print;//将函数地址存在*p中
(*p)("hello world");//*p找到函数地址,调用函数
return 0;
}
有趣的代码
(*(void (*)())0)();
void (*)():函数指针类型
(void (*)())0:将0强制转化为函数指针类型
*(void ()())0:解引用,就是一个函数
(*(void (*)())0)():调用函数,参数是无参,返回类型为void;
void (*signal(int , void(*)(int)))(int);
signal(int , void(*)(int)):函数,两个参数int,void(*)(int)
void(*)(int)函数指针类型
//去掉函数名和参数,剩余的就是返回类型。
void (*)(int):*在圆括号里,是指针,指向函数,函数的参数是int,返回类型是void
所以signal是函数名,
相当于一次声明
void (*)(int) signal( int , void(*)(int) )//但是不能这样写
简化写法:
//理解意义的写法typedef void(*)(int) pfun_t;给void(*)(int)重命名为pfun_t
typedef void(*pfun_t)(int);
pfun_t signal(int, pfun_t);
(六)函数指针数组
我们从例子中了解一下
#include<stdio.h>
int add(int a, int b)
{
return a + b;
}
int sub(int a, int b)
{
return a - b;
}
int mul(int a, int b)
{
return a * b;
}
int div(int a, int b)
{
return a / b;
}
int main()
{
int a = 10;
int b = 20;
int(*pa)(int, int) = add;//因为四个函数参数和返回值相同,所以可以写在一起
int(*parr[4])(int, int) = { add,sub,mul,div };
int i = 0;
for (i = 0; i < 4; i++)
{
printf("%d\n", parr[i](2, 3));
}
}
int(*parr[4])(int, int) = { add,sub,mul,div };
中parr先和[4]结合,是数组,有4个元素,每个元素的类型是int(*)(int, int)函数指针
练习:
写一个函数指针pf,能够指向my_strcpy——char* my_strcpy(char* desk, const char* src);
char*(*pf)(char*, const char*)
写一个函数指针数组pfarr,能够存放4个my_strcpy函数的地址
char*(*pfarr[4])(char*, const char*)
相当于函数指针加了个[]
使用案例:计算器的实现
1.
#include <stdio.h>
int add(int a, int b)
{
return a + b;
}
int sub(int a, int b)
{
return a - b;
}
int mul(int a, int b)
{
return a * b;
}
int div(int a, int b)
{
return a / b;
}
int main()
{
int x, y;
int input = 1;
int ret = 0;
do
{
printf("*************************\n");
printf(" 1:add 2:sub \n");
printf(" 3:mul 4:div \n");
printf(" 0.退出 \n" );
printf("*************************\n");
printf("请选择:");
scanf("%d", &input);
printf("输入操作数:");
scanf("%d %d", &x, &y);
switch (input)
{
case 1:
ret = add(x, y);
printf("ret = %d\n", ret);
break;
case 2:
ret = sub(x, y);
printf("ret = %d\n", ret);
break;
case 3:
ret = mul(x, y);
printf("ret = %d\n", ret);
break;
case 4:
ret = div(x, y);
printf("ret = %d\n", ret);
break;
case 0:
printf("退出程序\n");
break;
default:
printf("选择错误\n");
break;
}
} while (input);
return 0;
}
我们发现这样写虽然可以计算,但是当输入0时仍会出现:
说明逻辑错误,应该改为
2.
#include <stdio.h>
int add(int a, int b)
{
return a + b;
}
int sub(int a, int b)
{
return a - b;
}
int mul(int a, int b)
{
return a * b;
}
int div(int a, int b)
{
return a / b;
}
int main()
{
int x, y;
int input = 1;
int ret = 0;
do
{
printf("*************************\n");
printf(" 1:add 2:sub \n");
printf(" 3:mul 4:div \n");
printf(" 0.退出 \n" );
printf("*************************\n");
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;
}
但是,我们发现要添加计算总类时,就需要增加一个case,当总类过多时,较为冗杂。
3.改为函数指针数组
#include <stdio.h>
menu()
{
printf("*************************\n");
printf(" 1:add 2:sub \n");
printf(" 3:mul 4:div \n");
printf(" 0.退出 \n");
printf("*************************\n");
}
int add(int a, int b)
{
return a + b;
}
int sub(int a, int b)
{
return a - b;
}
int mul(int a, int b)
{
return a * b;
}
int div(int a, int b)
{
return a / b;
}
int main()
{
int x, y;
int input = 1;
int (*pfarr[5])(int, int) = {0,add,sub,mul,div};
do
{
menu();
printf("请选择:>");
scanf("%d", &input);
if (input >= 1 && input <= 4)
{
printf("请输入两个操作数:>");
scanf("%d%d", &x, &y);
int ret = pfarr[input](x, y);
printf("%d\n", ret);
}
else if (input == 0)
{
printf("退出\n");
}
else
{
printf("选择错误\n");
}
} while (input);
return 0;
}
这样添加,只需要在数组里直接增加。
函数指针数组的用途:转移表
(七)指向函数指针数组的指针
指向函数指针数组的指针是一个:指针
指针指向一个数组 ,数组的元素都是函数指针
#include<stdio.h>
int add(int a, int b)
{
return a + b;
}
int main()
{
//指针数组
int* arr[10];
//数组指针
int(*pa)[10]=&arr;
//函数指针
int(*pa)(int, int) = add;
int sum = (* pa)(1, 2);
printf("%d\n", sum);
//函数指针的数组
int(*parr[6])(int, int) = add;
//指向函数指针数组的指针
int(*(*pparr)[6])(int, int) = &parr;
return 0;
}
int(*(*pparr)[6])(int, int) = &parr;
(*pparr)
指针
(*pparr)[6]
指向一个数组,数组有六个元素
int(*)(int, int)
每个原宿类型是指针函数
(八)回调函数
#include <stdio.h>
int add(int a, int b)
{
return a + b;
}
int sub(int a, int b)
{
return a - b;
}
int mul(int a, int b)
{
return a * b;
}
int div(int a, int b)
{
return a / b;
}
int main()
{
int x, y;
int input = 1;
int ret = 0;
do
{
printf("*************************\n");
printf(" 1:add 2:sub \n");
printf(" 3:mul 4:div \n");
printf(" 0.退出 \n" );
printf("*************************\n");
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;
}
我们发现这个含数中
printf("输入操作数:");
scanf("%d %d", &x, &y);
ret = div(x, y);
两句多次出现,就只有调用的函数不同,
#include <stdio.h>
int add(int a, int b)
{
return a + b;
}
int sub(int a, int b)
{
return a - b;
}
int mul(int a, int b)
{
return a * b;
}
int div(int a, int b)
{
return a / b;
}
void calc(int(*pf)(int,int))//相当于是int(*pf)(int,int)=形参
{
int x, y;
printf("输入操作数:");
scanf("%d %d", &x, &y);
printf("ret = %d\n", pf(x,y));
}
int main()
{
int input = 1;
do
{
printf("*************************\n");
printf(" 1:add 2:sub \n");
printf(" 3:mul 4:div \n");
printf(" 0.退出 \n");
printf("*************************\n");
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函数的使用
知识预备
void *p
这个类型的指针可以接收任意类型的地址;但是void*类型指针不能进行解引用操作(没有具体类型,不知道解引用时的空间);也不能进行加减整数的操作。
quick sort
int
#include <stdio.h>
//qosrt函数的使用者得实现一个比较函数
int int_cmp(const void * p1, const void * p2)
{
return (*( int *)p1 - *(int *) p2);//整型指针p1指向的值减去整型指针p2指向的值
}
int main()
{
int arr[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };
int i = 0;
qsort(arr, sizeof(arr) / sizeof(arr[0]), sizeof (int), int_cmp);
//数组首地址,数组元素个数,元素字节,不同类型元素比较的函数
for (i = 0; i< sizeof(arr) / sizeof(arr[0]); i++)
{
printf( "%d ", arr[i]);
}
printf("\n");
return 0;
}
float
int float_cmp(const void* p1, const void* p2)
{
return ((int)(*(float*)p1 - *(float*)p2));
}
int main()
{
float arr[] = { 1.0, 3.0, 5.0, 7.0, 9.0, 2.0, 4.0, 6.0, 8.0, 0.0 };
int i = 0;
qsort(arr, sizeof(arr) / sizeof(arr[0]), sizeof(int), float_cmp);
//数组首地址,数组元素个数,元素字节,不同类型元素比较的函数
for (i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
{
printf("%f ", arr[i]);
}
printf("\n");
return 0;
}
struct
age
#include<stdio.h>
struct stu
{
char name[20];
int age;
};
int cmp_stu_by_age(const void* p1, const void* p2)
{
return ((struct stu*)p1)->age - ((struct stu*)p2)->age;
}
int main()
{
struct stu s[3] = { {"zhangsan,20"} ,{"lisi,30"}, {"wangwu,65"} };
int i = 0;
qsort(s, sizeof(s) / sizeof(s[0]), sizeof(s[0]), cmp_stu_by_age);
//数组首地址,数组元素个数,元素字节,不同类型元素比较的函数
for (i = 0; i < sizeof(s) / sizeof(s[0]); i++)
{
printf("%d", s[i]);
}
printf("\n");
return 0;
}
name
#include<stdio.h>
#include<string.h>
struct stu
{
char name[20];
int age;
};
int cmp_stu_by_name(const void* p1, const void* p2)
{
return strcmp(((struct stu*)p1)->name , ((struct stu*)p2)->name);
}
int main()
{
struct stu s[3] = { {"zhangsan,20"} ,{"lisi,30"}, {"wangwu,65"} };
int i = 0;
qsort(s, sizeof(s) / sizeof(s[0]), sizeof(s[0]), cmp_stu_by_name);
//数组首地址,数组元素个数,元素字节,不同类型元素比较的函数
return 0;
}
我们的冒泡函数只有:
#include<stdio.h>
void bubble_sort(int arr[], int sz)
{
int i = 0;
for (i = 0; i < sz - 1; i++)
{
int j = 0;
for (j = 0; j < sz - i - 1; j++)
{
if (arr[j] > arr[j + 1])
{
int tmp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = tmp;
}
}
}
}
struct stu
{
char name[20];
int age;
};
int main()
{
int arr[10] = { 9,8,7,6,5,4,3,2,1,0 };
int sz = sizeof(arr) / sizeof(arr[0]);
//struct stu s[3] = { {"zhangsan,20"} ,{"lisi,20"}, {"wangwu,20"} };
//float f[10] = { 1.0,2.0,3.0,4.0 };
//不能使用
bubble_sort(arr, sz);
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d", arr[i]);
}
return 0;
}
改造
#include <stdio.h>
//这段由使用bubble的程序员写
int int_cmp(const void* p1, const void* p2)
{
return (*(int*)p1 - *(int*)p2);
}
void _swap(void* p1, void* p2, int size)
{
int i = 0;
for (i = 0; i < size; i++)
{
char tmp = *((char*)p1 + i);
*((char*)p1 + i) = *((char*)p2 + i);
*((char*)p2 + i) = tmp;
//char tmp = *p1;
//*p1 = *p2;
//*p2 = tmp;
//p1++;
//p2++;
}
}
void bubble(void* base, int count, int size, int(*cmp)(void*, void*))
//比较方法不同,那你就把你的比较方法传进来
{
int i = 0;
int j = 0;
for (i = 0; i < count - 1; i++)
{
for (j = 0; j < count - i - 1; j++)
{
if (cmp((char*)base + j * size, (char*)base + (j + 1) * size) > 0)
{
_swap((char*)base + j * size, (char*)base + (j + 1) * size, size);
}
}
}
}
int main()
{
int arr[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };
//char *arr[] = {"aaaa","dddd","cccc","bbbb"};
int i = 0;
bubble(arr, sizeof(arr) / sizeof(arr[0]), sizeof(int), int_cmp);
for (i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
{
printf("%d ", arr[i]);
}
printf("\n");
return 0;
}
(九)指针和数组笔试题
1.一维数组
int a[] = {1,2,3,4};
printf("%d\n",sizeof(a));//16——计算的是数组总大小,单位是字节
printf("%d\n",sizeof(a+0));//4/8——首元素地址,大小为4或8
printf("%d\n",sizeof(*a));//4——首元素地址解引用,为此处元素
printf("%d\n",sizeof(a+1));//4/8——第二个元素的地址
printf("%d\n",sizeof(a[1]));//4——第二个元素的大小
printf("%d\n",sizeof(&a));//4/8——取出的是整个数组的地址
printf("%d\n",sizeof(*&a));//16——整个元素地址解引用,为所有元素
printf("%d\n",sizeof(&a+1));//4/8——&a是数组地址,+1跳过整个数组,还是地址
printf("%d\n",sizeof(&a[0]));//4/8第一个元素的地址
printf("%d\n",sizeof(&a[0]+1));//4/8第二个元素的地址
注:
数组名是首元素地址,除了&数组名和sizeof(数组名)
地址大小为4/8由系统平台决定
2.字符数组
2.1char arr[] = {'a','b','c','d','e','f'};
char arr[] = {'a','b','c','d','e','f'};
printf("%d\n", sizeof(arr));//6——计算的是数组大小,每个元素char大小为1
printf("%d\n", sizeof(arr+0));//4/8——首元素地址
printf("%d\n", sizeof(*arr));//1——arr首元素地址,*解引用,变为首元素
printf("%d\n", sizeof(arr[1]));//1
printf("%d\n", sizeof(&arr));//4/8——整个数组的地址,还是4/8
printf("%d\n", sizeof(&arr+1));//4/8——跳过整个数组后的地址,还是地址
printf("%d\n", sizeof(&arr[0]+1));//4/8——第二个元素的地址
printf("%d\n", strlen(arr));//随机值——没有\0,strlen会往后走
printf("%d\n", strlen(arr+0));//随机——和上一个一样
//printf("%d\n", strlen(*arr));//崩溃,*arr解引用为'a',是97,将97当作地址访问
//printf("%d\n", strlen(arr[1]));//崩溃
printf("%d\n", strlen(&arr));//随机——取整个数组的地址,也是从第一个开始,应该和第9,10相同
printf("%d\n", strlen(&arr+1));//随机值,比第13少6
printf("%d\n", strlen(&arr[0]+1));//随机值,比第13少1
2.2char arr[] = "abcdef";
char arr[] = "abcdef";
printf("%d\n", sizeof(arr));//7——后面有\0
printf("%d\n", sizeof(arr+0));//4/8——首元素地址
printf("%d\n", sizeof(*arr));//1——首元素地址解引用,是首元素
printf("%d\n", sizeof(arr[1]));//1——第二个元素
printf("%d\n", sizeof(&arr));//4/8——地址
printf("%d\n", sizeof(&arr+1));//4/8_跳过该地址后的地址,还是地址
printf("%d\n", sizeof(&arr[0]+1));//4/8——第二个地址
printf("%d\n", strlen(arr));//6——strlen求到\0
printf("%d\n", strlen(arr+0));//6——首元素地址开始计数
//printf("%d\n", strlen(*arr));//崩溃,将'a'传入strlen,strlen要地址
//printf("%d\n", strlen(arr[1]));//崩溃,同理
printf("%d\n", strlen(&arr));//6——strlen的const char*与&arr的char(*)[7]=&arr冲突,但也能算
printf("%d\n", strlen(&arr+1));//随机,后面有什么不知道
printf("%d\n", strlen(&arr[0]+1));//5——第二个元素开始计数
2.3char *p = "abcdef";
char *p = "abcdef";//首元素地址放在p中
printf("%d\n", sizeof(p));//4/8——计算指针变量p的大小
printf("%d\n", sizeof(p+1));//4/8——p+1是字符b的地址
printf("%d\n", sizeof(*p));//1——*p就是'a'
printf("%d\n", sizeof(p[0]));//1——p[0]=*(p+0),就是'a'
printf("%d\n", sizeof(&p));//4/8——地址
printf("%d\n", sizeof(&p+1));//4/8——跳过这个地址后的地址
printf("%d\n", sizeof(&p[0]+1));//4/8——'b'的地址
printf("%d\n", strlen(p));//6
printf("%d\n", strlen(p+1));//5
//printf("%d\n", strlen(*p));//报错
//printf("%d\n", strlen(p[0]));//报错
printf("%d\n", strlen(&p));//随机,p里存的是a的地址,不确定\0的位置
printf("%d\n", strlen(&p+1));//随机,p的地址后面的空间有什么不知道
printf("%d\n", strlen(&p[0]+1));//5——[0]限定是元素a的地址,+1为b,从b开始计数
3.二维数组
int a[3][4] = {0};
printf("%d\n",sizeof(a));//48——3*4*4
printf("%d\n",sizeof(a[0][0]));//4——一个元素的大小
printf("%d\n",sizeof(a[0]));//16——a[0]相当于第一行的数组名,数组名放在sizeof里面,直接计算第一行的大小
printf("%d\n",sizeof(a[0]+1));//4/8——第一行第二个元素的地址
printf("%d\n",sizeof(*(a[0]+1)));//4——第一行第二个元素解引用,是个int
printf("%d\n",sizeof(a+1));//4/8——a是二维数组的数组名,没有sizeof(数组名),也没有&(数组名),所以a是首元素的地址
//二维数组看作一维数组时,二维数组的首元素是它的第一行,a就是第一行的地址,a+1就是第二行的地址
//a+1就是第二行的地址
printf("%d\n",sizeof(*(a+1)));//16——第二行的地址解引用,是4*4
printf("%d\n",sizeof(&a[0]+1));//4/8——a[0]是第一行的数组名,数组名取地址+1就是第二行的地址
printf("%d\n",sizeof(*(&a[0]+1)));//16——第二行地址元素解引用,为4*4
printf("%d\n",sizeof(*a));//16——没有sizeof(数组名),也没有&(数组名),a是第一行地址,*a就是第一行元素,4个int
printf("%d\n",sizeof(a[3]));//16——sizeof不会计算括号里的表达式,我不会真的取访问数组,a[3]的类型和a[0]的一样
ps:
是不是很神奇,嘿嘿嘿
多看几遍就好了
指针(下)还有经典例题
头发留不住了