通过指针和名称将函数传递给另一个函数

时间:2013-06-06 07:20:43

标签: c++ function pointers

我正在学习函数指针,这个例子来自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++具体吗?

2 个答案:

答案 0 :(得分:7)

在C ++中,函数可以自动转换为函数指针,因此以下内容是等效的:

b = operation(20, a, subtract);
b = operation(20, a, &subtract);

由于&substract具有正确的类型(int (*)(int, int)),因此代码会按预期编译并运行。

  

具体是c ++吗?

无法真正回答这个问题,因为可能还有其他语言允许这样做。我更确定有。

答案 1 :(得分:3)

为什么指针不用于substract

实际上,所有函数名称都是其对应的实时范围中的指针。在您的代码中,当您定义函数substract()add()时。

您还定义了两个名为substractadd的变量,其类型是函数指针: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

因此,我们可以从程序集中看到,给定代码中的变量substractaddplus都是指针,并且来自于起点的标签。功能。

这两种方法有什么区别?

由于plusaddsubstract都是相同的类型函数指针,就像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?