【iOS】源码阅读(五)——类类的结构分析

文章目录

  • 前言
  • 类的分析
    • 类的本质
    • objc_class 、objc_object和NSObject
      • objc_object:所有对象的基类型
      • objc_class:类的底层结构
      • NSObject:面向用户的根类
    • 小结
  • 指针内存偏移
    • 普通指针----值拷贝
    • 对象----指针拷贝或引用拷贝
    • 用数组指针引出----内存偏移
  • 类的结构
    • Class ISA
    • Class surperclass
    • cache_t cache
    • class_data_bits_t bits
  • 总结

前言

  本篇博客主要是笔者在学习类&类的结构的底层探索和分析时所作的笔记,主要涉及实例对象的类以及类的结构。Objective-C的类结构是其动态性和面向对象特性的核心,理解类的内存布局和内部机制,对开发、调试和性能优化至关重要。

类的分析

类的本质

  在 OC 中,类(Class)本身是一个对象(objc_class 结构体),在探索类的本质之前,我们先在main文件里自定义一个继承自NSobjetc的TCJPerson类:

#import <Foundation/Foundation.h>
#import <objc/runtime.h>@interface TCJPerson : NSObject@end@implementation TCJPerson@endint main(int argc, const char * argv[]) {@autoreleasepool {TCJPerson *person = [TCJPerson alloc];Class cls = object_getClass(person);}return 0;
}

然后利用clang工具将oc语言的main文件输出为cpp文件,命令行如下:

clang -rewrite-objc main.m -o main.cpp

将文件拖到我们的objc源码中打开,方便我们调试和查找,可以发现,这段代码在cpp文件文件中如下:

//类型定义部分
#ifndef _REWRITER_typedef_TCJPerson
#define _REWRITER_typedef_TCJPerson
typedef struct objc_object TCJPerson; //将 TCJPerson 定义为 objc_object 结构体
typedef struct {} _objc_exc_TCJPerson; //空结构体,用于异常处理占位(实际未使用)
#endif//类的实现结构
struct TCJPerson_IMPL {struct NSObject_IMPL NSObject_IVARS; //继承自 NSObject 的实例变量
};/* @end */// @implementation TCJPerson
// @end
//main函数的底层转换
int main(int argc, const char * argv[]) {/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; TCJPerson *person = ((TCJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("TCJPerson"), sel_registerName("alloc"));//objc_getClass("TCJPerson"):获取 TCJPerson 的类对象(Class)//sel_registerName("alloc"):注册方法名 alloc,返回 SEL 类型的选择子//objc_msgSend:发送 alloc 消息给 TCJPerson 类,创建实例//强制类型转换 (TCJPerson *(*)(id, SEL))(void *) 是为了匹配 objc_msgSend 的函数指针签名Class cls = object_getClass(person);//获取 person 实例的类(即 TCJPerson 的类对象)}return 0;
}
  • TCJPerson 被定义为 objc_object,说明在底层,Objective-C 类实例的本质就是 objc_object 结构体。
  • 因为 TCJPerson 继承自 NSObject,所以它的底层结构会包含父类的实例变量。其中,NSObject_IVARS 就是 NSObject 的实例变量,通常就是 isa 指针(指向类的元数据)。

然后我们发现类在底层是用class接收的。
在这里插入图片描述

typedef struct objc_class *Class;

在左侧搜索栏查找objc_class,我们可以发现objc_class继承自objc_object。

请添加图片描述

点击进入objc_object的源码实现中:

在这里插入图片描述
小结

  • Objective-C 对象的本质:
    类实例(如 person)本质是 objc_object 结构体,包含 isa 指针(来自 NSObject_IVARS)。类(如 TCJPerson)本质是 objc_class 结构体,继承自 objc_object。所以满足万物皆对象。
  • 方法调用的本质:[TCJPerson alloc] 被编译为 objc_msgSend 的调用,动态查找并执行方法。
  • 内存布局:TCJPerson_IMPL 只包含 NSObject 的 isa,因为没有自定义实例变量。

objc_class 、objc_object和NSObject

objc_object:所有对象的基类型

底层源码:

struct objc_object {Class _Nonnull isa  OBJC_ISA_AVAILABILITY; //必须为非空的类指针(即指向 objc_class 结构体的指针)
};

这证明objc_object是所有 Objective-C 对象的最底层结构,包括实例对象、类对象、元类对象。其通过 isa 指针实现对象与类的关联(即“对象是什么类”)。

特点:

实例对象的 isa 指向它的类(如 TCJPerson 实例的 isa 指向 TCJPerson 类)。
类对象的 isa 指向元类(Meta Class)。
元类的 isa 指向根元类(Root Meta Class)。

关于类 元类 根元类

  1. ​​类(Class)​​
    ​​作用​​:定义对象的​​实例变量​​和​​实例方法​​(如 -init、-description)。
    ​​内存结构​​:每个类是一个 objc_class 结构体实例,包含方法列表、父类指针、缓存等。
    ​​对象关系​​:实例对象(如 MyObject *obj)的 isa 指针指向其类对象。
  2. ​​元类(Meta-class)​​
    ​​作用​​:定义类的​​类方法​​(如 +alloc、+new)。
    元类本身也是一个类,它的实例是类对象。
    ​​内存结构​​:元类也是一个 objc_class 结构体,但其方法列表存储类方法。
    ​​对象关系​​:类对象(如 MyObject.class)的 isa 指针指向其元类。
  3. ​​根元类(Root Meta-class)​​
    ​​作用​​:所有元类的最终基类,通常是 NSObject 的元类。
    根元类的类方法(如 +alloc)会被所有类的元类继承。
    根元类的 isa 指针指向自身,形成闭环。

在这里插入图片描述

​​Instance (MyObject)​​: 一个对象实例
​​Class (MyObject class)​​: 定义该实例的类
​​Meta-class (MyObject meta)​​: 定义该类的元类

isa 指针:形成继承链的核心,每个实例、类和元类都有一个指向其类型的指针
实例通过 isa 指向类,类通过 isa 指向元类,元类通过 isa 指向根元类
super_class 指针:形成继承体系
类通过 super_class 指向父类,元类通过 super_class 指向父元类

​​Root Meta-class (NSObject meta)​​: 所有元类的根元类
根元类的 isa 指向自己(self),形成闭环
根元类的 super_class 指向 NSObject 类

运行时​​方法查找路径​​:
实例方法首先在实例所属的类中查找
如果没找到,则沿着 super_class 链向上查找
类方法则在元类及其父元类中查找
​​
继承机制​​:实例继承自类,类继承自元类,元类继承自父元类,最终根元类继承自NSObject类

objc_class:类的底层结构

底层代码:

struct objc_class : objc_object { //继承自objc_objectobjc_class(const objc_class&) = delete;objc_class(objc_class&&) = delete;void operator=(const objc_class&) = delete;void operator=(objc_class&&) = delete;// Class ISA;Class superclass;            //父类指针cache_t cache;             // formerly cache pointer and vtable  //方法缓存class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags  //类的方法、属性、协议等数据Class getSuperclass() const {
#if __has_feature(ptrauth_calls)
#   if ISA_SIGNING_AUTH_MODE == ISA_SIGNING_AUTHif (superclass == Nil)return Nil;......
}

这说明objc_class是 objc_object 的子类,说明类本身也是对象(即“类对象”)。
存储类的元数据:方法列表、属性列表、协议列表、父类指针等。
因为其继承自 objc_object,所以其类对象也有 isa 指针(指向元类)。
其中,Class 是指向 objc_class 的指针(typedef struct objc_class *Class)。

NSObject:面向用户的根类

底层代码:

@interface NSObject <NSObject> {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"Class isa  OBJC_ISA_AVAILABILITY; //指向类对象的指针
#pragma clang diagnostic pop
}

NSObject 是 Objective-C 中所有类的根类(除了 NSProxy),提供面向开发者的基础方法(如 alloc、init、description)。

与 objc_object 的关系:
NSObject 的实例对象在底层就是 objc_object。
NSObject 的类对象在底层是 objc_class。
编译器会将 NSObject 的代码转换为对 objc_object 和 objc_class 的操作。

小结

通过对objc_class、objc_object和NSObject底层的大致了解,我们就可以明白三者存在以下金字塔关系:
请添加图片描述
objc_object:所有对象的终极基类(C结构体)
objc_class:继承objc_object,说明"类也是对象"
NSObject:继承链最顶端的公开类,封装了objc_class的面向对象接口

小结

所有的对象 + 类 + 元类 都有isa属性
所有的对象都是由objc_object继承来的
简单概括就是万物皆对象,万物皆来源于objc_object,有以下两点结论:
所有以 objc_object为模板创建的对象,都有isa属性
所有以 objc_class为模板创建的类,都有isa属性

在结构层面可以通俗的理解为上层OC 与 底层的对接:

下层是通过 结构体 定义的 模板,例如objc_class、objc_object
上层是通过底层的模板创建的一些类型,例如TCJPerson

objc_class、objc_object、isa、object、NSObject等的整体的关系如下图:
请添加图片描述

指针内存偏移

  在分析类的结构之前,我们需要先来学习一下指针内存偏移作为前缀知识。

普通指针----值拷贝

        //普通指针//值拷贝int a = 10;int b = 10;NSLog(@"&a:%d--%p", a, &a);NSLog(@"&b:%d--%p", b, &b);

请添加图片描述

通过代码及其运行结果可以看出来,变量a和b虽然都是被赋值为10,但是变量a和b的内存地址是不一样的,我们称这种方式为值拷贝。

对象----指针拷贝或引用拷贝

        //对象NSObject *obj1 = [[NSObject alloc] init];NSObject *obj2 = [[NSObject alloc] init];NSLog(@"%@--%p", obj1, &obj1);NSLog(@"%@--%p", obj2, &obj2);

在这里插入图片描述
通过运行结果,我们可以看到obj1和obj2对象不仅自身内存地址不一样,其指向的对象的内存地址也不一样,这被称为指针拷贝或引用拷贝。

用数组指针引出----内存偏移

        //数组指针int arr[4] = {1, 2, 3, 4};int *c = arr;NSLog(@"&arr:%p--%p--%p", arr, &arr[0], &arr[1]);NSLog(@"&c:%p--%p--%p", c, c + 1, c + 2);for (int i = 0; i < 4; i++) {int value = *(c + i);NSLog(@"value:%d", value);}

请添加图片描述

通过运行结果可以看到:

  • &a和&a[0]的地址是相同的——即首地址就代表数组的第一个元素的地址。
  • 第一个元素地址0x16fdff2f8和第二个元素地址0x16fdff2fc相差4个字节,也就是int的所占的4字节,因为他们的数据类型相同。
  • d、d+1、d+2这个地方的指针相加就是偏移地址。地址加1就是偏移,偏移一个位数所在元素的大小。
  • 可以通过地址,取出对应地址的值。

小结
在这里插入图片描述

类的结构

从objc_class的定义可以得出,类有4个属性:isa、superclass、cache、bits。

Class ISA

不但实例对象中有isa指针,类对象中也有isa指针关联着元类。
Class本身就是一个指针,占用8字节。

Class surperclass

顾名思义就是类的父类(一般为NSObject)superclass是Class类型,所以占用8字节。

cache_t cache

进入cache_t的实现源码,我们能看到(笔者对部分进行了注释,方便理解):

struct cache_t {
private:explicit_atomic<uintptr_t> _bucketsAndMaybeMask; //原子存储缓存桶指针或掩码(根据配置不同复用同一内存)//原子性​​:保证并发访问时的线程安全(如 objc_msgSend 高频调用场景)union {// Note: _flags on ARM64 needs to line up with the unused bits of// _originalPreoptCache because we access some flags (specifically// FAST_CACHE_HAS_DEFAULT_CORE and FAST_CACHE_HAS_DEFAULT_AWZ) on// unrealized classes with the assumption that they will start out// as 0.struct {
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED && !__LP64__// Outlined cache mask storage, 32-bit, we have mask and occupied.explicit_atomic<mask_t>    _mask; //缓存掩码uint16_t                   _occupied; //已占用槽位数
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED && __LP64__// Outlined cache mask storage, 64-bit, we have mask, occupied, flags.explicit_atomic<mask_t>    _mask;uint16_t                   _occupied;uint16_t                   _flags; //状态标志(FAST_CACHE_HAS_DEFAULT_CORE: 是否有默认核心实现 FAST_CACHE_HAS_CUSTOM_DEALLOC_INITIATION: 是否自定义释放逻辑)
#   define CACHE_T_HAS_FLAGS 1
#elif __LP64__// Inline cache mask storage, 64-bit, we have occupied, flags, and// empty space to line up flags with originalPreoptCache.//// Note: the assembly code for objc_release_xN knows about the// location of _flags and the// FAST_CACHE_HAS_CUSTOM_DEALLOC_INITIATION flag within. Any changes// must be applied there as well.uint32_t                   _unused;uint16_t                   _occupied;uint16_t                   _flags;
#   define CACHE_T_HAS_FLAGS 1
#else// Inline cache mask storage, 32-bit, we have occupied, flags.uint16_t                   _occupied;uint16_t                   _flags;
#   define CACHE_T_HAS_FLAGS 1
#endif

cache在英文中的意思是缓存。
cache_t是一个结构体,内存长度由所有元素决定:

_bucketsAndMaybeMask是long类型,它是一个指针,占用8字节;
mask_t是个uint32_t类型,_mask占用4字节;
_occupied和_flags都是uint16_t类型,uint16_t是 unsigned short 的别名,所以_occupied占用2字节;
_flags占用2字节;
所以,cache_t共占用16字节。

这里对cache简单了解一下,后面还会呢详细学习。

class_data_bits_t bits

class_data_bits_t实现源码如下:

struct class_data_bits_t {friend objc_class;// Values are the FAST_ flags above.uintptr_t bits;
private:bool getBit(uintptr_t bit) const{return bits & bit;}// Atomically set the bits in `set` and clear the bits in `clear`.// set and clear must not overlap.  If the existing bits field is zero,// this function will mark it as using the RW signing scheme.void setAndClearBits(uintptr_t set, uintptr_t clear){ASSERT((set & clear) == 0);uintptr_t newBits, oldBits = LoadExclusive(&bits);do {uintptr_t authBits= (oldBits? (uintptr_t)ptrauth_auth_data((class_rw_t *)oldBits,CLASS_DATA_BITS_RW_SIGNING_KEY,ptrauth_blend_discriminator(&bits,CLASS_DATA_BITS_RW_DISCRIMINATOR)): FAST_IS_RW_POINTER);newBits = (authBits | set) & ~clear;newBits = (uintptr_t)ptrauth_sign_unauthenticated((class_rw_t *)newBits,CLASS_DATA_BITS_RW_SIGNING_KEY,ptrauth_blend_discriminator(&bits,CLASS_DATA_BITS_RW_DISCRIMINATOR));} while (slowpath(!StoreReleaseExclusive(&bits, &oldBits, newBits)));}void setBits(uintptr_t set) {setAndClearBits(set, 0);}void clearBits(uintptr_t clear) {setAndClearBits(0, clear);}public:void copyRWFrom(const class_data_bits_t &other) {bits = (uintptr_t)ptrauth_auth_and_resign((class_rw_t *)other.bits,CLASS_DATA_BITS_RW_SIGNING_KEY,ptrauth_blend_discriminator(&other.bits,CLASS_DATA_BITS_RW_DISCRIMINATOR),CLASS_DATA_BITS_RW_SIGNING_KEY,ptrauth_blend_discriminator(&bits,CLASS_DATA_BITS_RW_DISCRIMINATOR));}void copyROFrom(const class_data_bits_t &other, bool authenticate) {ASSERT((flags() & RO_REALIZED) == 0);if (authenticate) {bits = (uintptr_t)ptrauth_auth_and_resign((class_ro_t *)other.bits,CLASS_DATA_BITS_RO_SIGNING_KEY,ptrauth_blend_discriminator(&other.bits,CLASS_DATA_BITS_RO_DISCRIMINATOR),CLASS_DATA_BITS_RO_SIGNING_KEY,ptrauth_blend_discriminator(&bits,CLASS_DATA_BITS_RO_DISCRIMINATOR));} else {bits = other.bits;}}

虽然我们不是很能看懂,但至少能看出来这里是oc运行时用来存储数据的。
根据上面的分析,class指针各为8字节,cache_t cache为16字节,所以想要获取bits的中的内容,只需通过类的首地址平移32字节即可。

总结

  在学习过程中,笔者关于LLDB调试出了比较多的问题,至今还没解决,所以这篇笔记还有待完善,等笔者解决完问题后,会再次进行编撰,LLDB在源码学习中还是很不错的工具。

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

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

相关文章

Baklib构建企业CMS高效协作与安全管控体系

企业CMS高效协作体系构建 基于智能工作流引擎的设计逻辑&#xff0c;现代企业内容管理系统通过预设多节点审核路径与自动化任务分配机制&#xff0c;有效串联市场、技术、法务等跨部门协作链路。系统支持多人同时编辑与版本追溯功能&#xff0c;结合细粒度权限管控模块&#x…

Linux环境变量与地址空间

哈喽&#xff0c;各位Linux初学者们&#xff01;今天咱们来聊聊Linux中那两个看起来很高大上但实际上跟我们日常使用息息相关的概念&#xff1a;环境变量和地址空间。别被这些术语吓到&#xff0c;我会用最接地气的方式给你解释清楚&#xff01; 一、环境变量&#xff1a;Linu…

Oracle SHARED POOL的SUB POOL技术

从Oracle 9i开始&#xff0c;SHARED POOL可以分为多个SUB POOL&#xff0c;其数量受以下几个因素影响&#xff1a; 系统CPU的数量。默认情况下&#xff0c;在Oracle中每4个CPU分配一个SUB POOL&#xff0c;最多不能超过7个。 共享池的大小。SUB POOL的最小容量随着Oracle版…

Collection集合遍历的三种方法

1.foreach循环遍历 格式&#xff1a;for&#xff08;元素的数据类型 变量名&#xff1a;数组或集合&#xff09;{ } 2.使用迭代器遍历 方法名称&#xff1a;Iterator<E> iterator&#xff08;&#xff09; 说明&#xff1a;返回集合中的迭代器对象&#xff0c;该迭代…

头歌之动手学人工智能-Pytorch 之autograd

目录 第1关&#xff1a;Variable 任务描述 编程要求 测试说明 没有伟大的愿望&#xff0c;就没有伟大的天才。——巴尔扎克开始你的任务吧&#xff0c;祝你成功&#xff01; 第2关&#xff1a;Variable 属性 任务描述 编程要求 测试说明 真正的科学家应当是个幻想家&a…

篇章二 数据结构——前置知识(二)

目录 1. 包装类 1.1 包装类的概念 1.2 基本数据类型和对应的包装类 1.3 装箱和拆箱 1.4 自动装箱和自动拆箱 1.5 练习 —— 面试题 2. 泛型 2.1 如果没有泛型——会出现什么情况&#xff1f; 2.2 语法 2.3 裸类型 1.没有写<> 但是没有报错为什么&#xff1f; …

Git典型使用场景相关命令

Git典型使用场景相关命令 1 建立本地仓库与远程仓库的联系2 作为开发者参与项目的常用命令2-1 一般步骤2-2 **合并与同步主分支改动**2-3 **查看日志和差异**2-4 **提交后想修改或撤销**2-5 分支管理2-6 清除未被追踪的文件&#xff08;谨慎使用&#xff09; 3 作为远程仓库管理…

redis缓存-更新策略-三大缓存问题

缓存&#xff1a;数据交换的缓冲区&#xff0c;存储的数据的临时地方&#xff0c;读写性能较高。 步骤&#xff1a; 先从redis里面查询 缓存命中&#xff1a;直接返回结果缓存未命中 从数据库里面查询 没有数据&#xff1a;返回null有数据&#xff1a;存到redis里面&#xff…

[TriCore] 01.QEMU 虚拟化 TriCore 架构中的寄存器 指令

目录 1.寄存器宏 - FIELD() 2.寄存器操作 - FIELD_SETTER() & FIELD_GETTER() 3.指令辅助方法 - HELPER() 3.1.辅助宏 3.2.指令示例 3.3.函数调用 4.PSW 寄存器读写 - psw_read() & psw_write() 1.寄存器宏 - FIELD() FIELD() 宏定义寄存器 MASK // include/hw…

《软件工程》第 4 章 - 需求获取

在软件工程中&#xff0c;需求获取是挖掘用户真实需求的关键步骤&#xff0c;它为后续的设计、开发和测试提供坚实基础。本章将围绕需求获取的流程、方法及工具展开&#xff0c;结合实际案例与 Java 代码&#xff0c;深入讲解这一重要环节。 4.1 软件需求的初始表示 4.1.1 用例…

react diff 算法

diff 算法作为 Virtual DOM 的加速器&#xff0c;其算法的改进优化是 React 整个界面渲染的基础和性能的保障&#xff0c;同时也是 React 源码中最神秘的&#xff0c;最不可思议的部分 diff 算法会帮助我们就算出 VirtualDOM 中真正变化的部分&#xff0c;并只针对该部分进行原…

Gin项目脚手架与标配组件

文章目录 前言设计思想和原则✨ 技术栈视频实况教程sponge 内置了丰富的组件(按需使用)几个标配常用组件主要技术点另一个参考链接 前言 软件和汽车一样&#xff0c;由多个重要零部件组装而成。 本文堆积了一些常用部件&#xff0c;还没来得及好好整理。先放着。 神兵利器虽多…

【Webtrees 手册】第 10章 - 用户体验

Webtrees 手册/用户体验 < Webtrees 手册 跳转到导航跳转到搜索 信息 手册部分仍在建设中 请耐心等待或随意贡献自己的力量:-)。 第 10 章 - 用户体验 <- 章节概述 目录 1多位系谱学家的合作 1.1家庭研究模型1.2“孤胆战士”模型1.3示范“本地家庭书”1.4模特“俱乐部”…

Linux 进程概念(下)

目录 前言 4.进程状态 一.普遍的操作系统层面上宏观概念&#xff1a; 二.具体的Linux操作系统的状态&#xff1a; 5.进程优先级&#xff08;了解&#xff09; 6.其他概念 进程切换 前言 本篇是接着上一篇的内容继续往下了解进程相关的一些概念&#xff01; 4.进程状态 运…

使用java实现word转pdf,html以及rtf转word,pdf,html

word,rtf的转换有以下方案&#xff0c;想要免费最靠谱的是LibreOffice方案, LibreOffice 是一款 免费、开源、跨平台 的办公软件套件&#xff0c;旨在为用户提供高效、全面的办公工具&#xff0c;适用于个人、企业和教育机构。它支持多种操作系统&#xff08;Windows、macOS、…

IP证书的作用与申请全解析:从安全验证到部署实践

在网络安全领域&#xff0c;IP证书&#xff08;IP SSL证书&#xff09;作为传统域名SSL证书的补充方案&#xff0c;专为公网IP地址提供HTTPS加密与身份验证服务。本文将从技术原理、应用场景、申请流程及部署要点四个维度&#xff0c;系统解析IP证书的核心价值与操作指南。 一…

GitLab 18.0 正式发布,15.0 将不再受技术支持,须升级【三】

GitLab 是一个全球知名的一体化 DevOps 平台&#xff0c;很多人都通过私有化部署 GitLab 来进行源代码托管。极狐GitLab 是 GitLab 在中国的发行版&#xff0c;专门为中国程序员服务。可以一键式部署极狐GitLab。 学习极狐GitLab 的相关资料&#xff1a; 极狐GitLab 官网极狐…

超简单Translation翻译模型部署

Helsinki-NLP/opus-mt-{en}-{zh}系列翻译模型可以实现200多种语言翻译&#xff0c;Helsinki-NLP/opus-mt-en-zh是其中英互译模型。由于项目需要&#xff0c;在本地进行搭建&#xff0c;并记录下搭建过程&#xff0c;方便后人。 1. 基本硬件环境 CPU&#xff1a;N年前的 Intel…

Go语言JSON 序列化与反序列化 -《Go语言实战指南》

JSON&#xff08;JavaScript Object Notation&#xff09;是一种常见的数据交换格式。Go 标准库提供了 encoding/json 包&#xff0c;用于方便地将结构体与 JSON 之间互转。 一、序列化&#xff08;Marshal&#xff09; 将 Go 中的数据结构&#xff08;如结构体、map、slice 等…

免费PDF工具-PDF24V9.16.0【win7专用版】

【百度】https://pan.baidu.com/s/1H7kvHudG5JTfxHg-eu2grA?pwd8euh 提取码: 8euh 【夸克】https://pan.quark.cn/s/92080b2e1f4c 【123】https://www.123912.com/s/0yvtTd-XAHjv https://creator.pdf24.org/listVersions.php