我正在学习函数指针,这个例子来自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
的指针,它被传递给函数操作。很明显。但是subtract
呢?
为什么不使用指针?两种方法有什么区别?是c++
具体吗?
答案 0 :(得分:7)
在C ++中,函数可以自动转换为函数指针,因此以下内容是等效的:
b = operation(20, a, subtract);
b = operation(20, a, &subtract);
由于&substract
具有正确的类型(int (*)(int, int)
),因此代码会按预期编译并运行。
具体是c ++吗?
无法真正回答这个问题,因为可能还有其他语言允许这样做。我更确定有。
答案 1 :(得分:3)
substract
?实际上,所有函数名称都是其对应的实时范围中的指针。在您的代码中,当您定义函数substract()
,add()
时。
您还定义了两个名为substract
和add
的变量,其类型是函数指针:int (*)(int, int)
。所以你可以声明一个新的函数指针plus
,并为它指定add
。所有三个变量都是指针。
以下是clang++
生成的汇编代码,可以为您提供详细说明。我删除了所有不相关的代码。函数名称有点难看,这是因为C++ name mangling,你可以忽略大写字母和数字来轻松理解名称。
功能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也有类似的方式:Function pointer in Ruby?