当前位置: 首页 > news >正文

【iOS】——Blocks

文章目录

  • 前言
  • 一、Blocks概要
    • 1.什么是Blocks
  • 二、Block模式
  • 1.block语法
    • 2.block类型变量
    • 3.截获自动变量值
    • 4._Block修饰符
    • 5.截获的自动变量
  • 三、Blocks的实现
    • 1.Block的实质
    • 2.截获自动变量值
    • 3._Block说明符
    • 4.Block存储域


前言

一、Blocks概要

1.什么是Blocks

Blocks是C语言的扩充功能,并且可以用一句话来表示Blocks的扩充功能:Blocks是带有自动变量(局部变量)的匿名函数。

匿名函数:Blocks是一种匿名函数,也就是没有特定名称的函数。它们可以在需要的地方定义和使用,而无需提前声明或命名。这使得Blocks非常灵活,可以作为参数传递给其他函数或方法,或者作为变量保存和执行。
自动变量(局部变量):Blocks可以捕获其定义范围内的自动变量(也称为局部变量)。当一个Block被定义时,它会在其内部创建一个副本,用于在Block执行时访问该变量的值。这意味着即使变量超出了其定义范围,Block仍然可以访问和使用该变量的值。
闭包:由于Blocks可以捕获自动变量,它们形成了一个封闭的环境,即闭包。这意味着Block可以在其定义范围之外访问和使用自动变量。当Block被传递到其他函数或方法时,它会携带其封闭环境中的自动变量,以便在执行时可以访问这些变量的值。

二、Block模式

1.block语法

完整形式的Block语法和一般的C语言函数相比,只有两点不同。第一点是没有函数名,因为它是匿名函数。第二点是返回值类型前带有“^”(插入记号)记号。下面是Block语法格式:

^ 返回值类型 参数列表 表达式

^int (int count){return count + 1;}

Block语法可以省略返回值类型

^(int count){return count + 1;}

如果不使用参数,Block语法还可以省略参数列表

^void (void){printf("Blocks\n");}

2.block类型变量

在C语言中可以将函数地址赋值给函数指针的类型变量中,同样在Block语法下,可将Block语法赋值给Block类型的变量中。示例如下:

int (^blk)(int) = ^(int count) {return count + 1;};
int (^blk_t)(int);
blk_t = blk;

Block类型变量声明
返回参数 (^变量名称)(接受参数);

同时使用typedef重命名Block类型,因为Block类型变量和平时的使用类型相同,为了方便我们使用,我们通常都是用typedef来重命名Block。

typedef 返回参数 (^该Block的名称)(接收参数)

此时就可以用该Block的名称代表该Block了。

typedef int (^blk_t)(int)blk_t blk = ^(int count) {return count + 1;};

3.截获自动变量值

Block截获自动变量值就是说在定义Block的时候,其之前的变量都会被该Block所截获,该Block后再改变变量的值然后再调用,其Block中所捕获的值不会受到改变。

int main(int argc, const char * argv[]) {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;
}

在这里插入图片描述

此时我们发现,在定义Block之后的这val,fmt两个变量被改变了,但是再次调用该Block还是定义Block之前的值,这就是捕获。我的理解就是定义Block之前的自动变量都会被该Block捕获,在定义Block后改变变量其值还是该Block之前的定义的值,不会收到影响。

4._Block修饰符

自动变量截获只能保存执行Block语法瞬间的值并且保存后就不能修改这个值,如果要修改这个自动变量的值就需要在在声明时给这个自动变量附加_Block说明符。

int main(int argc, const char * argv[]) {__block int val = 10;__block const char *fmt = "val = %d\n";void (^blk)(void) = ^{printf(fmt, val);val = 20;};val = 2;fmt = "These values were changed. val = %d\n";blk();printf(fmt, val);return 0;
}

在这里插入图片描述

5.截获的自动变量

前面提到向Block截获的自动变量赋值会报错,如果调用变更该对象的方法则不会产生编译错误。
用OC中的NSMutableArray来说,截获它其实就是截获该类对象用的结构体实例指针,就是说你只要不改变其指针的指向和其指针的值,他就不会出错。

