简介:
在前面我们已经讲过了函数,如何声明函数,如何定义函数,如何调用函数,而且知道了函数名就是函数的地址,那么是否可以用指针来存储函数地址呢?答案是可以的。使用指针来存储函数的地址就是这节的主要内容。
函数指针:
就是存储函数地址的指针,就是指向函数的指针,就是指针存储的值是函数地址,我们可以通过指针可以调用函数。
我们先来定义一个简单的函数:
1
2
3
4
5
6
7
8
9
|
//定义这样一个函数
void
easyFunc
(
)
{
printf
(
"I'm a easy Function\n"
)
;
}
//声明一个函数
void
easyFunc
(
)
;
//调用函数
easyFunc
(
)
;
|
上面三个步骤就是我们在学习函数的时候必须要做的,只有通过以上三步我们才算定义了一个完整的函数。
如何定义一个函数指针呢?前面我们定义其他类型的指针的格式是 类型 * 指针名 = 一个地址,比如:
1
|
int
*
p
=
&
a;
//定义了一个存储整形地址的指针p
|
也就是说如果我们要定义什么类型的指针就得知道什么类型,那么函数的类型怎么确定呢?函数的类型就是函数的声明把函数名去掉即可,比如上面的函数的类型就是:
1
|
void
(
)
|
我们再来声明一个有参数和返回值的函数:
1
|
int
add
(
int
a
,
int
b
)
;
|
上面函数的类型依旧是把函数名去掉即可:
1
|
int
(
int
a
,
int
b
)
|
既然我们知道了函数的类型那么函数指针的类型就是在后面加个 * 即可,是不是这样呢?
1
|
int
(
int
a
,
int
b
)
*
//这个是绝对错误的
|
上面这么定义是错误的,绝对是错误的,很多初学者都这样去做,总觉得就应该这样,其实函数指针的类型的定义正好比较特殊,它是这样的:
1
|
int
(
*
)
(
int
a
,
int
b
)
;
//这里的型号在中间,一定要用括号括起来
|
我们定义函数指针只需在 * 后面加个指针名称即可,也就是下面这样:
1
|
int
(
*
p
)
(
int
a
,
int
b
)
=
NULL
;
//初始化为 NULL
|
如果我们要给 p 赋值的话,我们就应该定义一个返回值类型为 int ,两个参数为 int 的函数:
1
2
3
4
5
|
int
add
(
int
a
,
int
b
)
{
return
a
+
b
;
}
p
=
add
;
//给函数指针赋值
|
经过上面的赋值,我们就可以使用 p 来代表函数:
1
2
|
p
(
5
,
6
)
;
//等价于 add(5, 6);
printf
(
"%d\n"
,
p
(
5
,
b
)
)
;
|
输出结果为:11
通过上面的指针函数来使用函数,一般不是函数的主要用法,我们使用函数指针主要是用来实现函数的回调,通过把函数作为参数来使用,接下来我们来看如何使用函数回调
函数回调:
函数回调的本质就是让函数指针作为函数参数,函数调用时传入函数地址,也就是函数名即可。
我们什么时候使用回调函数呢?咱们先举个例子,比如现在小明现在作业有个题不会做,于是给小红打电话说:我现在作业有个题不会做,你能帮我做下吗?然后把答案告诉我?小红听到后觉得这个题也不是立刻能做出来的,所以跟小明说我做完之后告诉你。这个做完之后告诉小明就是函数的回调,如何告诉小明,小红必须有小明的联系方式,这个联系方式就是回调函数。接下来我们用代码来实现:
小明需要把联系方式留给小红,而且还得得到答案,因此需要个参数来保存答案:
1
2
3
4
5
|
void
contactMethod
(
int
answer
)
{
//把答案输出
printf
(
"答案为:%d\n"
,
answer
)
;
}
|
小红这边得拿到小明的联系方式,需要用函数指针来存储这个方法:
1
2
3
4
5
6
|
void
tellXiaoMing
(
int
xiaoHongAnswer
,
void
(
*
p
)
(
int
)
)
{
p
(
xiaoHongAnswer
)
;
}
//当小红把答案做出来的时候,小红把答案通过小明留下的联系方式传过去
tellXiaoMing
(
4
,
contactMethod
)
;
|
上面的回调有人会问为什么我们不能直接 tellXiaoMing 方法中直接调用 contactMethod 函数呢?因为小红如果用函数指针作为参数的时候,不仅可以存储小明的联系方式,还可以存储小军的联系方式,这样的话我这边的代码就不用修改了,你只需要传入不同的参数就行了,因此这样的设计代码重用性很高,灵活性很大。
函数回调的整个过程就是上面这样,这里有个主要特点就是当我们使用回调的时候,一般用在一个方法需要等待操作的时候,比如上面的小红要等到答案做出来的时候才通知小明,不如当小明问小红时,小红直接能给出答案,就没必要有回调了,那执行顺序就是:
回调的顺序是:
上面的小红做题是个等待操作,比较耗时,小明也不能一直拿着电话等待,所以只有小红做出来之后,再把电话打回去才能告诉小明答案。
因此函数回调有两个主要特征:
- 函数指针作为参数,可以传入不同的函数,因此可以回调不同的函数
- 函数回调一般使用在需要等待或者耗时操作,或者得在一定时间或者事件触发后回调执行的情况下
我们使用函数回调来实现一个动态排序,我们现在有个学生的结构体,里面包含了姓名,年龄,成绩,我们有个排序学生的方法,但是具体是按照姓名排?还是年龄排?还是成绩排?这个是不确定的,或者一会还会有新需求,因此通过动态排序写好之后,我们只需传入不同的函数即可。
定义学生结构体:
1
2
3
4
5
6
7
8
|
//定义个结构体 student,包含name,age 和 score
struct
student
{
char
name
[
255
]
;
int
age
;
float
score
;
}
;
//typedef struct student 为 Student
typedef
struct
student
Student
;
|
定义比较结果的枚举:
1
2
3
4
5
6
7
8
|
//定义比较结果枚举
enum
CompareResult
{
Student_Lager
=
1
,
//1 代表大于
Student_Same
=
0
,
// 0 代表等于
Student_Smaller
=
-
1
// -1 代表小于
}
;
//typedef enum CompareResult 为 StudentCompareResult
typedef
enum
CompareResult
StudentCompareResult
;
|
定义成绩,年龄和成绩比较函数:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
|
/*
通过成绩来比较学生
*/
StudentCompareResult
compareByScore
(
Student
st1
,
Student
st2
)
{
if
(
st1
.
score
>
st2
.
score
)
{
//如果前面学生成绩高于后面学生成绩,返回 1
return
Student_Lager
;
}
else
if
(
st1
.
score
==
st2
.
score
)
{
//如果前面学生成绩等于后面学生成绩,返回 0
return
Student_Same
;
}
else
{
//如果前面学生成绩低于后面学生成绩,返回 -1
return
Student_Smaller
;
}
}
/*
通过年龄来比较学生
*/
StudentCompareResult
compareByAge
(
Student
st1
,
Student
st2
)
{
if
(
st1
.
age
>
st2
.
age
)
{
//如果前面学生年龄大于后面学生年龄,返回 1
return
Student_Lager
;
}
else
if
(
st1
.
age
==
st2
.
age
)
{
//如果前面学生年龄等于后面学生年龄,返回 0
return
Student_Same
;
}
else
{
//如果前面学生年龄小于后面学生年龄,返回 -1
return
Student_Smaller
;
}
}
/*
通过名字来比较学生
*/
StudentCompareResult
compareByName
(
Student
st1
,
Student
st2
)
{
if
(
strcmp
(
st1
.
name
,
st2
.
name
)
>
0
)
{
//如果前面学生名字在字典中的排序大于后面学生名字在字典中的排序,返回 1
return
Student_Lager
;
}
else
if
(
strcmp
(
st1
.
name
,
st2
.
name
)
==
0
)
{
//如果前面学生名字在字典中的排序等于后面学生名字在字典中的排序,返回 0
return
Student_Same
;
}
else
{
//如果前面学生名字在字典中的排序小于后面学生名字在字典中的排序,返回 -1
return
Student_Smaller
;
}
}
|
定义排序函数:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
/*
根据不同的比较方式进行学生排序
stu1[]:学生数组
count :学生个数
p :函数指针,来传递不同的比较方式函数
*/
void
sortStudent
(
Student
stu
[
]
,
int
count
,
StudentCompareResult
(
*
p
)
(
Student
st1
,
Student
st2
)
)
{
for
(
int
i
=
0
;
i
<
count
-
1
;
i
++
)
{
for
(
int
j
=
0
;
j
<
count
-
i
-
1
;
j
++
)
{
if
(
p
(
stu
[
j
]
,
stu
[
j
+
1
]
)
>
0
)
{
Student
tempStu
=
stu
[
j
]
;
stu
[
j
]
=
stu
[
j
+
1
]
;
stu
[
j
+
1
]
=
tempStu
;
}
}
}
}
|
定义结构体数组:
1
2
3
4
5
6
7
|
//定义四个学生结构体
Student
st1
=
{
"lingxi"
,
24
,
60.0
}
;
Student
st2
=
{
"blogs"
,
25
,
70.0
}
;
Student
st3
=
{
"hello"
,
15
,
100
}
;
Student
st4
=
{
"world"
,
45
,
40.0
}
;
//定义一个结构体数组,存放上面四个学生
Student
sts
[
4
]
=
{
st1
,
st2
,
st3
,
st4
}
;
|
输出排序前的数组,排序和排序后的数组:
1
2
3
4
5
6
7
8
9
10
11
12
|
//输出排序前数组中的学生名字
printf
(
"排序前\n"
)
;
for
(
int
i
=
0
;
i
<
4
;
i
++
)
{
printf
(
"name = %s\n"
,
sts
[
i
]
.
name
)
;
//输出名字
}
//进行排序
sortStudent
(
sts
,
4
,
compareByName
)
;
//输出排序后数组中的学生名字
printf
(
"排序后\n"
)
;
for
(
int
i
=
0
;
i
<
4
;
i
++
)
{
printf
(
"name = %s\n"
,
sts
[
i
]
.
name
)
;
}
|
总结:
这节主要讲了函数指针,包括函数指针的概念,定义方法,如何使用,还有使用场景等,一定要学会,最后着重讲解了函数回调,函数回调作为一种最基本的程序设计模式,在以后会经常用到,希望大家一定要理解。
链接: http://www.lingxiblogs.com/?p=198#comments