库函数
自定义函数
先写段程序,复习下自定义函数
#includevoid simple(void); using namespace std; int main(void) { cout << "main() will call the somple() function " << endl; simple(); return 0; } void simple(void) { cout << "I'm simple but a function." << endl; }
自定义函数的三个部分:
函数定义,函数声明,函数调用,缺一不可
1:定义函数函数分为有返回值和没有返回值的
没有返回值就是void函数,通用格式如下:
void functionName (parameterList)
{
statement(s)
return; //可写可不写,没返回值一般不写
}
其中的paramenterList是指传递给函数的参数类型和数量,例如:
void cheers (int n)
{
for(int i = 0; i cout << "Cheers!"; cout << endl; } 这里的int意思就是,在调用cheers函数的时候,应该给它一个Int值作为参数传递给它 有返回值的函数将生成一个值,并将它返回给调用函数,通用格式如下: typeName functionName(paramterList) { statements; return value; } 对于有返回值的函数,必须使用返回语句,以便把值返回给调用函数。 值本身可以是常量、变量、表达式,只是其结果的类型必须为typeName类型或者可以被转换为typeName(比如说我们返回类型是double,但是返回一个int表达式,int会被强转为double类型) C++对于返回值的类有限制:不能是数组,但是可以是其他任何类型,比如结构对象等(但是可以把数组作为结构或对象组成部分来返回) 函数在执行返回语句后结束。如果函数包含多条返回语句,则函数在执行遇到第一条返回语句后结束。 int bigger (int a , int b) { if(a>b) return a; else return b; } 看如下程序,体会自定义有返回值和没有返回值的函数 结果: Cheers! * 1)自定义无返回值cheers,自定义有返回值cube 2)把输入作为cube的参数,返回double类型 3)自定义函数cheers引用自定义函数cube 1)为什么需要原型? 函数原型描述了函数到编译器的结构,也就是它将函数的返回值类型及参数类型数量告诉编译器 cube()函数完成计算后,把返回值放到了指定位置——可能是寄存器,可能是内存,然后调用函数(main())从这个为之取得返回值 C++编译器并不会在文件中进一步查找,这样效率太低了,所以最好能提供原型 2)原型的语法 函数原型并不需要提供变量名,有类型列表有足够了。比如对于cheer()原型,我们只提供了参数类型: void cheers(int); 比方说我们去饭店吃饭,只需要打电话告诉饭店,我们有几个人,我们吃什么菜,我们有几个人,而不需要把每个人的名字告诉饭店一个道理 所以说比方说下面的原型: double cube(double x)写成double cube (double) 一样是可以编译的! 3)原型的功能 要确保以下几点: ·编译器正确处理函数返回值; ·编译器检查使用的参数数目是否正确 ·编译器检查使用的参数类型是否正确 但是如果是较大的数据类型转换为较小类型时,有些编译器会发出警告,指出数据可能丢失 比如把2.33E27转为int类型,这种值就不会被正确转换 C++通常按值传递参数,这意味着将数值参数传递给函数,而后者将其赋给新的变量 比如:double volume = cube (side); → side是一个变量,前面定义为5 cube的函数头如下:double cube (double x) → 函数被调用时,该函数创建一个新的名为x的double变量,把它初始化为5 → 这样cube()执行的操作将不会影响main()中的数据。 → cube使用的是side的副本,而不是原来的数据 用于接收传递值得变量被称为形参,比如这里double 的x 传递给函数的值被称为实参,比如原来的side 1)函数内定义两个变量,哪怕是相同类型,也要写全 void fifi (float a, float b) 2)与一个参数一样,原型中的变量名不必与定义中的变量名相同,甚至可以省略,但是最好写上,至少知道人家是干嘛用的 见如下程序:演示修改形参并不会影响调用函数中的数据 结果: Please enter a character: 1 * 1)声明了ch和times分别作为n_chars的两个参数,然后通过函数返回内容 题目如下: 美国许多州都采用某种纸牌游戏来发行彩票,让参与者从卡片中选择一定数目的选项。 例如:从51个数字中选取6个,随后管理者随机抽取6个数。如果参与者选择的数字与这6个完全相同,赢得几百万美金。函数计算中奖概率 首先,必须从51个数中选取6个数,而中奖的概率为1/R,R的计算公式为: 选择6个数时,分母为6!,分子是连续6个整数的乘积,从51开始连续递减,可以采用for循环计算 long double result = 1.0; for(n = numbers , p = picks ; p > 0 ; n-- , p--)(numbers是一共从多少个数中选择) result = result * n / p;(pick是选取数的个数) 程序: 结果(输出是R,概率是1/R): Enter the total number of choices on the game card and the number of picks allowed: * 1)这个程序就演示了可以在函数中使用两种局部变量。 首先是形参(numbers和picks);其次是其他局部变量(result、n、p) 形参和局部变量的主要区别是,形参从调用函数那里获得自己的值,而其他变量是从函数中获得自己的值 2)注意可以这样写: while((cin >> total >> choices) && choices <= total),代表如果输入成功 如何把函数和数组结合起来? 假设使用一个数组来记录家庭野餐中每人吃了多少甜品,现在要统计总数,来使用函数实现 我们返回值部分不能为数组,但是参数部分可以是 比如以下声明: int sum_arr ( int arr[] , int n ) 这里的arr看起来是一个数组,其实它是一个指针,但是函数编写时,可以把它看成是数组 下面程序将演示如同使用数组名那样使用指针 结果:Total cookies eaten: 255 1:函数如何使用指针处理数组 大多数情况下,C++和C一样,也将数组名视为指针,它将数组名解释为第一个元素的地址: cookies == & cookies [0] 但是也有一些例外,比如 1)数组声明使用数组名来标记存储位置 2)对数组名使用sizeof将得到整个数组的长度(字节为单位) 3)&用于数组名时,返回整个数组的地址 再来看这个函数调用:int sum = sun_arr(cookies , ArSize) cookies是数组名,而C++规定它是第一个元素的地址,因此函数传递的是地址。所以cookies类型必须是int指针,即int *。所以正确的函数头应该是: 在函数头或函数原型中,int * arr和int arr[]含义相同,它们都意味着arr是一个int指针 arr[i] == *(ar + i) &arr [i] == ar + i 将指针(数组名+1)意味着加上了一个与指针指向的类型的长度相等的值,就是指针偏移 sum_arr把cookies的第一个元素的地址和数组中的元素传递给了sum_arr函数。 sum_arr函数将cookies的地址赋给指针变量arr,将ArSize赋给int变量n 函数在传递数组的时候,使用的是原来的数组 看下面的函数,这回我们使用指针来演示上面的程序 结果: array adress: 0x3cebbff7d0 * 1) cout << "size of cookies: " << sizeof(cookies) << endl; 这里打印的是数组的大小 2)cout << "arr adress: " << arr << endl; 我们sum_array的地址和cookies地址完全一样,所以我们知道sum_array其实就是使用了cookies的地址来进行运算 3)cout << "size of arr: " << sizeof arr << endl;//这里返回的不是数组,而是指针,8个字节,int *占用8字节 先看一个案例:考虑对房地产数组执行的操作。两个基本操作是,将值读入到数组中和显示数组的内容。 另外添加另一个操作:重新评估每种房地产的值。(所有房地产都以相同的比率增加或减少) 结果: 输入: 显示: Property #1 : $1000 修改因子: *程序说明: 1)填充数组 该函数指定两个参数,一个是数组名,另一个是指定读取的最大元素数,该函数返回实际读取的元素数,还要注意判断它输入是否正确和输入是否大于0这两个条件,然后使用for循环,创建临时值,挨个放进去 2)显示数组 这个就是循环+显示数组元素 3)修改数组 它需要给函数传递三个参数,分别是因子,数组和元素数目,没有返回值 除了上面例子中提供数组中的数据类型、数组的起始位置和数组中元素数量,还有另一种方法: 即指定元素区间(range),这可以通过传递两个指针来完成:一个指针标识数组开头,另一个指针标识数组的尾部,看下例(还是饼干): 结果:Total cookies eaten: 255 * 1)定义了指针的开头和指针的结尾,求和就用指针内容 2)调用函数的开头结尾位置采用cookies和cookies + ArSize 三个const的用法 ①const int *pt ②in *const pt; ③const int *const pt; ①const int *pt等效于 int const *pt 使用const,就不能通过指针修改数组了,比如如下程序: 1)n= 10 但是如果加上const,就不能通过指针来修改了 结果如下: 报错说*pt是只读,所以不能通过const修改后的指针来修改原值 但是指针还是可以指向其他地方的,如下: 还是可以指向其他位置,比如m 1)n= 10 ②int * const pt; 这时候把*放到了const的前面,就可以通过pt指针修改原n值 结果: 再把pt指针指向m 这时候就报错了,说明*const pt可以修改指向数值的值,但是只能指向一个值,不能指向任意变量 ③const int *const pt; 这就说明了,你既不能通过指针修改内容,也不能指向其他位置 尽可能使用const 将指针参数声明为指向常量数据的指针有两条理由: ·这样可以避免由于无意间修改数据而导致的编程错误 ·使用const使得函数能够处理const和非const实参,否则将只能接收非const数据 如果条件允许,则应将指针形参声明为指向const的指针 如果函数形参是const,那么不管函数是const或者非const,都能进行调用 如果函数形参是非const,那么如果函数是const,就不能进行调用 所以在使用的时候,最好给形参设置成const指针! 跟一维数组类似,假设 int date[3] [4] = {{1,2,3,4},{5,6,7,8},{2,4,6,8}}; int total =sum(data , 3),sum函数就是数组名+数组中的元素 data是一个数组名,有三个元素,第一个元素本身是数组,有4个int组成,因此data类型是指向由4个int组成的数组的指针,正确的原型如下: int sum(int(*ar2)[4], int size))(指针的数组) 这声明了将4个指向int的指针组成的数组 而不是由一个指向由4个int组成的数组的指针 如:int ar2[4],(这是数组的指针) 二维数组函数写成下面这样子,可读性更强; int sum(int ar2[] [4] , int size); 因此二维数组的遍历就需要循环嵌套,外层访问行,内层访问列 可以用数组表示法的原因是:ar2指向数组的第一个元素,因此ar+r指向编号为r的元素,又因为元素本身又是数组,随意ar2[r]是由4个int组成的数组的名称,所以ar2[r][c]是4个int组成的数组中的一个元素(最简单的方法),必须对指针执行两次才能解除引用,如下: 先对行指针+r,利用*取出行元素,就是数组,然后对数组名+c,就是针对这个数组偏移,然后再*取出元素 ·char数组 char ghost[15] = "galloping"; ·用引号括起来的字符串常量(字符串字面值) ·被设置为字符串的地址的char指针 char * str = "galumping" 看如下例子:使用一个函数来计算特定的字符再字符串中出现的次数 3 m characters in minimum 函数无法返回字符串,但可以返回字符串的地址,这样效率更高 下面定义一个buildstr()函数,返回一个指针,接收两个参数,一个字符一个数字。函数使用new创建一个长度和数字参数相等的字符串,然后将每个元素都初始化为该数组 结果: Enter a character: V * 1)要注意new字符串的时候多留一位给我们的空格 2)new完了要记得delete 结构变量的行为更接近基本的单值变量,在这种情况下,函数将使用原始结构的副本,另外,函数也可以返回结构 与数组名就是数组的第一个元素的地址不同,结构名只是结构的名称,要获得结构的地址,必须使用地址运算符& 如果结构非常大,则赋值结构将增加内存要求,所以许多C程序员进行函数传递和返回,更喜欢指针来访问结构体的内容 C++提供了第三种选择——按引用类型 看如下程序: 结果: Two days :10 hours , 40 mins. *这里所有的参数类型都是结构体 定义两个结构,用于表示两种不同的描述位置的方法,然后开发一个函数,将一种格式转换为另一种格式,并显示结果 结果: Enter the x and y value: 30 * 1)函数引用结构体类型,结构体类型应该放在最前面,不然报错 2)while (cin >> rplace.x >> rplace.y),如果输入成功 可以对上述的程序进行修改 结果: Enter the x and y value: 30 * 1)函数内部传递的是指针,参数是指针,没有返回值 string对象和结构更为相似,例如,可以将一个结构赋给另一个结构,也可以将一个对象赋给另一个对象。可以将结构作为完整实体传递给函数 下面的程序声明了一个string对象数组,并将该数组传递给一个函数显示内容: 结果: Enter 5 favorite food: 假设 要使用一个array对象存储一年四个季度的开支,看如下程序: 结果: Enter Spring expensens: 212 *cin >> (*pa)[i];//pa是array对象的指针,需要转化成值再进行操作 C++函数又一个特点——可以自己调用自己(但是C++不允许main()调用自己),这种功能被称为递归。 如果递归函数调用自己,则被调用的函数也将调用自己,这将无限循环下去,除非代码又包含终止的内容,一般放在if语句中,如下:void类型的递归函数recurs()代码如下: 看如下的例子: 结果: counting down.... 4 * 1)当n递减到0的时候才开始执行下面的代码:cout << n << ": Kandom" << endl; 2)第二个部分将于与函数调用相反的顺序执行5次! 3)每次调用递归都会创建自己的一套变量,因此当程序到第五次调用时,将有5个独立的n变量,其中每个变量的值都不同,对程序如下修改! 结果如下: counting down.... 4(n at adress: 0x1d3adffc10) 在需要将一项工作不断分为两项较小、类似的工作,递归很有用。递归方法有时被称为分而治之策略,比如对标尺找到两端,找到终点并标记,然后同样的操作给标尺的左右两部分继续用。 见如下程序: 结果: | | | * 1)刚开始是两边两条斜杠,第二次变成三条,第三次变成5条,一直递归下去, 2)将递归次数写进了形参,然后形成判断条件 与数据项相似,函数也有地址。函数的地址是存储机器语言代码的内存的开始地址。我们可以编写一个将另外一个函数的地址作为参数的函数,这样第一个函数能找到第二个函数并运行,特殊的是,它允许在不同的时间传递不同函数的地址,这意味着可以在不同的时间使用不同的函数 比如我们要设计一个estimate()函数来估算指定行数的代码需要的时间,并希望不同的此恒许愿都将使用该函数。我们将程序员要使用的算法函数地址传递给esitmate(),为此必须: ·获取函数地址 ·声明一个指针 ·使用函数指针调用函数 1)获取函数地址 使用函数名即可,不用参数,比如说think()是函数,则think是该函数的地址 2)声明函数指针 比如:函数如下 double pan(int);则指针如下 double (*pf)(int); 写的时候直接把原型中的函数名用(*Name)替换了就行了 *一定要加(),不然就是函数名 double (*pf)(int);这时函数指针 double *pf(int),这就是一个函数了 3)使用指针调用函数 现在(*pf)扮演的角色就与函数名相同,因此使用(*pf)时,可将它看作是函数名即可! 例如: double (*pf)(int); pf = pam; 下面的程序演示如何使用函数指针,两次调用estimate()函数,依次传递besty()函数的地址,另一次传递pam()函数的地址。如下 结果: How many lines of code do you need? * 可以看到在最后一行代码我们声明了和Rick,Jack类型相同的函数指针*pf,使用的时候直接用名字参数即可: (*pf)(lines) << " hours." << endl; 看下例: 结果: *总结一下 1) (*pa[2])(av,3) ≠ *pa[2](av,3),括号优先级不一样 pa[2](av,3)来说,pa[2]是指针数组第三个元素,就是函数指针,最后*,就是第三个函数的值 (*pa[2])(av,3)来说,(*pa[2])就是第三个元素函数指针所指向的函数指针,然后赋值,出来的还是地址 2) 对于函数指针来说*pf和pf其实是等价的 3) 请注意pa和&pa的区别(pa是数组),pa是数组第一个元素的地址,即&pa[0],而&pa是整个数组(三个指针)的地址,从数字上说它们相同,但是它们类型不同。 比如pa+1是下一个元素的地址,而&pa+1是后面12个字节内存块的地址。 还有个差别,要得到第一个元素的值,只需要对pa解除即可,而&pa解除两次引用 **&pa == *pa == pa[0] 4) 用auto解放了复杂的声明过程 const double *(*pa[3])(const double *,int) = {f1,f2,f3}; 5) 还可以用typedef进行简化(并没有定义新的东西,而是起了一个别名,第五章说过) 比如:typedef int n,这里就把n变成int类型了 比如:typedef double real;这里real就变成了double类型了,而不是变量 实例如下: typedef const double *(*p_fun) (const double *,int),可以直接用p_fun类型了 p_fun p1 = f1; p_fun pa[3] ={f1,f2,f3}; 1:使用函数的三个步骤? 定义,调用,声明 2:请创建与下面的描述匹配的函数原型 a:igor()没参数,没返回值 void igor(void); b:tofu()接收一个int参数,且返回一个float float tofu(int) c:mpg()接收两个double,返回一个double double mpg(double,double) d:summation()将long数组名和数组长度作为参数,返回一个long long arr[n]; long summation(long [],int) e:doctor()接受一个字符串参数(不能修改),并返回double double doctor(const char *str) f:ofcourse()将boss结构作为参数,无返回值 void ofcourse(boss bs) 形参名可写可不写 g:plot将map结构的指针作为参数,返回字符串 char *plot(map *pt) 3:编写一个接受3个参数的函数,:int数组名、数组长度、和一个int值 ,并将数组所有元素都设置为该int值 void Set(int ar[], int n, int x) { for(i = 0;i { ar[i] = x; } } 4:编写一个接受三个参数的函数:指向数组区间中第一个元素的指针、指向数组区间最后一个元素后面的指针以及一个int值,并将数组中的每个元素都设置为该int值 *注意循环条件 5:编写一个double数组名和数组长度作为参数,并返回该数组中最大值的函数。该函数不应该修改数组内容。 6:为什么不对基本类型的函数参数使用const? 我们的C++一般是按值传递参数,而不像数组和结构这种,整体复制,修改副本其实对原来的值不影响,但是如果用指针了就必须加const 7:C++使用哪三种C风格的字符串 char数组。char ch[] = "Hello"; 字符串。"hello;"; 可以指向字符串首字符的指针。char * pt = "Hello.指的是地址 8:编写一个函数,原型如下: 该函数将字符串中所有的c1都替换为c2,并返回替换次数 int replace(char * str, char c1,char c2) 9:表达式*"pizza"代表:字符串首地址,取出p "taco[2]"呢,taco是数组,第三个元素,双引号就是首地址 10:C++允许按值传递结构,也允许传递结构的地址。如果glitz是一个结构变量,如何按值传递它?如何传递地址?两种方法有何利弊? 地址传递不能保护原始数据;但是按值传递内存消耗大 11:函数judge()返回类型是int,它将这样一个函数地址作为参数:将const char指针作为参数,并返回int值,写该函数原型 int judge(int (*pf) (const char)); 12:结构如下: struct applicant { char name[30]; int ratings[3]; } a:编写一个显示该结构体内容的函数,参数就是结构体 b.将该结构的地址作为参数,显示参数所指向结构的内容 13:假设f1()和f2()原型如下: void f1(applicant * a); const char * f2(const applicant * a1,const applicant * a2); 1)请将p1和p2分别声明为指向f1和f2的指针; 2)将ap声明为数组,它包含5个类型与p1相同的指针 3)将pa声明为一个指针,它指向的数组包含10个类型与p2相同的指针 使用typedef方法来实现。 1) 2)pf_1 ap[5]; 3p_f2 (*pa) [10];#include
Cheers!
Cheers!
Cheers!
Cheers!
Give me a number:
3
3 side cube = 27
Cheers!
Cheers!
Cheers!
Cheers!
Cheers!
Cheers!
Cheers!
Cheers!
就是头文件下面为什么要再声明一下#include
Enter a integer: 10
1111111111
Enter another character or press q_key to quie:
q
#include
49
6
You have one chance in 1.39838e+07 of winningPlease enter next two number(q to quit)100
10
You have one chance in 1.73103e+13 of winningPlease enter next two number(q to quit)#include
in sun_arr ( int * arr , int n)#include
size of cookies: 32
arr adress: 0x3cebbff7d0
size of arr: 8
Total cookies eaten: 255
arr adress: 0x3cebbff7d0
size of arr: 8
3 cookies eaten: 7#include
Enter value #1:1000
Enter value #2:2000
Enter value #3:3000
Enter value #4:4000
Enter value #5:5000
Property #2 : $2000
Property #3 : $3000
Property #4 : $4000
Property #5 : $5000
Enter revaluation factor: 2
Property #1 : $2000
Property #2 : $4000
Property #3 : $6000
Property #4 : $8000
Property #5 : $10000
Done!#include
int main(void)
{
int n =10;
int *pt = &n;
cout << "1)n= " << n << endl;
*pt = 20;//通过指针修改了n的对象
cout << "2)n= " << n << endl;
return 0;
}
2)n= 20int main(void)
{
int n =10;
const int *pt = &n;
cout << "1)n= " << n << endl;
*pt = 20;//通过指针修改了n的对象
cout << "2)n= " << n << endl;
return 0;
}
#include
*pt = 100
m = 100#include
1)n= 10
2)n = 20#include
int sum(int are[][4],int size)
{
int total = ;
for(int r = 0; r < size; r++)
{
for(int c = 0;c <4; c++)
{
total += ar2[r][c];
}
}
return total;
}
3:提取元素
ar2[r][c] == *(*(ar2+r)+c)
五、函数和C风格字符串
1:表示字符串的三种方法
#include
2 u characters in ululate#include
Enter a integer: 20
VVVVVVVVVVVVVVVVVVVV
++++++++++++++++++++-Done-++++++++++++++++++++#include
Three days :15 Hours, 12Minutes.#include
40
Distance = 50
Angel = 53.1301degree
Next two numbers(q to quit):-100
100
Distance = 141.421
Angel = 135degree
Next two numbers(q to quit):q#include
40
Distance = 50
Angel = 53.1301degree
Next two numbers(q to quit):-100
100
Distance = 141.421
Angel = 135degree
Next two numbers(q to quit):q#include
1: break
2: milk
3: icecr
4: saled
5: cake
Your list
1: break
2: milk
3: icecr
4: saled
5: cake#include
Enter Summer expensens: 256
Enter Fall expensens: 208
Enter Winter expensens: 244
EXPENSENS:
Spring: $212
Summer: $256
Fall: $208
Winter: $244
Total expenses: 920
void recurs(argumentlist)
{
statements1
if(test)
resurs(arguments)
statements2
}
#include
counting down.... 3
counting down.... 2
counting down.... 1
counting down.... 0
0: Kandom
1: Kandom
2: Kandom
3: Kandom
4: Kandom#include
counting down.... 3(n at adress: 0x1d3adffbe0)
counting down.... 2(n at adress: 0x1d3adffbb0)
counting down.... 1(n at adress: 0x1d3adffb80)
counting down.... 0(n at adress: 0x1d3adffb50)
0: Kandom(n at adress: 0x1d3adffb50)
1: Kandom(n at adress: 0x1d3adffb80)
2: Kandom(n at adress: 0x1d3adffbb0)
3: Kandom(n at adress: 0x1d3adffbe0)
4: Kandom(n at adress: 0x1d3adffc10)#include
| | | | |
| | | | | | | | |
| | | | | | | | | | | | | | | | |
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | |
double pam(int);
#include
100
Here is Rick's estimate:
100 Lines code will take 5 hours.
Here is Jack's estimate:
100 Lines code will take 7 hours.#include
PART1:--------------------
Adress Value
0xf0e7dff6a0: 1112.3
0xf0e7dff6a8: 1542.6
PART2:--------------------
Adress Value
0xf0e7dff6a0: 1112.3
0xf0e7dff6a8: 1542.6
0xf0e7dff6b0: 2227.9
0xf0e7dff6a0: 1112.3
0xf0e7dff6a8: 1542.6
0xf0e7dff6b0: 2227.9
PART3:--------------------
Adress Value
0xf0e7dff6a0: 1112.3
0xf0e7dff6a8: 1542.6
0xf0e7dff6b0: 2227.9
0xf0e7dff6b0: 2227.9
auto pb = pa;//这样pb也是函数指针数组void Set(int *begin,int *end , int x)
{
for (int *pt = begin; pt != end ; pt ++ )
{
*pt = x;
}
}
double Set(const double ar[],int len)
{
double max = arr[0];
for(int i = 1; i < size; i++)
{
if(max < arr[i]);
max = arr[i];
}
return max;
}
int replace(char * str,char c1,char c2)
{
int count;
while(*str)
{
if(*str == c1)
{
*str = c2;
count++;
}
str++;
}
return count;
}
void show(applicant ap)
{
for (int i = 0; i < 2; i++)
{
cout << ap.name[i] << ap.ratings[i] << endl;
}
}
void show(applicant *ap)
{
for (int i = 0; i < 2; i++)
{
cout << ap->name << ap->ratings[i] << endl;
}
}
typedef void *(pf_1)(applicant * a);//使用typedef直接变成类型
pf_1 p1 = f1;
typedef char const char *(*p_f2)(const applicant *a1,const applicant *a2);
p_f2 p2 = f2;



