【iOS】Block基础知识和底层探索

文章目录

  • 前言
  • Block的声明和创建
  • 问题引入
  • Block的底层结构
  • Block的执行流程
    • Block的创建与存储
    • Block的传递与调用
  • Block的捕获机制
    • 捕获局部变量
    • 捕获全局变量
    • 小结
  • Block的类型
  • __block修饰符
  • __block变量的包装结构体
    • block的实例结构体
    • block的执行逻辑
  • Block循环引用
    • 造成的原因
    • 解决方法
    • 小结
  • 总结

前言

  最近在复习OC知识,发现自己对于block的了解很少,很多东西当时都没有搞明白或者甚至不知道,然后就对block进行了重新学习。

Block的声明和创建

Block 的语法格式为:

返回值类型 (^Block名称)(参数列表) = ^返回值类型(参数列表) { 代码块 };

示例:

// 声明并创建一个无参数、无返回值的 Block
void (^myBlock)(void) = ^void(void) {NSLog(@"Block 执行");
};// 调用 Block
myBlock(); // 输出:"Block 执行"

问题引入

由此,我们有以下代码,后面所有的实例分析都是在此基础上:

请添加图片描述

运行结果:

请添加图片描述

我们可以看到,上述代码进行了两次block捕获,第一次捕获A和B的初始值,A为3,B为7;然后在myblock函数外对A、B变量进行自增修改处理,再次调用myblock函数进行捕获,变量A输出不变为3,变量B输出为自增后的值8,控制变量可以发现,造成这种结果,唯一不同的是我们在定义变量B时,在前面用了==__block==进行修饰。

由此我们产生了 一系列问题:block内部是什么结构?为什么在block外部修改变量不会影响到其在block内的捕获?为什么声明时用__block进行修饰后,外部修改变量block捕获也会变?可不可以直接在block内部进行变量修改?

下面,我们来对这些问题进行一一探讨。

Block的底层结构

我们用clang -rewrite-objc main.m -o main.cpp将上述代码文件转换成C++文件:

int main(int argc, const char * argv[]) {/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; int A = 3;__attribute__((__blocks__(byref))) __Block_byref_B_0 B = {(void*)0,(__Block_byref_B_0 *)&B, 0, sizeof(__Block_byref_B_0), 7};void(*myblock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, A, (__Block_byref_B_0 *)&B, 570425344));((void (*)(__block_impl *))((__block_impl *)myblock)->FuncPtr)((__block_impl *)myblock);A++;(B.__forwarding->B)++;z((void (*)(__block_impl *))((__block_impl *)myblock)->FuncPtr)((__block_impl *)myblock);}return 0;
}

我们可以看到,我们定义myblock函数的代码在C++中是这样的:

请添加图片描述

上述代码Block底层实现的编译器生成代码,代码的核心是将一个 Block 的底层结构体指针转换为函数指针,并赋值给 myblock变量,具体形式:

void(*myblock)(void) = ((void (*)())&__main_block_impl_0(...));

等号左边等名义个函数指针myblock,类型为void(*)(void)(无参数、无返回值);

等号右边通过调用__main_block_impl_0函数生成 Block 的底层结构体,并将其转换为函数指针后赋值给 myblock。

__main_block_impl_0是编译器自动生成的 Block 初始化函数,负责创建 Block 的底层结构体(如 struct __block_impl)并初始化其核心字段。其参数通常包括:

参数类型/含义
(void *)__main_block_func_0指向 Block 实际执行逻辑的函数指针(即 FuncPtr字段的内容)
&__main_block_desc_0_DATA指向 Block 描述符(struct __block_descriptor)的指针,包含 Block 的元数据(如大小、复制/销毁函数)
A被 Block 捕获的变量(如基本类型、不可变对象)
(__Block_byref_B_0 *)&B指向 __Block_byref_B_0结构体的指针,用于捕获可变对象或需要引用捕获的变量
570425344标志位(可能表示 Block 的行为选项,如是否复制捕获变量、是否启用优化等)

