4

我正在从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

为什么不使用指针呢?2种方法有什么区别?是c++具体的吗?

4

2 回答 2

7

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

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

由于&substract具有正确的类型(int (*)(int, int)),代码按预期编译和工作。

它是 c++ 特定的吗?

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

于 2013-06-06T07:25:11.937 回答
3

为什么不使用指针substract

事实上,所有的函数名都是在其对应的活动范围内的一个指针。在这里,在您的代码中,当您定义函数时substract()add().

您还定义了两个名为substractand的变量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,addsubstract都是相同类型的函数指针,就像 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 中的函数指针?

于 2013-06-06T07:31:57.177 回答