【问题标题】:Pass function to another function by pointer and by name [duplicate]通过指针和名称将函数传递给另一个函数[重复]
【发布时间】:2013-06-06 07:20:43
【问题描述】:

我正在从 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++ 特定的吗?

【问题讨论】:

  • 为什么不使用指针? - 也许是为了展示,你可以在不声明指针的情况下调用函数?
  • 函数名是const函数指针,不能为左值。
  • 很清楚。在那种情况下有什么区别?是语法糖吗?首选方式是什么?
  • @viakondratiuk 这取决于你需要什么。声明一个函数指针变量可以更改在运行时调用的函数(例如,如果我们考虑您的代码,您可以有两种添加方式,您可以通过更改 @987654327 的值在两个函数之间切换@指针,无需修改你的操作调用)。

标签: c++ function pointers


【解决方案1】:

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

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

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

它是特定于 c++ 的吗?

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

【讨论】:

  • 实际上同样的隐式转换也发生在plus的定义中,没有它就必须以= &amp;add;结尾。
【解决方案2】:

为什么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 标签给出了两个函数的起点,也是指向函数起点的地址。

我在代码中添加了一些 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

所以,从汇编中我们可以看出,你给定代码中的变量substractaddplus都是指针,并且来自函数起点的标签。

这两种方法有什么区别?

由于plusaddsubstract 都是相同类型的函数指针,正如Luchian Grigore 所说,它们是相同的。

从上面的汇编代码我们也可以看出,这两个方法是完全一样的方法调用,没有任何区别。

是否指定了C++

C家庭语言

和其他一些派生语言,如obj-c 直接支持函数指针。

Java 语言

在Java中有这样一个概念叫做函数指针,但是classimplemnts是一个接口`可以达到与函数指针相同的目的。

例如: 你可以先定义一个接口:

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?

【讨论】:

  • "你还定义了两个变量substractadd,类型是函数指针:int (*)(int, int)。所以你可以声明一个新的函数指针plus,并赋值add 给它。所有三个变量都是指针。” - 不,那是错误的。 addsubtract 是函数。它们可以(并且经常会)隐式转换为函数指针,但它们不是指针,就像数组不是指针一样,即使经常衰减为一个指针。当然,机器函数中的内存地址与变量一样,但标准从什么时候开始关心这个。
猜你喜欢
  • 2015-05-09
  • 2018-09-16
  • 1970-01-01
  • 1970-01-01
  • 2019-09-16
  • 2016-03-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多