从刚刚我们在main.m的cpp文件里看到的定义函数,我们继续探究找寻__main_block_impl_0函数的源码定义:

请添加图片描述

struct __main_block_impl_0包含以下核心成员:

成员类型/含义
implstruct __block_impl类型,Block 的核心实现结构体(封装函数指针、类型标识、标志位等)。
Descstruct __main_block_desc_0*类型,指向 Block 描述符(存储元数据,如复制/销毁函数)。
Aint类型,捕获的整型变量(值捕获)。
B__Block_byref_B_0*类型,指向引用捕获的可变对象的辅助结构体(引用捕获)。
构造函数初始化各成员,绑定 Block 的执行逻辑、捕获变量和元数据。

然后我们顺藤摸瓜找到了Block 的核心实现结构体struct __block_impl函数的源码:请添加图片描述

由上述过程,我们可以知道,Block的底层本质是一个结构体,内部封装了一些关键信息:

函数指针:指向Block对应的可执行代码

捕获的变量:Block执行时需要访问的外部变量()

执行上下文:包括 Block 的引用计数、所属的类(用于调试)等元数据

有一张图非常好地说明了block的底层调用和逻辑:

请添加图片描述

​ ——图片来自博客【iOS】Block底层分析@zhngxvy

Block的执行流程

Block 的执行分为​​创建​​、​​存储​​、​​传递​​和​​调用​​四个阶段,核心是​​函数指针的调用​​和​​捕获变量的管理​​。

Block的创建与存储

创建:通过 ^语法定义 Block 时,编译器会生成一个 struct Block_layout结构体实例,并将代码块编译为对应的机器指令(存储在invoke函数指针中)。

存储位置:Block 初始存储在栈上(栈 Block),但以下场景会触发 Block 被复制到堆上(堆 Block):

  • Block 被赋值给一个强引用的变量(如 __strong修饰的属性或局部变量)。
  • Block 被作为参数传递给一个异步函数(如 dispatch_async)。
  • Block 被显式复制(调用 Block_copy())。

Block的传递与调用

Block 可以像对象一样被传递(如作为方法参数、存储在集合中),其调用的本质是执行 invoke函数指针,并传递参数。

示例(Block 作为方法参数):

// 定义一个接受 Block 的方法
- (void)executeBlock:(void (^)(void))block {NSLog(@"准备执行 Block...");block(); // 调用 Block(执行 invoke 函数)NSLog(@"Block 执行完成");
}// 使用
[self executeBlock:^{NSLog(@"自定义 Block 逻辑");
}];

执行流程

  1. 定义 Block 时,编译器生成栈上的 struct Block_layout
  2. 将 Block 作为参数传递给 executeBlock:方法时,Block 被复制到堆上(因方法参数需要强引用)。
  3. 方法内部调用 block()时,触发invoke函数指针,执行 Block 的代码逻辑。

Block的捕获机制

Block 可以捕获外部作用域的变量(如局部变量、实例变量),捕获行为分为两种:值捕获和指针捕获

捕获局部变量

auto:自动变量,离开作用域就自动销毁,只存在于局部变量(没有特别关键字修饰,一般默认为auto)
static:静态局部变量

我们先来看以下代码:

#import <Foundation/Foundation.h>int main(int argc, const char * argv[]) {@autoreleasepool {int A = 18;//局部变量(自动变量)static int B = 52;//静态局部变量void(^jubuBlock)(void) = ^{NSLog(@"jubuBlock - A:%d - B:%d", A, B);};jubuBlock();NSLog(@"输出1 - A:%d B:%d", A, B);A = 20;B = 100;jubuBlock();NSLog(@"输出2 - A:%d B:%d", A, B);}return 0;
}

输出结果如下:

请添加图片描述

由此,我们可以发现,当我们在block函数外部修改局部变量时,block函数内部的自动变量不会受影响,而静态局部变量会跟着被修改。这是为什么呢?

