`

ios 4 新特性-----Objective-C的Block,递归与泛型

 
阅读更多

Apple在C,Objective-C和C++中扩充了Block这种文法的,并且在GCC4.2中进行了支持。现在我们可以在Mac 10.6和iOS 4中使用。如果是Mac 10.6 或 iOS 4.0 之前的平台,据说可以用http://code.google.com/p/plblocks/这个项目来支持Block语法。

 

Apple在 Snow Leopard中所用到的Grand Central Dispatch(GCD)就是基于Blocks实现的。Grand Central Dispatch是苹果开发的工具,目的是帮助开发者更容易的利用多核处理器的并行处理功能。关于Blocks以及GCD在苹果官方的介绍,请见:Introducing Blocks and Grand Central Dispatch。

 

你可以把它理解为函数指针,匿名函数,闭包,lambda表达式,这里暂且用块对象来表述,因为它们之间还是有些许不同的。

块对象

块对象是C级别的文法,同时也是一种运行时特征,即它允许您把函数表达式组合在一起,组合结果可作为参数传递也可保存,还可供多个线程使用。块对象的函数表达式可引用或持有局部变量。在其他的语言环境中,块对象有时也被称为closure或者lambda。如果您需创建可如数值般传递的工作单元(即代码段),则可使用块对象,它可为您提供更多的编程灵活性和更强大的功能。如需编写回调函数或对某个群体的所有项执行某种操作,也可使用块对象

 

 声明一个块

如果以内联方式使用块对象,则无需声明。块对象声明语法与函数指针声明语法相似,但是块对象应使用脱字符(^)而非星号指针 (*)。下面的代码声明一个aBlock变量,它标识一个需传入三个参数并具有float返回值的块。

 

float (^aBlock)(const int*, int, float);

 

 

创建一个块

块使用脱字符(^)作为起始标志,使用分号作为结束标志。下面的例子声明一个简单块,并且将其赋给之前声明的block变量(oneFrom)。

int (^oneFrom)(int);

oneFrom = ^(int anInt) {

    return anInt - 1;

};

 

结尾处的分号是标准C的行结束标志。如果未显式声明块表达式的返回值,则编译器会根据块内容自动进行推导

 

块可变变量

如果某个局部变量使用__block存储修饰符,则表示块应使用此变量的引用,并可更改它的值。对变量的任何改变都只在块的语法作用域内部,以及该作用域中定义的其它块中起作用。

块对象的特性

Blocks比C++0x中的lambda表达式更强的一点是,它可以是个数组类型:

int main(void)

{

    void (^p[2])(void) = { ^(void){ puts("Hello, world!"); }, ^(void){ puts("Goodbye!"); } };

    p[0](), p[1]();

}

 

这里p的类型为void(^[2])(void),表示含有2个void(^)(void)块引用元素的变量。

 

 

下面谈谈函数块对其外部临时变量的可访问情况。

static int global = 100;

int main(void)

{

    int local = 200;

    void (^p)(void) = ^(void){ printf("The answer is: %dn", global + local); };

    p();

}

 

 

对于FP比较熟悉的朋友可能会想到,如果一个外部变量能够随随便便被一个函数块修改的话,那么对于其本身的副作用仍然无法进行方便地多核并行编程。那么我们不妨试试看吧:

static int global = 100;

int main(void)

{

    int local = 200;

    void (^p)(void) = ^(void){

        printf("The answer is: %dn", global + local);

        global++;

        local--;    // compiling error:error: decrement of read-only variable \'local\'

    };

    p();

printf("After modified, global is: %d and local is %dn", global, local);

}

 

对于全局变量可以进行修改,但是对于main函数中的局部变量则不行。如果对local修改则无法通过编译。很显然,Blocks对此已经有了相应的机制。 那么我们如何能够对local进行修改呢?

static int global = 100;

int main(void)

{

    __block int local = 200;

    static int s = 10;

   

    void (^p)(void) = ^(void){

        printf("The answer is: %dn", global + local);

        global++;

        s++;

        local--;

    };

    p();

    printf("After modified, global is: %d and local is %d and s is: %dn", global, local, s);

}

 

这里引入了一个新的关键字 ——__block,用此声明一个局部变量可以被函数块修改。

Block是否可以递归

如果要使Blocks能够递归,那么在函数块中必须能够引用函数块的入口地址。我做了一些尝试,当函数块引用是全局的或static的,即函数块内所引用的函数 块引用变量的值在初始时就已经确定的,那么可以使用递归。

int main(void)

{

    void (^p)(int) = 0;

    static void (^ const blocks)(int) = ^(int i){ if(i > 0){ puts("Hello, world!"); blocks(i - 1); } };

    p = blocks;

    p(2);

}

 

如果在上述代码中将blocks前的static去掉,那么在运行时就会出错,因为blocks在被函数块引用时是未初始化值,所以调用它的话就访问了无效地址,或者所要执行的指令是未定义的。

Blocks结合泛型

由于泛型能够使我们更高效、合理地管理好自己的代码,同时也为部件化提供了许多便利之处。那么Blocks与泛型结合会产生什么新元素呢?

我们先举一个简单例子:

#import <Foundation/Foundation.h>

 

template <void pBlock(void)>

void BlockTest(void)

{

    pBlock();

}

 

void Hi(void)

{

    NSLog(@"Hi, there!");

}

 

int main(int argc, const char* argv[])

{

    BlockTest<Hi>();

}

 

 

上述代码中尚未出现Blocks,但是我们可以看到,一般的外部函数能够作为模板参数。那么Blocks是否可以这么做呢?我们不妨尝试一下:

#import <Foundation/Foundation.h>

 

template <void (^pBlock)(void)>

void BlockTest(void)

{

    pBlock();

}

 

int main(int argc, const char* argv[])

{

    BlockTest<^(void) { NSLog(@"Hi, there!"); }>();

}

 

编译时会在第11行出现error: no matching function for call to \'BlockTest()\'。C++标准中明确指出,模板参数必须为常量表达式,如果是函数的话必须是带有外部连接(即external- linkage)的函数指针。而Blocks表达式首先 就不是一个常量表达式,然后它也没有外部连接。我们下面看第二个例子:

#import <Foundation/Foundation.h>

 

template <typename T>

void BlockTest(void (&pBlock)(T))

{

    pBlock(T());

}

 

static void Hi(int a)

{

    NSLog(@"The value is: %dn", a);

}

 

int main(int argc, const char* argv[])

{

    BlockTest(Hi);

}

 

上述代码中使用了函数引用作为函数参数,然后由实参类型演绎出模板类型。这段代码将能正常地通过编译、连接并正常运行。那么我们下面再看一看 Blocks是否具有这个泛型特性:

#import <Foundation/Foundation.h>

 

template <typename T>

void BlockTest(void (^pBlock)(T))

{

    pBlock(T());

}

 

int main(int argc, const char* argv[])

{

    BlockTest(^(int a) { NSLog(@"The value is: %dn", a); });

}

 

编译后出现 error: no matching function for call to \'BlockTest(void (^)(int))\'。即使显式地将<int>模板实参加上也没用。也就是说Blocks的参数类型包括返回类型不能是一个泛型。我们再看第三个例子:

#import <Foundation/Foundation.h>

#include <iostream>

#include <typeinfo>

using namespace std;

 

template <typename T>

void BlockTest(T pBlock)

{

    pBlock();

    cout << "The type is: " << typeid(T).name() << endl;

}

 

static void Hi(void)

{

    NSLog(@"Hi, there!");

}

 

int main(int argc, const char* argv[])

{

  BlockTest(Hi);

}

 

这段代码展示了整个函数指针类型演绎出模板实参。对于目前已被很多编译器所实现的Lambda表达式,这是与泛型挂钩的唯一桥梁,那么Blocks是否具备这个特性呢?

#import <Foundation/Foundation.h>

#include <iostream>

#include <typeinfo>

using namespace std;

 

template <typename T>

void BlockTest(T pBlock)

{

    pBlock();

    cout << "The type is: " << typeid(T).name() << endl;

}

 

int main(int argc, const char* argv[])

{

    BlockTest(^(void) { NSLog(@"Hi, there!"); });

}

 

恭喜,我们成功了。这段代码能够正常编译和运行。各位可以自己看看输出结果。其中,类型信息是被压缩过的:F表示函数,P表示指针,v表示void 类型。Blocks 与C++0x中的lambda表达式一样,必须作为一个完整的类型。对其类型做拆分进行泛型化是非法的。由于C++0x的Lambda表达式的具体类型不对程序员开放,因此它即不能作为模板形参亦无法作为模板函数的形参,但是它可以在模板函数内使用泛型。

#include <iostream>

using namespace std;

 

template <typename T>

void LambdaTest(T)

{

    T a = 100;

    auto ref = [a](const T& b) -> T { return a + b; };

    cout << "The value is: " << ref(200) << endl;

}

int main(void)

{

    LambdaTest((short)0);

}

 

上述代码可以在VS2010以及Intel C++ Compiler11.0通过编译并正常运行。然而比较奇怪的是Blocks在模板函数内的表现就非常不好——

template <typename T>

void BlockTest(void)

{

    void (^pBlocks)(void) = ^{};

}

int main(int argc, const char* argv[])

{

    BlockTest<void>();

}

 

上面这段代码中,模板函数BlockTest中pBlocks根本就没用泛型,也无法通过编译,而且报的错误是internal error: segmentation fault。而只有下面这种情况才能通过编译,但实际上是没有任何意义的:

#import <Foundation/Foundation.h>

template <typename T>

void BlockTest(void)

{

    void (^pBlock)(void) = nil;

}

 

int main(int argc, const char* argv[])

{

    BlockTest<void>();

}

 

 

可见,Blocks作为Lambda表达式而言已经是非常棒的,但与lambda函数功能相比就稍逊一些,尤其是与泛型结合时,表现得很一般。其实这也是与 Blocks的实现有关的。Blocks仍然像Objective-C的很多特性那样,主要是靠动态实现的,在编译时所花的精力较少。能够在C以及 Objective-C中用上Lambda特性也很不错。

小结

Blocks在作用域,内存管理等方面具有自己独特的特性和需要留意的地方,在许多场景也很多实用的功能。虽然有时理解起来有点费劲,但是用好了,相信用好了可以大大提高代码可读性和效率等。

 

分享到:
评论

相关推荐

    n后问题---递归回溯法 n后问题---递归回溯法

    n后问题---递归回溯法 n后问题---递归回溯法 n后问题---递归回溯法 n后问题---递归回溯法 n后问题---递归回溯法 n后问题---递归回溯法 n后问题---递归回溯法 n后问题---递归回溯法 n后问题---递归回溯法 n后问题---...

    实例190 - 泛型化的折半查找法

    泛型化是Java编程语言中的一个重要特性,它允许在类、接口和方法中使用类型参数,从而提高了代码的重用性和安全性。实例190 - 泛型化的折半查找法,就是关于如何利用泛型优化传统折半查找算法的一个实践示例。折半...

    Python语言程序设计教程 北理工Python课程第6章-函数与递归-4-程序结构和递归 共20页.pdf

    【大纲】0-1-课程内容和安排介绍1-1-计算机的概念1-2-程序设计语言概述1-3-Python语言1-4-Python开发环境配置1-5-基本程序设计方法1-6-理解问题的计算部分1-7-温度转换程序实例2-1-Python程序元素分析2-2-程序编写...

    数据结构(c语言)---递归---Hanno.cpp

    数据结构(c语言) 对于汉诺塔的递归实现。在对学习数据结构递归的人,帮助他们对汉诺塔和递归思想的理解

    嵌入式C语言培训-C编程基础-递归函数视频教程

    本教程“嵌入式C语言培训-C编程基础-递归函数视频教程”聚焦于C语言的一个关键特性——递归函数。递归是编程中的一个强大工具,能够简化复杂问题的解决方案,但它也需要程序员有深入的理解才能正确使用。 递归函数...

    NOIP普及讲座1-递归与分治(C++版).pdf

    根据给定文件的信息,我们可以总结出以下关于递归与分治技术的重要知识点: ### 一、递归的基本概念 #### 定义: 递归是一种在程序设计中非常重要的方法,它指的是在一个函数或过程中直接或间接地调用自身来解决...

    main.c 哈夫曼编码实现_c语言 求WPL -----递归求解

    哈夫曼编码实现_c语言 (最小堆) 求WPL -----递归求解

    递归-----动态树实现递归

    3. **树的构造和修改**:递归也可以用于构建新的树结构,比如通过二叉搜索树插入新节点,或者在平衡树(如AVL树、红黑树)中进行旋转操作以保持平衡。 4. **树的分解**:在某些情况下,可能需要将一个大的树分解成...

    java-c语法7---method-递归---马克-to-win java视频

    java语法 method 递归 马克-to-win java视频 方法 重载

    汉诺塔递归算法--C语言

    汉诺塔问题是一个经典的递归算法问题,它涉及到三个柱子(塔)和若干个大小不一的圆盘。在初始状态下,所有圆盘按照大小顺序堆叠在第一个柱子(1号塔)上,大的在下,小的在上。目标是将所有的圆盘从1号塔移动到2号...

    二叉树的操作--递归非递归遍历、结点个数、树深度

    遍历递归的先中後序, 非递归的先中後序, 计算出深度 结点数 /* 运行结果: ------------------------ 请先序输入二叉树(如:ab三个空格表示a为根节点,b为左子树的二叉树) ab c 先序递归遍历二叉树: a b c 先序...

    算法基础与递归-百积问题-递归求公约数-求阶乘-斐波那契数列

    算法基础与递归 在计算机科学中,递归算法是一种非常重要的算法设计技术。递归算法的基本思想是将问题分解成更小的子问题,然后使用同样的方法解决这些子问题,直到问题的规模小到可以直接解决为止。本文将通过实验...

    计算器递归----c++

    ### 计算器递归——C++ #### 概述 本文档介绍了一个使用C++编写的递归计算器程序。...这对于初学者来说是一个很好的学习案例,可以帮助他们理解递归的概念和实践方法,同时也能加深对C++语言特性的了解。

    基础算法-递归-杨鑫20191010.pptx

    基础算法-递归-杨鑫20191010.pptx,基础算法-递归-杨鑫20191010.pptx,基础算法-递归-杨鑫20191010.pptx

    Objective-C-HTML-Parser.zip

    Objective-C 是苹果公司为其操作系统macOS和iOS开发的一种面向对象的编程语言,它极大地扩展了C语言,并引入了Smalltalk的动态特性。在本项目"Objective-C-HTML-Parser"中,我们关注的是如何在Objective-C中解析HTML...

    c# 泛型保存类结构,支持递归

    在C#编程中,泛型是一种强大的特性,它允许我们创建可重用的代码模板,这些模板可以在多种数据类型上工作。"c# 泛型保存类结构,支持递归"这一主题涉及到如何利用泛型来序列化和反序列化包含嵌套类的对象到XML文档中...

    0-1背包问题-递归算法 c语言实现

    ### 0-1 背包问题:递归算法 C语言实现 #### 一、问题背景及定义 0-1背包问题是一种经典的组合优化问题,在计算机科学与运筹学领域有着广泛的应用。该问题的基本形式如下:给定一组物品,每种物品都有自己的重量和...

    C语言目录递归经典代码Recurse-Directories-in-C

    在C语言中,目录递归是一项重要的编程技巧,它允许程序遍历文件系统中的所有子目录,处理每个目录中的文件和子目录。本篇将详细探讨如何实现C语言的目录递归,以及其背后的原理和应用。 目录递归通常用于需要遍历...

    数据结构:栈与递归--含分治与回溯.ppt

    数据结构:栈与递归--含分治与回溯.ppt

    工具类库-Excel-编号生成-Session共享-定时任务-递归-EF扩展.zip

    工具类库-Excel-编号生成-Session共享-定时任务-递归-EF扩展

Global site tag (gtag.js) - Google Analytics