理解复杂的块语法

11

我是一个Objective C和iOS开发的初学者,但是我已经是13年的.NET老手了。我很难在脑中绘制下面这个声明,它来自于Objective C编程指南

void (^(^a)(void (^) (void))) (void) = ...

这被用作为一个例子,说明为什么要使用typedef来定义块,但是我想更好地了解块定义语法,所以希望能够理解这个例子的意思。

到目前为止,我画出了以下的示意图:

enter image description here

我现在遇到的问题是,我对基本语法的理解只到这里:

[return_val] (^[block_name]) ([block_args]) = ...

如果是这种情况,那么我拥有的是一个返回void且没有参数的块,但它的名字是(^a) (void (^) void)。这意味着我的块名称本身就是一个块,而不是简单的字符串。

很明显我在这里还缺少一些东西。能否请有经验的人给予一些解释?根据该网站,它被简化为:

typedef void (^SimpleBlock) (void);
SimpleBlock (^complexBlock) (SimpleBlock) = ...

我只是不知道如何做到。

编辑:第三个 void 应该在括号里面。我已经修正了这一点。图片上是错误的,但我不想为此重新制作整个图片。 :) 如果它被证明是我的问题源头,我会在这里修复它。


你确定 void (^(^a)(void (^) void)) (void) 是正确的吗?第三个 void 不应该加括号吗?你引用的链接中在哪里可以找到这个表达式? - rmaddy
你说得对。我会在消息中修复它。话虽如此,在这里我不认为它会以实质性的方式改变任何事情,是吗?(另外,链接在第一段的帖子中。) - Ari Roth
有一个在YouTube上相当不错的视频教程。我发现它很有帮助。 http://www.youtube.com/watch?v=9FWqh24b9oE - thatzprem
1
有一篇非常棒的博客文章,作者是Nils Hayat,他非常清晰地解释了块语法,从C声明符的概念开始逐步建立理解。链接:块语法解释这可能不是一个很好的答案,但这篇博客文章太好了,我想让它突出显示。 - Nikita Kukushkin
一个或两个typedef可以让代码更易读。 - gnasher729
2个回答

10

在你的例子中,你漏掉了第三个 void 的一些括号

void (^(^a)(void (^)(void)))(void)

现在让我们来分解一下。从函数中返回一个块的基本语法是:

void (^f())(void) { 
    return ^{}; 
}

在这个例子中,返回的块 不带参数且返回 void

现在让我们构建你的例子。

void     (^myBlock)(void);                       // Block returning void, taking no args
void     (^myBlock)(void (^)(void));             // Block returning void, taking block as arg
int      (^myBlock)(void (^)(void));             // Block returning int, taking block as arg
void (^  (^myBlock)(void (^)(void))  )(void);    // Block returning block, taking block as arg

我已经将每行的中央部分对齐,以使阅读更容易。因此,难点似乎是返回一个块。在最后一行中,我们使用了我之前描述的语法从函数中返回一个块。

显然,typedefs 使阅读变得更加容易。

编辑:
考虑这个例子,在第一行中,我用直观的返回语法将 int 替换为一个带有 block 的块:

void (^ )(void) (^myBlock)(void (^)(void));          // Syntax we 'intuitively would use'
void (^         (^myBlock)(void (^)(void))  )(void); // Official syntax
我不完全确定我即将说的话,但我怀疑这种奇怪的语法之所以存在是为了避免编译器中的解析器混淆。第一种“直观”的语法会让编译器认为我们有一个不带参数返回void的块,剩下的字符会被视为语法错误。
在我看来,语法是你不需要过多质疑的东西(当然可以批评),因为它是语言设计的一部分,我们必须遵循规则(由一些希望聪明的工程师制定)使我们的代码能够编译。

1
虽然我理解所有这些,但脑海中浮现的问题是,为什么最终语法不像 void (^)(void) (^myBlock)(void (^)(void)); 这样,其中第一部分 void (^)(void) 是返回值?我认为混淆的部分在于为什么返回 int 的语法与返回 block 的语法如此不同。 - rmaddy
是的,这就是我遇到的问题。因此,当我阅读第二个示例时,我将其视为“名为myBlock的块,它接受一个未命名的参数,该参数是一个不带参数并返回void的块”。因此,阅读最后一行,我看到“一个未命名的块,它接受一个名为myBlock的参数,返回void并接受一个没有参数并返回void的未命名块,然后返回void”。对我来说,这有点奇怪。难道不应该是类似于void (^finalBlock(^myBlock)(void (^) (void)))(void)这样的东西吗?还是说这只是我需要适应的东西? - Ari Roth
请原谅我,但是所有这些括号让我立刻回到了几十年前的LISP时代。 :-) - mharper

0
void (^(^a)(void (^) (void))) (void)

将这些语法分解成几个部分:

  1. a 是一个变量。
  2. 它可以通过符号“^”进行解引用,就像 C 指针“*”一样:^a
  3. (^a)(void (^) (void) 是一个名为 a 的块,并以块 (void (^) (void) 作为参数。
  4. 它的返回值可以被解引用以获取块信息:^(^a)(void (^) (void))。(因此,根据暗示,返回值是一个块指针)
  5. 这个返回的块不需要参数:(^(^a)(void (^) (void))) (void)
  6. 这个返回的块不需要返回值:void (^(^a)(void (^) (void))) (void)

所以说,(^a) (void (^) void) 不是 意味着我的块的名称本身就是一个块,而不是一个简单的字符串。 块文字不必是

 [return_val] (^[block_name]) ([block_args])

编译器将会把插入符号之后的代码视为一个块。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接