Tips
1.
2.
3. 碰到地址就等价于指针变量,里面存放着该地址的指针变量
4. 数组指针是存放数组的地址,指向的是一个数组;函数指针存放的是函数的地址,指向的是一个函数。
5. 地址就是指针,地址就是指针
6. 数组指针,比如说int(*pa)[10];指针数组是一个数组,里面存放的都是指针,比如说:char* ch[5]; ,函数指针的话就是这样写的:函数返回类型(*pa)(参数数据类型)=函数地址。
7.
函数指针(存放函数地址/指向函数的指针)
创建函数指针
1. 函数指针就是指向函数的指针。我们发现:原来函数也是有地址的,比如说只需要&add。
2. 那么既然是一个地址,能不能把它存起来?当然。可以用函数指针存放函数地址。
创建格式为: 函数返回类型(*p)(参数数据类型)=&函数名/函数名
如int(*pf)(int, int)=&add。
3. 函数指针与数组指针非常非常类似。但是有区别:对于数组而言,arr是数组首元素的地址,&arr是整个数组的地址。对于函数而言add与&add两者没有区别,都是函数的地址,两个一样。
通过函数指针调用函数
1. 这时候还是得注意:指针的核心与灵魂所在就是解引用操作。
2. 比如说我要调用add函数,首先通过指针变量解引用找到函数,然后调用函数的话,传递参数自然不可或缺我就:int sum = (*pf)(2, 3),发现sum就是5,说明调用成功。
3. 而事实上,根据指针变量的转换关系:指针变量的值=指针变量指向哪=指针变量里面存放的地址=指针变量的名字本身=指针变量本内存条里面存放的数据。那么当
int(*pf)(int, int)=add的时候,pf就是add。那么既然pf就是add,我pf(2.3)不就OK了
有趣代码1
1,首先,void( * ) ( )是一个函数指针的指针类型
2. 把0这个整型强制类型转换成指针类型void( * )( ),于是:( void( * ) ( ) ) 0
3. 强制类型转换完之后,这时候已经变成一个函数指针了,在对它进行解引用调用函数并传递参数。于是有:(* ( void( * ) ( ) ) 0 ) ( ) ;
4.
5. 关于强制类型转换的理解:
1. 其实这个是涉及到数据类型的知识。
2. 不同的数据类型确定了看待内存数据的视角与解读方式,但是内存里面的二进制补码是岿然不动的,总而言之,就是解读方式发生了变化。
3. 像这个代码里面,原先的话0是一个整型,在属于它的32位二进制内存空间里面每一位都是0。而现在我把它的类型转换为函数指针类型,那么我就认为你们存放的是地址,欸里面是0,这就说明函数指针指向0,会去调用0地址处的函数。
有趣代码2
1. 首先,signal是一个函数名称
2. 函数的两个参数类型为int, void( * )( int )。于是: signal ( int , void ( * ) ( int ) )
3. 然后signal函数的返回类型是void ( * ) ( int )。于是:
void ( * signal ( int , void ( * ) ( int ) ) ) ( int );
4.
利用typedef简化有趣代码2
注意:对于数组指针啊,函数指针啊,这些指针的指针类型要死的恶心如
int( * )[10], void( * )(int ,char )等等
这时候其充当typedef简化对象,充当函数定义返回类型,充当数组定义时元素类型都需要形式上发生微调,东西都移到括号里面*的右边
函数指针数组
1. 有很多指针数组,如字符指针数组(存放的是字符指针),如char* arr[10];也有整型指针数组(存放的是整型指针),如int* arr[10]。那么函数指针数组的话,就是用来存放函数指针的。那么该如何写出一个函数指针数组呢?反正就按我的那张表来:如,int(*arr[10])(int, int)。再次强调一下:如果变量与*结合在一起,那么表示是一个指针;如果说与[ ]结合在一起,那么表示是一个数组。然后指针类型的话,一般来说潜意识就能知道。如果碰到有点复杂的,那么就去创建一个指针变量那么走一遍,把表达式去掉变量名就是了,比如说函数指针创建是int(*p)(int, int),那么这个p的类型就是int(*)(int, int)。
2. 注意:函数指针数组数组里面放的不是函数,数组里面放的是函数的指针也就是地址。
函数指针数组的用途:转移表
写一个计算器整数加、减、乘、除
代码1:
#include <stdio.h>
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(void)
{
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("%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);
return 0;
}
上面这个太繁琐,用函数指针数组来实现简洁多了
代码2:
#include <stdio.h>
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(void)
{
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;
int(*arr[5])(int, int) = { 0,add,sub,mul,div };
do
{
menu();
printf("请输入:");
scanf("%d", &input);
if (input == 0)
{
printf("退出成功\n");
break;
}
else if ((input >= 1) && (input <= 4))
{
printf("请输入两数:");
scanf("%d %d", &x, &y);
printf("%d\n", (*(arr + input))(x, y));
}
else
{
printf("输入错误\n");
}
} while (input);
return 0;
}
3. 其实这个函数指针数组有一种跳转的效果。你给我一个下标,我通过这个下标找到数组里面的某个函数指针然后去调用这个函数。因此在有一些叫法里面:这种函数指针数组叫做“转移表”
4. 函数指针数组里面的每一个函数指针它的指针类型必须是一模一样。(因为数组的每个元素类型必须一样),这也就说明函数指针数组里面的每一个函数指针所指向的函数的参数数据类型与个数与返回类型必须一样
指向函数指针数组的指针
1. 我们之前已经知道,int(*p)(int,int)就是函数指针,而int(*p[10])(int, int)就是函数指针数组。之前比如说:int arr[10],那我就可以int (*pA)[10]=&arr。那我现在int(*pf[10])(int, int)就是函数指针数组可不可以&pf呢?当然可以。int(*(*ppf)[10])(int, int)=&pf。这边必须要再次强调一下:如果变量名与*结合,那么就表示是一个指针;如果与[ ]结合,那么就表示是一个数组,至于到底与谁先结合,这就是根据优先级来的。
2.
3.
4. 这个本质上就是一个数组指针。因为是指向数组的嘛。
5. 再来回顾一下指针变量的创建。1. *p结合表示这个p一个指针变量。2. 再加上其指向内存空间里面数据的类型
6. 与此同时还要知道,数组的类型是构造类型,如int [10];数组名的类型与&arr的类型都是指针类型,这类型的话一般潜意识就能知道,复杂搞不懂的话创建走一遭去掉p就是。
回调函数 (函数也是有地址的,地址就是指针)
回调函数不需要像一般的函数这样去定义/声明,好像类似于函数指针里面已经定义好了
注: 函数名与函数都是一模一样的,代表函数地址,地址就是指针
1. 回调函数又是一个比较有意思的东西,我们之前学过函数指针与函数指针数组,说到底这两个东西的核心在于函数指针。因此函数指针非常非常非常非常关键。而函数指针非常重要的应用就是
用函数指针来实现回调函数。
2. 回调函数就是一个通过函数指针调用的函数。首先,你把函数指针\地址当成一个参数传递给另外一个函数 -> 然后当这个参数指针被用来调用其指向的函数,我们就说这个是回调函数。回调函数不是由本函数的实现方直接调用。而是在特定的事件或条件发生时由另外的一方调用,由于对该事件和条件进行响应。说白了,就是用函数指针调用其指向的函数。
例子1(用回调函数改造上面的计算器)
#include <stdio.h>
void menu()
{
printf("*******************************\n");
printf("****** 1. add 2. sub *****\n");
printf("****** 3. mul 4. div *****\n");
printf("****** 0. exit *****\n");
printf("*******************************\n");
}
int add(int x, int y)
{
return x + y;
}
int sub(int x, int y)
{
return x - y;
}
void mul(int x, int y)
{
return x * y;
}
void div(int x, int y)
{
return x / y;
}
void calc(int(*pf)(int,int))
{
printf("请输入两个数字:");
int x = 0;
int y = 0;
scanf("%d %d", &x, &y);
int ret = pf(x, y);
printf("%d\n", ret);
}
int main()
{
int input = 0;
do
{
menu();
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;
}
例子2(qsort函数)
1. 首先先得复习冒泡排序:
void bubble_sort(int* str, int sz)
{
//趟数
int i = 0;
int j = 0;
for (i = 0; i < sz - 1; i++)
{
for (j = 0; j <= (sz - (i + 2)); j++)
{
if (*(str + j) < *(str + j + 1))
{
int tmp = *(str + j);
*(str + j) = *(str + j + 1);
*(str + j + 1) = tmp;
}
}
}
}
int main()
{
int arr[10] = { 2,3,5,6,8,1,4,9,0,7 };
int sz = sizeof(arr) / sizeof(arr[0]);
bubble_sort(arr, sz);
//打印结果
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
2. 但有缺陷与限制,只能排整数,因为参数在那里写死了。
3. C里面有qsort函数可以对任意元素类型的数组的元素排序
4. 其底层用快速排序而不是冒泡排序,但我们只会冒泡排序。
5.
6. 看一下qsort的参数:
1. void* base 表示待排序数组的起始地址
2. size_t num 表示数组元素个数
3. size_t width 表示数组一个元素占几个字节
4. 比较函数 int (*cmp)(const void*, const void*)
7. 想想, 对冒泡排序函数而言:
void bubble_sort(int* str, int sz)
{
//趟数
int i = 0;
int j = 0;
for (i = 0; i < sz - 1; i++)
{
for (j = 0; j <= (sz - (i + 2)); j++)
{
if (*(str + j) < *(str + j + 1))
{
int tmp = *(str + j);
*(str + j) = *(str + j + 1);
*(str + j + 1) = tmp;
}
}
}
}
假设要升级实现不仅能够排整型数组,也可以排序浮点型数组,结构体数组等等,发现:
其实上面这个冒泡排序函数的 i 的循环是不用发生变化的,j 也是不用发生变化的,但是:这个if 语句里面的相邻数值元素的比较方式是要发生变化的 (比如说结构体之间,不能直接大于小于这么简单的去比)那么显然,里面相邻数值元素的交换方式也是要发生变化的
8. 既然要这么变化来变化去,那就这样,我把它抽离出来,这部分到外面来形成一个函数,你来写,写完后你把地址传过来,我用函数指针接受
9. 来好好看一下这个比较函数的函数指针参数的形式:
int (*cmp)(const void*, const void*) 第一个const void* 是两相邻元素的第一个元素起始地址,第二个const void* 是两相邻元素的第二个元素起始地址。
然后是关于这个void* 的有关内容
首先:如果 float*的指针 = int*的指针,编译器会报警告的,因为等号两边不兼容
void*是一个指针类型,是无具体类型的指针, 好处在于是一个通用指针,兼容性极好,可以接受任何类型的指针;坏处在于不能直接解引用操作/++/--(需要强制转换类型)void就是一个瞎子,它不知道怎么去解读二进制补码
10, 接下来,讲讲这个比较函数的返回值int是怎么一回事呢?与strcmp一样,如果第一个数据>第二个数据,就返回大于0的数字;如果第一个数据<第二个数据,就返回小于0的数字,相等的话,就返回0
用qsort函数实现整型数组的排序
int cmp_int(const void* e1, const void* e2)
{
return *(int*)e2 - *(int*)e1;
}
int main()
{
int arr[10] = { 2,3,5,6,8,1,4,9,0,7 };
int sz = sizeof(arr) / sizeof(arr[0]);
qsort(arr, sz, sizeof(arr[0]), cmp_int);
//打印结果
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
用qsort函数实现结构体数组的排序1
struct Stu
{
char name[20];
int age;
};
int cmp_Stu_by_age(const void* e1, const void* e2)
{
return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
}
int main()
{
struct Stu arr[3] = { {"zhang",12}, {"li",14} ,{"shen",13} };
int sz = sizeof(arr) / sizeof(arr[0]);
qsort(arr,sz,sizeof(arr[0]),cmp_Stu_by_age);
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%s %d\n", (arr + i)->name, (arr + i)->age);
}
return 0;
}
用qsort函数实现结构体数组的排序2
#include <string.h>
struct Stu
{
char name[20];
int age;
};
int cmp_Stu_by_name(const void* e1, const void* e2)
{
return strcmp(((struct Stu*)e2)->name, ((struct Stu*)e1)->name);
}
int main()
{
struct Stu arr[3] = { {"zhang",12}, {"li",14} ,{"shen",13} };
int sz = sizeof(arr) / sizeof(arr[0]);
qsort(arr, sz, sizeof(arr[0]), cmp_Stu_by_name);
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%s %d\n", (arr + i)->name, (arr + i)->age);
}
return 0;
}
strcmp比较两个字符串是对应位置字符一对一对比较下去,并且其返回类型恰好与qsort的比较函数的返回类型雷同
用冒泡排序的内核来实现qsort指针
void Swap(char* buf1,char* buf2,int width)
{
int i = 0;
for (i = 0; i < width; i++)
{
char tmp = *buf1;
*buf1 = *buf2;
*buf2 = tmp;
buf1++;
buf2++;
}
}
void my_qsort(void* base, size_t sz, size_t width, int (*cmp)(const void*, const void*))
{
int i = 0;
int j = 0;
for (i = 0; i < sz - 1; i++)
{
for (j = 0; j <= (sz - (i + 2)); j++)
{
if (cmp((char*)base + width * j, (char*)base + width * (j + 1)) > 0)
{
Swap((char*)base + width * j, (char*)base + width * (j + 1), width);
}
}
}
}
//
int cmp_int(const void* e1, const void* e2)
{
return *(int*)e1 - *(int*)e2;
}
int main()
{
int arr[10] = { 2,3,5,6,8,1,4,9,0,7 };
int sz = sizeof(arr) / sizeof(arr[0]);
my_qsort(arr, sz, sizeof(arr[0]), cmp_int);
//打印结果
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
最后补充:
1. 计算机内存里面的基本单位都是字节,这里面都是二进制补码.
2. 一切都是二进制补码,这就使得数据类型显得尤为重要。
3. 把void*的指针变为char*的指针就可以实现以字节为基本单位的步长。
4.
5.