Closures in OOC
Closures in OOC
接上一篇Complexity Behind Closure,这次来专注于Rock是如何在C里实现Closure的。
这篇文章同时发布在Github上。
Block as Blocks
首先,需要指出的是,在C里面并不是完全没有办法使用Closure。Apple的GCC Fork里就给C添加了Block,用于实现Closure:
(Stolen from Wiki)
#include <stdio.h>
#include <Block.h>
typedef int (^IntBlock)();
IntBlock MakeCounter(int start, int increment) {
__block int i = start;
return Block_copy( ^ {
int ret = i;
i += increment;
return ret;
});
}
int main(void) {
IntBlock mycounter = MakeCounter(5, 2);
printf("First call: %d\n", mycounter());
printf("Second call: %d\n", mycounter());
printf("Third call: %d\n", mycounter());
/* because it was copied, it must also be released */
Block_release(mycounter);
return 0;
}
/* Output:
First call: 5
Second call: 7
Third call: 9
*/
除去在代码风格上的偏见后,这个扩展看起来不是很差,不过依然有很多限制,比如考虑下面的功能:
isFound := false
filelist each(|filename, mode|
f := func -> String{
filename split("/") each(strip()) strip()[-1]
}
last := f(filename)
if(last == "mysdk"){
isFound = true
return true
}
false
)
尝试这用Apple的block扩展来实现一下?看起来一点不现实。并且就算实现了,也只能在Apple的GCC Fork下面工作,显然并不是我们在追求的东西。
Way to Variable
好的,在开始解释OOC的Closure之前,让我们先来想想Closure要有什么特性:
- 能够自由的使用“自由变量”。 这里的“自由”是指在语法上定义于Closure以前的变量,比如
void test(Int a){
Int b;
// closure
}
这时closure要有能力读写变量a和b。当然,这是闭包的基本要求。
能够定义在任何地方。在Object Pascal/Delphi里,虽然我们能够定义nested function,但它的位置并不是自由的——当你在函数中间写了一个nested function时,编译器会要求你把它移动到顶端。但显然很多情况下我们需要的不仅仅是一个“临时”函数,我们还需要一个包含了之前定义过的所有变量的环境。
能够“携带”定义时的环境。考虑下面的情况:
f := func(a: Int) -> Func->Int{
b: Int = a*2
return func-> Int { a + b }
}
f1 := f(3)
f1() toString() println()
f1 = f(4)
f1() toString() println()
这个时候应该输出什么? 显然是9和12。但如果我们的闭包不能携带这个环境的话,是没法输出正确的结果的。
Way in OOC
好的,现在让我们来看看在OOC里,closure是怎么实现的,这次让我们从最简单的情况开始:
main: func {
a := 3
f := func -> Int { a }
}
一个闭包,它只用来返回之前定义过的变量的值。在这里,如果你执行"#{f()}" println()的话,会得到结果3。好的,让我们首先看看OOC生成了什么,然后再慢慢解释: (另外,需要注意为什么我们这里要加上main函数? 因为如果没有main的话,所有的变量都会变成全局,在闭包里本身就可以直接读写,也就失去的例子的意义)
lang_Numbers__Int a = 3;
__simpleclosure_simpleclosure_closure3_ctx* __simpleclosure_ctx4 = lang_Memory__gc_malloc(((lang_types__Class*)__simpleclosure_simpleclosure_closure3_ctx_class())->size);
(*(__simpleclosure_ctx4)) = (__simpleclosure_simpleclosure_closure3_ctx) {
a
};
lang_types__Closure __simpleclosure_closure5 = (lang_types__Closure) {
simpleclosure____simpleclosure_simpleclosure_closure3_thunk,
__simpleclosure_ctx4
};
lang_types__Closure f = __simpleclosure_closure5;
return 0;
}
lang_Numbers__Int simpleclosure____simpleclosure_simpleclosure_closure3(lang_Numbers__Int a) {
return a;
}
lang_Numbers__Int simpleclosure____simpleclosure_simpleclosure_closure3_thunk(__simpleclosure_simpleclosure_closure3_ctx* __context__) {
return simpleclosure____simpleclosure_simpleclosure_closure3((*__context__).a);
}
对于简单的两行代码,rock生成了一大堆东西。好的,让我们开始看到底发生了什么。
- 首先,OOC里closure是一个结构体,它包含两个部分:
context和function pointer。function pointer很简单,就如同名字上说的,我们把closure当成一个普通函数,这个函数的指针就是function pointer。而context则包含了所有closure需要的变量。用C的代码来说的话,就像这样:
typedef struct {
Closure_Context* context;
void (* thunk)(Closure_Context*);
} myclosure;
typedef struct {
Int a;
} Closure_Context;
当我们声明一个closure时,首先,这个closure会变成一个普通的函数,假设它的名字是
closure_impl,但不同的地方是,除了本身声明时的参数外,这个closure还接受一个Closure_Context*作为参数。随后,我们初始化一个Closure_Context,它的成员包含了所有这个closure里用到的外部变量。在刚才那个例子里,我们只有一个Int a。最后,把这两个指针组合成myclosure,我们的声明就完成了。到了这里,相信你早就明白该怎么使用它了,只要简单的
myclosure->thunk(myclosure->context),一切就自然的完成了。让我们看看是不是真的这样:
main: func {
a := 3
f := func -> Int { a }
f()
}
编译之后:
lang_Numbers__Int a = 3;
__simpleclosure_simpleclosure_closure3_ctx* __simpleclosure_ctx4 = lang_Memory__gc_malloc(((lang_types__Class*)__simpleclosure_simpleclosure_closure3_ctx_class())->size);
(*(__simpleclosure_ctx4)) = (__simpleclosure_simpleclosure_closure3_ctx) {
a
};
lang_types__Closure __simpleclosure_closure5 = (lang_types__Closure) {
simpleclosure____simpleclosure_simpleclosure_closure3_thunk,
__simpleclosure_ctx4
};
lang_types__Closure f = __simpleclosure_closure5;
((lang_Numbers__Int (*)(void*)) f.thunk)(f.context);
虽然比起手写的代码看起来要复杂,但跟我们的基本思想是完全一致的。
Pain in Context
不过,到这里其实一切并没有结束,因为我有意忽略了一个重要的地方——如何确定我们的context? 很显然,每个closure都有不同的context,不但意味这每个closure的context都要有自己的定义,还意味着我们必须自行推断需要把那些变量放进context里。好吧,让我们先看看第一个问题。
假设我们有这么一个代码:
foo: func{
b: Int = 1
f := func{
b + 1
}
}
bar: func{
a, b: Int
f := func{
a = 1
b = 2
}
}
那么根据刚才介绍的构造,每一个f都会变成一个(context,thunk)的集合。thunk没有任何问题——简单的把f写成函数取地址就够了,那么让我们想想context该怎么构造。如果直接写的话,那么会是这样:
typedef struct{
int b;
} foo_f_context;
typedef struct{
int* a;
int* b;
} bar_f_context;
这样,我们可以通过foo_f_context* -> b来访问b(只读),而可以通过bar_f_context* -> a来读写变量a。对,这种最简单的方法就是OOC所采用了。原因很简单——OOC不是C,在编译代码时所有closure访问了哪些变量都已经确定下来,我们只需要扫描一次,然后生成对应了struct就足够了。因此你会在头文件里找到这样的定义:
struct ___simpleclosure_simpleclosure_closure3_ctx {
lang_Numbers__Int a;
};
因为一切都是自动生成的,我们并不担心会出错。并且,这种方式非常简单而且高效。不过,如果打算直接在C代码里使用闭包,那问题就要复杂的多了——毕竟我们不可能给每一个闭包手写一个结构体。纵使可以用Hashmap来自由的访问变量,如何收集变量是一个大问题。现在回头看来,似乎Apple挑选了一个最好的方式,在没有破坏C的结构之下引入了闭包。
Closures in OOC的更多相关文章
- Swift学习(三):闭包(Closures)
定义 闭包(Closures)是独立的函数代码块,能在代码中传递及使用. 语法 {(parameters) -> return type in statements } 注:闭包表达式语法可以使 ...
- Swift 06.Closures
Closures --闭包 看了好些文章.由于自己也是刚开始学习swift,闭包还是不是很明白.暂时先放放.等看完后面的.加深感触后,在回头总结闭包的概念. 数组中常用的闭包函数 在Swift的数组中 ...
- swift基础语法(四) 函数、闭包(Closures)
//函数基本定义 func 函数名(参数名:参数类型=默认值) ->返回值类型{代码块} //无参无返回值函数 func hsmin(){ } //单参无返回值函数 func prin(st:S ...
- 《OOC》笔记(4)——自动化地将C#代码转化为C代码(结构版)
<OOC>笔记(4)——自动化地将C#代码转化为C代码(结构版) 我在<C表达面向对象语言的机制——C#版>中已经说明了从C#到C的转换方法.这次看<OOC>也是想 ...
- 《OOC》笔记(3)——C语言变长参数va_list的用法
<OOC>笔记(3)——C语言变长参数va_list的用法 C语言中赫赫有名的printf函数,能够接受的参数数目不固定,这就是变长参数.C#里也有params这个关键字用来实现变长参数. ...
- 《OOC》笔记(1)——C语言const、static和extern的用法
<OOC>笔记(1)——C语言const.static和extern的用法 C语言中const关键字用法不少,我只喜欢两种用法.一是用于修饰函数形参,二是用于修饰全局变量和局部变量. 用c ...
- 《OOC》笔记(0)——为何要看这本书
<OOC>笔记(0)——为何要看这本书 <OOC>全名是<Object-oriented Programming with ANSI-C>,作者Axel-Tobia ...
- c语言实现面向对象OOC
这种问题比较锻炼思维,同时考察c和c++的掌握程度.如果你遇到过类似问题,此题意义自不必说.如果用c实现c++,主要解决如何实现封装,继承和多态三大问题,本文分两块说. 1.封装 // Example ...
- C语言的OOP实践(OOC)
OOC 面向对象 C 语言编程实践 - 文章 - 伯乐在线http://blog.jobbole.com/105105/ ---硬着头皮看完了,但是感觉还是抽象有不理解的地方,感觉用C实现OOP好难啊 ...
随机推荐
- Codeforces 113A-Grammar Lessons(实现)
A. Grammar Lessons time limit per test 5 seconds memory limit per test 256 megabytes input standard ...
- tomcat的webapps文件夹下放更新后的项目就訪问不了
昨天给同事更新完程序,同事说更新后的程序訪问不了.它曾经的程序叫tj52,更新后的程序叫webapp.也就是tomcat的文件夹有两个文件架,一个叫webapp,一个叫tj52.最后另外一同事给了解决 ...
- openstack shelve/unshelve/stop浅析
声明: 本博客欢迎转发,但请保留原作者信息! 博客地址:http://blog.csdn.net/halcyonbaby 内容系本人学习.研究和总结,如有雷同,实属荣幸! stop的虚拟机仅仅是将虚拟 ...
- 接口自动化测试:Thrift框架RPC协议客户端开发
import java.lang.Thread.State;import java.util.Iterator;import java.util.List; import org.apache.thr ...
- 【Android开发日记】第一个任务Android Service!Service靴+重力感应器+弹出窗口+保持执行
前言: 近期在写一个小程序,需求是手机摇一摇就弹窗出来.第一次使用了Service,学习了两天,实现了Service弹窗,开机启动,Service启动和销毁,Service保持一直执行. 满足了自己的 ...
- 快速构建Windows 8风格应用29-捕获图片与视频
原文:快速构建Windows 8风格应用29-捕获图片与视频 引言 本篇博文主要介绍Windows 8中相机的概念.捕获图片与视频的基本原理.如何实现捕获图片与视频.相机最佳实践. 一.相机 关于相机 ...
- 机器学习学习-Types of learning
Types of learning 基于个人理解.于我们在面对一个详细的问题时.可以依据要达到的目标选择合适的机器学习算法来得到想要的结果.比方,推断一封电子邮件是否是垃圾邮件,就要使用分类(clas ...
- C# 编译器选项 /platform(指定输出平台)32位程序运行到x64平台的问题
如果说你编译的exe运行时报错: “尝试读取或写入受保护的内存.这通常指示其他内存已损坏” 这很有可能是你是以非托管的方式错误地引用了64位的API中去. 为什么会这样? 那你就要考虑VS的编译器选项 ...
- C# 利用 HttpWebRequest 和 HttpWebResponse 模拟登录有验证码的网站
原文:C# 利用 HttpWebRequest 和 HttpWebResponse 模拟登录有验证码的网站 我们经常会碰到需要程序模拟登录一个网站,那如果网站需要填写验证码的要怎样模拟登录呢?这篇文章 ...
- scala lift环境搭建
Intellij IDEA + scala插件 工欲善其事,必先利其器! 学习scala已经有一段时间了,对scala这门语言爱不释手,但同时也为scala糟糕的IDE工具支持感到懊恼(我是一个100 ...