C语言函数指针知识点总结

这篇文章主要讲解了“C语言函数指针知识点总结”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“C语言函数指针知识点总结”吧!

创新互联建站拥有10余年成都网站建设工作经验,为各大企业提供网站设计制作、成都网站设计服务,对于网页设计、PC网站建设(电脑版网站建设)、重庆APP开发公司、wap网站建设(手机版网站建设)、程序开发、网站优化(SEO优化)、微网站、域名注册等,凭借多年来在互联网的打拼,我们在互联网网站建设行业积累了很多网站制作、网站设计、网络营销经验,集策划、开发、设计、营销、管理等网站化运作于一体,具备承接各种规模类型的网站建设项目的能力。

函数指针和一个简单的函数

我们从一个非常简单的”Hello World“函数入手,来见识一下怎样创建一个函数指针。

1 2 3 4 5 6 7 8 9 10 11 12 13 14#include
 //
函数原型
void

sayHello();
 //函数实现void

sayHello(){
    printf("hello
world\n"
);} //
main函数调用
int

main() {
    sayHello();}

我们定义了一个名为sayHello的函数,它没有返回值也不接受任何参数。当我们在main函数中调用它的时候,它向屏幕输出出”hello world“。非常简单。接下来,我们改写一下main函数,之前直接调用的sayHello函数,现在改用函数指针来调用它。

1 2 3 4int

main() {
    void

(*sayHelloPtr)() = sayHello;
    (*sayHelloPtr)();}

第二行void (*sayHelloPtr)()的语法看起来有些奇怪,我们来一步一步分析。

  1. 这里,关键字void的作用是说我们创建了一个函数指针,并让它指向了一个返回void(也就是没有返回值)的函数。

  2. 就像其他任何指针都必须有一个名称一样,这里sayHelloPtr被当作这个函数指针的名称。

  3. 我们用*符号来表示这是一个指针,这跟声明一个指向整数或者字符的指针没有任何区别。

  4. *sayHelloPtr两端的括号是必须的,否则,上述声明变成void
    *sayHelloPtr()
    *会优先跟void结合,变成了一个返回指向void的指针的普通函数的声明。因此,函数指针声明的时候不要忘记加上括号,这非常关键。

  5. 参数列表紧跟在指针名之后,这个例子中由于没有参数,所以是一对空括号()

  6. 将上述要点结合起来,void (*syaHelloPtr)()的意义就非常清楚了,这是一个函数指针,它指向一个不接收参数且没有返回值的函数。

在上面的第二行代码,即void (*sayHelloPtr)() = sayHello;,我们将sayHello这个函数名赋给了我们新建的函数指针。关于函数名的更多细节我们会在下文中讨论,现在暂时可以将其看作一个标签,它代表函数的地址,并且可以赋值给函数指针。这就跟语句int
*x = &myint;
中我们把myint的地址赋给一个指向整数的指针一样。只是当我们考虑函数的时候,我们不需要加上一个取地址符&。简而言之,函数名就是它的地址。接着看第三行,我们用代码’(*sayHelloPtr)();·‘解引用并调用了函数指针。

  1. 在第二行被声明之后,sayHelloPtr作为函数指针的名称,跟其他任何指针没有差别,能够储值和赋值。

  2. 我们对sayHelloPtr解引用的方式也与其他任何指针一样,即在指针之前使用解引用符*,也就是代码中的*sayHelloPtr

  3. 同样的,我们需要在其两端加上括号,即(*sayHelloPtr),否则它就不被当做一个函数指针。因此,记得声明和解引用的时候都要在两端加上括号。

  4. 括号操作符用于C语言中的函数调用,如果有参数参与,就将其放入括号中。这对于函数指针也是相似的,即代码中的(*sayHelloPtr)()

  5. 这个函数没有返回值,也就没有必要将它赋值给任何变量。单独来说,这个调用跟sayHello()没什么两样。

接下来,我们再对函数稍加修改。你会看到函数指针奇怪的语法,以及用调用普通函数的方法来调用赋值后函数指针的现象。

1 2 3 4int

main() {
void

(*sayHelloPtr)() = sayHello;
sayHelloPtr();}

跟之前一样,我们将sayHello函数赋给函数指针。但是这一次,我们用调用普通函数的方法调用了它。稍后讨论函数名的时候我会解释这一现象,现在只需要知道(*syaHelloPtr)()syaHelloPtr()是相同的即可。