为了探寻这一原因,我们将上述代码文件转化为cpp文件,我们可以发现__main_block_impl_0结构体定义如下:

请添加图片描述

我们可以得到以下结论:

  • A:捕获的自动变量(按值复制)。
  • B:捕获的静态局部变量(按指针复制,存储其内存地址)。

这就可以解释刚刚的问题:为什么在block函数外部修改自动变量,内部的变量值不会产生改变,而修改静态变量就会变?到这里,block捕获局部变量的本质就很明显了:

自动变量:按值捕获(复制其当前值到block内部)。即使外部变量后续被修改,block内部使用的仍是捕获时的副本。

静态局部变量:按指针捕获(存储其内存地址)。外部变量修改时,block通过指针访问的是最新值。

捕获全局变量

首先,有以下代码:

#import <Foundation/Foundation.h>int A = 18;//全局变量
static int B = 52;//静态全局变量int main(int argc, const char * argv[]) {@autoreleasepool {void(^quanjuBlock)(void) = ^{NSLog(@"quanjuBlock - A:%d - B:%d", A, B);};quanjuBlock();NSLog(@"输出1 - A:%d B:%d", A, B);A = 20;B = 100;quanjuBlock();NSLog(@"输出2 - A:%d B:%d", A, B);}return 0;
}

输出结果:请添加图片描述

我们再来看这部分源码:

请添加图片描述

我们发现,这部分的源码跟上面局部变量有点出入,在全局变量一直在__main_block_impl_0结构体定义中并没有我们声明的全局变量A和B,即全局、全局静态变量并没有出现在我们的Block实现结构体中,说明二者无法被捕获。而全局变量存储在内存中,打印的一直是最新的值。

小结

自动变量(局部变量)静态局部变量全局变量
存储区域栈(Stack)全局数据区(Data Segment)全局数据区(Data Segment)
生命周期随作用域结束(如函数返回)销毁程序启动时创建,程序结束时销毁程序启动时创建,程序结束时销毁
默认捕获机制按值拷贝(复制当前值到block内部)按指针拷贝(存储变量内存地址)按指针拷贝(存储变量内存地址)
block内访问形式直接使用拷贝后的副本(独立于原变量)通过指针解引用访问原变量通过指针解引用访问原变量
外部修改的影响不影响block内部的副本(block捕获的是历史值)影响block内部(指针指向的原变量被修改)影响block内部(指针指向的原变量被修改)
是否支持修改捕获值默认不可直接修改(需__block修饰)可直接修改(通过指针操作原变量)可直接修改(通过指针操作原变量)

产生这种差异的原因

auto和static:因为作用域的问题,自动变量的内存随时可能被销毁,所以要捕获就赶紧把它的值拿进来,防止调用的时候访问不到。

静态局部变量:存储在全局数据区,生命周期长于block,因此block捕获其指针后,即使原变量所在作用域销毁(如函数返回),仍可通过指针访问最新值。

注意:尽管静态局部变量的生命周期很长,但其**作用域(可访问范围)**仅限于声明它的函数内部:

  • 函数外部无法直接访问该变量(即使通过指针或引用)。
  • 不同函数中声明的同名静态局部变量相互独立(因为各自存储在全局数据区的不同位置)。

全局变量:在Block中访问局部变量相当于是跨函数访问,要先将变量存储在Block里(捕获),使用的时候再从Block中取出,而全局变量是直接访问。

tips:

自动变量按值捕获是安全的(避免悬垂指针),但可能增加内存拷贝开销(大对象需注意)。

静态/全局变量按指针捕获更高效(无拷贝),但需注意多线程并发修改时的线程安全问题。

Block的类型

上述说到Block含有isa指针,而OC对象的isa指针指向它的类型,那么Block的类型都有哪些呢?

首先有以下代码:

void (^block)(void) = ^{NSLog(@"hello world!");
};
NSLog(@"%@ %@", block, [block class]);
NSLog(@"%@", [[block class] superclass]);
NSLog(@"%@", [[[block  class] superclass] superclass]);

