【问题标题】:what is the meaning of (*(int (*)())a)()?(*(int (*)())a)() 是什么意思?
【发布时间】:2016-12-21 12:05:35
【问题描述】:

我是学习 C++ 的初学者。今天看到一个这样的指针函数

(*(int (*)())a)()

我很困惑这是什么意思以及我如何容易理解它。

【问题讨论】:

  • 前面的评论提供了一个有用的链接,但我想补充一点,从 C++ 开始,这绝对不是你应该打扰的事情。有了接口和 std::function 和 lambda,C++ 比函数指针有很多概念
  • "我是 [a] 初学者" -- 那么你应该忽略这样的东西。当你变得更有经验时,你应该继续忽略它。这是晦涩的废话。

标签: c++ pointers


【解决方案1】:

让我们添加一个 typedef,以帮助做出正面或反面:

typedef int (*int_func_ptr)();

(*(int_func_ptr)a)();

所以a 被强制转换为特定原型的函数指针,取消引用(这是多余的),然后调用。

【讨论】:

  • 为什么投反对票?这在我看来是正确的,是一种很好的分析方法。
  • @Bathsheba - 在我编辑其余零散的想法之前,可能有人不喜欢最初的版本。没关系,我希望它对现在的人有所帮助。
【解决方案2】:

int (*)() 是一个函数指针类型,它返回int 并且不接受任何参数。

我假设a 是一个函数指针,它的类型“擦除”了我们需要转换为这个指针类型的实际类型(也许这样人们可以在vector 中存储一堆不同的函数指针),所以(int(*)())a) 将执行该转换。

之后我们要调用函数,所以提供的代码取消引用指针*,然后用括号()调用它

示例

我有一个函数foo,看起来像这样:

int foo()
{
    std::cout << "foo\n";
    return 1;
}

然后通过reinterpret_cast,我得到一个指向函数的指针,该函数返回void(出于类型擦除的原因):

 void(*fptr)() = reinterpret_cast<void(*)()>(&::foo); //§5.2.10/6

稍后,我想调用那个函数,所以我需要将它重新转换回原来的类型,然后调用它:

 (*(int (*)())fptr)(); // prints `foo`

Demo

取消引用它实际上是不必要的,以下是等效的:

((int (*)())fptr)();

为什么它们是等价的解释归结为“标准说函数类型和函数指针类型都可以调用”

如果您精通标准,可以查看 §5.2.2[expr.call] 说明

函数调用是一个后缀表达式,后跟括号,其中包含一个可能为空的、以逗号分隔的 initializer-clauses 列表,这些列表构成函数的参数。后缀表达式应具有函数类型或指向函数类型的指针

【讨论】:

  • 仅有条件地支持将函数指针转换为void*。我不会依赖它。
  • @StoryTeller:谢谢。我不知道。这是首先想到的。我已将帖子更新为更符合标准(将其转换为不同类型的函数指针,然后返回)。
  • "&::"
  • @MichaelTsai - ::foo 应用范围解析来获取对全局定义的foo 的引用(毕竟我们可以在命名空间中拥有函数)。 &amp;::foo 明确获取所述函数的地址。
  • 为什么 ((int ()())fptr)()= (*(int ()())fptr)()
【解决方案3】:

StoryTeller 和 Andy 给出了正确答案。另外,我将给出一般规则。

StoryTeller 使用typedef int (*int_func_ptr)(); 创建了一个正确且有用的 typedef,它定义了一个函数指针类型。这里要记住两件事。

  • typedefs 的通用语言设计: 它完全模仿了给定类型对象的声明!只需在声明前加上 typedef 即可使声明的标识符成为类型别名而不是变量。也就是说,如果int i; 声明一个整数变量,typedefint i;i 声明为int 类型的同义词。因此,声明一个函数指针变量 将简单地读取int (*int_func_ptr)();。正如 StoryTeller 所做的那样,用 typedef 作为前缀,使其成为类型别名。

  • 函数指针的转换是出了名的令人困惑。一个原因是必需的括号

    1. 括号有几个不相关的用途:

      • 它们对表达式进行分组以指示子表达式的优先级,如(a+b) * c
      • 它们在声明和调用中分隔函数参数。
      • 它们对类型转换中使用的类型名称进行分隔。
    2. 我们在此处为所有三个目的都有括号!

    3. The operator precedence 对于函数指针声明是“不自然的”。当然,这是因为它们对于更频繁的使用来说是很自然的:没有括号,声明将是看起来更熟悉的 int *int_func();,它声明了一个返回 int 指针的 function proper .原因是参数括号比解引用星号具有更高的优先级,因此为了推断类型,我们首先在心理上执行调用,而不是解引用。可以调用的是函数。1 调用的结果可以取消引用,结果是一个int。
      将其与原始的int (*int_func_ptr)(); 进行比较:额外的括号迫使我们首先取消引用,因此标识符必须是某种指针。解引用的结果是可以调用的,所以必须是函数;调用的结果是int

函数指针声明或 typedef 看起来不自然的另一个原因是声明的标识符往往位于表达式的 中心。 原因是 运算符在左侧 在标识符的右边被应用(解引用,函数调用,最后是结果类型声明一直到左边)。

下一条规则是关于构造转换。转换中使用的类型名称是从相应的变量声明中简单地通过省略变量名来构造的!这在简单的案例:由于int i 声明了一个int 变量,所以没有i(int) 是对应的强制转换。

如果我们将其应用于函数指针类型,int (*int_func_ptr)() 会通过省略变量名称并将类型名称放入括号中,从而转换为看起来很怪异的 (int (*)())。请注意,强制星号优先的括号仍然存在,即使没有什么可以取消引用!如果没有它们,(int *()) 将派生自 int *int_func(),因此表示一个返回指针的函数。2

可能令人惊讶的是,在声明中恰好有一个地方可以在语法上存在变量名,因此即使是非常复杂的类型表达式在强制转换中也得到了很好的定义:正是这个地方可以定义一个变量名演员表类型。

有了这些规则,让我们重新审视一下原来的表达方式:

(*(int (*)())a)()

在最外层,我们有两对括号。第二对是空的,因此必须是函数调用运算符。这意味着左边的操作数具有函数类型:

*(int (*)())a

操作数是括号中表示优先级的表达式。它包含三个部分:星号、括号中的表达式和标识符a。由于括号内的表达式和变量a 之间没有运算符,所以它一定是一个强制转换,实际上就是我们上面仔细研究过的那个。 * 和类型转换具有相同的优先级并从右到左进行评估:首先将a 转换为函数指针,然后* 取消引用以获得“正确的函数”(它们是C++ 中没有真正的对象)。这很合适,因为上面的函数调用运算符将​​应用于此结果。


1 C 也允许直接调用函数 pointers,而不首先取消引用,这是语法糖,在声明中不考虑。

2 虽然表达式在语法上有效,但在 C 或 C++ 中不允许对函数进行强制转换。

【讨论】:

  • 我认为提及using 以便于理解typedefs 会很有用。另外,当我认为您的意思是括号时,您会说括号。
  • @AndyG 谢谢你的括号...我明白了,括号可以有各种形状。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-02-15
  • 1970-01-01
  • 2015-08-28
  • 2011-06-05
  • 2015-01-03
  • 2014-01-16
相关资源
最近更新 更多