int main(int argc, const char * argv[]) {id array = [[NSMutableArray alloc] init];void (^blk)(void) = ^{id obj = [[NSObject alloc] init];[array addObject:obj];};blk();return 0;
}

这里因为你并没有改变其array指针的指向和值,而只是在其地址后边新加了一个值,所以他就不会报错。

int main(int argc, const char * argv[]) {id array = [[NSMutableArray alloc] init];void (^blk)(void) = ^{array = [[NSMutableArray alloc] init];};blk();return 0;
}

但是你这样写就改变了array的初始地址,所以他就会报错。

另外,在使用C语言数组是必须小心使用其指针。

int main(int argc, const char * argv[]) {const char text[] = "hello";  //这里是定义一个数组void (^blk)(void) = ^{printf("%c\n", text[2]);};blk();return 0;
}

这里你看似没有什么错误,但是会出现下面的编译错误。
在这里插入图片描述
因为在现在的Blocks中,截获自动变量的方法并没有实现对C语言数组的截获,这时我们可以使用指针来解决该问题。

int main(int argc, const char * argv[]) {const char *text = "hello";  //这里是使用指针引用void (^blk)(void) = ^{printf("%c\n", text[2]);};blk();return 0;
}

三、Blocks的实现

1.Block的实质

Block实质是Objective-C对闭包的对象实现,简单说来,Block就是对象。

下面分别从表层到底层来分析一下:

表层分析Block的实质:它是一个类型

Block是一种类型,一旦使用了Block就相当于生成了可赋值给Block类型变量的值。举个例子:

int (^blk)(int) = ^(int count){return count + 1;
};

等号左侧的代码表示了这个Block的类型:它接受一个int参数,返回一个int值。
等号右侧的代码是这个Block的值:它是等号左侧定义的block类型的一种实现。

如果我们在项目中经常使用某种相同类型的block,我们可以用typedef来抽象出这种类型的Block:

typedef int(^AddOneBlock)(int count);AddOneBlock block = ^(int count){return count + 1;//具体实现代码
};

这样一来,block的赋值和传递就变得相对方便一些了, 因为block的类型已经抽象了出来。

深层分析Block的实质:它是Objective-C对象

Block其实就是Objective-C对象,因为它的结构体中含有isa指针。

下面将Objective-C的代码转化为C++的代码来看一下block的实现。

int main()
{void (^blk)(void) = ^{printf("Block\n");};return 0;
}

通过clang编辑器转换为c++:

struct __block_impl {void *isa;int Flags;int Reserved;void *FuncPtr;
};//block结构体
struct __main_block_impl_0 {struct __block_impl impl;struct __main_block_desc_0* Desc;//Block构造函数__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {impl.isa = &_NSConcreteStackBlock;//isa指针impl.Flags = flags;impl.FuncPtr = fp;Desc = desc;}};//将来被调用的block内部的代码:block值被转换为C的函数代码
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {printf("Block\n");
}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)};//main 函数
int main()
{void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));return 0;
}

首先我们看一下从原来的block值(OC代码块)转化而来的C++代码:

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {printf("Block\n");
}

这里,*__cself 是指向Block的值的指针,也就相当于是Block的值它自己(相当于C++里的this,OC里的self)。
而且很容易看出来,cself 是指向mainblockimpl0结构体实现的指针。 结合上句话,也就是说Block结构体就是mainblockimpl0结构体。Block的值就是通过mainblockimpl_0构造出来的。


下面来看一下这个结构体的声明:

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;}
};

可以看出,_mainblockimpl0结构体有三个部分:

第一个是成员变量impl,它是实际的函数指针,它指向_mainblockfunc0。来看一下它的结构体的声明:

struct __block_impl {void *isa;int Flags;int Reserved;  //今后版本升级所需的区域void *FuncPtr; //函数指针
};

第二个是成员变量是指向_mainblockdesc0结构体的Desc指针,是用于描述当前这个block的附加信息的,包括结构体的大小等等信息

static struct __main_block_desc_0 {size_t reserved;  //今后升级版本所需区域size_t Block_size;//block的大小} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};

第三个部分是mainblockimpl0结构体的构造函数,mainblockimpl0 就是该 block 的实现

__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;}

