【iOS】—— 浅看block源码

news2025/7/25 10:40:46

block

文章目录

  • block
    • 如何通过终端clang生成源码cpp文件
    • block实质
    • 截获自动变量
    • 全局变量和静态变量的截获
    • __block说明符
    • iOS开发“强弱共舞”——weak和strong配套使用解决block循环引用问题

如何通过终端clang生成源码cpp文件

之前在学习block中学习的比较浅,只看了oc高级编程书上有的源码,学习的比较浅,这节来对之前的作以补充。
首先来看看怎么将oc文件生成cpp的源码文件:
在这里插入图片描述

将项目大文件里的二级文件,打开终端 cd 并将此文件拖入:
然后再输入:

clang -rewrite-objc main.m

在这里插入图片描述

回车打开之前的项目文件,即可看到生成了一个cpp文件,打开就是oc的c++源码。
打开后发现有60000多行,很抽象,关键部分看最前面和最后面即可。

block实质

我们现在时文件中写一个最简单的block,看看源码是什么:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        void (^blk)(void) = ^{
            printf("Block\n");
        };
        blk();
    }
    return 0;
}

源码:

//经过clang转换后的C++代码
struct __block_impl {
  void *isa;//指向所属类的指针
  int Flags;//标志性参数,暂时没用到所以默认为0
  int Reserved;//今后版本升级所需的区域大小。
  void *FuncPtr;//函数指针,指向实际执行的函数,也就是block中花括号里面的代码内容。
};

struct __main_block_impl_0 {
  struct __block_impl impl;//上面点1中的结构体的变量
  struct __main_block_desc_0* Desc;//上面点2中的结构体的指针 
  
  __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 {
	printf("Block\n");
}

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

int main(int argc, const char * argv[]) {
	void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
	
	((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
    return 0;
}

我们来分开说一下每部分的意义:
1.第一个结构体

struct __block_impl {
  void *isa;//指向所属类的指针
  int Flags;//标志性参数,暂时没用到所以默认为0
  int Reserved;//今后版本升级所需的区域大小。
  void *FuncPtr;//函数指针,指向实际执行的函数,也就是block中花括号里面的代码内容。
};
  • 结构体的名称:impl即implementation的缩写,换句话说这一部分是block的实现部分结构体。
  • void *isa:声明一个不确定类型的指针,用于保存Block结构体实例。
  • int Flags:标识符。
  • int Reserved:今后版本升级所需的区域大小。
  • void *FuncPtr:函数指针,指向实际执行的函数,也就是block中花括号里面的代码内容。

2.在介绍struct __main_block_impl_0结构体之前,先介绍一下这个结构体里的static struct __main_block_desc_0结构体
这个结构体在上面一坨源码的最底下:

static struct __main_block_desc_0 {
  size_t reserved;  //今后版本升级所需区域的大小(一般填0)
  size_t Block_size;   //Block的大小
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
  • 第一个成员变量指的是今后版本升级所需区域的大小(一般填0)。
  • 第二个成员变量是Block的大小。
    __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};:
    1⃣️.这就是和我们平时用结构体一样,在定义完最后写一个结构体实例变量,变量名就是__main_block_desc_0_DATA。
    2⃣️.其中reserved为0,Block_size是sizeof(struct __main_block_impl_0)。

3.接下来看struct __main_block_impl_0结构体

struct __main_block_impl_0 {
  struct __block_impl impl;//上面点1中的结构体的变量
  struct __main_block_desc_0* Desc;//上面点2中的结构体的指针
  
  __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;
  }
};
  • 第一个成员变量是impl,也就是上面点1中的结构体的变量。
  • 第二个成员变量是Desc指针,就是上面点2中的结构体的指针。
  • 剩下的代码是:初始化含有这些结构体的__main_block_impl_0结构体的构造函数,给结构体里的元素赋值。

4.然后是static void __main_block_func_0

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

这个就比较简单了,Blcok执行的实际代码块。也是点3中fp指针指向的函数。括号中的参数__cself是相当于OC语言版的self,代表的是Block本身。

5.最后来看main函数

int main(int argc, const char * argv[]) {
	void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
	
	((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
    return 0;
}

这两句代码不长但是比较恶心,我们先来看第一句,把强制类型转换去一下试试:

//加了个临时变量temp,看着更方便点
struct __main_block_impl_0 temp = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);
struct __main_block_impl_0 *blk = &temp;

该源代码将__main_block_impl_0结构体类型的自动变量,也就是栈上生成的__main_block_impl_0结构体实例的指针,赋值给__main_block_impl_0结构体指针类型的变量blk。
第二行代码就是相当于源代码中的blk(),即使用该Block部分。去掉转换部分就是:

(*blk->impl.FuncPtr)(blk);

这是使用函数指针调用函数。由Block语法转换的__main_block_impl_0函数的指针被赋值成员变量FunPtr中。

截获自动变量

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);
    };
    blk();
    return 0;
}