运行后结果如下:
请添加图片描述
我们可以发现这个block的类型是NSGlobalBlock,其父类是NSBlock,根父类是NSObject,这也能说明Block是一个OC对象。

除了NSGlobalBlock,Block还有哪些类型,我们先来看如下代码:

        void (^block1)(void) = ^{NSLog(@"hello world!");};NSLog(@"%@ %@", block1, [block1 class]);int A = 3;void (^block2)(void) = ^{NSLog(@"%d", A);};NSLog(@"%@ %@", block2, [block2 class]);NSString *string = @"hello world!";void (^block3)(void) = ^{NSString *stringCopy = [string copy];};NSLog(@"%@ %@", block3, [block3 class]);

当我们在ARC环境下运行代码后,有如下结果:
请添加图片描述

随后,我们在项目的Bulid Settings中关闭ARC:

请添加图片描述

随即得到如下结果:

请添加图片描述

我们可以发现,在MRC环境下,若block不捕获任何外部变量,且未被复制,则其类型为__NSGlobalBlock__;若block捕获外部变量,但未被复制,则其类型为__NSStackBlock__;若block被复制,则其类型为__NSMallocBlock__

但如果是在ARC环境下,即使不显式调用copy也会是__NSMallocBlock__类型,这是因为在ARC下,编译器会自动插入内存管理代码(如retainreleasecopy),核心目的是确保block在需要跨作用域存活时保持有效,确保对象(包括block)在其生命周期内被正确管理

所以,我们可以做出如下总结:

存储位置触发条件生命周期
栈上block未被复制(仅在局部作用域使用,未被赋值给id类型变量或作为参数传递)离开作用域时自动销毁(无法跨作用域使用)
堆上block被复制(显式调用copy,或隐式被id类型变量持有、作为方法参数传递等)。由引用计数管理,直到release计数归零后销毁。
全局区block不捕获任何外部变量,且未被复制。程序启动到结束时一直存在(全局唯一)。

__block修饰符

在我们前面的学习中,我们知道在block函数外部修改自动变量,函数内部捕获的值不会受到影响,那如果我们想修改在block中捕获的自动了变量的值怎么办?

首先,我们肯定想到说,想修改?那我直接在函数内部进行修改行不行!我们试一下:

请添加图片描述

我们会发现,在block函数内部直接修改自动变量会发生报错:Variable is not assignable (missing __block type specifier),翻译过来就是:变量未分配(丢失 __block 类型限定符)。

这是因为自动变量的默认捕获机制:在OC中,block对自动变量的默认捕获方式是按值复制Copy by Value):

  • 当block定义时,会复制自动变量的当前值到block内部的副本中。
  • block内部访问该变量时,实际访问的是副本,而非原变量
  • 因此,若在block内部尝试修改该变量(如赋值操作),编译器会报错:因为修改的是副本,原变量无法被block直接修改,这是不允许的。

那么我们真的没有办法修改自动变量了吗😭有的兄弟有的!

如果我们需要在block内部修改自动变量,需用__block修饰符声明该变量。好的,我们现在进行实际操作试试😋:

请添加图片描述

OK!对自由变量A加了__block修饰后就可以在block函数内部进行修改了✅

嘶~这个__block到底有什么魔力?下面我们来探索一下。

先说结论:__block的作用是将自动变量转换为块级变量(Block Variable),使其被block和原作用域共享同一实例。

__block变量的包装结构体

现在我们来看看__block这部分的cpp源码:

请添加图片描述
我们发现,被__block修饰的自动变量A在源码中,编译器生成了一个包装结构体__Block_byref_A_0,用于存储被修饰变量的值及其元数据。

其成员含义:

成员类型说明
__isavoid*指向类对象的指针(通常为_NSConcreteStackBlock或堆上的类,初始可能为NULL)。
__forwarding__Block_byref_A_0*转发指针,指向变量的实际存储位置(栈或堆)。当block被拷贝到堆时,此指针会指向堆中的副本。
__flagsint标志位(如是否被拷贝、是否需要释放等)。
__sizeint结构体的大小(用于内存管理)。
Aint被__block修饰的原始变量

通过此结构体,block和原作用域的变量共享同一内存空间,实现“跨作用域修改变量”的能力。

block的实例结构体

block的实例结构体( __main_block_impl_0)源码如下:

请添加图片描述

block的实例通过此结构体表示,包含block的执行逻辑、描述信息及对__block变量的引用:

结构体成员类型说明
implstruct __block_implblock的基类结构体,包含isa(类型标识)、Flags(标志位)、FuncPtr(执行函数指针)
Descstruct __main_block_desc_0*指向block描述符的指针,记录block的内存布局和回调函数(如拷贝、销毁)
A__Block_byref_A_0*指向__block变量包装结构体的指针(通过引用捕获,而非值拷贝)
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_A_0 *_A, int flags=0) : A(_A->__forwarding) {  // 初始化时,A指向包装结构体的转发指针impl.isa = &_NSConcreteStackBlock;  // 标记为栈上blockimpl.Flags = flags;                 // 设置标志位(如是否需要拷贝)impl.FuncPtr = fp;                  // 绑定执行函数(如__main_block_func_0)Desc = desc;                        // 绑定描述符
}

A(_A->__forwarding)表示block实例的A成员直接指向__Block_byref_A_0结构体的__forwarding指针。这确保了无论block是否被拷贝到堆,A始终指向变量的实际存储位置(栈或堆)。

block的执行逻辑

block的实际执行体(__main_block_func_0:),其源码如下:

通过__cself(block实例自身)访问捕获的变量。

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {__Block_byref_A_0 *A = __cself->A;  // 获取包装结构体指针(A->__forwarding->A) = 19;  // 修改被包装的变量A的值(通过转发指针)NSLog((NSString *)&__NSConstantStringImpl__... , (A->__forwarding->A));  // 打印修改后的值
}

通过A->__forwarding->A访问被__block修饰的变量A。__forwarding指针确保即使block被拷贝到堆,仍能正确访问变量的实际存储位置(栈或堆副本)。

Block循环引用

Block的本质是一个对象(继承自NSObject),其内存管理遵循ARC规则。当Block捕获外部对象(如self)时:

  • 默认情况下,Block会强引用捕获的对象(对对象类型变量按引用捕获,基本类型按值捕获)。

  • 如果外部对象(如self)本身强引用该Block(例如将Block作为self的属性),会形成强引用闭环:

    self → Block(强引用) → self(强引用)

    此时两者的引用计数均无法归零,导致内存泄漏。

造成的原因

Block作为对象的属性,且内部捕获self

当对象的属性是Block,且Block内部访问了self(如调用self的方法或属性),同时对象强引用该Block时,形成循环引用。

// ViewController.h
@interface ViewController : UIViewController
@property (nonatomic, strong) void (^myBlock)(void); // Block属性(强引用)
@end// ViewController.m
@implementation ViewController- (void)viewDidLoad {[super viewDidLoad];// 1. 创建Block,内部捕获self(强引用)self.myBlock = ^{ NSLog(@"Self: %@", self);  // 捕获self(强引用)};// 2. self强引用myBlock(属性是strong)// 此时形成循环引用:self → myBlock → self
}@end

循环路径:

self(ViewController实例)通过strong属性强引用myBlock→ myBlock通过默认的强引用捕获self→ 两者引用计数均无法归零。

Block被嵌套对象持有,且捕获外层self

当Block被另一个对象(如manager)持有,而self又持有该manager,同时Block内部捕获self时,也可能形成循环。

