如果以内联方式使用块对象,则无需声明。块对象声明语法与函数指针声明语法相似,但是块对象应使用脱字符(^)而非星号指针 (*)。下面的代码声明一个aBlock变量,它标识一个需传入三个参数并具有float返回值的块。
float (^aBlock)(const int*, int, float);
l 创建一个块
int (^oneFrom)(int);
oneFrom = ^(int anInt) {
return anInt - 1;
};
结尾处的分号是标准C的行结束标志。如果未显式声明块表达式的返回值,则编译器会根据块内容自动进行推导。
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,用此声明一个局部变量可以被函数块修改。
如果要使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在被函数块引用时是未初始化值,所以调用它的话就访问了无效地址,或者所要执行的指令是未定义的。
我们先举一个简单例子:
#import
template
void BlockTest(void)
{
pBlock();
}
void Hi(void)
{
NSLog(@"Hi, there!");
}
int main(int argc, const char* argv[])
{
BlockTest();
}
上述代码中尚未出现Blocks,但是我们可以看到,一般的外部函数能够作为模板参数。那么Blocks是否可以这么做呢?我们不妨尝试一下:
#import
template
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
template
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
template
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))\'。即使显式地将
#import
#include
#include
using namespace std;
template
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
#include
#include
using namespace std;
template
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
using namespace std;
template
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
void BlockTest(void)
{
void (^pBlocks)(void) = ^{};
}
int main(int argc, const char* argv[])
{
BlockTest();
}
上面这段代码中,模板函数BlockTest中pBlocks根本就没用泛型,也无法通过编译,而且报的错误是internal error: segmentation fault。而只有下面这种情况才能通过编译,但实际上是没有任何意义的:
#import
template
void BlockTest(void)
{
void (^pBlock)(void) = nil;
}
int main(int argc, const char* argv[])
{
BlockTest();
}
可见,Blocks作为Lambda表达式而言已经是非常棒的,但与lambda函数功能相比就稍逊一些,尤其是与泛型结合时,表现得很一般。其实这也是与 Blocks的实现有关的。Blocks仍然像Objective-C的很多特性那样,主要是靠动态实现的,在编译时所花的精力较少。能够在C以及Objective-C中用上Lambda特性也很不错。