转化后的代码:

//经过clang转换后的C++代码
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;
  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;
  int val = __cself->val; 
  
	printf(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 argc, const char * argv[]) {
	int dmy = 256;
    int val = 10;
    const char *fmt = "val = %d\n";
    void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, fmt, val));
    return 0;
}

与上次不同的是在Block语法表达式中使用的自动变量被当作成员变量追加到了__main_block_impl_0结构体中:

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) 

初始化时自动变量fmt和val进行了赋值操作:

	impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
    fmt = "val = %d\n";
    val = 10;

在转换后的代码中,截获到__main_block_impl_0结构体实例的成员变量上的自动变量,这些变量在Block语法表达式之前被声明定义,所以之后即使改变自动变量的值也不会对Block语法中的内容有所变化。

总的来说,所谓“截获自动变量值”意味着在执行Block语法时,Block语法表达式所使用的自动变量值被保存到Block的结构体实例(即Block自身中)。
总结一下就是说:
调用__main_block_impl_0的构造函数,构造函数正常有两个参数,func_0和desc_0,在截获自动变量时,会把需要截获的自动变量也放入参数列表中,同时__main_block_impl_0中也会增加两个成员变量a,b,构造函数带参数就是自动给这两个成员变量赋值。在调用func_0时,直接通过__cself(相当于self,其传递的参数也是其本身)的a、b(此时的a,b就是成员变量中的,因为上面的构造函数已经赋值了)

全局变量和静态变量的截获

我们以以下代码为例:

int global_val = 10; // 全局变量
static int static_global_val = 20; // 静态全局变量

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        static int static_val = 30; // 静态局部变量
        
        void (^myLocalBlock)(void) = ^{
            global_val *= 1;
            static_global_val *= 2;
            static_val *= 3;
            
            printf("static_val = %d, static_global_val = %d, global_val = %d\n",static_val, static_global_val, global_val);
        };
        
        myLocalBlock();
    }
    return 0;
}

源码:

struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};

int global_val = 10;
static int static_global_val = 20;


struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int *static_val;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_static_val, int flags=0) : static_val(_static_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_val = __cself->static_val; // bound by copy

            global_val *= 1;
            static_global_val *= 2;
            (*static_val) *= 3;

            printf("static_val = %d, static_global_val = %d, global_val = %d\n",(*static_val), static_global_val, global_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[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        static int static_val = 30;

        void (*myLocalBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &static_val));

        ((void (*)(__block_impl *))((__block_impl *)myLocalBlock)->FuncPtr)((__block_impl *)myLocalBlock);
    }
    return 0;
}

我们主要来看下这段代码:

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int *static_val = __cself->static_val; // bound by copy

            global_val *= 1;
            static_global_val *= 2;
            (*static_val) *= 3;

            printf("static_val = %d, static_global_val = %d, global_val = %d\n",(*static_val), static_global_val, global_val);
        }

我们发现只有static_val是从__cself中获取的值

再来看看上面的一段代码:

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int *static_val;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_static_val, int flags=0) : static_val(_static_val) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
  • 我们发现全局变量,静态全局变量,我们的Block都没有用结构体去特地保存它
  • 只有对于我们的静态局部变量会来保存,但这里要注意,我们使用的不是int static_val,而是int *static_val
  • 也就是说我们使用一个指针来保存的静态局部变量
  • 它会直接保存该变量的地址,之后的操作也是直接对该值本身进行操作,而不是向之前截获的那些变量,等于是重新开辟空间进行保存