// Manager.h
@interface Manager : NSObject
@property (nonatomic, copy) void (^block)(void);
@end// ViewController.m
@implementation ViewController {Manager *_manager;
}- (void)viewDidLoad {[super viewDidLoad];_manager = [[Manager alloc] init];// 1. Manager持有block(copy后为强引用)_manager.block = ^{ NSLog(@"ViewController: %@", self);  // Block捕获self(强引用)};// 2. self持有_manager(强引用)// 循环路径:self → _manager → block → self
}@end

解决方法

  解决循环引用的核心是打破强引用闭环,通常通过==弱引用(__weak__unsafe_unretained)==弱化Block对self的引用

使用__weak修饰self

在Block内部通过__weak修饰的weakSelf引用self,避免Block强引用self。

// ViewController.m
@implementation ViewController- (void)viewDidLoad {[super viewDidLoad];// 1. 定义弱引用指向self(打破循环)__weak typeof(self) weakSelf = self;// 2. Block捕获weakSelf(弱引用)self.myBlock = ^{ // 使用weakSelf替代self(可能为nil)NSLog(@"Self: %@", weakSelf); };// 3. self仍强引用myBlock,但myBlock弱引用self → 无闭环
}@end

__weak typeof(self) weakSelf = self创建了一个弱引用weakSelf,其引用计数不增加,Block捕获weakSelf(弱引用),因此self的引用计数不会因Block的持有而增加。

循环路径被打破:self → myBlock → weakSelf(弱引用,不增加计数)。

使用__unsafe_unretained(不推荐)

__unsafe_unretained__weak类似,但不会自动将失效的指针置为nil,可能导致野指针(访问已释放的对象)。

__unsafe_unretained typeof(self) unsafeSelf = self;
self.myBlock = ^{ NSLog(@"Self: %@", unsafeSelf);  // 若self已释放,unsafeSelf可能为野指针
};

__unsafe_unretained仅在以下场景使用:目标对象生命周期明确短于Block(无需置nil)或无法使用__weak(如兼容旧版本iOS)。

在Block执行完毕前确保self存活

若Block需要长时间执行(如异步任务),可通过临时强引用确保self在Block执行期间不被释放,执行完毕后自动释放。

__weak typeof(self) weakSelf = self;
self.myBlock = ^{__strong typeof(weakSelf) strongSelf = weakSelf;  // 临时强引用if (strongSelf) {[strongSelf doSomething];  // 在Block执行期间,strongSelf保持self存活}
};  // strongSelf在此处销毁,不影响self的引用计数

__strong typeof(weakSelf) strongSelf = weakSelf在Block内部创建一个临时强引用strongSelf,若weakSelf未失效(self仍存活),strongSelf会强引用self,确保Block执行期间self不被释放,Block执行完毕后,strongSelf销毁,self的引用计数恢复正常。

小结

所以,我们可以总结:Block循环引用的核心是强引用闭环,解决方案的关键是弱化其中一个环节的引用。最常用的方法是使用__weak修饰self,在Block内部通过弱引用访问self,打破循环。同时需注意:

  • 避免在Block内部直接强引用self(默认行为)。
  • 若需在Block执行期间确保self存活,可结合临时强引用(__strong)。
  • __unsafe_unretained需谨慎使用,防止野指针崩溃。

总结

  Block是Objective-C中强大的闭包工具,在iOS开发中十分重要,常用于处理异步操作、回调、集合操作等,核心能力是捕获并保存环境状态,支持灵活的异步编程和回调逻辑。理解其存储位置(栈/堆/全局)变量捕获规则(值/指针/引用)内存管理(ARC/MRC)循环引用解决方案是掌握Block的关键。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/pingmian/92893.shtml
繁体地址,请注明出处:http://hk.pswp.cn/pingmian/92893.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

1.Ansible 自动化介绍

1-Ansible 自动化介绍 Ansible 自动化介绍 手动执行任务和自动化执行任务 手动执行任务的麻烦事&#xff1a; 很容易漏掉某个步骤&#xff0c;或者不小心执行错步骤&#xff0c;而且很难验证每个步骤是不是真的按预期完成了。管理一大堆服务器时&#xff0c;很容易出现配置…

