5.指针参数:
5.1一级指针传参:
当我们在函数调用,并使用一级指针作为参数时,很容易理解:一级指针 p 中存放的是数组 arr 中首元素的地址,即传址做参,于是我们就可以在函数参数设计时,使用一级指针进行接收,就可以达到我们的目的。
void test(int* p)void test(int* p) //传递的是一级指针,存储的是arr首元素的地址,使用一级指针进行接收 { int i = 0; for (i = 0; i < 5; i++) { printf("%d ", *p + i); } } int main() { int arr[5] = { 1,2,3,4,5 }; int* p = arr; //数组名为首元素地址 test (p); //等价于:test(arr); return 0; }
5.2 二级指针传参:
首先最基础的用法是传递二级指针,就使用二级指针进行接收。我们直接上示例即可:
void test(int** p) //传递二级指针,使用二级指针进行接收 { printf("%c", **p); } int main() { char a = 'w'; char* pa = &a; char** ppa = &pa; test(ppa); return 0; }
当函数参数为二级指针时,都可以接收什么参数?
我们都知道,二级指针是用来存储一级指针的地址的,那么除了定义二级指针、存储一级指针地址,我们在前面还学过一个知识点也是用来存储地址的——指针数组。那么指针数组可以做为函数参数进行传递吗?答案是:可以
//指针数组做函数参数: void test(int** p) { int i = 0; for (i = 0; i < 3; i++) { printf("%c ", **p + i); } } int main() { char a = 'a'; char b = 'b'; char c = 'c'; char* arr[3] = { &a,&b,&c }; test(arr); return 0; }
6.函数指针:
6.1函数指针:
在我们的程序中,各种值和组成成分都有自己的一片空间,我们的自定函数也不例外:
void test() { printf("hehe\n"); } int main() { printf("%p\n", test); printf("%p\n", &test); return 0; }
自定义函数也有着自己的储存空间,那么当我们想要将函数的地址储存起来时,又该如何进行处理呢?函数指针给出了答案。其定义格式为:
函数返回类型( * + 函数指针名 )(函数参数类型)= 函数名;
int Add(int x, int y) { int z = x + y; return z; } int main() { int a = 0; int b = 0; scanf("%d %d", &a, &b); int ret = Add(a, b); printf("a + b = %d\n", ret); printf("\n"); int(*p)(int, int) = Add; //Add函数的返回类型为int类型,函数指针名为p,两个参数类型分别为int类型、int类型 printf("%p\n", p); return 0; }
我们很清楚的看到,通过使用函数指针就可以将函数的地址存储起来,并且我们可以通过使用函数指针优化我们的代码,提升我们代码的可读性:
比如:
void(*signal(int,void(*)(int)(int);
很明显这段代码的可读性非常差,理解起来非常麻烦,于是我们可以通过使用函数指针来提升我们代码的可读性:
typedef void(*pf_t)(int); pf_t signal(int,pf_t);
6.2 函数指针数组:
对于函数指针,同样可以使用函数指针数组存储多个函数指针:
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 main() { int a = 0; int b = 0; scanf("%d %d", &a, &b); int ret1 = Add(a, b); int ret2 = Sub(a, b); int ret3 = Mul(a, b); printf("ADD = %d SUB = %d Mui = %d\n", ret1, ret2, ret3); //使用函数指针从存放函数地址: int(*padd)(int, int) = Add; int(*psub)(int, int) = Sub; int(*pmul)(int, int) = Mul; //函数指针数组: int(*p[3])(int, int) = { padd,psub,pmul }; //通过函数指针数组打印函数地址: int i = 0; for (i = 0; i < 3; i++) { printf("指针p[%d]中存放的地址为:%p\n", i, p[i]); } //通过函数指针数组调用函数: for (i = 0; i < 3; i++) { int RET = p[i](a, b); printf("%d ", RET); } printf("\n"); return 0; }
7.指向函数指针数组的指针:
7.1 书写格式:
函数返回类型( * ( * 指针名 ))( 函数参数类型) = &函数名;
7.2 示例:
使用中我们会遇到的指向数组的指针的实际使用基础案例:
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 main() { int a = 0; int b = 0; scanf("%d %d", &a, &b); //使用函数指针从存放函数地址: int(*padd)(int, int) = Add(a, b); int(*psub)(int, int) = Sub(a, b); int(*pmul)(int, int) = Mul(a, b); //函数指针数组: int(*p[3])(int, int) = { padd,psub,pmul }; //使用指针指向函数指针数组: //即pp为指向函数指针数组的指针 int* pp = &p; //上面这种形式是为了便于我们理解 //而更多的会写成下面这种形式: int(*(*p_p))(int, int) = &p; //验证指向函数指针数组的指针指向是否正确: printf("%p\n", &p); printf("%p\n", pp); printf("%p\n", p_p); return 0; }
8.回调函数:
8. 1 定义:
我们前面都知道了,函数的调用除了我们最基础的调用方式之外,通过函数指针我们也可以实现对函数的调用。而这种通过函数指针调用函数的方式,就称为回调函数
。在实际的使用中,如果你把函数的地址(即使用指针)传递给另一个函数 ,且当这个指针调用了它指向的函数时我们就把这样的函数称作回调函数。
8.2 使用实例:
我们一起来看一个回调函数的基础使用实例:
//函数1: void test() { printf("test\n"); } //函数2: //使用函数指针接收,并对函数test进行调用: void TEST(void(*p)()) { //使用函数指针调用test函数: p(); printf("TEST\n"); } int main() { //将函数地址传递给另一个函数: TEST(test); return 0; }
在这个过程中,函数 test 就被作为函数参数传递了出去,并且在函数 TEST 中被函数指针接收,且该指针调用了它指向的 test 函数,于是我们就可以说 test 函数是回调函数。
结合我们之前写的三个算数函数我们再来看一看回调函数在实际使用中的真实使用方式:
//计算函数功能实现: void Add(int x, int y) { int z = x + y; printf("%d + %d = %d\n", x, y, z); } void Sub(int x, int y) { int z = x - y; printf("%d - %d = %d\n", x, y, z); } void Mul(int x, int y) { int z = x * y; printf("%d * %d = %d\n", x, y, z); } //使用函数指针调用函数: void calc(void(*p)(int, int)) { int x = 0; int y = 0; printf("请输入两个操作数:>"); scanf("%d %d", &x, &y); p(x, y); } void menu() { printf("********************\n"); printf("***** 1.ADD *****\n"); printf("***** 2.SUB *****\n"); printf("***** 3.MUL *****\n"); printf("***** 0.EXIT *****\n"); printf("********************\n"); printf("请输入:"); } int main() { int input; do { menu(); scanf("%d", &input); switch (input) { case 1: calc(Add); break; case 2: calc(Sub); break; case 3: calc(Mul); break; default: break; } } while (input); return 0; }
8.3 使用回调函数模拟库函数 qsort 的实现:
库函数 qsort
是基于快速排序法实现的一个排序函数。而通过上面的学习,我们可以通过使用回调函数来模拟出库函数 qsort 的功能实现:(注:这里我们的模拟实现方式采用冒泡方式)
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; } } 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; }
9.总结:
今天我们对指针的知识又有了新的了解,学习了字符数组、数组指针、指针数组、数组参数、指针参数、函数指针、函数指针数组、指向函数指针数组的指针以及回调函数的相关知识,算是给我们的指针进阶内容画上了一个完美的句号,希望我的文章和讲解能对大家的学习提供一些帮助。
当然,本文仍有许多不足之处,欢迎各位小伙伴们随时私信交流、批评指正!我们下期见~