Block 语法
Blocks是C语言的扩种功能,是带有自动变量(局部变量)的匿名函数。
^void (int event){ printf("buttonId: %d event = %d\n", i , event);}复制代码
与一般函数相比,有两点不同
- 没有函数名(匿名函数)
- 带有"^"(插入记号)
以下为Block语法
^
返回值类型
参数列表
表达式
如:
^int (int count){ return count+1;}复制代码
可以省略返回值类型
^
参数列表
表达式
省略返回值类型时,如果表达式中有return语句就使用该返回值的类型,如果表达式中没有人return语句就使用void类型。表达式中含有多个return语句时,所有return的返回值类型必须相同。
如:
^(int count){ return count+1;}复制代码
如果不是用参数,参数列表也可以省略。
^
表达式
如:
^void (void){ printf("Blocks\n");}复制代码
可省略为:
^{ printf("Blocks\n");}复制代码
Block 类型变量
在定义C语言函数时,就可以将所定义函数的地址赋值给函数指针类型的变量中。
int func(int count){ return count+1;}int (*funcptr) (int) = &func;复制代码
这样一来,函数func的地址就能赋值给函数指针类型变量funcptr中了。
注:引用知乎fan wang关于指针的回答,解释一下指针类型变量原理
作者:fan wang 链接:https://www.zhihu.com/question/31022750/answer/50629732 来源:知乎 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
一图胜千言1. 声明变量:C语言声明一个变量时,编译器在内存中留出一个唯一的地址单元来存储变量,如下图,变量var初始化为100,编译器将地址为1004的内存单元留给变量,并将地址1004和该变量的名称关联起来。
2.创建指针:变量var的地址是1004,是一个数字,地址的这个数字可以用另一个变量来保存它,假设这个变量为p,此时变量p未被初始化,系统为它分配了空间,但值还不确定,如下图所示。 3.初始化指针,将变量var的地址存储到变量p中,初始化后(p=&var),p指向var,称为一个指向var的指针。指针是一个变量,它存储了另一个变量的地址。 4.声明指针:typename *p 其中typename指的是var的变量类型,可以是 short ,char ,float,因为每个类型占用的内存字节不同,short占2个字节,char占1个字节,float占4个字节,指针的值等于它指向变量的第一个字节的地址 。*是间接运算符,说明p是指针变量,与非指针变量区别开来。 5.*p和var指的是var的内容;p和&var指的是var的地址 6.既然指针*p的值等于var,p的值等于&var,为什么要多发明这一个指针符号增加记忆量呢。指针主要的功能有两个:避免副本和共享数据。指针的重要功能是函数之间传递参数。 talk is cheap, show me the code! 假设用c语言设计一个游戏,控制人物向前走的函数为 go_forward(),这个函数接收游戏人物的坐标(int x,int y) 两个变量,对这两个变量进行加减操作。
#includevoid go_forward(int position_x,int position_y){ position_x=position_x+1; position_y=position_y+1;}int main(){ int x=0; int y=0; go_forward(x,y); printf("当前坐标为:%d,%d \n",x,y); return 0;}复制代码
你希望执行go_forward()函数后x,y坐标都+1,输出为(1,1),但是结果还是(0,0)原因为C语言调用函数的方式是按值传递参数,以x参数为例,刚开始main函数中有一个x的局部变量,值为0,当计算机调用go_forward()函数时,它将变量x的值复制给了参数position_x,这只是一个赋值过程将变量x赋值给变量position_x,相当于 position_x=x 命令,而这个命令,x的值是不发生变化的,结果如下图所示,x的值仍为0,position_x的值变为1。
解决方法,传递指针,用指针告诉go_forward()函数参数x的值的地址,go_forward()函数就能修改对应地址中的内容。所以用指针的主要原因是让函数共享存储器,一个函数可以修改另一个函数创建的数据,只要提供数据在内存中的地址,修改代码如下。
#includevoid go_foward(int *position_x,int*position_y){ *position_x=*position_x+1; *position_y=*position_y+1;}int main(){ int x=0; int y=0; go_forward(&x,&y); printf("当前坐标为:%d,%d \n",x,y); return 0;}复制代码
运行结果:当前坐标为:1,1
同样地,在Block语法下,可以将Block语法赋值给生命为Block类型的变量中。即源代码中一旦使用Block语法就相当于生成了可赋值给Block类型变量的“值”。
声明Block类型变量的示例如下:
int (^blk)(int);复制代码
与前面的使用函数指针的源代码对比可知,声明Block类型变量仅仅是将声明函数指针类型变量的"*"变为"^"。该Block类型变量与一般C语言变量完全相同,可用作:
- 自动变量(局部变量)
- 函数参数
- 静态变量
- 静态全局变量
- 全局变量
int (^blk)(int) = ^(int count){ return count+1;}复制代码
由"^"开始的Block语法生成的Block被赋值给变量blk中。因为与通常的变量相同,所以当然也可以由Block类型变量向Block类型变量赋值。
int (^bkl1)(int) = blk;int (^blk2)(int);blk2 = blk1;复制代码
在函数参数中使用Block类型变量可以向函数传递Block。
void func(int (^block)(int)){}复制代码
在函数返回值中指定Block类型,可以将Block作为函数的返回值返回。
int (^func()(int)){ return ^(int count){ return count+1; }}复制代码
由此可知,在函数参数和返回值中使用Block类型变量是,记述方式极为复杂,所以,使用typedef来解决。
typedef int (^blk_t)(int);复制代码
如上所示,通过使用typedef可声明“blk_t”类型变量。
void func(int (^block)(int)){}复制代码
可以变为:
void func(blk_t blk){}复制代码
int (^func()(int)){ return ^(int count){ return count+1; }}复制代码
可以变为:
blk_t func(){ return ^(int count){ return count+1; }}复制代码
另外,将赋值给Block类型变量中的Block方法像C语言通常的函数调用那样使用,这种方法与使用函数指针类型变量调用函数的方法几乎完全相同。 例:变量funcptr为函数指针类型是,想下面这样调用函数指针类型变量。
int result = (*funcptr) (10);复制代码
变量blk为Block 类型的情况下,这样调用Block类型变量:
int result = blk(10);复制代码
在函数参数中使用Block类型变量并在函数中执行Block的例子如下:
int func(blk_t blk, int rate){ return blk(rate);}复制代码
在OC中也可以:
- (int) methodUsingBlock:(blk_t) blk rate:(int)rate{ return blk(rate);}复制代码
Block类型变量可完全像C语言变量一样使用,因此也可以使用指向Block类型变量的指针,即Block的指针类型变量。
typedef int(^blk_t)(int);blk_t blk = ^(int count){ return count+1;};blk_t *blkptr = &blk;(*blkptr)(10);复制代码
截获自动变量值
int main(){ int dmy = 256; int val = 10; const char *fmt = "val = %d\n"; void (^blk)(void)=^{ printf(fmt,val); }; val = 2; fmt = "These values were changed.val=%d\n"; blk(); return 0;}复制代码
运行结果为:
val = 10复制代码
在该源代码中,Block语法的表达式使用的是它之前声明的自动变量fmt和val。Block中,Block表达式截获所使用的自动变量的值,即保存该自动变量的瞬间值。因为Block表达式保存了自动变量的值,所以在执行Block语法后,即使改写Block中使用的自动变量的值也不会影响Block执行时自动变量的值。
__block 说明符
由于Block表达式截获了所使用的自动变量的值,如果在Block中尝试修改自动变量的值会编译错误:
int val = 0;void (^blk)(void) = ^{ val = 1; };blk();printf("val = %d\n",val);复制代码
编译之后:
Variable is not assignable (missing __block type specifier)复制代码
若想在Block语法的表达式中将值赋给在Block语法外声明的自动变量,需要在该自动变量上附加__block说明符。
上述代码修改为:
__block int val = 0;void (^blk)(void) = ^{ val = 1; };blk();printf("val = %d\n",val);复制代码
运行结果为:
val = 1复制代码
使用附有__block说明符的自动变量可在Block中赋值,该变量成为__block变量。
截获的自动变量
如果截获Objective-C对象,调用变更该对象的方法如下:
id array = [[NSMutableArray alloc] init];void (^block)(void)=^{ id obj = [[NSObject alloc] init]; [array addObject : obj];};复制代码
编译运行后发现没有问题,但是向截获的变量array赋值则会产生编译错误。以上代码中截获的变量值为NSMutableArray类的对象。如果用C语言来描述,即是截获NSMutableArray类对象用的结构体实例指针。虽然赋值给截获的自动变量array的操作会产生编译错误,但使用截获的值缺不会有任何问题。
另外,在使用C语言数组时必须小心使用其指针。
const char text[] = "hello";void (^blk)(void) = ^{ printf("%c\n",text[2]);};复制代码
编译后报错:
Cannot refer to declaration with an array type inside block复制代码
这是因为在现在的Blocks中,截获自动变量的方法并没有实现对C语言数组的截获,这时候,可以使用指针解决该问题。
const char *text = "hello";void (^blk)(void) = ^{ printf("%c\n",text[2]);};复制代码