admin 管理员组

文章数量: 1087652

C语言学习总结(三)

函数指针

指针即地址,因此函数指针就是函数的地址,这里所讲的地址是函数的入口地址.那么如何得到函数的地址呢?恩,没错就是&+函数名,但其实在C语言中函数名就是函数的地址!来看这样一段代码:

void test()
{printf("hehe\n");
}
int main()
{printf(" test=%p\n", test);printf("&test=%p\n", &test);printf("*test=%p\n", *test);system("pause");return 0;
}

程序运行结果: 这三个的值相等。对就是相等!其实分析一下也不难理解!首先C语言规定函数名就是函数的地址,&函数名也是函数的地址,因此第一个和第二个的值相等是意料之中的!但第三个呢?解引用加函数名,就是去找这个函数,但其实在这里编译器并不关心这个*号,加不加它是无所谓的!不妨再看这样一段代码

void test()
{printf("hehe\n");
}
int main()
{test();(*test)();(&test)();system("pause");return 0;
}

输出结果:
hehe hehe hehe.结果也是一样的!
总结:函数名就是函数的地址!调用函数用函数名就OK了!
那么如何定义一个函数指针呢?
函数指针定义: 下面哪一个是函数指针呢?

    void* pfun1(int);void (*pfun2)(int);

首先来分析操作符的优先级和结合性:第一个语句中括号的优先级高,pfun1先和()结合,因此它是一个函数,是函数就必然有返回值和参数,再一看函数的参数是int型,返回类型为void * 类型!
第二个语句pfun2先和*结合,因为括号提升了它们的结合性!所以pfun2是一个指针,是指针就必然指向,它指向一个返回值为void型,参数为整型的函数!
是否理解看一下这个语句表示啥?

int* (*p)(int, int);

答案:p是一个函数指针,指向返回类型为int*,有两个参数且都是整型的函数.
有兴趣的可以再看看这两个:

(*(void(*)())0)();
void(*signal(int, void(*)(int)))(int);

《c陷阱与缺陷》

函数指针数组

有关指针数组,数组指针的相关知识以前总结过,就不再说明!有兴趣的话可以浏览

函数指针数组,顾名思义一个数组的元素 都是函数指针。
怎么定义函数指针数组呢?代码如下:

int (*p[10])(int);

分析一下:首先p和[]先结合,形成一个数组,那么这个数组的内容是什么呢?int(*)(int)是不是很熟悉,对它就是函数指针类型,因此p是一个函数指针数组。
说了这么多,那这东西到底怎么用呢?
虽然你不会每天都使用函数指针,它确实有自己的用武之地,最常见的用途就是转换表(jump take)和作为参数传递给另一个函数。
回调函数
回调函数:通过函数指针调用函数!即把一个函数的地址传给另一个函数,用这个指针实现对其指向的函数进行调用,我们就称这个函数为回调函数!
也许有人会问,那回调函数到底有什么意义呢?在需要该函数的时候直接调用不就了,为什么需要搞这么多花样呢?这其实和变量作为函数参数是一样的,是一种“以不变应万变的思想”,形参是不变的,但实参却可以变化,只要两者的类型一样即可,为了实现不论实参是什么,函数都可以完成相应的操作,因此一般将回调函数的形参设置为 void * 类型,因为 void * 可以接受任何指针类型的参数!
C语言函数库中有一个排序函数,qsort函数这个函数呢可以实现对任意类型的数据的排序,因为它其中的一个参数就是函数指针,来实现对里两个变量的比较,因此只要我们写好这个比较函数,就OK了!
qsort函数的原型

void qsort( void *base, size_t num, size_t width, int (__cdecl *compare )(const void *elem1, const void *elem2 ) );

这个函数总共有四个参数,第一个参数是待排序数组的首地址,第二个参数是排序数组的元素个数,第三个参数是每个元素的大小(以字节为单位),第四个参数是一个函数指针,指向的是实现两个值比较的函数;
函数的使用方法:

#include<stdio.h>
#include<stdlib.h>
#include<search.h>
void show(int* arr, int len)    
{int i = 0;for (i = 0; i < len; i++){printf("%d ", arr[i]);}printf("\n");
}
int cmp_int(const void* x, const void* y) 
{return *(int*)x>*(int*)y;
}
int main()
{int arr[10] = { 3, 5, 2, 5, 7, 8, 4, 8, 1, 0 };int len = sizeof(arr) / sizeof(arr[0]);show(arr,len);qsort(arr, len, sizeof(int), cmp_int);show(arr, len);system("pause");return 0;
}

