为什么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 标签给出了两个函数的起点,也是指向函数起点的地址。
我在代码中添加了一些 cmets 来展示函数指针是如何工作的,它以 ### 开头。
函数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 也有一些类似的方式:Function pointer in Ruby?