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

news2025/6/2 21:22:24

文章目录

  • 前言
  • 类的分析
    • 类的本质
    • 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

@end

int 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_object
  objc_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_AUTH
        if (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.coloradmin.cn/o/2394333.html

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

相关文章

基于CangjieMagic的RAG技术赋能智能问答系统

目录 引言 示例程序分析 代码结构剖析 导入模块解读 智能体配置详情 提示词模板说明 主程序功能解析 异步聊天功能实现 检索信息展示 技术要点总结 ollama 本地部署nomic-embed-text 运行测试 结语 引言 这段时间一直在学习CangjieMagic。前几天完成了在CangjieMa…

算力租赁革命:弹性模式如何重构数字时代的创新门槛​

一、算力革命&#xff1a;第四次工业革命的核心驱动力​ 在科技飞速发展的当下&#xff0c;我们正悄然迎来第四次工业革命。华为创始人任正非在一场程序设计竞赛中曾深刻指出&#xff0c;这场革命的基础便是大算力。随着 5G、人工智能、大数据、物联网等信息技术的迅猛发展&am…

图论回溯

图论 200.岛屿数量DFS 给你一个由 ‘1’&#xff08;陆地&#xff09;和 ‘0’&#xff08;水&#xff09;组成的的二维网格&#xff0c;请你计算网格中岛屿的数量。岛屿总是被水包围&#xff0c;并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。此外&#xff…

RFID测温芯片助力新能源产业安全与能效提升

在“双碳”目标驱动下&#xff0c;新能源产业正经历爆发式增长。无论是电动汽车、储能电站还是风光发电场&#xff0c;设备安全与能效提升始终是行业核心命题。而温度&#xff0c;这个看似普通的物理参数&#xff0c;却成为破解这一命题的关键密码。RFID测温芯片&#xff08;集…

S32K3 工具篇9:如何在无源码情况下灵活调试elf文件

S32K3 工具篇9&#xff1a;如何在无源码情况下灵活调试elf文件 一&#xff0c;文档简介二&#xff0c; 功能实现2.1 代码工具准备2.2 elf修改功能实现&#xff1a;Fun2功能跳过2.2.1 PC越过Fun22.2.2 Fun2替换为nop 2.3 elf修改功能实现&#xff1a;Fun4替换Fun2入口2.3.1 link…

Nacos 配置文件总结

Nacos 配置文件总结 文章目录 Nacos 配置文件总结1 、在 Nacos 服务端添加配置文件1. 启动Nacos Server。2. 新建配置文件。3. 发布配置集后&#xff0c;我们便可以在配置列表中查看相应的配置文件。4. 配置nacos数据库5. 运行 Nacos 容器6. 验证安装结果7. 配置验证 2 、在 Na…

ASP.NET Web Forms框架识别

ASP.NET 支持三种不同的开发模式&#xff1a; Web Pages&#xff08;Web 页面&#xff09;、MVC&#xff08;Model View Controller 模型-视图-控制器&#xff09;、Web Forms&#xff08;Web 窗体&#xff09;&#xff1a; Web Pages 单页面模式MVC 模型-视图-控制器Web Form…

哈工大计统大作业-程序人生

摘 要 本项目以“程序人生-Hellos P2P”为核心&#xff0c;通过编写、预处理、编译、汇编、链接及运行一个简单的Hello程序&#xff0c;系统探讨了计算机系统中程序从代码到进程的全生命周期。实验基于Ubuntu环境&#xff0c;使用GCC工具链完成代码转换&#xff0c;分析了预处…

设计模式——装饰器设计模式(结构型)

摘要 文中主要介绍了装饰器设计模式&#xff0c;它是一种结构型设计模式&#xff0c;可在不改变原有类代码的情况下&#xff0c;动态为对象添加额外功能。文中详细阐述了装饰器模式的角色、结构、实现方式、适合场景以及实战示例等内容&#xff0c;还探讨了其与其他设计模式的…

途景VR智拍APP:开启沉浸式VR拍摄体验

在数字化时代&#xff0c;VR技术以其沉浸式的体验逐渐走进了人们的日常生活。途景VR智拍APP作为一款集看图和拍照于一体的VR软件&#xff0c;为用户带来了全新的视觉体验和便捷的拍摄方式&#xff0c;无论是专业摄影师还是普通用户&#xff0c;都能轻松上手&#xff0c;拍出令人…

Linux环境搭建MCU开发环境

操作系统版本&#xff1a; ubuntu 22.04 文本编辑器&#xff1a; vscode 开发板&#xff1a; stm32f103c8t6 调试器&#xff1a; st-link 前言 步骤一&#xff1a; 安装交叉编译工具链 步骤二&#xff1a; 创建工程目录结构 步骤三&#xff1a; 调试…

【基础算法】高精度(加、减、乘、除)

文章目录 什么是高精度1. 高精度加法解题思路代码实现 2. 高精度减法解题思路代码实现 3. 高精度乘法解题思路代码实现 4. 高精度除法 (高精度 / 低精度)解题思路代码实现 什么是高精度 我们平时使用加减乘除的时候都是直接使用 - * / 这些符号&#xff0c;前提是进行运算的数…

Windows最快速打开各项系统设置大全

目录 一、应用背景 二、设置项打开方法 2.1 方法一界面查找&#xff08;最慢&#xff09; 2.2 方法二cmd命令&#xff08;慢&#xff09; 2.3 方法三快捷键&#xff08;快&#xff09; 2.4 方法四搜索栏&#xff08;快&#xff09; 2.5 方法五任务栏&#xff08;最快&am…

嵌入式编译工具链熟悉与游戏移植

在自己的虚拟机Ubuntu系统下&#xff0c;逐步编译 mininim源码(波斯王子重制开源版&#xff09; 指令流程 sudo apt-get remove liballegro5-dev liballegro-image5-dev \liballegro-audio5-dev liballegro-acodec5-dev liballegro-dialog5-dev sudo apt-get install automak…

DeepSeek-R1-0528,官方的端午节特别献礼

DeepSeek&#xff1a;端午安康&#xff01;刻在国人骨子里的浪漫 2025 年 05 月 28 日 | DeepSeek 端午特别献礼 当粽叶飘香时&#xff0c;DeepSeek 悄然带来一份节日惊喜 版本号 DeepSeek-R1-0528 正式上线 官方赋予它的灵魂是&#xff1a; 思考更深 推理更强 用户通过官网…

001 flutter学习的注意事项及前期准备

在学习flutter之前&#xff0c;还需要进行一些初始的配置&#xff0c;然后才可以学习flutter 1.安装flutter 国内官网&#xff1a;https://flutter.cn​​​​​​ 国际官网&#xff1a;https://flutter.dev 安装完成后&#xff0c;按照官网上面的操作步骤进行配置&#xf…

CS144 - Lecture 1 记录

CS144 - Lecture 1 由于没讲义&#xff0c;全看课了&#xff0c;系统性的总结有点难&#xff0c;记一些有趣的东西吧。 数据链路和网络层的传输 我们可以看见&#xff0c;对于发送方&#xff0c;我们的数据链路层为我们的网络层提供服务&#xff0c;在经过路由的时候&#xf…

【数据结构】——二叉树--链式结构

一、实现链式结构二叉树 二叉树的链式结构&#xff0c;那么从名字上我们就知道我们这个二叉树的底层是使用链表来实现的&#xff0c;前面我们的二叉树是通过数组来实现的&#xff0c;那么在其是完全二叉树的情况下&#xff0c;此时我们使用数组来实现就会使得其空间浪费较少&a…

充电便捷,新能源汽车移动充电服务如何预约充电

随着新能源汽车的普及&#xff0c;充电便捷性成为影响用户体验的关键因素之一。传统的固定充电桩受限于地理位置和数量&#xff0c;难以完全满足用户需求&#xff0c;而移动充电服务的出现&#xff0c;为车主提供了更加灵活的补能方式。通过手机APP、小程序或在线平台&#xff…

基于 Chrome 浏览器扩展的Chroma简易图形化界面

简介 ChromaDB Manager 是基于 Chrome 浏览器扩展的一款 ChromaDB&#xff08;一个流行的向量数据库&#xff09;的数据查询工具。提供了一个用户友好的界面&#xff0c;可以直接从浏览器连接到本地 ChromaDB 实例、查看集合信息和分片数据。本工具特别适合开发人员快速查看和…