输出结果:
3 5 2 5 7 8 4 8 1 0
0 1 2 3 4 5 5 7 8 8
请按任意键继续. . .
是不是很强大,只要我们写好比较函数就OK了,不用管内部的排序和交换之类的东西。

模拟实现qsort函数,实现对字符串的比较。

#define _CRT_SECURE_NO_WARNINGS 2
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
void show(char** arr, int len)//输出函数,数组传参发生降级,因此形参用char**
{int i = 0;for (i =0 ; i < len; i++){printf("%s  ", arr[i]);}printf("\n");
}
int cmp_str(const void* str1, const void* str2)//比较连个字符串的大小
{return (strcmp(*( char**)str1, *( char**)str2));//因为实参是字符串地址的地址,因此在强转之后还需要一次解引用。
}
void swap(void* x, void* y,int width)//交换函数
{char* p = (char*)x;//这里的char没有类型的含义,只是为了实现一个字节一个字节的交换char* q = (char*)y;while (width--){(*p) ^= (*q);   //利用异或实现连个变量的交换,不必引入中间变量。(*q) ^= (*p);(*p) ^= (*q);p++, q++;}
}void my_qsort(void* arr, int len, int sz, int cmp_str(const void*,const void*))//模拟实现qsort函数
{int i = 0;int j = 0;char*p = (char*)arr;for (i = 0; i < len; i++){for (j = 0; j < len - i - 1; j++)//采用冒泡排序法进行排序{if (cmp_str((p + j*sz), p + (j + 1)*sz)>0){swap(((p + j*sz)), ((p + (j + 1)*sz)), sz);}}}
}
int main()
{char* arr[5] = { "abcd", "lmnop", "efghijk", "qrstuvw","xyz" };int len = sizeof(arr) / sizeof(arr[0]);printf("排序前:");show(arr, len);my_qsort(arr,len,sizeof(char*),cmp_str);printf("排序后:");show(arr, len);system("pause");return 0;
}


注:交换函数并没有交换字符串本身,因为字符串存储在字符常量区,不允许进行修改,因此在这里我们通过交换两个字符串的地址实现了对字符串的交换,即通过传址的方式将字符串的地址的地址传进去,然后在函数里面进行交换。

函数指针数组的应用

#define _CRT_SECURE_NO_WARNINGS 2
#include<stdio.h>
#include<stdlib.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;
}
int Mul(int x, int y)//乘
{return x * y;
}
int Div(int x, int y)//除
{if (y == 0){printf("Error\n");return -1;}elsereturn x / y;
}
int main()
{char op[5] = { '0', '+', '-', '*', '/' };int(*p[5])(int x,int y) = {0, Add, Sub, Mul,Div};//函数指针数组,存放五个函数的地址unsigned select = 0;int x = 0;int y = 0;int ret = 0;do{menu();printf("Please select: ");scanf("%d", &select);if (select <= 4 && select >= 1){printf("请输入操作数 :<x,y>\n");scanf("%d%d", &x, &y);ret = (p[select])(x, y);printf("%d %c %d = %d\n", x, op[select], y, ret);}if (select == 0){printf("退出\n");}if (select>4){printf("输入有误!\n");}} while (select);system("pause");return 0;
}

函数功能很简单!

指向函数指针数组的指针

概念:指向函数指针数组的指针是一个数组指针,且这个数组的元素全是是函数指针!
定义:

void fun(char* str)
{printf("%s\n", str);
}
int main()
{void(*pfun)(char*) = fun;            //pfun是函数指针void(*pfun_arr[])(char*) = { pfun }; //pfun_arr函数指针数组void(*(*ppfun_arr)[])(char*) = &pfun_arr;//ppfun_arr函数指针数组指针system("pause");return 0;}

如何理解?
首先 ppfun_arr和* 结合使得其成为一个指针,指向的类型为void( * ()[])(char*),这显然是一个函数指针数组。因此ppfun_arr是一个指向函数指针数组的指针。

本文标签: C语言学习总结(三)