iOS--block再学习

block再学习

  • 什么是block
    • block是带有自动变量的匿名函数
    • block语法
  • block的实现
    • block的实质
    • 截获自动变量
    • __blcok说明符
    • Block存储域
    • __block变量存储域
      • 使用__block变量用结构体成员变量__forwarding的原因
    • 截获对象

什么是block

Block时c语言的扩充功能,它允许开发者定义一段可重用的代码,并在需要时像变量一样使用这段代码。
对于block最重要的几个特点,总结如下:

  • Block本质上是一个OC对象,具有自己的isa指针。
  • 它可以看作是带有自动捕获变量能力的匿名函数。
  • Block可以捕获和存储它所在的环境中的变量和常量。

首先我们先从它可以看作是带有自动捕获变量能力的匿名函来了解和分析block ;

block是带有自动变量的匿名函数

首先我们先来了解一下自动变量的概念 ;

在 Objective-C(OC)中,自动变量(Automatic Variables)通常指的是在函数或方法内部定义的局部变量,这些变量在函数或方法被调用时自动在栈(stack)上分配内存,并在函数或方法执行完毕后自动释放内存。这些变量的作用域仅限于定义它们的函数或方法内部。

其实在这个地方我们可以把自动变量理解为局部变量 ;
在Objective-C中,Block可以捕获其定义范围内可见的局部变量,但是它们捕获这些变量的方式取决于这些变量的存储类型修饰符。修饰符之后仔讲 ;

匿名函数如它的名称一样,是一种没有名称的函数。

那我们为什么要用block,而不直接用函数呢,明明普通函数能实现,
以下是使用Block的一些主要原因:

  • 闭包(Closure)特性:
    Block可以捕获其定义范围内的变量和常量,包括外部函数的局部变量和全局变量。这使得Block可以访问和操作这些变量,就像它们是Block自己的局部变量一样。这种闭包特性使得Block能够封装和保存函数的状态,从而实现更复杂的逻辑。

  • 匿名性:
    Block没有名称,因此它们可以作为参数传递给其他函数或方法,或者作为属性存储在对象中。这使得Block可以方便地在代码之间传递和使用,而无需担心命名冲突或额外的命名空间管理。

  • 类型安全:
    虽然Block在语法上类似于C函数,但它们是Objective-C的类型,并且支持类型检查。这意味着可以在编译时捕获与Block类型相关的错误,从而提高代码的质量和可维护性。

  • 简洁性: Block可以内联定义在需要使用它们的代码块中,无需单独声明和定义函数。这使得代码更加简洁和易读,同时减少了函数调用的开销。

  • 异步编程:
    在Objective-C中,异步编程通常涉及到回调函数的使用。使用Block作为回调函数可以简化代码结构,并减少嵌套回调的复杂性。例如,使用Grand
    Central Dispatch (GCD)进行异步任务时,可以使用Block作为任务完成后的回调处理程序。

  • 响应式编程:
    Block可以与响应式编程模式结合使用,以处理异步事件和流数据。通过使用Block来处理事件和数据流,可以创建更加灵活和可响应的应用程序。

  • 与Objective-C对象的集成:
    Block可以与Objective-C对象无缝集成,并且可以轻松地在Block内部访问和操作对象属性和方法。这使得Block成为处理Objective-C对象和集合类的强大工具。

  • 内存管理:
    在Objective-C中,Block的内存管理可以通过__block修饰符和ARC(自动引用计数)来管理。这减少了手动管理内存的需要,并降低了内存泄漏和野指针的风险。

    简单的说,block提供了更多的灵活性和便利性,特别是在与Objective-C对象交互的上下文中。

    顺便说一下:带有自动变量的匿名函数这一概念并不指blocks,它还存在于许多其他程序语言中;

block语法

Block 声明和定义:

returnType (^blockName)(parameters) = ^(parameters) {  // Block 的实现代码  
};
  • returnType:Block 返回的类型,如果 Block 没有返回值,则为 void。
  • blockName:Block 的名称,可选,通常省略以创建匿名 Block。
  • parameters:Block 接收的参数列表,如果没有参数则为空。
  • ^:这是 Block 的字面量语法,用于开始定义 Block。

如:

int (^addBlock)(int, int) = ^(int a, int b) {  return a + b;  
};

使用 Block:

int sum = addBlock(3, 4); // sum 现在为 7

Block 类型声明:
当不使用 typedef 来声明 Block 变量类型时:

// 声明一个返回 int 类型,接受两个 int 类型参数的 Block 变量  
int (^addBlock)(int, int) = ^(int a, int b) {  return a + b;  
};  // 调用 Block  
int result = addBlock(3, 4);  
NSLog(@"The result is: %d", result); // 输出 "The result is: 7"

使用 typedef 来声明 Block 变量类型时:

// 使用 typedef 声明 Block 类型别名  
typedef int (^IntToIntBlock)(int, int);  // 使用类型别名声明 Block 变量  
IntToIntBlock multiplyBlock = ^(int a, int b) {  return a * b;  
};  // 调用 Block  
int product = multiplyBlock(3, 4);  
NSLog(@"The product is: %d", product); // 输出 "The product is: 12"

第二种声明很简单,主要注意一下第一种声明,或者两者间的区别 ;
如:

int (^ func ()) (int) {}

上面这个形式看起来就挺怪的,但实际上这是一个返回值为block类型的函数func;

block的实现

block的实质

block实际上是作为普通的c语言代码来处理的 ;
原代码:
在这里插入图片描述

通过clang转换后的可读源代码:
在这里插入图片描述

通过block使用的匿名函数实际上被作为简单的语言函数来处理;
_cself为指向block得变量 ;
从上面转换过来的源码可以看出block在c语言中的结构就是一个结构体 ;
结构体的声明如下:
在这里插入图片描述

这事一个嵌套的结构体,这里就顺便给出其中的两个结构体的声明 ;
在这里插入图片描述

在这里插入图片描述

isa指针在对象,类,元类中就了解了,这也说明了block是一种对象;flag是一种标志,具体不太清楚;Reserved是版本升级所需要的区域;FuncPtr是指向函数的指针,实际上这个函数也是block中的具体实现 ;
Block——size是Block的大小,也是结构体的大小 ;

在这里插入图片描述

这里的构造函数初始化了Block结构体的成员变量,Blcok中自动变量的捕获也在这里完成 ;
这里会想下面这样初始化在:
在这里插入图片描述

isa指向了这个对象的类;FuncPtr和Desc分别指向了它们的构造函数,完成成员变量的初始化 ;

主函数中的
在这里插入图片描述

转换后:
在这里插入图片描述

这段代码将在栈上生成的__main_block_impl_0结构体实例的指针赋值给变量blk ;
对应了:
在这里插入图片描述

而:
在这里插入图片描述

则对应着blk();

截获自动变量

先给出转换后的源代码:
在这里插入图片描述

对比一下前面的源代码,这里只有结构体和使用时调用的函数不一样 ;
先看结构体:
在这里插入图片描述

其实从这个结构我们也可以猜到Block对自动变量的捕捉是通过成员变量赋值实现的 ;

这里也解答了,为什么Block无法捕获c语言中的数组 ,因为在c语言中数组是无法直接赋值的,但可以通过指针实现 ;

顺便注意一下,Blcok语法表达式中没有使用的自动变量是不会被捕获的,甚至在Block结构体中都不会声明它的成员变量 ;

在这里插入图片描述

这里相当于setting方法;
在这里插入图片描述

这个相当于getting方法 ;

总的来说,“截获自动变量”意味着在执行Block语法时,Block表达式所使用的自动变量被保存到Block结构体中去了 ;

__blcok说明符

一般来说,Blcok会截获Block语法中使用过的自动变量,即使在Block外对这些自动变量进行重写,也不会改变Block中已经捕获的值;如果尝试在Block内部进行重写 ,编译器在编译时会自动检测并抛出错误;
这也意味着不便,有什么办法能在Block中直接改写Block中的自动变量呢 ;

这里有两种方法:
1.c语言中有部分变量是允许Block改写值的;

  • 静态变量
  • 全局变量
  • 静态全局变量

具体可以看看两端代码转换前后的对比:
在这里插入图片描述

在这里插入图片描述

这里发现全局变量的使用与c语言中无二,甚至不会有Block的捕获机制;

但静态变量在结构体中是以指针捕获的;至于为什么用指针,这是超出作用域使用变量的最简单方法 ;
那为什么普通的自动变量为什么不采用指针捕获;这是因为静态变量与其他变量之前生命周期的差别 ;