简单来说,函数指针,就是指向函数的指针;其核心是函数地址和函数原型

1 2 3 4 5 6 7 8void

sayHello(){
    printf("hello
world\n"
);} int

main() {
    void

(*sayHelloPtr)() = sayHello;
    (*sayHelloPtr)();}

上面的main函数如下来写,更能体现函数指针的本质

1 2 3 4 5int

main() {
    void

*ptr = NULL;
//
声明一个指针
    prt
= sayhello;
//
指向一个函数
  (*
(
void

(*)()) ptr)();
//
调用函数指针,或者说以指定原型 进行函数调用
}

void (*)() 是函数原型
((void (*)())ptr) 是将ptr转换为 上面的原型
(*(void (*)())ptr)()是以指定原型进行函数调用

理解上面这3步,函数指针就是:
(1)用一个指针记住函数地址,以便于后续使用
(2)需要使用时,用指定函数原型进行转换和调用。

实际应用中,都是简化为直接声明为指定好函数原型的指针(所以叫函数指针),这样调用时就不需要函数原型转换了。

带参数的函数指针

好了,这一次我们来创建一个新的函数指针吧。它指向的函数仍然不返回任何值,但有了参数。

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17#include
 //函数原型void

subtractAndPrint(
int

x,
int

y);
 //函数实现void

subtractAndPrint(
int

x,
int

y) {
    int

z = x - y;
    printf("Simon
says, the answer is: %d\n"
,
z);
} //main函数调用int

main() {
    void

(*sapPtr)(
int,
int)
= subtractAndPrint;
    (*sapPtr)(10,
2);
    sapPtr(10,
2);
}

跟之前一样,代码包括函数原型,函数实现和在main函数中通过函数指针执行的语句。原型和实现中的特征标变了,之前的sayHello函数不接受任何参数,而这次的函数subtractAndPrint接受两个int作为参数。它将两个参数做一次减法,然后输出到屏幕上。

  1. 在第14行,我们通过’(*sapPtr)(int, int)’创建了sapPtr这个函数指针,与之前的区别仅仅是用(int, int)代替了原来的空括号。而这与新函数的特征标相符。

  2. 在第15行,解引用和执行函数的方式与之前完全相同,只是在括号中加入了两个参数,变成了(10, 2)

  3. 在第16行,我们用调用普通函数的方法调用了函数指针。

带参数且有返回值的函数指针

这一次,我们把subtractAndPrint函数改成一个名为subtract的函数,让它把原本输出到屏幕上的结果作为返回值。

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20#include
 //
函数原型
int

subtract(
int

x,
int

y);
 //
函数实现
int

subtract(
int

x,
int

y) {
    return

x - y;
} //
main函数调用
int

main() {
  int

(*subtractPtr)(
int,
int)
= subtract;
   int

y = (*subtractPtr)(10, 2);
  printf("Subtract
gives: %d\n"
,
y);
   int

z = subtractPtr(10, 2);
  printf("Subtract
gives: %d\n"
,
z);
}

这与subtractAndPrint函数非常相似,只是subtract函数返回了一个整数而已,特征标也理所当然的不一样了。

  1. 在第13行,我们通过int (*subtractPtr)(int, int)创建了subtractPtr这个函数指针。与上一个例子的区别只是把void换成了int来表示返回值。而这与subtract函数的特征标相符。

  2. 在在第15行,解引用和执行这个函数指针,除了将返回值赋值给了y以外,与调用subtractAndPrint没有任何区别。

  3. 在第16行,我们向屏幕输出了返回值。

  4. 18到19行,我们用调用普通函数的方法调用了函数指针,并且输出了结果。

这跟之前没什么两样,我们只是加上了返回值而已。接下来我们看看另一个稍微复杂点儿的例子——把函数指针作为参数传递给另一个函数。

把函数指针作为参数来传递

我们已经了解过了函数指针声明和执行的各种情况,不论它是否带参数,或者是否有返回值。接下来我们利用一个函数指针来根据不同的输入执行不同的函数。

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#include
 //
函数原型
int

add(
int

x,
int

y);
int

subtract(
int

x,
int

y);
int

domath(
int

(*mathop)(
int,
int),
int

x,
int

y);
 //
加法 x+ y
int

add(
int

x, init y) {
    return

x + y;
} //
减法 x - y
int

subtract(
int

x,
int

y) {
    return

x - y;
} //
根据输入执行函数指针
int

