前言

block在Objective C开发中应用非常广泛,我们知道block会捕获外部对象,也知道使用block要防止循环引用。

“知其然而不知其所以然”是一件非常痛苦的事情,那么block这套机制在OC中是怎样实现的呢?本文通过从C/C++到汇编层面分析block的实现原理。


Clang

clang是XCode的编译器前端。编译器前端负责语法分析,语义分析,生成中间代码(intermediate representation )。

比方当你在XCode中进行build一个.m文件的时候。实际的编译命令例如以下

clang -x objective-c -arch x86_64
-fmessage-length=0
-fobjc-arc...
-Wno-missing-field-initializers ...
-DDEBUG=1 ...
-isysroot iPhoneSimulator10.1.sdk
-fasm-blocks ...
-I headers.hmap
-F 所须要的Framework
-iquote 所须要的Framework ...
-c ViewController.m
-o ViewController.o

Objective C也能够用GCC来编译,只是那超出了本文的范畴,不做解说。

Clang除了能够进行编译之外,还有其它一些使用方法。

比方本文分析代码的核心命令就是这个:

clang -rewrite-objc 文件.m

通过这个命令。我们能够把Objective C的代码用C++来表示。

对于想深入理解Clang命令的同学。能够用命令忙自带的工具来查看帮助文档

man clang

或者阅读官方文档:文档地址


查看汇编代码

在XCode中。对于一个源文件,我们能够通过例如以下方式查看其汇编代码。这对我们分析代码深层次的实现原理非常实用,这个在后面也会遇到。

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvSGVsbG9fSHdj/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" width="300">


Objective C对象内存模型

为了本文解说的更清楚。我们首先来看看一个Objective C对象的内存模型。我们首先新建一个类。内容例如以下

DemoClass.h

@interface DemoClass : NSObject
@property (nonatomic, copy) NSString * value;
@end

DemoClass.m

@implementation DemoClass
- (void)demoFunction{
DemoClass * obj = [[DemoClass alloc] init];
}
@end

然后。我们用上文提到的Clang命令将DemoClass.m转成C++的表示。

clang -rewrite-objc DemoClass.m

转换完成后当前文件夹会多一个DemoClass.cpp文件,这个文件非常大,接近十万行。

我们先搜索这种方法名称demoFunction,以方法作为切入

static void _I_DemoClass_demoFunction(DemoClass * self, SEL _cmd) {
DemoClass * obj = ((DemoClass *(*)(id, SEL))(void *)objc_msgSend)((id)((DemoClass *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("DemoClass"), sel_registerName("alloc")), sel_registerName("init"));
}

能够看到。转换成C++后。一个实例方法转换为一个静态方法,这种方法的内容看起来非常乱,由于有各种的类型强制转换,去掉后就比較清楚了。

static void _I_DemoClass_demoFunction(DemoClass * self, SEL _cmd) {
DemoClass * obj = objc_msgSend(objc_msgSend(objc_getClass("DemoClass"), sel_registerName("alloc")), sel_registerName("init"));
}

能够看到:

  • 转换后添加了两个參数:self_cmd
  • 方法的调用转换成了objc_msgSend。这是一个C函数,两个參数各自是ClassSEL

关于objc_msgSend内发生的事情,參见我之前的一篇博客:

到这里。我们知道了一个OC的实例方法详细是怎么实现的了。

那么,一个OC对象在内存中是怎样存储的呢?我们在刚刚的方法的上下能够找到这个类的完整实现,

//类相应的结构体
struct DemoClass_IMPL {
struct NSObject_IMPL NSObject_IVARS;
NSString *_value;
};
//demoFunction方法
static void _I_DemoClass_demoFunction(DemoClass * self, SEL _cmd) {
DemoClass * obj = objc_msgSend(objc_msgSend(objc_getClass("DemoClass"), sel_registerName("alloc")), sel_registerName("init"));
}
//属性value的getter方法
static NSString * _I_DemoClass_value(DemoClass * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_DemoClass$_value)); }
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool); //属性value的setter方法
static void _I_DemoClass_setValue_(DemoClass * self, SEL _cmd, NSString *value) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct DemoClass, _value), (id)value, 0, 1); }

我们側重来看看类相应的结构体