在这个结构体的构造函数里,isa指针保持这所属类的结构体的实例的指针。_mainblockimlp0结构体就相当于Objective-C类对象的结构体,这里的_NSConcreteStackBlock相当于Block的结构体实例,也就是说block其实就是Objective-C对于闭包的对象实现。

2.截获自动变量值

使用Block的时候,不仅可以使用其内部的参数,还可以使用Block外部的局部变量。而一旦在Block内部使用了其外部变量,这些变量就会被Block保存。
通过先前的例子我们知道
Block可以截获局部变量。
修改Block外部的局部变量,Block内部被截获的局部变量不受影响。
修改Block内部到局部变量,编译不通过。

通过C++的代码来看一下Block在截获变量的时候都发生了什么:

struct __main_block_impl_0 {struct __block_impl impl;struct __main_block_desc_0* Desc;const char *fmt;  //被添加int val;          //被添加__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, const char *_fmt, int _val, int flags=0) : fmt(_fmt), 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) {const char *fmt = __cself->fmt; // bound by copyint val = __cself->val; // bound by copyprintf(fmt,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 dmy = 256;int val = 10;const char *fmt = "var = %d\n";void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, fmt, val));val = 2;fmt = "These values were changed. var = %d\n";((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);return 0;
}

单独抽取_mainblockimpl0来看一下:

struct __main_block_impl_0 {struct __block_impl impl;struct __main_block_desc_0* Desc;const char *fmt; //截获的自动变量int val;         //截获的自动变量__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, const char *_fmt, int _val, int flags=0) : fmt(_fmt), val(_val) {impl.isa = &_NSConcreteStackBlock;impl.Flags = flags;impl.FuncPtr = fp;Desc = desc;}
};

我们可以看到,在block内部语法表达式中使用的自动变量(fmt,val)被作为成员变量追加到了_mainblockimpl0结构体中(注意:block没有使用的自动变量不会被追加,如dmy变量)。
在初始化block结构体实例时(请看mainblockimpl0的构造函数),还需要截获的自动变量fmt和val来初始化mainblockimpl0结构体实例,因为增加了被截获的自动变量,block的体积会变大。

再来看一下函数体的代码:

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {const char *fmt = __cself->fmt; // bound by copyint val = __cself->val; // bound by copyprintf(fmt,val);
}

从这里看就更明显了:fmt,var都是从__cself里面获取的,更说明了二者是属于block的。而且从注释来看(注释是由clang自动生成的),这两个变量是值传递,而不是指针传递,也就是说Block仅仅截获自动变量的值,所以这就解释了即使改变了外部的自动变量的值,也不会影响Block内部的值。

既然我们无法在Block中改变外部变量的值,所以也就没有必要在Block内部改变变量的值了,因为Block内部和外部的变量实际上是两种不同的存在:前者是Block内部结构体的一个成员变量,后者是在栈区里的临时变量。

现在我们知道:被截获的自动变量的值是无法直接修改的,但是有两个方法可以解决这个问题:

改变存储于特殊存储区域的变量。
通过__block修饰符来改变。

3._Block说明符

前面提到Block中使用自动变量后,在Blockj的结构体实例中重写该自动变量也不会改变原先截获的自动变量的值。不过这样的话就不能在Block中保存值了,不是很方便。因此下面提供两种方法来解决。
第一种方法是:C语言中有一个变量允许Block改写值,即静态变量,也就是用static修饰的变量。但是他并不太好,因为变量作用域结束的同时,原来的自动变量被废弃,因此Block 中超过变量作用域而存在的变量同静态变量一样,将不能通过指针访问原来的自动变量。
第二种方法是:使用_Block说明符。更准确的表达方式是“_Block存储域类说明符”。C语言中有以下存储域类说明符:

  • typedef
  • extern
  • static
  • auto
  • register

使用__block修饰符,它类似于static、auto、和register说明符,用于指定将变量值设置到哪个存储域中。

下面是个例子:

int main(int argc, const char * argv[]) {__block int val = 10;void (^blk)(void) = ^{val = 1;};return 0;
}

其源代码如下:

//__block说明符修饰后的变量的结构体
struct __Block_byref_val_0 {void *__isa;  //指向所属类的指针__Block_byref_val_0 *__forwarding;  //指向自己的内存地址的指针int __flags;  //标志性参数,暂时没用到所以默认为0int __size;  //该结构体所占用的大小int val;  //该结构体存储的值,即原变量的赋的值
};//block本体
struct __main_block_impl_0 {struct __block_impl impl;  //block的主体struct __main block desc 0* Desc;  //存储该block的大小__Block_byref_val_0 *val;  //__block修饰的变量的值//构造函数__main_block_impl_0(void *fp, struct __main_block_desc 0 *desc, __Block_byrefval_0 *_val, int flags=0) : val(_val->__forwarding) {impl.isa = &_NSConcreteStackBlock;impl.Flags = flags;impl.FuncPtr = fp;Desc = desc;
};//封装的block逻辑,存储了block的代码块
static void __main_block_func_0(struct__main_block_impl_0 *_cself) {__Block_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) {//根据auto变量的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用_Block_object_assign(&dst->val, src->val, BLOCK_FIELD_IS_BYREF);
}static void __main_block_dispose_0(struct __main_block_imp1_0* src) {//自动释放引用的auto变量(相当于release)_Block_object_dispose(src->val, BLOCK_FIELD_IS_BYREF);
}static struct __main_block_desc_0 {unsigned long reserved;  //保留字段unsigned long Block_size;  //block大小void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);  //copy的函数指针,下面使用构造函数进行了初始化void (*dispose)(struct __main_block_impl_0*);  //dispose的函数指针,下面使用构造函数进行了初始化
}//构造函数,初始化保留字段、block大小及两个函数__main_block_desc_0_DATA = {0,sizeof(structmain_block_impl_0),__main_block_copy_O, __main_block_dispose_0
};
int main() {//之前的 __block int val = 10;变成了结构体实例struct __Block_byref_val_0 val = {0,  //isa指针&val,  //指向自身地址的指针0,  //标志变量sizeof(__Block_byref_val_0),  //block大小10  //该数据的值};blk = &__main_block_impl_0(
__main_block_func_0, &__main_block_desc_0_DATA, &val, 0x22000000);return 0;
}

通过源代码发现__block说明符修饰的变量变成了一个结构体,它是利用这个结构体来进行存储数据的:

struct __Block_byref_val_0 val = {0,  //isa指针&val,  //指向自身地址的指针0,  //标志变量sizeof(__Block_byref_val_0),  //block大小10  //该数据的值};

那么为什么会有成员变量__forwarding呢?
因为__block变量用结构体成员变量__forwarding可以实现无论__block变量配置在栈上还是堆上时都能够正确的访问__block变量。

4.Block存储域

Block转换为Block的结构体类型的自动变量,_Block变量转换为 block变量的结构体类型的自动变量。所谓结构体类型的自动变量就是栈上生成的该结构体的实例。

Block的所属类:

_NSConcreteStackBlock //栈
_NSConcreteGlobalBlock //全局(.data区)
_NSConcreteMallocBlock //堆

全局Block:

在记述全局变量的地方使用Block语法时,生成的Block为_NSConcreteGlobalBlock类对象。

void (^blk)(void) = ^{printf("Global Block\n");};int main(void) {······
}

其isa指针初始化如下:

impl.isa = &_NSConcreteGlobalBlock;

该类Block用结构体实例设置在程序的数据区域中。因为在使用全局变量的地方不能使用自动变量,所以不存在对自动变量进行截获。只在截获自动变量时,Block 用结构体实例截获的值才会根据执行时的状态变化。也就是说,全局Block不可以截获自动变量,否则其就不可以设置为全局Block。

也就是说,即使在函数内而不在记述广域变量的地方使用Block语法时,只要Block不截获自动变量,就可以将Block用结构体实例设置在程序的数据区域。以上的这些情况下,Block为_NSConcreteGlobalBlock类对象。即Block配置在应用程序的数据区域中。

栈Block:

其isa指针初始化如下:

impl.isa = &_NSConcreteStackBlock;

除上述的设置两种全局Block之外的Block语法生成的Block为_NSConcreteStackBlock类对象,且设置在栈上。

配置在全局变量上的Block,从变量作用域外也可以通过指针安全地使用。但设置在栈上的Block,如果其所属的变量作用域结束,该Block就被废弃。由于__block变量也配置在栈上,同样地,如果其所属的变量作用域结束,则该__block变量也会被废弃。我们也就无法再访问到该变量了。

为了解决无法访问被废弃变量这个问题,就出现了堆block,同时也出现了将Block和__block变量从栈上复制到堆上的方法。这样即使Block 语法记述的变量作用域结束,堆上的Block还可以继续存在。下面我们就来说说:

堆Block:

其isa指针初始化如下:

impl.isa = &_NSConcreteMallocBlock;

堆上的Block其实就是将栈上的Block复制过来而成的,有时我们在_block变量配置在堆上的状态下,也可以访问栈上的__block变量。在此情形下,只要栈上的结构体实例成员变量__forwarding指向堆上的结构体实例,那么不管是从栈上的__block 变量还是从堆上的__block 变量都能够正确访问。

在这里插入图片描述

当ARC有效时,大多数情形下编译器会恰当地进行判断,自动生成将Block从栈上复制到堆上的代码。这是为了防止其被废弃而导致我们访问错误。
下面这个例子就是堆Block,用它来说明:

typedef int (^blk_t)(int);blk_t func(int rate) {return ^(int count) {return rate * count;};
}

源代码:

blk_t func(int rate) {//因为ARC处于有效的状态,所以blk_t tmp实际上与附有__strong 修饰符的blk_t __strong tmp 相同blk_t tmp = &__func_block_impl_0(__func_block_func_0,&__func_block_desc_0_DATA, rate);//通过 objc4运行时库的runtime/objc-arrmm可知,objc_retainBlock函数实际上就是_Block_copy 函数tmp = objc_retainBlock(tmp);//等同于 tmp = _Block_copy(tmp);//最后将tmp放入自动释放池中进行返回return objc_autoreleaseReturnValue(tmp);
}    
/**将通过Block语法生成的Block,*即配置在栈上的Block用结构体实例*赋值给相当于Block类型的变量tmp中。*/
tmp = _Block_copy(tmp);
/**_Block_copy 函数*将栈上的Block复制到堆上。*复制后,将堆上的地址作为指针赋值给变量tmp。*/return objc_autoreleaseReturnValue(tmp);
/**将堆上的Block作为Objective-c对象*注册到autoreleasepool中,然后返回该对象。*/

通过上面的例子就可以知道将 Block 作为函数返回值返回时,编译器会自动生成复制到堆上的代码。

相关文章:

【iOS】——Blocks

文章目录 前言一、Blocks概要1.什么是Blocks 二、Block模式1.block语法2.block类型变量3.截获自动变量值4._Block修饰符5.截获的自动变量 三、Blocks的实现1.Block的实质2.截获自动变量值3._Block说明符4.Block存储域 前言 一、Blocks概要 1.什么是Blocks Blocks是C语言的扩…...

体验OceanBase OBD V2.5.0 组件内扩容和组件变更

背景 OBD 是OceanBase的命令行部署工具,在 obd V2.5.0 版本之前,其主要功能主要是部署各类组件,例如 oceanbase-ce,obproxy-ce,obagent 等。然而,它并不支持组件的变更操作以及组件内部的扩缩容调整。具体来说: 1、若…...

关于前端的学习

目录 前言: 1.初识HTML: 1.1超文本: 1.2标记语言: 2.关于html的基本框架: 3.HTML基本文字标签: 3.1.h标题标签: 3.3 文本内容: 3.4换行的和分割的: 3.5 特殊文字标签: 3.5.1表面上看着三对的结果呈现都是一样的: 3.5.2但是其背后的效果其实是不一样的: 3.6转义字符:…...

DataX脚本告别手动编写,用大模型或Java代码自动生成

在离线数仓开发中,DataX支持多种数据源,性能好抽取速度快。美中不足的是,对需要抽取的每一张表都需要写配置文件,这样很繁琐和耗时。可以用大模型提示词来节省这方面的工作量。ChatGPT等大模型并不能很智能提供一个完全正确的,需要提供一个模板让它参考。 方法1:用大模型…...

ASP.NET通过Appliaction和Session统计在人数和历史访问量

目录 背景: Appliaction: Session: 过程: 数据库: Application_Start: Session_Start: Session_End: Application_End: 背景: 事件何时激发Application_Start在调用当前应用…...

在基于全志V851se的TinyVision上手动构建 Linux 6.1 + Debian 12 镜像

构建 SyterKit 作为 Bootloader SyterKit 是一个纯裸机框架,用于 TinyVision 或者其他 v851se/v851s/v851s3/v853 等芯片的开发板,SyterKit 使用 CMake 作为构建系统构建,支持多种应用与多种外设驱动。同时 SyterKit 也具有启动引导的功能&a…...

使用jenkins-pipeline进行利用项目文件自动化部署到k8s上

Discard old builds:丢弃旧的构建,目的是管理存储空间、提升性能以及保持环境整洁 Do not allow concurrent builds: 禁止并发构建是指同一时间内只允许一个构建任务执行,避免多个构建同时运行可能带来的问题 Do not allow the pipeline to resume if the controller resta…...

unity发布安卓获取读取权限

一、Player Settings 设置 Player Settings>Player>Other Settings> Android > Write Permission > External (SDCard). 二、代码 using System.Collections; using System.Collections.Generic; using System.IO; using UnityEngine; using UnityEngine.Andr…...

VSCode下使用github初步

由于各种需要,现在需要统一将一些代码提交搞github,于是有了在VSCode下使用github的需求。之前只是简单的使用git clone,代码提交这些用的是其他源代码工具,于是得学习实操下,并做一记录以备后用。 安装 VSCode安装 …...

华为设备配置命令大全

目录 一、华为设备常用命令视图 二、返回命令和保存命令 三、设置设备名称 四、关闭泛洪信息 五、设置设备接口的IP地址和子网掩码 六、交换机的登录 6.1、设置Consile接口密码 6.2、设置Telent接口密码 七、VLAN配置 7.1、创建VLAN 7.2、进入vlan视图 7.3、把端口…...

详解基于快速排序算法的qsort的模拟实现

目录 1. 快速排序 1.1 快速排序理论分析 1.2 快速排序的模拟实现 2. qsort的模拟实现 2.1 qsort的理论分析 2.2 qsort的模拟实现 qsort函数是基于快速排序思想设计的可以针对任意数据类型的c语言函数。要对qsort进行模拟实现,首先就要理解快速排序。 1. 快…...

鸿蒙Harmony应用开发—ArkTS声明式开发(绘制组件:Polyline)

折线绘制组件。 说明: 该组件从API Version 7开始支持。后续版本如有新增内容,则采用上角标单独标记该内容的起始版本。 子组件 无 接口 Polyline(value?: {width?: string | number, height?: string | number}) 从API version 9开始&#xff0c…...

项目风险管理

项目风险管理 1 规划风险管理2 识别风险1.2 输出 3 实施定性风险分析3.1 输入3.2 输出 4 实施定量风险分析4.1 输入4.2 输出 5 规划风险应对5.1 输入5.2 输出 6 实施风俗应对6.1 输入6.2 输出 7 监督风险7.1 输入7.2 输出 项目风险是一种不确定的事件或条件,一旦发生…...

glib交叉编译

Glib交叉编译 逸一时,误一世。 —— 田所浩二「夏夜银梦」 交叉编译 GLib 涉及到在一个平台上生成能够在另一个平台上运行的目标文件。在这种情况下,我们将会在一台主机(通常是开发机器)上使用交叉编译工具链来构建 GLib 库&#…...

Android11实现能同时开多个录屏应用(或者共享屏幕或投屏时录屏)

1.概述 Android原生对MediaProjection的管理逻辑,是如果服务端已经保存有MediaProjection的实例,那么再次创建的时候,之前的MediaProjection实例就会被暂停,并且引用指向新的实例,也就导致了当开启后一个录屏应用时&a…...

音视频实战---音频重采样

1、使用swr_alloc()创建重采样实例 2、使用av_opt_set_int函数设置重采样输入输出参数 3、使用swr_init函数初始化重采样器 4、使用av_get_channel_layout_nb_channels函数计算输入源的通道数 5、给输入源分配内存空间–av_samples_alloc_array_and_samples 6、计算输出采…...

主存中存储单元地址的分配

主存中存储单元地址的分配 为什么写这篇文章? 因为我看书中这部分时,看到下面的计算一下子没反应过来: 知识回顾(第1章) 计算机系统中,字节是最小的可寻址的存储单位,通常由8个比特(bit&…...

Python和R的区别是什么,Python与R的应用场景是什么?

如果你这么问,那么你可能正站在数据科学的起点。对于志在成为数据专业人员的你来说,学习编程是无疑的。我想行你早就听过Python 与R的比较之声,并在选择中感到困惑。在此,我想说,也算是一种安慰吧:对于语言…...

azure databricks 常用的JDBC连接

做个笔记常用的spark-jdbc连接 1、mysql 的连接 def query_mysql(database,sqlstr):jdbcUsernamejdbcHostname " "jdbcDatabase ""jdbcPort 3306mysql_df spark.read \.format("jdbc") \.option("driver","com.mysql.cj.jdb…...

功能齐全的免费 IDE Visual Studio 2022 社区版

面向学生、开放源代码和单个开发人员的功能齐全的免费 IDE 下载地址 Visual Studio 2022 社区版 - 下载最新的免费版本 Visual Studio 2022 Community Edition – Download Latest Free Version 准备安装 选择需要安装的程序 安装进行中 使用C学习程序设计相关知识并培养编程…...

web vue 项目 Docker化部署

Web 项目 Docker 化部署详细教程 目录 Web 项目 Docker 化部署概述Dockerfile 详解 构建阶段生产阶段 构建和运行 Docker 镜像 1. Web 项目 Docker 化部署概述 Docker 化部署的主要步骤分为以下几个阶段: 构建阶段(Build Stage)&#xff1a…...

java_网络服务相关_gateway_nacos_feign区别联系

1. spring-cloud-starter-gateway 作用:作为微服务架构的网关,统一入口,处理所有外部请求。 核心能力: 路由转发(基于路径、服务名等)过滤器(鉴权、限流、日志、Header 处理)支持负…...

MongoDB学习和应用(高效的非关系型数据库)

一丶 MongoDB简介 对于社交类软件的功能,我们需要对它的功能特点进行分析: 数据量会随着用户数增大而增大读多写少价值较低非好友看不到其动态信息地理位置的查询… 针对以上特点进行分析各大存储工具: mysql:关系型数据库&am…...

Python实现prophet 理论及参数优化

文章目录 Prophet理论及模型参数介绍Python代码完整实现prophet 添加外部数据进行模型优化 之前初步学习prophet的时候,写过一篇简单实现,后期随着对该模型的深入研究,本次记录涉及到prophet 的公式以及参数调优,从公式可以更直观…...

论文解读:交大港大上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架(一)

宇树机器人多姿态起立控制强化学习框架论文解析 论文解读:交大&港大&上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架(一) 论文解读:交大&港大&上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化…...

Unsafe Fileupload篇补充-木马的详细教程与木马分享(中国蚁剑方式)

在之前的皮卡丘靶场第九期Unsafe Fileupload篇中我们学习了木马的原理并且学了一个简单的木马文件 本期内容是为了更好的为大家解释木马(服务器方面的)的原理,连接,以及各种木马及连接工具的分享 文件木马:https://w…...

华硕a豆14 Air香氛版,美学与科技的馨香融合

在快节奏的现代生活中,我们渴望一个能激发创想、愉悦感官的工作与生活伙伴,它不仅是冰冷的科技工具,更能触动我们内心深处的细腻情感。正是在这样的期许下,华硕a豆14 Air香氛版翩然而至,它以一种前所未有的方式&#x…...

Linux离线(zip方式)安装docker

目录 基础信息操作系统信息docker信息 安装实例安装步骤示例 遇到的问题问题1:修改默认工作路径启动失败问题2 找不到对应组 基础信息 操作系统信息 OS版本:CentOS 7 64位 内核版本:3.10.0 相关命令: uname -rcat /etc/os-rele…...

A2A JS SDK 完整教程:快速入门指南

目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库&#xff…...

GO协程(Goroutine)问题总结

在使用Go语言来编写代码时,遇到的一些问题总结一下 [参考文档]:https://www.topgoer.com/%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/goroutine.html 1. main()函数默认的Goroutine 场景再现: 今天在看到这个教程的时候,在自己的电…...