通过指针和名称将一个函数传递给另一个函数
Pass function to another function by pointer and by name
我正在学习函数指针,这个例子来自wiki:
int add(int first, int second)
{
return first + second;
}
int subtract(int first, int second)
{
return first - second;
}
int operation(int first, int second, int (*functocall)(int, int))
{
return (*functocall)(first, second);
}
int main()
{
int a, b;
int (*plus)(int, int) = add;
a = operation(7, 5, plus);
b = operation(20, a, subtract);
cout << "a = " << a << " and b = " << b << endl;
return 0;
}
正如我所看到的,plus
是一个指向函数add
的指针,它被传递给函数operation。很明显。那么subtract
呢?
为什么不使用指针?两种方法的区别是什么?是c++
特异性的吗?
在c++中,函数可以自动转换为函数指针,所以下面是等价的:
b = operation(20, a, subtract);
b = operation(20, a, &subtract);
由于&substract
具有正确的类型(int (*)(int, int)
),因此代码按预期编译并工作。
是c++特有的吗?
不能真正回答这个问题,因为可能有其他语言允许这样做。
为什么substract
不使用指针?
实际上,所有函数名都是对应活动作用域中的指针。这里,在您的代码中,当您定义函数substract()
时,add()
。
还定义了两个名为substract
和add
的变量,它们的类型是函数指针:int (*)(int, int)
。因此,您可以声明一个新的函数指针plus
,并将add
赋值给它。这三个变量都是指针。
下面是clang++
生成的汇编代码,可以给你一个详细的解释。我已经删除了所有不相关的代码。函数名读起来有点难看,这是由于c++的命名混乱,你可以忽略大写字母和数字,以方便地理解名称。
函数add()
:
.text
.globl _Z3addii
.align 16, 0x90
.type _Z3addii,@function
_Z3addii: # @_Z3addii
.cfi_startproc
# BB#0: # %entry
movl %edi, -4(%rsp)
movl %esi, -8(%rsp)
movl -4(%rsp), %esi
addl -8(%rsp), %esi
movl %esi, %eax
ret
.Ltmp6:
.size _Z3addii, .Ltmp6-_Z3addii
.cfi_endproc
函数substract
:
.globl _Z8subtractii
.align 16, 0x90
.type _Z8subtractii,@function
_Z8subtractii: # @_Z8subtractii
.cfi_startproc
# BB#0: # %entry
movl %edi, -4(%rsp)
movl %esi, -8(%rsp)
movl -4(%rsp), %esi
subl -8(%rsp), %esi
movl %esi, %eax
ret
.Ltmp7:
.size _Z8subtractii, .Ltmp7-_Z8subtractii
.cfi_endproc
_Z3addii
和_Z8subtractii
标签给出了两个函数的起始点,这也是一个指向函数起始点的地址。
我在代码中添加了一些注释,以显示函数指针是如何工作的,从###
开始。
函数main
:
.globl main
.align 16, 0x90
.type main,@function
main: # @main
.cfi_startproc
# BB#0: # %entry
pushq %rbp
.Ltmp16:
.cfi_def_cfa_offset 16
.Ltmp17:
.cfi_offset %rbp, -16
movq %rsp, %rbp
.Ltmp18:
.cfi_def_cfa_register %rbp
subq $32, %rsp
movl $7, %edi
movl $5, %esi
leaq _Z3addii, %rax ### Here, the assembly just load the label of _Z3addii, not a plus related variable, so in fact they are the same type.
movl $0, -4(%rbp)
movq %rax, -24(%rbp)
movq -24(%rbp), %rdx ### move the value of the function pointer to the rdx register.
callq _Z9operationiiPFiiiE
movl $20, %edi
leaq _Z8subtractii, %rdx ### Here, just load the label -f _Z8subsractii, which is the value of the function pointer substract. move it directly to rdx register.
movl %eax, -8(%rbp)
movl -8(%rbp), %esi
callq _Z9operationiiPFiiiE
leaq _ZSt4cout, %rdi
leaq .L.str, %rsi
movl %eax, -12(%rbp)
callq _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc
movl -8(%rbp), %esi
movq %rax, %rdi
callq _ZNSolsEi
leaq .L.str1, %rsi
movq %rax, %rdi
callq _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc
movl -12(%rbp), %esi
movq %rax, %rdi
callq _ZNSolsEi
leaq _ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_, %rsi
movq %rax, %rdi
callq _ZNSolsEPFRSoS_E
movl $0, %ecx
movq %rax, -32(%rbp) # 8-byte Spill
movl %ecx, %eax
addq $32, %rsp
popq %rbp
ret
.Ltmp19:
.size main, .Ltmp19-main
.cfi_endproc
在上面的main
函数集合中,我们看到所有的函数指针值都被移到了寄存器rdx
中。下面的操作函数:
函数operation()
:
.globl _Z9operationiiPFiiiE
.align 16, 0x90
.type _Z9operationiiPFiiiE,@function
_Z9operationiiPFiiiE: # @_Z9operationiiPFiiiE
.cfi_startproc
# BB#0: # %entry
pushq %rbp
.Ltmp10:
.cfi_def_cfa_offset 16
.Ltmp11:
.cfi_offset %rbp, -16
movq %rsp, %rbp
.Ltmp12:
.cfi_def_cfa_register %rbp
subq $16, %rsp
movl %edi, -4(%rbp)
movl %esi, -8(%rbp)
movq %rdx, -16(%rbp)
movq -16(%rbp), %rdx
movl -4(%rbp), %edi
movl -8(%rbp), %esi
callq *%rdx ### directly jump to the address, which is the value of the rdx register.
addq $16, %rsp
popq %rbp
ret
.Ltmp13:
.size _Z9operationiiPFiiiE, .Ltmp13-_Z9operationiiPFiiiE
.cfi_endproc
那么,从程序集可以看出,代码中的substract
、add
、plus
变量都是指针,都来自于函数起始点的标签。
这两种方法的区别是什么?
由于plus
、add
和substract
都是相同类型的函数指针,就像Luchian Grigore所说的那样,它们是相同的。
从上面的汇编代码中,我们也可以发现,这两个方法绝对是同一个方法调用,没有任何区别。
是否指定了C++
?
C
家族语言
和其他一些派生语言,如obj-c
,直接支持函数指针。
在Java中,有一个叫做函数指针的概念,但是一个class
(implemnts
一个接口)可以达到和函数指针一样的目的。
:你可以先定义一个接口:
interface StringFunction {
int function(String param);
}
,然后定义一个可以接受实现接口的对象的函数:
public void takingMethod(StringFunction sf) {
//stuff
int output = sf.function(input);
// more stuff
}
然后你可以定义不同的类implements
接口StringFunction
,并使用它作为takingMethod()
的参数
在Python中,函数名只是变量的一种类型,你可以像下面这样直接使用它:
def plus_1(x):
return x + 1
def minus_1(x):
return x - 1
func_map = {'+' : plus_1, '-' : minus_1}
func_map['+'](3) # returns plus_1(3) ==> 4
func_map['-'](3) # returns minus_1(3) ==> 2
Ruby Ruby也有一些类似的方法:
- 函数向量_指针有不同的原型,我可以构建一个吗
- 为什么在没有显式默认构造函数的情况下,将另一个结构封装在联合中作为成员的结构不能编译
- 创建一个函数以在输入为负数或零时输出字符串.第一次执行用户定义的函数
- 基于另一个成员参数将函数调用从类传递给它的一个成员
- 如何仅为一个函数添加延迟
- 构造函数正在调用一个使用当前类类型的函数
- C++-试图将函数指针推回到另一个CPP文件中的矢量时出错
- 有一个打印语句的函数是一种糟糕的编程实践吗
- 有没有什么方法可以使用一个函数中定义的常量变量,也可以由c++中同一程序中的其他函数使用
- 输入到文件并输出到另一个文件,并将流文件传递给函数
- 我不明白为什么我声明一个空的内部结构并将其传递给构造函数
- 如何创建函数管道,以便函数一个接一个地运行?
- 如何巧妙地编写两个函数——一个用于检查是否存在解决方案,另一个用于获取所有解决方案
- 在c++中的复制构造函数/一个声明语句中的初始化的延续中使用chain方法
- C :基类调用自己的虚拟函数 - 一个反图案
- 如何在这个交换函数(一个单独的链表)中找到错误
- 两个相同的函数(一个使用模板模式,另一个不使用)
- 你怎么能一次给一个函数一个参数呢
- 为什么要做两个函数?(一个是非const,另一个是const)
- 当代码在其他地方使用时,如何保证函数一个接一个地被调用