可以用一句话来表示Blocks的扩充功能:带有局部变量的匿名函数。
###Blocks语法
Blocks完整表达式语法:
^返回值类型(参数列表) {
}
如果无返回值类型或者无参数可以缩写:
^ {
}
###Block类型变量
先看C语言中函数func地址赋值给函数指针类型变量funcptr:
int func(int count) {
return count + 1;
}
int (*funcptr)(int) = &func;
在Block语法下,可将Block赋值给声明为Block类型的变量:
int (^funcptr)(int) = ^int(int count) {};
Block类型变量用途:
Block作为函数参数
void func(int (^blk) (int)) {
}
Block作为返回类型
int (^ func() (int)) {
return ^(int count) {return count + 1;};
}
用typedef可以简写
typedef int (^ blk_t) (int);
通过typedef可声明"blk_t"类型变量;void func(int (^blk) (int)) {};改写为
void func(blk_t blk) {};
int (^ func() (int)) {};
改写为
blk_t func() {};
##截获自动变量
- (void)func {
int val = 10;
void (^blk)() = ^{NSLog(@"%d", val);};
val = 2;
blk();
}
函数输出为10,而是执行Block语法的自动变量的瞬间值;
##__block说明符
自动变量值截获只能保存执行block语法执行的瞬间值。保存后就不能修改该值。弱想在上面的block语法中实现block内赋值,需要用到__block说明符
__block int val = 10;
void(^blk)() = ^{val = 2; NSLog(@"%d", val);};
##截获的自动变量
#Block的实现
##Block实质
Block是“带有自动变量的匿名函数”,但Block究竟是什么呢?通过clang(LLVM编译器)具有转换为我们可读源代码的功能。通过“-rewrite-objc”选项就能将含有Block语法的源代码变换为C++源代码
clang -rewrite-objc 源代码名称
int main() {
void (^blk)(void) = ^{printf("Block\n");};
blk();
return 0;
}
通过clang可变换为以下形式:
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0 *Desc;
//构造函数
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags = 0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
print("Block\n");
}
static struct __main_block_desc_0 {
unsigned long reserved;
unsigned long Block_size;
`} __main_block_desc_0_Data {
0,
sizeof(struct __main_block_impl_0)
}
int main() {
void (*block)(void) = (void * (void))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA);
((void (*) struct __block_impl *)) ((struct __block_impl *)blk) ->FuncPtr)((struct __block_impl *)blk);
return 0;
}
先将源代码分成几部分逐步理解,首先看源代码中的Block语法:
^{printf("Block\n");};
可以看到变换后的源代码
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
printf("Block\n");
}
通过Block使用的匿名函数实际上被作为简单的c语言来处理。另外,根据Block语法所属的函数名(此处为main)和该Block语法在函数出现的顺序值(此处为0)来给经clang变换的函数命名。
该函数的参数__cself,为指向Block值得变量。我们先看看该参数的声明。
struct __main_block_impl_0 *__cself
与C++的this和OC的self相同,参数__cself是__main_block_impl_0结构体的指针。
该结构体声明如下:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_impl_desc_0* Desc;
}
由于转换后的源代码中,也一并写入了其构造函数,所以看起来复杂,如果去除该构造函数,__main_block_impl_0结构体将变得非常简单:
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
第二个成员变量为Desc指针,__main_block_impl_desc_0结构体声明:
struct __main_blok_impl_desc_0 {
unsigned long reserved;
unsigned long Block_size;
};
其成员名称为今后版本升级所需要的区域和Block大小。
初始化含有这些结构体的__main_block_impl_0结构体的构造函数。
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
impl.isa = NSConcreteStackBlock;
impl.Flags = flag;
impl.FuncPtr = fp;
Desc = desc;
}
NSConcreteStackBlock表明该Block在栈上,我们先看看该构造函数的调用。
void(*blk)(void) = (void(*)(void)&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA);
因为转换较多,看起来不是很清楚,去掉转换部分如下:
struct __main_block_impl_0 tmp = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);
struct __main_block_impl_0 *blk = &tmp;
这样就容易理解了。该源代码将__main_block_impl_0结构体类型的自动变量,即栈上生成的__main_block_impl_0结构体实例的指针,赋值给__main_block_impl_0结构体指针类型的变量blk,以下为对应的源代码。
void (^blk)(void) = ^{print("Block\n");};
栈上生成的__main_block_impl_0结构体实例指针赋值给__main_block_impl_0结构体指针类型的变量blk。该源码的Block就是__main_block_impl_0结构体类型的自动变量。
下面看看__main_block_impl_0结构体实例构造参数。
__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_Data);
第一个参数是C语言函数指针。为Block匿名函数在C语言中的体现。第二个参数为静态全局变量初始化的__main_bloock_desc_0结构体实例指针。以下为初始化代码。
static struct __main_block_desc_0 __main_block_desc_0_DATA = {
0,
sizeof(struct __main_block_impl_0)
};
由此可知,该源代码使用Block,即__main_block_impl_0结构体的实例大小,进行初始化。
下面看看栈上的__main_block_impl_0结构体(即Block)是如何根据这些参数进行初始化的,如果展开__main_block_impl_0结构体的__block_impl结构体,可记为如下形式:
struct __main_block_impl_0 {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
struct __main_block_desc_0* Desc;
}
该结构体根据构造函数像下面这样进行初始化。
isa = &_NSConcreteStackBlock;
Flags = 0;
Reserved = 0;
FuncPtr = __main_block_func_0;
Desc = &__main_block_desc_0_DATA;
再来看看使用该Block部分。
blk();
这部分变换为以下源码:
((void (*))(struct __block_impl *))((struct __block _block_impl *)blk)->FuncPtr)((struct __block_impl *)blk);
去掉转换部分。
(*blk->impl.FuncPtr)(blk);
这就是简单的函数指针调用函数,由Block语法转换的__main_block_func_0函数的指针被赋值给成员变量FuncPtr中,另外也说明了__main_block_func_0函数参数__cself指向Block值
##截获自动变量
- (void)func {
int val = 10;
void (^blk)() = ^{NSLog(@"%d", val);};
val = 2;
blk();
}
代码通过clang进行转换。
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0 *Desc;
int val;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _val, int flags=0) : val(_val) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_func_0(struct __main_block_impl_0 *__cself) {
int val = __cself->val;
printf(val);
}
static struct __main_block_desc_0 {
unsigned long reserved;
unsigned long Block_size;
} __main_block_desc_0_Data = {
0,
sizeof(struct __main_block_impl_0)
};
int main() {
int val = 10;
void(*blk)(void) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, val);
return 0;
}
Block语法表达式中使用的自动变量被作为成员变量追加到__main_block_impl_0结构体中。
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0 *Desc;
int val;
}
Block语法表达式中没有使用的自动变量不会被追加,Block的自动变量截获只针对Block中使用的自动变量。下面看看初始化该结构体的构造函数的差异。
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _val, int flags=0) : val(_val) {}
使用执行Block语法时的自动变量val和初始化__main_block_impl_0结构体实例。
impl.isa = &_NSConcreteStackBlock;
impl.Flags = 0;
impl.FuncPtr = __main_block_func_0;
Desc = &__main_block_desc_0_DATA;
val = 10;
由此可知,在__main_block_impl_0结构体实例(Block)中,自动变量被截获。下面再来看Block匿名函数实现
^{printf(val);}
该源代码转化为以下函数:
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int val = __cself->val;
printf(val);
}
在转换后的源码中,截获的__main_block_impl_0结构体实例的成员变量上的自动变量,这些变量在Block语法表达式之前被声明定义。因此,原来的源代码表达式无需改动便可以使用截获的自动变量执行。
##__block说明符
我们再来回顾截取自动变量的例子
^{printf(val);}
该源码转换结果如下:
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int val = __cself->val;
printf(val);
}
Block中使用的被截获的自动变量就如“带有自动变量的匿名函数”,在Block结构体实例中重写该自动变量也不会改变原先截获的自动变量。自动变量val虽然被捕获进来了,但是是用 __cself->val来访问的。Block仅仅捕获了val的值,并没有捕获val的内存地址。所以在__main_block_func_0这个函数中即使我们重写这个自动变量val的值,依旧没法去改变Block外面自动变量val的值。
OC可能是基于这一点,在编译的层面就防止开发者可能犯的错误,因为自动变量没法在Block中改变外部变量的值,所以编译过程中就报编译错误
int val = 0;
void (^blk)(void) = ^{val = 1;};
#import
int global_i = 1;
static int static_global_j = 2;
int main(int argc, const char * argv[]) {
static int static_k = 3;
int val = 4;
void (^myBlock)(void) = ^{
global_i ++;
static_global_j ++;
static_k ++;
NSLog(@"Block中 global_i = %d,static_global_j = %d,static_k = %d,val = %d",global_i,static_global_j,static_k,val);
};
global_i ++;
static_global_j ++;
static_k ++;
val ++;
NSLog(@"Block外 global_i = %d,static_global_j = %d,static_k = %d,val = %d",global_i,static_global_j,static_k,val);
myBlock();
return 0;
}
运行结果
Block 外 global_i = 2,static_global_j = 3,static_k = 4,val = 5
Block 中 global_i = 3,static_global_j = 4,static_k = 5,val = 4
这里就有2点需要弄清楚了
1.为什么在Block里面不加__bolck不允许更改变量?
2.为什么自动变量的值没有增加,而其他几个变量的值是增加的?自动变量是什么状态下被block捕获进去的?
为了弄清楚这2点,我们用clang转换一下源码出来分析分析。
int global_i = 1;
static int static_global_j = 2;
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int *static_k;
int val;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_static_k, int _val, int flags=0) : static_k(_static_k), val(_val) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int *static_k = __cself->static_k; // bound by copy
int val = __cself->val; // bound by copy
global_i ++;
static_global_j ++;
(*static_k) ++;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_45_k1d9q7c52vz50wz1683_hk9r0000gn_T_main_6fe658_mi_0,global_i,static_global_j,(*static_k),val);
}
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
int main(int argc, const char * argv[]) {
static int static_k = 3;
int val = 4;
void (*myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &static_k, val));
global_i ++;
static_global_j ++;
static_k ++;
val ++;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_45_k1d9q7c52vz50wz1683_hk9r0000gn_T_main_6fe658_mi_1,global_i,static_global_j,static_k,val);
((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);
return 0;
}
首先全局变量global_i和静态全局变量static_global_j的值增加,以及它们被Block捕获进去,这一点很好理解,因为是全局的,作用域很广,所以Block捕获了它们进去之后,在Block里面进行++操作,Block结束之后,它们的值依旧可以得以保存下来。
接下来仔细看看自动变量和静态变量的问题。
在__main_block_impl_0中,可以看到静态变量static_k和自动变量val,被Block从外面捕获进来,成为__main_block_impl_0这个结构体的成员变量了。
接着看构造函数,
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_static_k, int _val, int flags=0) : static_k(_static_k), val(_val)
这个构造函数中,自动变量和静态变量被捕获为成员变量追加到了构造函数中。main里面的myBlock闭包中的__main_block_impl_0结构体,初始化如下
void (*myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &static_k, val));
impl.isa = &_NSConcreteStackBlock;
impl.Flags = 0;
impl.FuncPtr = __main_block_impl_0;
Desc = &__main_block_desc_0_DATA;
*_static_k = 4;
val = 4;
再研究一下源码,我们注意到__main_block_func_0这个函数的实现
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int *static_k = __cself->static_k; // bound by copy
int val = __cself->val; // bound by copy
global_i ++;
static_global_j ++;
(*static_k) ++;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_45_k1d9q7c52vz50wz1683_hk9r0000gn_T_main_6fe658_mi_0,global_i,static_global_j,(*static_k),val);
}
回到上面的例子上面来,4种变量里面只有静态变量,静态全局变量,全局变量这3种是可以在Block里面被改变值的。仔细观看源码,我们能看出这3个变量可以改变值的原因。
1、静态全局变量,全局变量由于作用域的原因,于是可以直接在Block里面被改变。他们也都存储在全局区。
2、静态变量传递给Block是内存地址值,所以能在Block里面直接改变值。
根据官方文档我们可以了解到,苹果要求我们在自动变量前加入 __block关键字(__block storage-class-specifier存储域类说明符),就可以在Block里面改变外部自动变量的值了。
总结一下在Block中改变变量值有2种方式,一是传递内存地址指针到Block中,二是改变存储区方式(__block)。静态变量的这种方法似乎也适用于自动变量的访问。但我们为什么没有这么做呢?
实际上在由Block语法生成的值Block上,可以存储超过其变量作用域的被截获对象的自动变量。变量作用域结束的同时,原来·的自动变量被废弃,因此静态变量的地址将不能访问。
下面我们来实际用__block说明符,来变更值
__block int val = 10;
void (^blk)(void) = ^{val = 1;};
该代码变换后如下:
struct __Blcok_byref_val_0 {
void *isa;
__Blcok_byref_val_0 *__forwarding;
int __flags;
int __size;
int val;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __block_impl_desc_0 *Desc;
__Block_byref_val_0 *val;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_val_0 *_val, int flags=0): val(_val->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_blok_func_0(struct __main_block_impl_0 *__cself) {
__Blcok_byref_val_0 *val = __cself->val;
(val->__forwarding->val) = 1;
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0 *src) {
__Block_object_assign(&dst->val, src->val, BLOCK_FIELD_IS_BYREF);
}
static void __main_block_dispose_0(struct __main_block_impl_0 *src) {
_Block_object_dispose(src->val, BLOCK_FIELD_IS_BYREF);
}
static struct __main_block_desc_0 {
unsigned long reserved;
unsigned long Block_size;
void (*copy)(struct __main_blok_impl_0 *, struct __main_block_impl_0 *);
void(*dispose)(struct __main_block_impl_0 *);
} __main_block_desc_0_DATA = {
0,
sizeof(struct __main_block_impl_0),
__main_block_copy_0,
__main_block_dispose_0
};
int main() {
__Block_byref_val_0 val = {
0,
&val,
0,
sizeof(__Block_byref_val_0),
10
};
blk = &__main_block_impl_0(__main_block_func_0, &__mian_block_desc_0_DATA, &val, Ox22000000);
return 0;
}
__block int val = 10;
这个__block变量val是怎么转换过来的呢?
__Block_byref_val_0 val = {
0,
&val,
0,
sizeof(__Block_byref_val_0),
10
};
我们发现,他竟然变成为了结构体实例,__block变量也同Block一样变成__Block_byref_val_0结构体类型的自动变量,既栈上生成的__Block_byref_val_0结构体实例。该变量初始化为10,且这个值也出现在结构体实例的初始化中,这意味着该结构体持有相当于原自动变量的成员变量。
该结构体声明如下:
struct __Block_byref_val_0 {
void *__isa;
__Block_byref_val_0 *__forwarding;
int __flags;
int __size;
int val;
};
如同初始化时的源代码一样,该结构体中最后的成员变量val相当于原自动比变量的成员变量
^{val = 1;}
该源代码转换如下:
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_val_0 *val = __cself->val;
(val->__forwarding->val) = 1;
}
刚刚在Block中向静态变量赋值时,使用了指向该静态变量的指针。而向__block变量赋值要比这个更为复杂。Block的__main_block_impl_0结构体实例持有指向__block变量的__Block_byref_val_0结构体实例的指针。
__Block_byref_val_0结构体实例的成员变量__forwarding持有指向该实例自身的指针。通过成员变量__forwarding访问成员变量val。(成员变量val是该实例自身持有的变量,它相当于原自动变量)
__block变量的__Block_byref_val_0结构体并不在Block用__main_block_impl_0结构体中,这样做是为了在多个Block中使用__block变量。
__block int val = 10;
void (^blk0)(void) = ^{val = 0;};
void (blk1)(void) = ^{val = 1;};
Block类型变量blk0和blk1访问__blkvk变量val。
__Block_byref_val_0 val = {0, &val, 0, sizeof(__Block_byref_val_0), 10};
blk0 = &__main_block_impl_0(__main_block_func_), &__main_block_desc_0_DATA, &val, 0x22000000);
blk0 = &__main_block_impl_1(__main_block_func_), &__main_block_desc_1_DATA, &val, 0x22000000);
两个都使用了__Blcok_buref_val_0结构体实例val的指针。这样一来就可以从多个Blcok中使用同一个__block变量。当然,反过来从一个Block中使用多个__block变量也是可以的。只是要增加Block的结构体成员变量与构造函数的参数,便可对应使用多个__block变量。
##Block存储域
通过前面说明可知,Block转换为Block的结构体类型的自动变量,__block变量转换为__block变量的结构体类型的自动变量。所谓结构体类型的自动变量,即栈上生成的该结构体的实例。
Block与__block变量的实质:
名称 | 实质 |
---|---|
Block | 栈上Block的结构体实例 |
__block变量 | 栈上__block变量的结构体实例 |
另外,通过之前的说明可知Block也是OC对象。将Block当做OC对象来看,该Block的类为_NSConcreteStackBlock。虽然该类并没有出现源代码中,但有很多相似的类,例如:
void (^blk)(void) = ^{printf("Global Block\n");};
int main() {
}
该Block结构体成员isa初始化如下:
impl.isa = &_NSConcreteStackBlock;
该Block的类为_NSConcreteStackBlock类。此Block即该Block用结构体实例设置在程序的数据区域。因为在使用全局变量的地方不能使用自动变量,所以不存在对自动变量的截获。由此Block用结构体实例的内容不依赖于执行的状态,所以整个程序中只需要一个实例,因此将Block用结构体实例设置在与全局变量相同的数据区域中即可。
typedef int (^blk_t)(int);
for (int rate = 0; rate < 0; ++rate) {
blk_t blk = ^(int count){return rate * count;};
}
上面Block语法生成的Block用结构体实例在每次for循环中截获的值都不同。但是以下代码中在不截获自动变量时,Block用结构体实例每次截获的值都完全相同。
typedef int (^blk_t)(int);
for (int rate = 0; rate < 0; ++rate) {
blk_t blk = ^(int count){return count;};
}
也就是说,即使在函数内而不在记述广域变量的地方使用Block语法时,只要Block不截获自动变量,就可以将Block用结构体实例设置在程序的数据区域。
虽然通过clang转换的源代码通常是_NSConcreteStackBlock类对象,但实现上却有不同,总结如下:
-记述全局变量的地方有Bock语法时
-Block语法的表达式中不使用应截获的自动变量时
在以上这些情况下,Block为_NSConcreteGlobalBlock类对象。即Block配置在程序的数据区域中。除此之外的Block语法生成的Block为_NSConcreteStackBlock类对象,且设置在栈上。
配置在全局变量的Block,从变量作用域外也可以通过指针安全地使用。但设置在栈上的Block。但设置在栈上的Block,如果其所属变量作用域结束,该Block就被废弃。由于__block变量也配置在栈上,同样道理。
Block提供了将Block和__block变量从栈上复制到堆上的方法来解决这个问题。将配置在栈上的Block复制到堆上,这样即使Block语法记述的变量作用域结束,堆上Block还可以继续存在。
复制到堆上的Block将_NSConcreteMallocBlock类对象写入Block结构体实例的成员变量isa。
impl.isa = &_NSConcreteMallocBlock;
而__block变量用结构体成员变量__forwarding可以实现无论__block变量配置在栈上还是堆上都能够正确的访问__block变量。
有时在__blokc变量配置在堆上的状态下,也可以访问栈上的__block变量。在此情况下,只要栈上的结构体实例成员变量__forwarding指向堆上的结构体实例,那么不管是从栈上的__block变量还是堆上的__block变量都能够正确访问。
那么Block提供的复制方法究竟是什么呢?实际上当ARC有效时,大多情况编译器会恰当进行判断,自动生成将Block从栈上复制到堆上的代码。
typedef int (^blk_t)(int);
blk_t func(int rate)
{
return ^(int count){return rate * count;};
}
该源代码为返回配置在栈上的Block的函数。即程序执行中从该函数返回函数调用方时变量作用域才结束,因此栈上的Block也被废弃
blk_t func(int rate) {
blk_t temp = &__func_block_impl_0(__func_bock_func_0, &__func_block_desc_0_DATA, rate);
temp = objc_retainBlock(temp);
return objc_autoreleaseReturnValue(temp);
}
另外,因为ARC处于有效的状态,所以blk_t temp实际上与附有__string修饰的blk_t __strong temp相同。
objc_retainBlock函数实际上就是Block_copy函数。即:
temp = _Block_copy(temp);
return objc_autoreleaseReturnValue(temp);
/*
将通过Blcok语法生成的Block,
即配置在栈上的Block用结构体实例
赋值给相当于Blcok类型的变量temp中。
*/
temp = _Block_copy(temp);
/*
_Block_copy函数
将栈上的Block复制到堆上
复制后,将堆上的地址作为指针赋值给变量temp
*/
return objc_autoreleaseReturnValue(temp);
/*
将堆上的Block作为OC对象
注册到autoreleasepool中,然后返回该对象
*/
将Block作为函数返回值时,编译器会自动生成复制到堆上的代码。
Ctrl + B
Ctrl + I
Ctrl + Q
Ctrl + L
Ctrl + K
Ctrl + G
Ctrl + H
Ctrl + O
Ctrl + U
Ctrl + R
Ctrl + Z
Ctrl + Y
Markdown 是一种轻量级标记语言,它允许人们使用易读易写的纯文本格式编写文档,然后转换成格式丰富的HTML页面。 —— [ 维基百科 ]
使用简单的符号标识不同的标题,将某些文字标记为粗体或者斜体,创建一个链接等,详细语法参考帮助?。
本编辑器支持 Markdown Extra , 扩展了很多好用的功能。具体请参考Github.
Markdown Extra 表格语法:
项目 | 价格 |
---|---|
Computer | $1600 |
Phone | $12 |
Pipe | $1 |
可以使用冒号来定义对齐方式:
项目 | 价格 | 数量 |
---|---|---|
Computer | 1600 元 | 5 |
Phone | 12 元 | 12 |
Pipe | 1 元 | 234 |
###定义列表
Markdown Extra 定义列表语法:
项目1
项目2
: 定义 A
: 定义 B
定义 C
定义 D
定义D内容
代码块语法遵循标准markdown代码,例如:
@requires_authorization
def somefunc(param1='', param2=0):
'''A docstring'''
if param1 > param2: # interesting
print 'Greater'
return (param2 - param1 + 1) or None
class SomeClass:
pass
>>> message = '''interpreter
... prompt'''
###脚注
生成一个脚注[^footnote].
[^footnote]: 这里是 **脚注** 的 *内容*.
### 目录
用 `[TOC]`来生成目录:
@[toc]
### 数学公式
使用MathJax渲染*LaTex* 数学公式,详见[math.stackexchange.com][1].
- 行内公式,数学公式为:$\Gamma(n) = (n-1)!\quad\forall n\in\mathbb N$。
- 块级公式:
$$ x = \dfrac{-b \pm \sqrt{b^2 - 4ac}}{2a} $$
更多LaTex语法请参考 [这儿][3].
### UML 图:
可以渲染序列图:
```mermaid
sequenceDiagram
张三->>李四: 嘿,小四儿, 写博客了没?
Note right of 李四: 李四愣了一下,说:
李四-->>张三: 忙得吐血,哪有时间写。
或者流程图:
即使用户在没有网络的情况下,也可以通过本编辑器离线写博客(直接在曾经使用过的浏览器中输入write.blog.csdn.net/mdeditor即可。Markdown编辑器使用浏览器离线存储将内容保存在本地。
用户写博客的过程中,内容实时保存在浏览器缓存中,在用户关闭浏览器或者其它异常情况下,内容不会丢失。用户再次打开浏览器时,会显示上次用户正在编辑的没有发表的内容。
博客发表后,本地缓存将被删除。
用户可以选择 把正在写的博客保存到服务器草稿箱,即使换浏览器或者清除缓存,内容也不会丢失。
**注意:**虽然浏览器存储大部分时候都比较可靠,但为了您的数据安全,在联网后,请务必及时发表或者保存到服务器草稿箱。
##浏览器兼容