产生这个问题的原因:
原因在于,我们的静态变量是存在数据区的,在程序结束前它其实一直都会存在,之所以会被称为局部,只是说出了作用域无法调用到它了,并不是说这块数据不存在了。因此我们只要自己准备好一个指针,保证出了作用域依然能调用到他就行;而对于自动变量,它们真正的问题在于一但出了作用域,直接被释放了,所以要在结构体里开辟空间重新存放,进行值传递
在这里插入图片描述

__block说明符

来看书上的例子:

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

转换后的源码:

struct __Block_byref_val_0 {
 void *__isa;
 __Block_byref_val_0 *__forwarding;
 int __flags;
 int __size;
 int val;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_val_0 *val; // by ref
  __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_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_val_0 *val = __cself->val; 
  (val->__forwarding->val) = 1;
  printf("val = %d\n", (val->__forwarding->val));
}

static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
	_Block_object_assign((void*)&dst->val, (void*)src->val, BLOCK_FIELD_IS_BYREF);
}

static void __main_block_dispose_0(struct __main_block_impl_0*src {
	_Block_object_dispose((void*)src->val, BLOCK_FIELD_IS_BYREF);
}

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __main_block_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(int argc, const char * argv[]) {
	__Block_byref_val_0 val = {
    	0,
    	(__Block_byref_val_0 *)&val, 
    	0, 
    	sizeof(__Block_byref_val_0), 
    	10
    };
    blk = &__main_block_impl_0(
    	__main_block_func_0, &__main_block_desc_0_DATA, &val, 0x22000000);
    return 0;
}

和没加修饰符的比较来说,主要有两处不同:

struct __Block_byref_val_0 {
 void *__isa;
 __Block_byref_val_0 *__forwarding;
 int __flags;
 int __size;
 int val;
};

在第一个结构体中,我们发现多了一个__Block_byref_val_0 *__forwarding,这个相当于指向该结构体本身的一个指针。

第二个地方:

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_val_0 *val = __cself->val; 
  (val->__forwarding->val) = 1;
  printf("val = %d\n", (val->__forwarding->val));
}

在这里我们发现打印的并不是__Block_byref_val_0 *val而是(val->__forwarding->val)。

最后来看一下主函数:

int main(int argc, const char * argv[]) {
	__Block_byref_val_0 val = {
    	0,
    	(__Block_byref_val_0 *)&val, 
    	0, 
    	sizeof(__Block_byref_val_0), 
    	10
    };
    
    blk = &__main_block_impl_0(
    	__main_block_func_0, &__main_block_desc_0_DATA, &val, 0x22000000);
    return 0;
}

看下其中赋值部分:

__Block_byref_val_0 val = {
    	0,
    	(__Block_byref_val_0 *)&val, 
    	0, 
    	sizeof(__Block_byref_val_0), 
    	10
    };

这个__block变量val变为了__Block_byref_val_0结构体变量。通过调用 static void __main_block_func_0函数(通过__Block_byref_val_0结构体成员变量__forwarding访问成员变量val),将10赋给val。

iOS开发“强弱共舞”——weak和strong配套使用解决block循环引用问题

__weak是为了解决循环引用

如果一个对象A持有了一个block,同时block内又持有了对象A,为了解决循环引用我们要在用__weak修饰完对象A后再去持有它,这样就解决了循环引用。

__strong可以防止block持有的对象提前释放

我们用GCD延迟方法打印self的信息:

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    [self dismissViewControllerAnimated:YES completion:nil];
    
    __weak typeof(self) weakSelf = self;
    void (^Block) (void) = ^{
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"%@", weakSelf);
        });
    };
    Block();
}

输出结果:

在这里插入图片描述
点击屏幕,当前控制器消失,同时被销毁掉,5秒后打印的weakSelf就是一个(null),而我们如果在block内使用__strong后就能保证再打印完strongSelf之后再释放当前控制器。

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    [self dismissViewControllerAnimated:YES completion:nil];
    
    __weak typeof(self) weakSelf = self;
    void (^Block) (void) = ^{
        __strong typeof(self) strongSelf = weakSelf;
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"%@", strongSelf);
        });
    };
    Block();
}

请添加图片描述

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

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

相关文章