struct DemoClass_IMPL {
struct NSObject_IMPL NSObject_IVARS;
NSString *_value;
};
//我们依次查找不清楚的定义
struct NSObject_IMPL {
Class isa;
};
typedef struct objc_class *Class;
struct objc_class {
Class isa ;
};

能够看到,OC类实际是依照下面方式来存储对象的

  • isa指针。指向objc_class类型的结构体。这个结构体中存储了方法的列表等类相关的信息,由于objc_msgSend中。发给对象的实际是一个字符串。运行时就是通过isa找到类对象。然后通过字符串找到方法的实际运行的。
  • ivar。属性背后的存储对象,到这里也能看出来一个普通的属性就是ivar+getter+setter.

也就是说。仅仅要有isa指针。指向一个类对象,那么这个结构就能处理OC的消息机制。也就能当成OC的对象来用。


Block的本质

我们改动DemoClass.m中的内容例如以下

typedef void(^VoidBlock)(void);
@implementation DemoClass - (void)demoFunction{
NSInteger variable = 10;
VoidBlock temp = ^{
NSLog(@"%ld",variable);
};
temp();
}
@end

然后。又一次用clang转换为C++代码。有关这段代码的内容例如以下:

struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
struct __DemoClass__demoFunction_block_impl_0 {
struct __block_impl impl;
struct __DemoClass__demoFunction_block_desc_0* Desc;
NSInteger variable;
__DemoClass__demoFunction_block_impl_0(void *fp, struct __DemoClass__demoFunction_block_desc_0 *desc, NSInteger _variable, int flags=0) : variable(_variable) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __DemoClass__demoFunction_block_func_0(struct __DemoClass__demoFunction_block_impl_0 *__cself) {
NSInteger variable = __cself->variable; // bound by copy NSLog((NSString *)&__NSConstantStringImpl__var_folders_hj_392p68d55td2kdxrbd9h15g40000gn_T_Test_c7592d_mi_0,variable);
} static struct __DemoClass__demoFunction_block_desc_0 {
size_t reserved;
size_t Block_size;
} __DemoClass__demoFunction_block_desc_0_DATA = { 0, sizeof(struct __DemoClass__demoFunction_block_impl_0)}; static void _I_DemoClass_demoFunction(DemoClass * self, SEL _cmd) {
NSInteger variable = 10;
VoidBlock temp = ((void (*)())&__DemoClass__demoFunction_block_impl_0((void *)__DemoClass__demoFunction_block_func_0, &__DemoClass__demoFunction_block_desc_0_DATA, variable));
((void (*)(__block_impl *))((__block_impl *)temp)->FuncPtr)((__block_impl *) temp);
}

我们还是以方法作为切入点,看俺详细是怎么实现的。

_I_DemoClass_demoFunctionDemoFunction转换后的方法。我们去掉一些强制转化代码,这样看起来更清楚

static void _I_DemoClass_demoFunction(DemoClass * self, SEL _cmd) {
NSInteger variable = 10;
VoidBlock temp = &__DemoClass__demoFunction_block_impl_0(__DemoClass__demoFunction_block_func_0, &__DemoClass__demoFunction_block_desc_0_DATA, variable));
(temp->FuncPtr)(temp);
}

从上至下,三行的左右依次是

  • 初始化一个variable(也就是block捕获的变量)
  • 调用结构体__DemoClass__demoFunction_block_impl_0的构造函数来新建一个结构体,而且把地址赋值给temp变量(也就是初始化一个block)
  • 通过调用temp变量内的函数指针(C的函数指针)来运行实际的函数。

通过这些分析,我们知道了Block的大致实现

block背后的内存模型实际上是一个结构体。这个结构体会存储一个函数指针来指向block的实际运行代码。

接着,我们来深入的研究下block背后的结构体。也就是这个结构体__DemoClass__demoFunction_block_impl_0:

struct __block_impl {
void *isa; //和上文提到的OC对象isa一样,指向的类对象,用来找到方法的实现
int Flags; //标识位
int Reserved; //保留
void *FuncPtr; //Block相应的函数指针
}; struct __DemoClass__demoFunction_block_impl_0 {
//结构体的通用存储结构
struct __block_impl impl;
//本结构体的描写叙述信息
struct __DemoClass__demoFunction_block_desc_0* Desc;
//捕获的外部变量
NSInteger variable;
//构造函数(也就是初始化函数,用来在创建结构体实例的时候。进行必要的初始化工作)
struct __DemoClass__demoFunction_block_impl_0 {
struct __block_impl impl;
struct __DemoClass__demoFunction_block_desc_0* Desc;
NSInteger variable;
__DemoClass__demoFunction_block_impl_0(void *fp,
struct __DemoClass__demoFunction_block_desc_0 *desc,
NSInteger _variable,
int flags=0) : variable(_variable) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};

我们在回头看看block初始化那句代码

//OC
VoidBlock temp = ^{
NSLog(@"%ld",variable);
};
//C++
VoidBlock temp = &__DemoClass__demoFunction_block_impl_0(__DemoClass__demoFunction_block_func_0,
&__DemoClass__demoFunction_block_desc_0_DATA,
variable));

在相应之前代码块的构造函数,我们能够清楚的看到,在初始化的时候三个參数依次是

  • 函数指针__DemoClass__demoFunction_block_func_0
  • block的描写叙述结构体(全局静态结构体)__DemoClass__demoFunction_block_desc_0_DATA
  • 捕获的变量variable

接着。我们来看看block背后的C函数__DemoClass__demoFunction_block_func_0

static void __DemoClass__demoFunction_block_func_0(struct __DemoClass__demoFunction_block_impl_0 *__cself) {
NSInteger variable = __cself->variable; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_hj_392p68d55td2kdxrbd9h15g40000gn_T_DemoClass_c7592d_mi_0,variable);
}

Tips:

内存中存储区域可分为下面几个区域:

  • TEXT 代码区
  • DATA 数据区
  • Stack 栈区
  • HEAP 堆区

上文的字符串@”%ld”。相应C++代码是)&__NSConstantStringImpl__var_folders_hj_392p68d55td2kdxrbd9h15g40000gn_T_DemoClass_c7592d_mi_0,是存储在数据区的。

这样即使程序中有多个@”%ld”,也不会创建多个实例。

能够看到。这个C函数的參数是__DemoClass__demoFunction_block_impl_0,也就是一个block类型。然后在方法体内部,使用这个block类型的參数。

最后,我们分析下block的描写叙述信息。也就是这段代码

static struct __DemoClass__demoFunction_block_desc_0 {
size_t reserved;
size_t Block_size;
} __DemoClass__demoFunction_block_desc_0_DATA = { 0, sizeof(struct __DemoClass__demoFunction_block_impl_0)};

这段代码不难理解,就是声明一个描写叙述信息的结构体,然后初始化这个结构体类型的全局静态变量。

分析到这里,上面代码的大多数内容我们都理解了,可是有一点我们还没有搞清楚。就是isa指向的内容_NSConcreteStackBlock

impl.isa = &_NSConcreteStackBlock;

可是,到这里我们知道了为什么Block能够当作OC对象来用的原因:就是这个指向类对象的isa指针。


Block的类型

上文提到了_NSConcreteStackBlock是Block一种。block一共同拥有三种类型

  • NSConcreteStackBlock 栈上分配,作用域结束后自己主动释放
  • NSConcreteGlobalBlock 全局分配,相似全局变量。存储在数据段。内存中仅仅有一份
  • NSConcreteHeapBlock 堆上分配

我们仍然尝试用Clang转换的方式,来验证我们的理论。

将DemoClass.m内容修改动为

#import "DemoClass.h"

typedef void(^VoidBlock)(void);

@interface DemoClass()
@property (copy, nonatomic) VoidBlock heapBlock; @end
VoidBlock globalBlock = ^{}; @implementation DemoClass - (void)demoFunction{
VoidBlock stackBlock = ^{};
stackBlock();
_heapBlock = ^{};
} @end

然后。转成C++后,分别相应例如以下

全局globalBlock

impl.isa = &_NSConcreteGlobalBlock;

栈上stackBlock

impl.isa = &_NSConcreteStackBlock;

属性Block

impl.isa = &_NSConcreteStackBlock;

What the fuck! 怎么属性的block是栈类型的。难道不该是堆类型的吗?

到这里。C/C++层面的代码已经无法满足我们的需求了。我们试着把代码转成汇编。一探到底:

方便分析属性block到底是怎么实现的。我们改动.m文件

