为什么不使用指针substract
?
事实上,所有的函数名都是在其对应的活动范围内的一个指针。在这里,在您的代码中,当您定义函数时substract()
,add()
.
您还定义了两个名为substract
and的变量add
,其类型是函数指针:int (*)(int, int)
。所以你可以声明一个新的函数指针plus
,并分配add
给它。这三个变量都是指针。
下面是由 生成的汇编代码clang++
,可以给你一个详细的解释。我已经删除了所有不相关的代码。函数名读起来有点难看,这是因为C++ 名称 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 语言
在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
在 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 中的函数指针?