sentinel源码入门

sentinel是一个流量治理组件&#xff0c;主要用来做降级、熔断和限流&#xff0c;以及监控等&#xff0c;sentinel的使用相对来说比较简单&#xff0c;这篇博客主要介绍sentinel的源码 入口 在使用sentinel的时候&#xff0c;我们需要引入sentinel的相关jar包&#xff0c;然后…

如何使用码匠连接 ClickHouse

目录 在码匠中集成 ClickHouse 在码匠中使用 ClickHouse 关于码匠 ClickHouse 数据源是一种列式数据库管理系统&#xff0c;由俄罗斯 Yandex 公司开发和维护。它专注于高性能、高并发、高可扩展性和低延迟的数据处理&#xff0c;特别是在海量数据的 OLAP&#xff08;联机分析…

性能分析利器:火焰图

什么是火焰图 火焰图&#xff08;FlameGraph&#xff09;是是由 Linux 性能优化大师 Brendan Gregg 发明的。通过 perf 等工具分析得到结果&#xff0c;看起来就像是火焰&#xff0c;这也是它的名字的由来。火焰图以一个全局的视野来看待时间分布&#xff0c;它从底部往顶部&am…

Dockerfile部署SpringBoot项目

Dockerfile部署SpringBoot项目 文章目录 利用Dockerfile部署SpringBoot项目 1、创建一个SpringBooot项目并且打成jar包2、在Linux中创建一个文件夹&#xff0c;来做docker测试3、将jar包上传到Linux中4、编写Dockerfile文件5、制作镜像6、启动容器7、查看容器启动日志8、访问接…

高效能自动化港口数字化码头智慧港航,中国人工智能企业CIMCAI世界港航人工智能领军者,成熟港口码头人工智能产品中国人工智能企业

打造高效能自动化港口数字化码头智慧港航&#xff0c;中国人工智能企业CIMCAI中集飞瞳世界港航人工智能领军者&#xff0c;成熟港口码头人工智能产品全球顶尖AI科技CIMCAI成熟AI产品全球前三船公司及港口落地&#xff0c;包括全球港口/堆场智能闸口验箱&#xff0c;全球港口岸边…

Vulnhub系列:Raven 1

该篇为Vulnhub系列靶机渗透&#xff0c;本次靶机存在4个flag。下面开始我们今天的渗透之旅。Raven靶机有很多种思路&#xff0c;我将对其进行一一整理。首先进行信息收集&#xff0c;利用arp-scan和nmap&#xff0c;进行靶机的ip及端口扫描发现了22、80、111端口。下面访问80端…

【LeetCode】剑指 Offer 23. 链表中环的入口节点 p139 -- Java Version

题目链接&#xff1a;https://leetcode.cn/problems/c32eOV/ 1. 题目介绍&#xff08;23. 链表中环的入口节点&#xff09; 给定一个链表&#xff0c;返回链表开始入环的第一个节点。 从链表的头节点开始沿着 next 指针进入环的第一个节点为环的入口节点。如果链表无环&#x…

怎么清空回收站?3分钟解决!

真实案例&#xff1a;回收站清空不了怎么办&#xff1f;怎样清空回收站里的文件&#xff1f; “各位大神&#xff0c;怎么恢复回收站里清空的文件&#xff1f;我按照百度上的方法&#xff0c;还是不行。” 当你在电脑上删除文件时&#xff0c;它们通常会被移到回收站&#xf…

mpi4py 运行过程中出现Read -1, expected xxx, errno = 1 解决方案

目录 问题描述 代码1&#xff08;串行&#xff09; 代码2&#xff08;并行&#xff09; 代码2执行时所用指令 错误信息 解决方案 解决方案1 解决方案2 问题描述 今天正在学习使用mpi4py&#xff0c;在对比运行以下2个代码时疯狂报错&#xff1a; 代码1&#xff08;串…

HTML 脚本

HTML 脚本 JavaScript 是可插入 HTML 页面的编程代码。 JavaScript 使 HTML 页面具有更强的动态和交互性。 JavaScript 插入 HTML 页面后&#xff0c;可由所有的现代浏览器执行。 在线实例 插入一段脚本 如何将脚本插入 HTML 文档。 使用 <noscript> 标签 如何应对不…