#import "DemoClass.h"
typedef void(^VoidBlock)(void);
@interface DemoClass()
@property (copy, nonatomic) VoidBlock heapBlock;
@end
@implementation DemoClass
- (void)demoFunction{
_heapBlock = ^{};
}
@end

转换成汇编后。在方法demoFunction部分,我们能看到相似汇编代码

bl  _objc_retainBlock
adrp x8, _OBJC_IVAR_$_DemoClass._heapBlock@PAGE
add x8, x8, _OBJC_IVAR_$_DemoClass._heapBlock@PAGEOFF
.loc 1 0 0 ; /Users/hl/Desktop/OCTest/OCTest/DemoClass.m:0:0
ldr x1, [sp, #8]
.loc 1 21 5 ; /Users/hl/Desktop/OCTest/OCTest/DemoClass.m:21:5
ldrsw x8, [x8]
add x8, x1, x8
.loc 1 21 16 is_stmt 0 ; /Users/hl/Desktop/OCTest/OCTest/DemoClass.m:21:16
ldr x1, [x8]
str x0, [x8]
.loc 1 21 16 discriminator 1 ; /Users/hl/Desktop/OCTest/OCTest/DemoClass.m:21:16
mov x0, x1
bl _objc_release

也就是说,在方法返回之前,依次调用了

_objc_retainBlock
_objc_release

那么,_objc_retainBlock就是block从栈到堆的黑魔法。

我们通过Runtime的源代码来分析这种方法的实现:

id objc_retainBlock(id x) {
return (id)_Block_copy(x);
} // Create a heap based copy of a Block or simply add a reference to an existing one.
// This must be paired with Block_release to recover memory, even when running
// under Objective-C Garbage Collection.
BLOCK_EXPORT void *_Block_copy(const void *aBlock)
__OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_2);

到这里我们就清楚了,编译器为我们自己主动插入了_objc_retainBlock,而这个函数会把栈上的block复制到堆上。

Tips: 通常在写属性的时候,block都会声明为copy。这是显式的表示,即使block是栈上的,也会复制到堆上。事实上在赋值的时候,编译器已经自己主动帮我们做了这些,所以事实上使用strong也能够。

那么,一个暂时变量的block会被复制到堆上么?

改动demoFunction:

- (void)demoFunction{
VoidBlock stackBlock = ^{};
}

继续查看汇编:

Ltmp7:
.loc 1 23 15 prologue_end ; /Users/hl/Desktop/OCTest/OCTest/Test.m:23:15
mov x0, x8
bl _objc_retainBlock
mov x8, #0
add x1, sp, #8 ; =8
str x0, [sp, #8]
.loc 1 24 1 ; /Users/hl/Desktop/OCTest/OCTest/Test.m:24:1
mov x0, x1
mov x1, x8
bl _objc_storeStrong
ldp x29, x30, [sp, #32] ; 8-byte Folded Reload
add sp, sp, #48 ; =48
ret

我们仍然看到了_objc_retainBlock,也就是说即使是一个在函数中的block,在ARC开启的情况下,仍然会复制到堆上。


__block

通过之前的解说,我们知道了block怎样捕获外部变量,也知道了block的几种类型。那么block怎样改动外部变量呢?

block是不能够直接改动外部变量的,比方

NSInteger variable = 0;
_heapBlock = ^{
variable = 1;
};

直接这么写,编译器是不会通过的,想想也非常easy。由于变量可能在block运行之前就被释放掉了,直接这么赋值会导致野指针。

在OC层面。我们能够通过添加__block关键字。那么加了这个关键字后,实际的C++层面代码是什么样的呢?

- (void)demoFunction{
__block NSInteger variable = 0;
VoidBlock stackBlock = ^{
variable = 1;
};
}

在转换成C++代码后,例如以下:

static void _I_DemoClass_demoFunction(DemoClass * self, SEL _cmd) {
__Block_byref_variable_0 variable = {0,&variable, 0, sizeof(__Block_byref_variable_0), 0};
VoidBlock stackBlock = &__DemoClass__demoFunction_block_impl_0(( __DemoClass__demoFunction_block_func_0,
&__DemoClass__demoFunction_block_desc_0_DATA,
(__Block_byref_variable_0 *)&variable,
570425344);
}

能够看到。__block NSInteger variable = 0转换成了一个结构体

__Block_byref_variable_0 variable = {0,&variable, 0, sizeof(__Block_byref_variable_0), 0};

这个结构体定义例如以下:

struct __Block_byref_variable_0 {
void *__isa;
__Block_byref_variable_0 *__forwarding;
int __flags;
int __size;
NSInteger variable; //这个是要改动的变量
};

通过初始化我们能够看到

  • __isa指向0
  • __forwarding 指向__Block_byref_variable_0自身
  • __flags为0
  • __size就是结构题的大小
  • variable是我们定义的原始值0

到这里。我们有一点疑惑

  • 为什么要存在一个__forwarding来指向自身呢?

我们来看看block的方法体。也就是这部分

^{
variable = 1;
}

转换成C++后:

static void __DemoClass__demoFunction_block_func_0(struct __DemoClass__demoFunction_block_impl_0 *__cself) {
__Block_byref_variable_0 *variable = __cself->variable; // bound by ref
variable->__forwarding->variable) = 1;
}

也就是说__forwarding存在的意义就是通过它来訪问到变量的地址,假设这个指针一直指向自身。那么它也就没有存在的意义,也就是在将来的某一个时间点,它一定会指向另外一个数据结构。

我们在上文中讲到,ARC开启的时候,栈上的block会被复制到堆上。

在没有复制之前:

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvSGVsbG9fSHdj/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" width="300">

复制之后

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvSGVsbG9fSHdj/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" width="600">

这样,我们就清楚原因了:

即使发生了复制,仅仅要改动__forwarding的指向。我们就能够保证栈上和堆上的block都訪问同一个对象。


Block对对象的捕获

到这里,我们分析的block都是捕获一个外部值。并非对象。值和对象最大的差别就是对象有生命周期,对象我们须要考虑引用计数。

改动DemoFunction

- (void)demoFunction{
NSObject * obj = [[NSObject alloc] init];
VoidBlock stackBlock = ^{
[obj description];
};
stackBlock();
}

再转换成C++后,我们对照之前捕获NSInteger,发现多了两个生命周期管理函数

static void __DemoClass__demoFunction_block_copy_0(struct __DemoClass__demoFunction_block_impl_0*dst, struct __DemoClass__demoFunction_block_impl_0*src)
{
_Block_object_assign((void*)&dst->obj, (void*)src->obj, 3/*BLOCK_FIELD_IS_OBJECT*/);
} static void __DemoClass__demoFunction_block_dispose_0(struct __DemoClass__demoFunction_block_impl_0*src)
{
_Block_object_dispose((void*)src->obj, 3/*BLOCK_FIELD_IS_OBJECT*/);
}

我们再查看下Block_object_assignBlock_object_dispose的定义

// Used by the compiler. Do not call this function yourself.
BLOCK_EXPORT void _Block_object_assign(void *, const void *, const int);
// Used by the compiler. Do not call this function yourself.
BLOCK_EXPORT void _Block_object_dispose(const void *, const int);

也就是说,编译器通过这两个函数来管理Block捕获对象的生命周期。当中

  • _Block_object_assign相当于ARC中的reatain,在block从栈上复制到堆上的时候调用
  • _Block_object_dispose相当于ARC中的release,在block堆上废弃的时候调用

总结

  • block在C语言层面就是结构体,结构体存储了函数指针和捕获的变量列表
  • block分为全局,栈上。堆上三种。ARC开启的时候。会自己主动把栈上的block复制到堆上
  • __block变量在C语言层面也是一个结构体
  • block捕获对象的时候会添加对象的引用计数。

本文的Github地址:LeoMobileDeveloper,如有问题欢迎issue。也能够在我的CSDN博客下评论,我会及时更正。

Objective C block背后的黑魔法的更多相关文章

  1. words2

    餐具:coffee pot 咖啡壶coffee cup 咖啡杯paper towel 纸巾napkin 餐巾table cloth 桌布tea -pot 茶壶tea set 茶具tea tray 茶盘 ...

  2. Automake

    Automake是用来根据Makefile.am生成Makefile.in的工具 标准Makefile目标 'make all' Build programs, libraries, document ...

  3. iOS开发神器InjectionIII

    最近发现了一款适用于iOS开发的神器,希望可以和大家一起分享,同时自己也将有用的东西记录下来,没错就是InjectionIII! 先看一下使用流程: 1.在MAC的App Store里面搜索下载这个工 ...

  4. Xcode辅助工具之热重载插件利器

    该博客首发于github.io 2018-06-13 13:43:44 文章最新修改于: 2019-03-31 13:47:20 昨天刚刚看完iOSTips微信公众号推送的文章, Injection: ...

  5. Objective-C( block的使用)

    block block用来保存一段代码 block的标志:^ block跟函数很像:可以保存代码.有返回值.有形参.调用方式跟调用方法一样 block内部可以访问外面的变量 默认情况下,block内部 ...

  6. 初学Objective - C语法之代码块(block)

    一.block声明 1.无参数,无返回值: void (^sayHi)(); 2.有参数,有返回值: NSInteger (^operateOfValue)(NSInteger num); block ...

  7. 黑幕背后的Autorelease

    http://blog.sunnyxx.com/2014/10/15/behind-autorelease/ 我是前言 Autorelease机制是iOS开发者管理对象内存的好伙伴,MRC中,调用[o ...

  8. 前端精选文摘:BFC 神奇背后的原理

    BFC 已经是一个耳听熟闻的词语了,网上有许多关于 BFC 的文章,介绍了如何触发 BFC 以及 BFC 的一些用处(如清浮动,防止 margin 重叠等).虽然我知道如何利用 BFC 解决这些问题, ...

  9. Objective的字符串拼接 似乎没有Swift方便,但也可以制做一些较为方便的写法

    NSString *str1 = @"字符串1"; NSString *str2 = @"字符串2"; //在同样条件下,Objective的字符串拼接 往往只 ...

随机推荐

  1. niubi-job:一个分布式的任务调度框架设计原理以及实现

    niubi-job的框架设计是非常简单实用的一套设计,去掉了很多其它调度框架中,锦上添花但并非必须的组件,例如MQ消息通讯组件(kafka等).它的框架设计核心思想是,让每一个jar包可以相对之间独立 ...

  2. Django底层原理简介与安装

    Django环境目录搭建一栏: 利用wsgiref模块封装好的socket搭建服务端: #利用wsgiref模块封装好的socket演示操作(例如accept\recv) #也可以实现socket服务 ...

  3. js的编码函数

    js对文字进行编码,涉及3个函数:escape,encodeURI,encodeURIComponent,相应3个解码函数:unescape,decodeURI,decodeURIComponent ...

  4. Swift 3:新的访问控制fileprivate和open

    在swift 3中新增加了两种访问控制权限 fileprivate和 open.下面将对这两种新增访问控制做详细介绍. fileprivate 在原有的swift中的 private其实并不是真正的私 ...

  5. kb-07-RMQ线段树--07(动态规划)

    RMQ是一类解决区间最值查询的算法的通称:.一共有四类:在代码中有说明: 下面是ST算法,就是动态规划做法: 来看一下ST算法是怎么实现的(以最大值为例): 首先是预处理,用一个DP解决.设a是要求区 ...

  6. 算法复习——状压dp

    状压dp的核心在于,当我们不能通过表现单一的对象的状态来达到dp的最优子结构和无后效性原则时,我们可能保存多个元素的有关信息··这时候利用2进制的01来表示每个元素相关状态并将其压缩成2进制数就可以达 ...

  7. 如何用1个小时了解JSON

    W3school ↑↑↑学这个,1个小时够了.下面是节选: 代码例子1: <html> <body> <h2>在 JavaScript 中创建 JSON 对象< ...

  8. Python 使用cx_freeze 生成exe文件

    在python中比较常用的python转exe方法有三种,分别是cx_freeze,py2exe,PyInstaller.py2exe恐怕是三者里面知名度最高的一个,但是同时相对来说它的打包质量恐怕也 ...

  9. UML系列,使用UML实现GOF Design patterns,常用模式类图解读

    1.单例:Singleton, DirectedAssociation

  10. 《Linux命令行与shell脚本编程大全 第3版》

    第一部分 Linux 命令行 第1章  初识Linux she1.1   什么是Linux 21.1.1 深入探究Linux 内核 31.1.2 GNU 工具 61.1.3 Linux 桌面环境 81 ...