C语言(Head First C)-8:高级函数:函数指针 qsort() 可变参数函数
来源:互联网 发布:应变数据采集仪 编辑:程序博客网 时间:2024/05/17 23:36
该系列文章系个人读书笔记及总结性内容,任何组织和个人不得转载进行商业活动!
8:高级函数:函数指针 qsort() 可变参数函数
基本函数很好用,但有时需要更多功能;
本章学习:
如何把函数作为参数传递,从而提高代码的智商;
学会使用比较器函数排序;
使用可变参数让代码伸缩自如;
场景:
过滤某个字符串数组中的数据;
首先用字符串函数过滤数组:
(Code8_1)
/* * 过滤某个字符串数组中的数据 */#include <stdio.h>#include <string.h>int NUM_ADS = 5;char * ADS[] = { "Amn", "Bxyz", "Cmn", "Dxyz", "Emn",};//非常量字符串子串查找函数int findSubstring(char str[],char substr[]){ int lengthstr = strlen(str); int lengthsubstr = strlen(substr); int i = 0; int j = 0; while (i<lengthstr && j < lengthsubstr) { if (str[i] == substr[j]) { i++; j++; }else{ i++; } } if (j == (lengthsubstr)) { return 0; }else{ return 1; } }//非常量字符串子串查找调用void findWord(){ int i; char word[20]; puts("需要过滤的字符串:"); fgets(word,sizeof(word),stdin); word[strlen(word) - 1] = '\0'; for(i = 0;i<NUM_ADS;i++){ if(findSubstring(ADS[i],word) == 0){ printf("%s\n",ADS[i]); } }}//常量字符串子串查找void find(){ int i; for(i = 0;i<NUM_ADS;i++){ if(strstr(ADS[i],"A")){ printf("%s\n",ADS[i]); } }}int main(int args,char * argv[]){ // find(); findWord(); return 0;}
log:
需要过滤的字符串:
xyz
Bxyz
Dxyz
在这个代码示例中我提供了几个函数:
之所以自己写了一个查找字符串子串的方法是因为库函数strstr只能用于比较常量字符串;
所以我们自己写了一个查找字符串子串的通用方法;
和预想的一样,在遍历数组之后,我们找到了匹配的字符串;值得注意的是我们的匹配条件是固定的,当然我们可以通过复制这个函数来修改匹配条件进行使用;
但,复制函数会产生更多代码;而且,每个函数只能进行一种固定的条件匹配来进行过滤;
我们需要更高端的东西;
把代码传给函数:
我们可以把测试(条件)代码传给find()函数;如果可以把代码打包传给函数,就相当于传给find()函数一台测试机,函数可以再用测试机测试所有数据;
这样,find()函数中大部分都可以原封不动;而用传进来的代码进行条件匹配;
把函数名告诉find():
把原来代码中的搜索条件提取出来,并改写成函数;
int xy_no_A(char * s){
return strstr(s,"xy") && !strstr(s,"A");
}
现在如果能把这个函数作为参数传给find(),就能在find()中进行调用,注入测试;
如果有这样的方式:那么只要能写一个接受字符串并返回真/假的函数,就可以复用同一个find()函数了;
问题:
如何在形参中保存函数名?如果你有函数名,又如何用它来调用函数呢?
答案:
函数名是指向函数的指针:它可以引用存储器中某段代码;
需要注意的是函数名和指向函数的指针还是有区别的,函数名是L-value(Location),指针变量是R-value(Readout),因此函数名不能像指针变量那样自加或自减;
说明:
在C语言中,函数名也是指针;当创建一个叫int go_to_warp(int speed)函数的同时,也会创建一个叫go_to_warp的指针变量(在存储器的常量区),变量中保存了函数的地址;只要把函数指针类型的参数传给find(),就能调用它指向的函数了;
go_to_warp(4);//当调用函数时,你在使用函数指针;
函数指针的语法:
C中没有函数类型,因为函数的类型不止一种;创建函数时,返回类型或形参列表的变化,都会引起函数类型的变化;
函数类型是由这些东西组合定义的;
如何创建函数指针:
对于int go_to_warp(int speed)函数来说,想创建一个指针变量指向这个函数的地址,可以像这样做:
int (*warp_fn)(int);//创建一个warp_fn变量,保存函数地址;
warp_fn = go_to_warp;
warp_fn(4);
之所以这样做,是因为需要把函数的返回类型和接收参数类型告诉C编译器;一旦声明了函数指针变量,就可以向其他变量一样使用它,可以赋值,添加到数组中,或传给函数;
注意:char **是一个指针,通常用来指向字符串数组,即指向字符指针的指针;
修改find()函数,重新运行代码:
(Code8_2)
8_2-find.h
extern int NUM_ADS;extern char * ADS[];//条件提取出来的函数int xy_no_A(char * s);int xy_no_(char * s);void find(int (*func)(char *));8_2-find.c
/* * 过滤某个字符串数组中的数据 */#include <stdio.h>#include <string.h>int NUM_ADS = 5;char * ADS[] = { "Amn", "Bxyz", "Cmn", "Dxyz", "Emn",};int xy_no_A(char * s){ return strstr(s,"xy") && !strstr(s,"A");}int xy_no_B(char * s){ return strstr(s,"xy") && !strstr(s,"B");}void find(int (*func)(char *)){ int i; for(i = 0;i<NUM_ADS;i++){ if(func(ADS[i])){ printf("%s\n",ADS[i]); } }}8_2-findmain.c
/* * 过滤某个字符串数组中的数据 */#include <stdio.h>#include <string.h>#include "8_2-find.h"int main(int args,char * argv[]){ find(xy_no_A); return 0;}
log:
Bxyz
Dxyz
我们运行的是 find(xy_no_A);
这样,find()函数每次就可以运行不同的结构;有了函数指针,就可以吧函数传给函数,用更少的代码创建更强大的程序;
函数指针是C最强大的特性之一;
小结:
-函数指针是指针,调用函数时,函数名前面的*可加可不加,两者等价;
-也可以用&取得函数的地址,也可以不写;
-即使省略*和&,C编译器也能识别它们,这样的代码更好读;
用C标准库排序:
排序函数如何才能对任何类型的数据进行排序;
用函数指针设置顺序:
答案是,C标准库的排序函数会接收一个比较器函数指针,用于判断两条数据是大于、小于还是等于;
qsort()函数:
头文件:#include <stdlib.h>
qsort(void * array, //数组指针
size_t length, //数组长度
size_t item_size, //数组元素长度
int (*compar)(const void*,const void*));//用来比较数组中两项数据大小的函数指针;
别忘了,void*指针可以指向任何数据类型;
使用比较器函数,会告诉qsort()两个元素哪个排在前边:
1)第一个>第二个,返回正数;
2)第一个<第二个,返回负数;
3)两值相等,返回0;
int排序聚焦:
观察qsort()函数接收的比较器函数的签名,会发现它接收两个void*,也就是两个void指针;
void指针可以保存任何类型数据的地址,但使用前必须把它转换为具体类型;
比较器函数声明如下:
int compare_scores(const void * score_a,const void* socre_b);
值以指针的形式传给函数,因此做的第一件事就是从指针中提取整型值;
(Code8_3)
/* * 过滤某个字符串数组中的数据 */#include <stdio.h>#include <stdlib.h>#include <string.h>//比较数字int compare_scores(const void * score_a,const void* socre_b){ int a = *(int *)score_a; int b = *(int *)socre_b; return a - b;}//比较字符串int compare_names(const void * score_a,const void* socre_b){ char ** a = (char **)score_a; char ** b = (char **)socre_b; return strcmp(*a,*b);}int main(int args,char * argv[]){ int scores[] = {9,1,8,2,7,3,6,4,5}; qsort(scores,9,sizeof(int),compare_scores); for (int i = 0; i<9; i++) { printf("%i ",scores[i]); } printf("\n"); char * name[] = {"af","dw","ee"}; printf("%lu\n",sizeof(char *)); qsort(name,3,sizeof(char *),compare_names); for (int i = 0; i<3; i++) { printf("%s ",name[i]); } printf("\n"); return 0;}
log:
1 2 3 4 5 6 7 8 9
8
af dw ee
小结:
如果用b-a,可以交换最终的排序;
需要注意在使用前需要对void*类型进行转换;
别忘了,名字数组是一个字符指针数组,每一项的大小是sizeof(char *);
qsort()改变了数组元素的顺序,是在原数组上进行的改动;
字符串数组中的每一项都是字符指针(char *),当qsort()调用比较器函数时,会发送两个指向数组元素的指针,也就是说比较器函数接收的是指向字符指针的指针;
创建函数指针数组:
如果想在数组中保存函数,必须告诉编译器函数的具体特征:返回类型以及接收什么参数;
void (*funcs[])(int) = {"函数名1","函数名2",...};
注:C语言的枚举,对应的值是整形数从0开始,依次递增的;
函数指针数组可以配合枚举数组将代码进行简化:
函数指针数组让代码易于管理,它们让代码变得更短、更易于扩展,从而可以伸缩;
要点:
-函数指针中保存了函数的地址;
-函数名其实是函数指针;(注意两者的不同,函数名是L-value,在存储器中不分配变量);
-如果你有函数shoot(),那么shoot和&shoot都指向了shoot()函数;
-可以用“返回类型(*变量名)(参数类型)”来声明新的函数指针;
-如果fp是函数指针,那么可以用fp(参数,...)调用函数;
-也可以用(*fp)(参数,...),两种都能工作;
-C标准库(stdlib.h头文件)中有一个qsort()的排序函数;
-qsort()接收指向比较器函数的指针,比较器函数可以比较两个值的大小;
-比较器函数接收两个指针,分别指向待排序数组中的两项;
-如果数据保存在数组中,就可以用函数指针数组将函数与数据项关联起来;
让函数能伸能缩:
printf()函数可以根据传入的格式化参数进行打印:想打印几个就传递几个;
我们的函数如何能做到?
现在我们有4种酒(枚举),每种酒的单价,我们可以通过一个price方法来获取price(AWINE)(使用switch-case匹配);
但如果我们想计算一个酒单上的罗列的酒的总价的话,可以定义一个total函数,然后像这样调用:
total(3,AWINE,CWINE);//接收的是酒的杯数和名字
参照后续代码;
(Code8_4)
/* * */#include <stdio.h>enum drink{ AWINE, BWINE, CWINE, DWINE,};double price(enum drink d){ switch (d) { case AWINE: return 1.0; break; case BWINE: return 2.0; break; case CWINE: return 3.0; break; case DWINE: return 4.0; break; default: break; }}int main(int args,char * argv[]){ price(drink.AWINE); return 0;}
可变参数函数:
参数数量可变的函数被称为可变参数函数(variadic function);
C语言标准库中有一组宏(macro)可以帮助建立自己的可变参数函数;
你可以把宏想象成一种特殊的函数,他可以修改源代码;
举个例子来说:
(Code8_5)
/* * */#include <stdio.h>#include <stdarg.h>//处理可变参数代码的头文件void print_ints(int args,...){ va_list ap; va_start(ap,args); int i; for (i = 0; i<args; i++) { printf("Argument:%i\n",va_arg(ap,int)); } va_end(ap);}int main(int args,char * argv[]){ print_ints(3,78,90,100); return 0;}
log:
Argument:78
Argument:90
Argument:100
分析:
可变参数跟在普通参数后边;
va_start表示可变参数从哪里开始,va_start(ap,args)表示从args参数开始后面都是可变参数;
args中保存了变量的数量;
过程分解:
1)包含stdarg.h头文件:
所有处理可变参数函数的代码都在stdarg.h中;
2)使用‘...’告诉函数还有更多参数:
在C语言中,函数参数后的省略号'...'表示还有更多参数;
3)创建va_list:
va_list用来保存传给函数的其他参数;
4)说明可变参数从哪里开始:
需要把最后一个普通参数的名字告诉C,在这个例子中就是args变量;
5)然后逐一读取可变参数:
参数现在全保存在va_list中,可以用va_arg读取他们;
va_arg接收两个值:va_list和要读取参数的类型;本例中所有参数都是int;
6)最后,销毁va_list:
当读完所有参数,要用va_end宏告诉C你做完了;
7)现在可以调用函数了;
print_ints(3,78,90,100);
函数与宏:
宏用来在编译前重写代码,这里的几个宏va_start、va_arg、va_end看起来很像函数,但实际隐藏在它们背后的是一些神秘的指令;在编译前,预处理器会根据这些指令在程序中插入巧妙的代码;
它们只是被设计成不同函数的样子,预处理会把它们替换成其他代码;
预处理器:
预处理器在编译之前运行,他会做很多事,包括把头文件包含进代码;
注意:
我们不能只使用可变参数,而不用普通参数;至少需要一个普通参数,只有这样才能把它的名字传给va_start;
如果从va_arg中读取比传入函数更多的参数会发生未知错误;以相异类型读取参数,也会发生不确定错误;
现在继续之前的问题:计算一个酒单上的罗列的酒的总价;
(Code8_6)
/* * */#include <stdio.h>#include <stdarg.h>enum drink{ AWINE, BWINE, CWINE, DWINE,};double price(enum drink d){ switch (d) { case AWINE: return 1.0; break; case BWINE: return 2.0; break; case CWINE: return 3.0; break; case DWINE: return 4.0; break; default: break; }}double total(int args,...){// double total = 0; va_list ap;// va_start(ap,args);// int i; for (i = 0; i<args; i++) {// total += price(va_arg(ap,enum drink));// } va_end(ap);// return total;}int main(int args,char * argv[]){ printf("%.2f\n", total(4, AWINE, BWINE, CWINE, DWINE)); return 0;}
log:
10:00
要点:
-接收数量可变参数的函数叫可变参数函数;
-为了创建可变参数函数,需要包含stdarg.h头文件;
-可变参数将保存在va_list中;
-可以用va_start()、va_arg()和va_end()控制va_list;
-至少需要一个普通参数;
-读取参数时不能超过给出参数个数;
-需要知道读取参数的类型;
C语言工具箱:
-有了函数指针就可以把函数当数据传递;
-每个函数的名字都是一个指向函数的指针;
-函数指针是唯一不需要加*和&运算符的指针(当然也可以加上);
-qsort()会排序数组;
-排序函数接收比较器函数指针;
-比较器函数决定如何排序两条数据;
-有了函数指针数组,就可以根据不同类型的数据运行不同的函数;
-参数数量可变的函数叫“可变参数函数”;
-包含stdarg.h就可以创建可变参数函数;
- C语言(Head First C)-8:高级函数:函数指针 qsort() 可变参数函数
- 高级函数--Head First C读书笔记
- c语言可变参数函数
- C语言函数可变参数
- C语言可变参数函数
- C语言可变参数函数
- C语言可变参数函数
- C语言可变参数函数
- C语言可变参数函数
- C语言可变参数函数
- C语言 qsort() 函数
- C语言--qsort函数
- C语言---qsort函数
- C语言qsort()函数
- c可变函数参数
- C可变参数函数
- C可变参数函数
- C函数可变参数
- 【机器学习】Andrew Ng——02单变量线性回归
- Docker Private Registry搭建(三)
- arguments.callee使用
- MyBatis Generator 自动化生成代码工具
- sprintf的用法
- C语言(Head First C)-8:高级函数:函数指针 qsort() 可变参数函数
- 自学编程之路——位运算
- javascript中的函数
- 屏蔽鼠标右键
- 下载文件的一种简单方法js
- ubuntu动态链接库连接出错 cannot open shared object file: No such file or directory
- iOS开发之同一应用设置不同图标和名称
- ECLIPSE/JAVAWEB (一)三大框架之STRUTS框架 持续更新中...
- beego