非接触式流量监测设备——雷达流速仪

一、设备概述 雷达流速仪是一款非接触且可连续测量流速、水位、流量的一体式流量监测设备。它采用雷达平面微波技术&#xff0c;通过非接触方式测量水体的流速和水位。根据内置的软件算法&#xff0c;计算并输出实时断面流量及累计流量&#xff1b;可用于泵站、河道、灌渠、地…

扬帆优配|三大油气巨头都嗨了,龙头总市值盘中破万亿

近期组织盯上哪些股&#xff1f; 3月7日早盘&#xff0c;周期板块大幅上涨。石油板块涨超4%遥遥领先&#xff0c;海越能源涨停。油气巨头我国石油盘中涨近6%&#xff0c;总市值一度突破万亿元。我国石化盘中涨幅到达6.86%&#xff0c;股价创四年来新高。我国海油盘中涨停&#…

Rocketmq技术详解

Rocketmq技术详解 运维部署 docker-compose.yml version: 3.5 services:rmqnamesrv:image: foxiswho/rocketmq:servercontainer_name: rmqnamesrvports:- 9876:9876volumes:- ./logs:/opt/logs- ./store:/opt/storenetworks:rmq:aliases:- rmqnamesrvrmqbroker:image: foxisw…

后疫情时代,EIoT助力实体零售ESG战略落地

多点智能物联总经理刘汉彻 2023一开年&#xff0c;烟火气回归&#xff0c;各行各业都在摩拳擦掌&#xff0c;准备在后疫情元年“大干一场”。对企业而言&#xff0c;“降本增效”已成为跨行业的共同认知&#xff0c;实体零售也给自身的发展标定了方向&#xff1a;“彻底回归商…

CSS清楚浮动

先看看关于浮动的一些性质 浮动使元素脱离文档流 浮动元素可以设置宽高&#xff0c;在CSS中&#xff0c;任何元素都可以浮动&#xff0c;浮动元素会生成一个块级框&#xff0c;而不论其本身是何种元素。 如果没有给浮动元素指定高度&#xff0c;&#xff0c;那么它会以内容的…

JAVA SE: IO流

一、Java流式输入输出原理Java对于输入输出是以流(Stream)的方式进行的&#xff0c;JDK提供各种各样的“流”类&#xff0c;以获取不同类型的数据。可以理解为将管道插入到文件中&#xff0c;然后从管道获取数据。这个管道外边还可以套管道&#xff0c;外边的管道对数据进行处理…

Transwarp KunDB 备份恢复工具安装和备份工作自动化

备份恢复工具安装安装包# KunDR压缩包 KUNTOOL-Image-Registry-3.0.0-X86_64-final.tar.gzservice_client └── kuntools-3.0.0-final├── kundr-3.0.tar.gz└── kuntools-3.0.tar.gzkundr-3.0.tar.gz ├── bin │ ├── kunkun.sh │ ├── kuntools │ └──…

软件测试(进阶篇)(1)

一)如何根据需求来设计测试用例&#xff1f; 1)验证功能的正确性&#xff0c;合理性&#xff0c;无二义性&#xff0c;逻辑要正确 2)分析需求&#xff0c;细化需求&#xff0c;从需求中提取出测试项&#xff0c;根据测试项找到测试点&#xff0c;根据测试点具体的来进行设计测试…

C++基础——C++面向对象之类、对象与继承基础总结(类和对象概念、构造函数与析构函数、this指针、继承)

【系列专栏】&#xff1a;博主结合工作实践输出的&#xff0c;解决实际问题的专栏&#xff0c;朋友们看过来&#xff01; 《QT开发实战》 《嵌入式通用开发实战》 《从0到1学习嵌入式Linux开发》 《Android开发实战》 《实用硬件方案设计》 长期持续带来更多案例与技术文章分享…

14_MySQL视图

1. 常见的数据库对象2. 视图概述2.1 使用视图的好处视图一方面可以帮我们使用表的一部分而不是所有的表&#xff0c;另一方面也可以针对不同的用户制定不同的查询视图。比如&#xff0c;针对一个公司的销售人员&#xff0c;我们只想给他看部分数据&#xff0c;而某些特殊的数据…