domath(
int

(*mathop)(
int,
int),
int

x,
int

y) {
    return

(*mathop)(x, y);
} //
main函数调用
int

main() {
 //
用加法调用domath
int

a = domath(add, 10, 2);
printf("Add
gives: %d\n"
,
a);
 //
用减法调用domath
int

b = domath(subtract, 10, 2);
printf("Subtract
gives: %d\n"
,
b);
}

我们来一步一步分析。

  1. 我们有两个特征标相同的函数,add和subtract,它们都返回一个整数并接受两个整数作为参数。

  2. 在第六行,我们定义了函数int domath(int (*mathop)(int, int), int x, int y)。它第一个参数int
    (*mathop)(int, int)
    是一个函数指针,指向返回一个整数并接受两个整数作为参数的函数。这就是我们之前见过的语法,没有任何不同。它的后两个整数参数则作为简单的输入。因此,这是一个接受一个函数指针和两个整数作为参数的函数。

  3. 19到21行,domath函数将自己的后两个整数参数传递给函数指针并调用它。当然,也可以像这么调用。mathop(x, y);

  4. 27到31行出现了我们没见过的代码。我们用函数名作为参数调用了domath函数。就像我之前说过的,函数名是函数的地址,而且能代替函数指针使用。

main函数调用了两次domath函数,一次用了add,一次用了subtract,并输出了这两次结果。

函数名和地址

既然有约在先,那我们就讨论一下函数名和地址作为结尾吧。一个函数名(或称标签),被转换成了一个指针本身。这表明在函数指针被要求当作输入的地方,就能够使用函数名。这也导致了一些看起来很糟糕的代码却能够正确的运行。瞧瞧下面这个例子。

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#include
 //
函数原型
void

add(
char

*name,
int

x,
int

y);
 //
加法 x + y
void

add(
char

*name,
int

x,
int

y) {
    printf("%s
gives: %d\n"
,
name, x + y);
} //
main函数调用
int

main() {
     //
一些糟糕的函数指针赋值
    void

(*add1Ptr)(
char*,
int,
int)
= add;
    void

(*add2Ptr)(
char*,
int,
int)
= *add;
    void

(*add3Ptr)(
char*,
int,
int)
= &add;
    void

(*add4Ptr)(
char*,
int,
int)
= **add;
    void

(*add5Ptr)(
char*,
int,
int)
= ***add;
     //
仍然能够正常运行
    (*add1Ptr)("add1Ptr",
10, 2);
    (*add2Ptr)("add2Ptr",
10, 2);
    (*add3Ptr)("add3Ptr",
10, 2);
    (*add4Ptr)("add4Ptr",
10, 2);
    (*add5Ptr)("add5Ptr",
10, 2);
     //
当然,这也能运行
    add1Ptr("add1PtrFunc",
10, 2);
    add2Ptr("add2PtrFunc",
10, 2);
    add3Ptr("add3PtrFunc",
10, 2);
    add4Ptr("add4PtrFunc",
10, 2);
    add5Ptr("add5PtrFunc",
10, 2);
}

这是一个简单的例子。运行这段代码,你会看到每个函数指针都会执行,只是会收到一些关于字符转换的警告。但是,这些函数指针都能正常工作。

  1. 在第15行,add作为函数名,返回这个函数的地址,它被隐式的转换为一个函数指针。我之前提到过,在函数指针被要求当作输入的地方,就能够使用函数名。

  2. 在第16行,解引用符作用于add之前,即*add,在返回在这个地址的函数。之后跟函数名一样,它被隐式的转换为一个函数指针。

  3. 在第17行,取地址符作用于add之前,即&add,返回这个函数的地址,之后又得到一个函数指针。

  4. 18到19行,add不断地解引用自身,不断返回函数名,并被转换为函数指针。到最后,它们的结果都和函数名没有区别。

显然,这段代码不是优秀的实例代码。我们从中收获到了如下知识:其一,函数名会被隐式的转换为函数指针,就像作为参数传递的时候,数组名被隐式的转换为指针一样。在函数指针被要求当作输入的任何地方,都能够使用函数名。其二,解引用符*和取地址符&用在函数名之前基本上都是多余的。

感谢各位的阅读,以上就是“C语言函数指针知识点总结”的内容了,经过本文的学习后,相信大家对C语言函数指针知识点总结这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是创新互联,小编将为大家推送更多相关知识点的文章,欢迎关注!


网页题目:C语言函数指针知识点总结
文章URL:http://ybzwz.com/article/gcopgc.html