2025年云手机场景适配的行业观察

2025年的市场中&#xff0c;云手机品牌百花齐放&#xff0c;不同品牌在性能、功能和场景适配性上的差异日益显著。随着云计算技术的快速发展&#xff0c;云手机已从 尝鲜工具 演变为游戏、办公、企业运营等场景的刚需工具。现市面上也有着更多的云手机品牌&#xff0c;结合实测…

Date/Calendar/DateFormat/LocalDate

作用说明Date用于定义时间&#xff0c;提供date对象间的比较方法Calendar(日历类),提供对时间的运算方法DateFormat是接口&#xff0c;它的实现类SimpleDateFormat用来规范时间输出形式LocalDate&#xff0c;在JDK1.8之后引入&#xff0c;方便了对时间的运算方法介绍Date常用方…

在Python 3.8环境中安装Python 3.6兼容包的方法

在Python 3.8环境中安装Python 3.6兼容包的方法 用户的需求是&#xff1a;在Python 3.8环境中重新安装原本为Python 3.6设计的包。这通常涉及兼容性问题&#xff0c;因为Python 3.8可能引入了一些语法或API变更&#xff0c;导致旧包无法直接运行。以下是逐步解决方案&#xff…

三种DuckDB电子表格插件的union all查询性能对比

我选取了最稳定、兼容性最好的三种&#xff1a;官方excel对应函数read_xlsx()、官方spatial对应函数st_read()、rusty_sheet对应函数read_sheet。 1.建立两个包含前50万和后54万的xlsx文件&#xff0c;用于比较。利用官方excel的copy()to进行。 D copy (from v1 order by l_ord…

Python 中使用多进程编程的“三两”问题

文章目录一、简介二、选择合适的启动方式三、手动终止所有的进程小结一、简介 这里简单介绍在Python中使用多进程编程的时候容易遇到的情况和解决办法&#xff0c;有助于排查和规避某类问题&#xff0c;但是具体问题还是需要具体分析&#xff0c;后续会补充更多的内容。 二、…

Ansible部署应用

目录Ansible概述1&#xff1a;什么是Ansible2&#xff1a;Ansible的架构组成3&#xff1a;Ansible与SaltStack的对比安装部署Ansible服务1&#xff1a;系统环境设置2&#xff1a;安装Ansible&#xff08;第一台&#xff09;2&#xff1a;配置主机清单3&#xff1a;修改Ansible配…

疏老师-python训练营-Day44预训练模型

浙大疏锦行 知识点回顾&#xff1a; 预训练的概念常见的分类预训练模型图像预训练模型的发展史预训练的策略预训练代码实战&#xff1a;resnet18 作业&#xff1a; 尝试在cifar10对比如下其他的预训练模型&#xff0c;观察差异&#xff0c;尽可能和他人选择的不同尝试通过ctrl进…

AI入门学习--如何写好prompt?

写好Prompt&#xff08;提示词&#xff09;是驾驭AI模型的核心技能。以下是结合测试工程师需求的 结构化方法论 和 黄金模板一、prompt设计金字塔终极心法&#xff1a; Prompt 对AI的测试需求文档&#xff0c;需像设计测试用例一样&#xff1a;可执行&#xff1a;明确输入输出…

Linux编程 IO(标准io,文件io,目录io)

标准IO C语言标准IO概述标准IO&#xff08;Standard Input/Output&#xff09;是C语言中用于处理文件和数据流的一组函数库&#xff0c;定义在<stdio.h>头文件中。与低级IO&#xff08;如read/write&#xff09;相比&#xff0c;标准IO提供了缓冲机制&#xff0c;提高了数…

C# WPF本地Deepseek部署