2.使用__block说明符:
和它类似的还有

  • static
  • auto
  • regist
    其中auto表示作为自动变量存储在栈中,static表示作为静态变量存储在数据区中 ;
    只有__block得存储域待会讲;
    先来看看使用__Block的自动变量在Block中的具体实现:
    在这里插入图片描述

在这里插入图片描述

对比之前的,我们发现多了一个__block_byref_val_0结构体,它和block的结构体很像,但多了一个forwarding指针指向自身 ;
看起来就是把val变量转换为一个结构体变量来捕获 ;
在这里插入图片描述

这里结构体的结构可以参考下图:
在这里插入图片描述

当使用Block时:
在这里插入图片描述

这里看起来有点复杂;那为什么有成员变量__forwarding?虽然后面讲,但我猜是通过它来延长自动变量的生命周期之类的 ;

另外要注意一点,__block变量的____block_byref_val_0结构体对象并不在__main_block_impl_0结构体中,__main_block_impl_0结构体中的是它的指针,而对象在主函数中,这样做就可以在多个Block中使用__blcok变量 ;

Block存储域

Block转换为Block的结构体类型的自动变量,__block变量转换为__block变量的结构体类型的自动变量,所谓结构体类型的自动变量,即栈上生成的该结构体的实例 ;
但我们知道,除了栈以外,存储域还有数据区域和堆 ;
所以Block对象的存储域也不仅仅在栈 ;
下面给出Block对象的类和存储域间的关系:
在这里插入图片描述

平时用到的Block对象大多在栈,当

  • 记述全局变量的地方有Block语法时
  • Block语法的表达式中不使用应截获的自动变量时

时,Block为——NSConcreteGlobalBlock类对象;

#import "ViewController.h"
#import <objc/runtime.h>
typedef int(^blk_t)(int);
blk_t glbblk  = ^(int count){return count ;} ;
@interface ViewController ()@end@implementation ViewController- (void)viewDidLoad {[super viewDidLoad];// Do any additional setup after loading the view.for (int rate = 0; rate < 10; ++rate) {blk_t blk = ^(int count){return count ;} ;Class blockClass = object_getClass((__bridge id _Nullable)((__bridge void *)(glbblk)));NSLog(@"%@",NSStringFromClass(blockClass)) ;printf("%d\n",blk(1)) ;}}
#import "ViewController.h"
#import <objc/runtime.h>
typedef int(^blk_t)(int);
blk_t glbblk  = ^(int count){return count ;} ;
@interface ViewController ()@end@implementation ViewController- (void)viewDidLoad {[super viewDidLoad];// Do any additional setup after loading the view.for (int rate = 0; rate < 10; ++rate) {blk_t blk = ^(int count){return count ;} ;Class blockClass = object_getClass((__bridge id _Nullable)((__bridge void *)(blk)));NSLog(@"%@",NSStringFromClass(blockClass)) ;printf("%d\n",blk(1)) ;}}

配置在全局变量上的Block,从变量作用域外也可以通过指针安全使用,但设置在栈上的Block,如果其所属的作用域结束,该Block就被废弃;同上,配置在栈上的__block变量也是如此 ;
那么我们在使用中,Block时如何超出变量作用域存在的:
Blocks提供了讲Block和__block变量从栈上复制到堆上的方法 ;这样就能保证Block不被废弃 ;
当Block被复制到堆上时,isa指针也就是类会被赋为_NSConcrete MallocBlock,__block变量也是如此,但它的成员变量forwarding可以同时访问栈上和堆上的__block变量 ;

这个所谓的复制方法,其实就是oc中经常使用的copy,当我们对一个Block对象使用copy时有三种情况:
在这里插入图片描述

不过在提到使用copy方法前,我们要知道,但ARC有效时,大多数情形下编译器都会恰当地进行判断,自动生成讲Block从栈上复制到堆上的代码 ;

除此之外的情形就必须手动复制了,比如像方法或函数的参数中传递Block时;但如果在方法或函数中适当地复制了传递过来的参数,那也不必手动复制了,如:

  • Cocoa框架的方法切方法名中含有usingBlock等时
  • GCD的API

书上讲的必须手动复制的例子:

NSArray* array = [self getBlockArray] ;typedef void(^blk01_t)(void);blk01_t blk = [array objectAtIndex:0] ;blk () ;- (id) getBlockArray {int val = 10 ;return [[NSArray alloc] initWithObjects:^{NSLog(@"%d",val);},^{NSLog(@"%d",val);}, nil];
}

但不太清楚的一点是我在实际运行的时候程序不会异常,而是会正常执行;

再回到调用copy方法,我们会发现不管Block配置在何处,用copy方法都不会引起任何问题,在不确定时调用copy方法即可 ;不过将Block从栈上复制到堆上是相当消耗cpu的;

如下:

 blk = [[[[blk copy] copy] copy] copy] ;

多次调用copy方法进行复制在ARC下是没有任何问题的 ;

__block变量存储域

当把使用了__block变量的Block从栈上复制到堆上时,__block
变量也会产生影响:
在这里插入图片描述

当把栈上的Block从栈上复制到堆上时,Block中使用的__block变量也会一并复制到堆上;当该Block已经复制到堆上时,复制Block对__block变量没有任何影响 ;
在这里插入图片描述
当多个Block中使用相同的__block变量时,任何一个Block从栈复制到堆,都会把__block变量也复制到堆上,即使其他Block再复制到堆上,也只会增加__blockde1引用计数;这和oc的内存管理是一样的 ;

在这里插入图片描述

同理,废弃Block也是一样的:
在这里插入图片描述

使用__block变量用结构体成员变量__forwarding的原因

不管__block变量配置在栈上还是在堆上,都能正确地访问变量
即通过Block的复制,__block变量也从栈复制到堆。此时可同时访问栈上的和堆上的__block变量。对于这句话,看

__block int val = 0 ;void (^blk) (void) = [^{ ++ val ;} copy] ;++val ;blk () ;NSLog(@"%d",val) ;

其中Block中的val为复制到堆上的__block变量用结构体实例 ;
而外面的val为栈上的结构体实例 ;

当复制到堆上时,成员变量的forwarding的值替换为目标堆上的__block变量用结构体实例的地址;

所以其实void (^blk) (void) = [^{ ++ val ;} copy] ;
++val ;
这两句都可以转换为:

++ (val.__forwarding->val) ;

在这里插入图片描述

无论是在Block语法中,Block语法外使用__block变量,还是__block变量配置在栈上还是堆上,都可以顺利的访问同一个__block变量 ;

截获对象

书上的例子大概都是基于MRC实现的,不过我在手动管理时也没有成功,所以这部分和内存管理息息相关 ;

typedef void(^blk_t)(id);blk_t blk ;{id array = [[NSMutableArray alloc] init] ;blk = [^(id obj) {[array addObject:obj] ;NSLog(@"%ld",[array count] ) ;} copy] ;}blk([[NSObject alloc] init]) ;blk([[NSObject alloc] init]) ;blk([[NSObject alloc] init]) ;

这里的array,当在调用blk时,已经超出了他的作用域,按理来说应该被废弃,但这时发生了截获对象,可能类似于前面截获自动变量的值,但也有些不一样 ;与前者相比,最明显的区别在于:
在这里插入图片描述

因为涉及到了对象,简单的c语言结构体中不能含有__strong修饰的变量 ;但是oc可以通过引用计数进行内存管理 ;
所以在__main_block_desc_0结构体中增加的成员变量copy和dispose以及作为指针赋值给该成员变量的__main_block_copy_0函数(copy函数)和__main_block_dispose_0函数 (dispose函数);

这个时候代入内存管理来看 ;
copy函数使用_Blokc_object-assign函数(相当于retain)将对象类型对象赋值给Block用结构体成员变量array中并持有该对象 ;

dispose函数使用_Block_object_dispose函数(相当于release),释放赋值在Block用结构体成员变量array中的对像 ;

那么这些函数什么时候调用呢:在Block从栈复制到堆时,以及堆上的Block被废弃时会调用 ;
在这里插入图片描述

什么时候栈上的Block会复制到堆:

  • 调用Block的copy实例方法时
  • Block作为函数返回值返回时
  • 将Block赋值给附有__strong修饰符id类型的类或Block类型成员变量时
  • 在方法名中含有usingBlock的Cocoa框架方法或GCD的API中传递Block时 ;

上面的这些情况下,栈上的Block被复制到堆上,但其实可以总结为_Block_copy函数被调用时Block从栈复制到堆上 ;
相对的,在释放复制到堆上的Block后,谁都不持有Block,而使其被废弃时调用dispose函数,这相当于对象的dealloc实例方法 ;

通过这种构造,通过使用附有__strong修饰符的自动变量,Block截获的对象能够超出变量作用域存在 ;也就是引用计数管理 ;

因此,Block中使用对象类型成员变量时,除了一下情形外,推介调用Block的copy实例方法 ;

  • Block作为函数返回值返回时
  • 将Block赋值给附有__strong修饰符id类型的类或Block类型成员变量时
  • 在方法名中含有usingBlock的Cocoa框架方法或GCD的API中传递Block时

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

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

相关文章

pytorch之猫狗识别项目

1. 导入资源包 资源包&#xff1a; import torchvision&#xff1a;PyTorch 提供的视觉库&#xff0c;包含了常用的计算机视觉模型架构、数据集以及图像转换工具。 from torchvision import datasets, models&#xff1a;导入 torchvision 中的 datasets 和 models 模块&#…

spring boot +Scheduled 动态定时任务配置

通常情况下我们设定的定时任务都是固定的,有时候需要我们动态的配置定时任务,下面看代码 import com.mybatisflex.core.query.QueryWrapper; import com.yzsec.dsg.web.modules.exportpwd.entity.ExportPwd; import com.yzsec.dsg.web.modules.exportpwd.entity.table.Export…

如何使用GPT-4o函数调用构建一个实时应用程序?

本教程介绍了如何使用OpenAI最新的LLM GPT-4o通过函数调用将实时数据引入LLM。 我们在LLM函数调用指南(详见https://thenewstack.io/a-comprehensive-guide-to-function-calling-in-llms/)中讨论了如何将实时数据引入聊天机器人和代理。现在&#xff0c;我们将通过将来自Fligh…

bat脚本简介

一、bat脚本 概念定义 BAT 批处理是一种在 Windows 系统中用于将一系列命令组合成一个可执行文件&#xff08;.bat 文件&#xff09;的脚本技术。 允许用户将多个操作命令按顺序编写在一起。形成一个自动化执行的流程。批处理文件可以包含各种系统命令和程序调用。 如文件操作…

中国现在最厉害的书法家颜廷利:东方伟大思想家哲学家教育家

中国书法界名人颜廷利教授&#xff0c;一位在21世纪东方哲学、科学界及当代中国教育领域内具有深远影响力的泰斗级人物&#xff0c;不仅以其深厚的国学修为和对易经姓名学的独到见解著称&#xff0c;还因其选择在济南市历城区的龙泉大街以及天桥区的凤凰山庄与泉星小区等地设立…

如何在隔离环境中设置 LocalAI 以实现 GPU 驱动的文本嵌入

作者&#xff1a;来自 Elastic Valeriy Khakhutskyy 你是否想在 Elasticsearch 向量数据库之上构建 RAG 应用程序&#xff1f;你是否需要对大量数据使用语义搜索&#xff1f;你是否需要在隔离环境中本地运行&#xff1f;本文将向你展示如何操作。 Elasticsearch 提供了多种方法…

多曝光融合算法(三)cv2.createAlignMTB()多曝光图像融合的像素匹配问题

文章目录 1.cv2.createAlignMTB() 主要是计算2张图像的位移&#xff0c;假设位移移动不大2.多曝光图像的aline算法&#xff1a;median thresold bitmap原理讲解3.图像拼接算法stitch4.多曝光融合工具箱 1.cv2.createAlignMTB() 主要是计算2张图像的位移&#xff0c;假设位移移动…

Python中猴子补丁是什么,如何使用

1、猴子补丁奇遇记 &#x1f412; 在Python的世界深处&#xff0c;隐藏着一种神秘而又强大的技巧——猴子补丁&#xff08;Monkey Patching&#xff09;。这是一项允许你在程序运行时动态修改对象&#xff08;如模块、类或函数&#xff09;的行为的技术。它得名于其“快速修补…

问题排查: Goalng Defer 带来的性能损耗

本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。 本作品 (李兆龙 博文, 由 李兆龙 创作)&#xff0c;由 李兆龙 确认&#xff0c;转载请注明版权。 文章目录 引言问题背景结论 引言 性能优化之路道阻且长&#xff0c;因为脱敏规定&#xff0c;…

vite常识性报错解决方案

1.导入路径不能以“.ts”扩展名结束。考虑改为导入“xxx.js” 原因&#xff1a;当你尝试从一个以 .ts 结尾的路径导入文件时&#xff0c;ESLint 可能会报告这个错误&#xff0c;因为它期望导入的是 JavaScript 文件&#xff08;.js 或 .jsx&#xff09;而不是 TypeScript 文件&…

coap-emqx:使用libcoap与emqx通信

# emqx开启CoAP网关 请参考【https://blog.csdn.net/chenhz2284/article/details/139562749?spm1001.2014.3001.5502】 # 写一个emqx的客户端程序&#xff0c;不断地往topic【server/1】发消息 【pom.xml】 <dependency><groupId>org.springframework.boot<…

开源与新质生产力

在这个信息技术迅猛发展的时代&#xff0c;全球范围内的产业都在经历着深刻的变革。在这样的背景下&#xff0c;“新质生产力”的概念引起了广泛的讨论。无论是已经成为或正努力转型成为新质生产力的企业&#xff0c;都在寻求新的增长动力和竞争优势。作为一名长期从事开源领域…

Linux用户和用户组的管理

目录 前言一、系统环境二、Linux用户组的管理2.1 新增用户组2.2 删除用户组2.3 修改用户组2.4 查看用户组 三、Linux用户的管理3.1 新增用户3.2 删除用户3.3 修改用户3.4 查看用户3.5 用户口令&#xff08;密码&#xff09;的管理 总结 前言 本篇文章介绍如何在Linux系统上实现…

OrangePi Kunpeng Pro深度评测:性能与体验的完美融合

文章目录 一、引言二、硬件开箱与介绍1.硬件清单2.硬件介绍 三、软件介绍四、性能测试1. 功率测试2. cpu测试2.1 单线程cpu测试2.2 多线程cpu测试 五、实际开发体验1. 搭建API服务器2. ONNX推理测试3. 在线推理平台 五、测评总结1. 能与硬件配置2. 系统与软件3. 实际开发体验个…

探索智慧商场的功能架构与应用

在数字化和智能化的浪潮下&#xff0c;智慧商场已经成为零售业的重要发展方向之一。智慧商场系统的功能架构设计与应用&#xff0c;结合了现代信息技术和零售业的实际需求&#xff0c;为商场的管理和运营提供了全新的解决方案。本文将深入探讨智慧商场的功能架构与应用&#xf…

matlab---app

一 基础 标签和信号灯没有回调函数 clc,clear,close all %清理命令区、工作区&#xff0c;关闭显示图形 warning off %消除警告 feature jit off %加速代码运行 ysw{i}i %循环赋值 celldisp(ysw) %显示元胞数组ysw.y1{1}[1,2] …

《软件定义安全》之二:SDN/NFV环境中的安全问题

第2章 SDN/NFV环境中的安全问题 1.架构安全 SDN强调了控制平面的集中化&#xff0c;从架构上颠覆了原有的网络管理&#xff0c;所以SDN的架构安全就是首先要解决的问题。例如&#xff0c;SDN实现中网络控制器相关的安全问题。 1.1 SDN架构的安全综述 从网络安全的角度&…

@BeforeAll 和 @AfterAll 必须是 static 的原因

BeforeAll 和 AfterAll 必须是 static 的原因 执行时机&#xff1a; BeforeAll 方法在所有测试方法之前运行。AfterAll 方法在所有测试方法之后运行。 实例化前/后的执行&#xff1a; 因为 BeforeAll 是在所有测试方法执行之前运行的&#xff0c;所以它在任何一个测试实例创建…

基于springboot的教学管理系统的设计

管理员账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;管理员管理&#xff0c;教师管理&#xff0c;学生管理&#xff0c;课程管理 教师账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;学生管理&#xff0c;课程管理&#xff0c;课程表…

数据结构---树与二叉树

个人介绍 hello hello~ &#xff0c;这里是 code袁~&#x1f496;&#x1f496; &#xff0c;欢迎大家点赞&#x1f973;&#x1f973;关注&#x1f4a5;&#x1f4a5;收藏&#x1f339;&#x1f339;&#x1f339; &#x1f981;作者简介&#xff1a;一名喜欢分享和记录学习的…