模型下载地址 using LLama; using LLama.Common; using System; using System.IO; using System.Threading.Tasks; using System.Windows; using System.Windows.Input;namespace YF_Talk {public partial class MainWindow : Window{private LLamaWeights _model;private LLa…

【Abp.VNext】Abp.Vnext框架模块学习

1、Abp.Vnext-集成 Volo.Abp.Core2、Abp.vNext-Web模块 Volo.Abp.AspNetCore.MVC框架&#xff08;framework文件夹&#xff09; 七、Abp.vNext-应用模块-Identity身份认证 业务模块&#xff08;modules文件夹->identity&#xff09; 1、添加领域模型 Volo.Abp.Identity.Doma…

【完整源码+数据集+部署教程】火柴实例分割系统源码和数据集:改进yolo11-rmt

背景意义 研究背景与意义 在计算机视觉领域&#xff0c;实例分割技术作为一种重要的图像处理方法&#xff0c;近年来得到了广泛的关注和应用。实例分割不仅能够识别图像中的物体类别&#xff0c;还能精确地分割出每个物体的轮廓&#xff0c;提供更为细致的视觉信息。这一技术在…

飞算JavaAI云原生实践:基于Docker与K8s的自动化部署架构解析

一、飞算JavaAI详细介绍 1.1 飞算JavaAI飞算JavaAI是飞算云智推出的一款革命性Java开发辅助工具&#xff0c;它通过人工智能技术深度赋能传统软件开发流程&#xff0c;特别为大学生课程设计、毕业设计等实践教学环节提供了强有力的技术支持。在当前高校计算机相关专业教学中&am…

小程序打通美团核销:解锁到店综合业态私域密码,赋能6000+门店破局增长

数字化浪潮奔涌而来&#xff0c;棋牌室、台球厅、亲子乐园等线下综合业态面临经营转型的关键节点。小程序与美团核销功能的深度耦合&#xff0c;正成为撬动私域流量的核心杠杆&#xff0c;为超6000家门店打通了一条低成本、高转化的经营快车道。过往经营模式中&#xff0c;线上…

Linux Shell:Nano 编辑器备忘

打开文件 sudo nano /etc/apt/sources.list选中多行&#xff0c;然后删除 用方向键将光标定位到要删除的起始位置按下 Alt A 设置锚点用方向键选择要删除的区域 (以上 3 步是为了选中文本)用 Ctrl K(剪切) 或 Alt D(直接删除) 全选并删除 按下 Alt \ 将光标移动到文件开头…

常见的设计模式(2)单例模式

目录 一、版本一&#xff1a;禁用构造与拷贝 二、版本二&#xff1a;注册析构函数/嵌套垃圾回收 &#xff08;1&#xff09;使用atexit注册程序结束时的函数 &#xff08;2&#xff09;使用对象嵌套垃圾回收 三、版本三&#xff1a;线程安全 四、版本四&#xff1a;编译器…

JAiRouter 0.2.1 更新啦:内存优化 + 配置合并 + IP 限流增强,运维体验再升级

JAiRouter 0.2.1 更新啦&#xff1a;内存优化 配置合并 IP 限流增强&#xff0c;运维体验再升级 如果你已经在 0.2.0 生产环境中稳定运行&#xff0c;那么这篇更新会让你无痛升级&#xff0c;直接“更轻、更稳、更省心”。 &#x1f4ce; 官方仓库 & issue 直达 https://…

学习嵌入式第二十六天

文章目录IO(续上)1.标准IO1.标准IO的接口2.流的定位2.文件IO1.概念&#xff1a;2.系统调用和库函数3.文件IO函数接口习题IO(续上) 1.标准IO 1.标准IO的接口 fwrite 原型&#xff1a;size_t fwrite(const void *ptr, size_t size, size_t nmemb,FILE *stream); 功能&#xff1…

GDB 程序启动参数设置深度指南

GDB 程序启动参数设置深度指南 1. 概述 在程序调试过程中&#xff0c;正确设置启动参数对于验证程序行为、重现特定场景至关重要。GDB提供多种灵活的方式设置启动参数&#xff0c;特别是当您需要调试命令行参数处理逻辑或配置敏感型应用时。 2. 参数设置的核心方法 2.1 启动GDB…