【iOS】--对象的底层结构
源码
先转一下源码
//#import <Foundation/Foundation.h>
#import <objc/runtime.h>@interface LGPerson : NSObject
@property (nonatomic, strong) NSString *KCName;
@end@implementation LGPerson@endint main(int argc, const char * argv[]) {@autoreleasepool {// insert code here...NSLog(@"Hello, World!");}return 0;
}
clang -rewrite-objc main.m -o main.cpp
编译成功了之后就会出现这样的一个.cpp文件,这就是转换后的C++文件了:
对象
点开.cpp文件之后在里面找到我们刚才定义的继承类:
- OC中的LGPerson类底层是struct LGPerson_IMPL结构体
- OC中@interface LGPerson : NSObject,LGPerson继承NSObject底层是typedef struct objc_object LGPerson;这样体现的
由此可见:OC对象的本质就是结构体。
我们再顺藤摸瓜,找到struct NSObject_IMPL NSObject_IVARS;这个类型:
struct NSObject_IMPL结构体事实上是一个Class类型的isa指针,那么也就是说明,每一个新定义的类,它对应的结构体中都会有一个这个Class类型的isa指针。
并且NSObject底层是struct objc_object结构体。
Class superclass; //指向其父类
cache_t cache; //缓存
class_data_bits_t bits; //类的数据
可以看出objc_class是继承objc_object的。
看到这我们先来浅总结一下:
每个类的底层都会有一个Class类的isa指针。
Class底层是struct objc_class *类型,NSObject底层是struct objc_object结构体,id底层是struct objc_object *类型。
struct objc_object的实现是:
struct objc_object {Class _Nonnull isa __attribute__((deprecated));
};
也就是说NSObject底层实现的结构体里只有一个成员变量isa,又因为Class底层是struct objc_class *类型,所以 NSObject的本质是objc_class。
id底层实现是struct objc_object 类型,怪不得声明id类型变量时 后面不用再加""了,因为它定义的时候就定义为了一个Class的指针。
- SEL是struct objc_selector *类型。
- IMP是void (*)(void )函数指针类型。
首先数入参LGPerson * self,SEL _cmd,所有OC方法都有这两个隐藏参数,所以我们可以在OC的方法中使用self指针,但是这两个参数对外是不显示的。
然后可以看到getter和setter里是通过首地址指针+对应成员变量的地址值指针的偏移量的方式取和存的,最终通过(*(NSString **)还原为string类型。取值的过程就是:先拿到当前成员变量的地址,再去取这个地址里面所存的值
objc_class 和 objc_object 有什么关系?
- 结构体objc_class继承自objc_object,其中objc_object也是一个结构体,而且有一个isa属性,所以objc_class也拥有了isa属性。
- main.cpp底层编译文件中,NSObject的isa在底层是由class定义的,其中class的底层编码来自于objc_class类型,所以NSObject也拥有了isa属性。
- NSObject是一个类,用它来初始化一个实例对象 objc,objc满足objc_object的特性(有isa属性),主要是因为isa是由NSObject从objc_class继承过来的,而objc_class继承自objc_object,objc_object有isa属性,所以对象都有一个isa,isa 表示指向,来自于当前的objc_object。
- objc_object是当前的根对象,所以所有的对象都拥有isa 属性。
objc_object与对象的关系:
- 所有对象都是以objc_object为模板继承过来的。
- 所有对象都来自于NSObject,但是其底层是一个objc_object的结构体类型,所以objc_object与对象的关系是继承关系。
类结构
struct objc_class : objc_object {...省略无关代码// Class ISA; //ISA(从objc_object继承过来的)Class superclass; //指向其父类cache_t cache; //缓存class_data_bits_t bits; //类的数据class_rw_t *data() const {return bits.data();}void setData(class_rw_t *newData) {bits.setData(newData);}...省略无关代码
}
从上面的结构体,可以看出:类是一个结构体,里面存放了isa、superClass、cache、bits等。
Class ISA
isa指针保存着指向类对象的内存地址,类对象全局只有一个,因此每个类创建出来的对象都会默认有一个isa属性,保存类对象的地址,也就是class,通过class就可以查询到这个对象的属性和方法,协议等;
isa分两种类型(isa指针是什么含义的时候):
- 指针型isa:64位的0或者1的整体内容代表所指向的Class的地址,也就是可以通过isa的内容来获得类对象的地址。
- 非指针型isa:isa的值的部分代表Class的地址,之所以这样是因为我们在寻址过程中,只有三四十位数就可以保证我们寻找到所有Class地址了,多出来的位可以用来存储其他相关内容,来达到节省内存的目的。
每个OC对象都含有一个isa指针,__arm64__之前,isa仅仅是一个指针,保存着对象或类对象内存地址,在__arm64__架构之后,apple对isa进行了优化,变成了一个共用体(union)结构,同时使用位域来存储更多的信息。
- nonpointer:用来标记这个对象是不是tagpointer类型的对象,因为iOS对oc对象进行了优化处理,有些对象是tagpointer类型的,因此这些对象是没有isa指针的,tagpointer的内存一般是在栈中的,而不是在堆里面;tagpointer对象一般是NSNumber类型的数值较小的数,或NSString类型的较小的字符串。
- has_assoc:用来标记有没有关联对象。
- has_cxx_dtor:该对象是否有C++或者Objc的析构器,如果有析构函数,则需要做析构逻辑,如果没有,则可以更快的释放对象。
- shiftcls:存储的isa指针地址,也就是类对象的地址。
- magic:用于调试器判断当前对象是真的对象还是没有初始化的空间。
- weakly_referenced:对象是否被指向或者曾经指向一个ARC的弱变量,没有弱引用的对象可以更快释放。
- deallocating:标志对象是否正在释放内存。
has_sidetable_rc:标记对象是否使用了Sidetable,当对象引用计数大于10时,则需要借用该变量存储进位。 - extra_rc:当表示该对象的引用计数值,实际上是引用计数值减1, 例如:如果对象的引用计数为10,那么extra_rc为9。如果引用计数大于10,则需要使用到下面的 has_sidetable_rc。
union isa_t
{Class cls;uintptr_t bits;struct {uintptr_t nonpointer : 1;//->表示使用优化的isa指针uintptr_t has_assoc : 1;//->是否包含关联对象uintptr_t has_cxx_dtor : 1;//->是否设置了析构函数,如果没有,释放对象更快uintptr_t shiftcls : 33; // MACH_VM_MAX_ADDRESS 0x1000000000 ->类的指针uintptr_t magic : 6;//->固定值,用于判断是否完成初始化uintptr_t weakly_referenced : 1;//->对象是否被弱引用uintptr_t deallocating : 1;//->对象是否正在销毁uintptr_t has_sidetable_rc : 1;//1->在extra_rc存储引用计数将要溢出的时候,借助Sidetable(散列表)存储引用计数,has_sidetable_rc设置成1uintptr_t extra_rc : 19; //->存储引用计数,实际的引用计数减一,存储的是其对象以外的引用计数};
};
inline void
objc_object::initIsa(Class cls, bool nonpointer, UNUSED_WITHOUT_INDEXED_ISA_AND_DTOR_BIT bool hasCxxDtor)
{ ASSERT(!isTaggedPointer()); isa_t newisa(0);if (!nonpointer) {newisa.setClass(cls, this);} else {ASSERT(!DisableNonpointerIsa);ASSERT(!cls->instancesRequireRawIsa());#if SUPPORT_INDEXED_ISAASSERT(cls->classArrayIndex() > 0);newisa.bits = ISA_INDEX_MAGIC_VALUE;// isa.magic is part of ISA_MAGIC_VALUE// isa.nonpointer is part of ISA_MAGIC_VALUEnewisa.has_cxx_dtor = hasCxxDtor;newisa.indexcls = (uintptr_t)cls->classArrayIndex();
#elsenewisa.bits = ISA_MAGIC_VALUE;// isa.magic is part of ISA_MAGIC_VALUE// isa.nonpointer is part of ISA_MAGIC_VALUE
# if ISA_HAS_CXX_DTOR_BITnewisa.has_cxx_dtor = hasCxxDtor;
# endifnewisa.setClass(cls, this);
#endifnewisa.extra_rc = 1;}// This write must be performed in a single store in some cases// (for example when realizing a class because other threads// may simultaneously try to use the class).// fixme use atomics here to guarantee single-store and to// guarantee memory order w.r.t. the class index table// ...but not too atomic because we don't want to hurt instantiationisa() = newisa;
}
源码主要是初始化isa,两种方式:
- 通过cls初始化:
非nonpointer,存储着Class、Meta-Class对象的内存地址信息。 - 通过bits初始化:
nonpointer,进行一系列的初始化操作。
Class superclass
superclass指向该对象或该类的父类,class*本身也是一个指针,占8字节。
cache_t cache
cache缓存,追踪进去看一下cache_t结构体的类型,而不是结构体指针类型(占8字节),就需要计算一下了:
struct cache_t {struct bucket_t *_buckets; // 8 散列表mask_t _mask; // 4 散列长度-1mask_t _occupied; // 4 已经缓存的方法数量
}
#if __LP64__
typedef uint32_t mask_t; // x86_64 & arm64 asm are less efficient with 16-bits
#else
typedef uint16_t mask_t;
#endifstruct bucket_t {
private:// IMP-first is better for arm64e ptrauth and no worse for arm64.// SEL-first is better for armv7* and i386 and x86_64.
#if __arm64__MethodCacheIMP _imp;//函数的内存地址cache_key_t _key;//SEL作为key
#elsecache_key_t _key;//SEL作为keyMethodCacheIMP _imp;//函数的内存地址
#endif
}
同时这里引入了bucket_t(散列表),cache_t哈希表结构,哈希表内部存储的bucket_t,bucket_t中存储的是SEL和IMP的键值对。
class_data_bits_t bits
class_data_bits_t作为属性bits的类型,也是个结构体,其数据结构如下:
struct class_data_bits_t {friend objc_class;//这里声明objc_class为class_data_bits_t的友元类,使得objc_class可以访问class_data_bits_t中的私有方法uintptr_t bits;......
从源码中可以看到,class_data_bits_t有一个属性uintptr_t bits,bits的类型是uintptr_t类型数据,那么uintptr_t是什么数据结构呢?以下是通过查看一些资料得到的解释:
是无符号整数类型,能够存储指针。这通常意味着它与指针的大小相同。 它可选地在C++ 11和更高版本的标准中定义。 想要一个可以保存体系结构指针类型的整数类型的常见原因是对指针执行特定于整数的操作,或者通过将指针提供为整数“句柄”来模糊指针的类型
这里面其实利用位域存储数据,简单来说因为这个bits跟指针大小相同,在iOS中指针大小是8个字节,也就是64位,但是通常单一数据无法用满这64位的,会有很多空位,造成空间浪费。通过位运算,把不同的数据按不同的位置存进这64位内存空间的不同位置里,这样就可以提高内存利用率。这里的bits其实就是这样的,他不只存储class_rw_t结构体指针,还存储其他的信息。
看到这里我们获取会有一些疑惑,那就是类结构里面我们没有看到方法列表、属性列表甚至成员列表等,那这些东西存在哪里呢?由前面分析知道bits里有个class_rw_t指针,而且objc_class里面的data和getData方法都涉及到class_rw_t的读写:
class_rw_t *data() const {return bits.data();
}
void setData(class_rw_t *newData) {bits.setData(newData);
}
class_rw_t
struct class_rw_t {// Be warned that Symbolication knows the layout of this structure.uint32_t flags;uint16_t witness;
#if SUPPORT_INDEXED_ISAuint16_t index;
#endifexplicit_atomic<uintptr_t> ro_or_rw_ext;Class firstSubclass;Class nextSiblingClass;private:using ro_or_rw_ext_t = objc::PointerUnion<const class_ro_t, class_rw_ext_t, PTRAUTH_STR("class_ro_t"), PTRAUTH_STR("class_rw_ext_t")>;const ro_or_rw_ext_t get_ro_or_rwe() const {return ro_or_rw_ext_t{ro_or_rw_ext};}void set_ro_or_rwe(const class_ro_t *ro) {ro_or_rw_ext_t{ro, &ro_or_rw_ext}.storeAt(ro_or_rw_ext, memory_order_relaxed);}void set_ro_or_rwe(class_rw_ext_t *rwe, const class_ro_t *ro) {// the release barrier is so that the class_rw_ext_t::ro initialization// is visible to lockless readersrwe->ro = ro;ro_or_rw_ext_t{rwe, &ro_or_rw_ext}.storeAt(ro_or_rw_ext, memory_order_release);}class_rw_ext_t *extAlloc(const class_ro_t *ro, bool deep = false);public:void setFlags(uint32_t set){__c11_atomic_fetch_or((_Atomic(uint32_t) *)&flags, set, __ATOMIC_RELAXED);}void clearFlags(uint32_t clear){__c11_atomic_fetch_and((_Atomic(uint32_t) *)&flags, ~clear, __ATOMIC_RELAXED);}// set and clear must not overlapvoid changeFlags(uint32_t set, uint32_t clear){ASSERT((set & clear) == 0);uint32_t oldf, newf;do {oldf = flags;newf = (oldf | set) & ~clear;} while (!CompareAndSwap(oldf, newf, &flags));}class_rw_ext_t *ext() const {return get_ro_or_rwe().dyn_cast<class_rw_ext_t *>(&ro_or_rw_ext);}class_rw_ext_t *extAllocIfNeeded() {auto v = get_ro_or_rwe();if (fastpath(v.is<class_rw_ext_t *>())) {return v.get<class_rw_ext_t *>(&ro_or_rw_ext);} else {return extAlloc(v.get<const class_ro_t *>(&ro_or_rw_ext));}}class_rw_ext_t *deepCopy(const class_ro_t *ro) {return extAlloc(ro, true);}const class_ro_t *ro() const {auto v = get_ro_or_rwe();if (slowpath(v.is<class_rw_ext_t *>())) {return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->ro;}return v.get<const class_ro_t *>(&ro_or_rw_ext);}void set_ro(const class_ro_t *ro) {auto v = get_ro_or_rwe();if (v.is<class_rw_ext_t *>()) {v.get<class_rw_ext_t *>(&ro_or_rw_ext)->ro = ro;} else {set_ro_or_rwe(ro);}}const method_array_t methods() const {auto v = get_ro_or_rwe();if (v.is<class_rw_ext_t *>()) {return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->methods;} else {return method_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseMethods};}}const property_array_t properties() const {auto v = get_ro_or_rwe();if (v.is<class_rw_ext_t *>()) {return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->properties;} else {return property_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseProperties};}}const protocol_array_t protocols() const {auto v = get_ro_or_rwe();if (v.is<class_rw_ext_t *>()) {return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->protocols;} else {return protocol_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseProtocols};}}
}
从这个结构体可以看到,其中有const method_array_t methods()、const property_array_t properties() 等这样的方法,因此可以推测类结构中的方法列表、属性列表是从这里获取的。以const method_array_t methods()为例,获取类的实例方法也是通过调用methods()来获取的。比如说runtime函数class_copyMethodList,其代码如下:
Method *
class_copyMethodList(Class cls, unsigned int *outCount)
{unsigned int count = 0;Method *result = nil;if (!cls) {if (outCount) *outCount = 0;return nil;}mutex_locker_t lock(runtimeLock);const auto methods = cls->data()->methods();ASSERT(cls->isRealized());count = methods.count();if (count > 0) {auto iterator = methods.signedBegin();auto end = methods.signedEnd();result = (Method *)malloc((count + 1) * sizeof(Method));count = 0;for (; iterator != end; ++iterator) {result[count++] = _method_sign(&*iterator);}result[count] = nil;}if (outCount) *outCount = count;return result;
}
其中const auto methods = cls->data()->methods()就是通过class_rw_t的methods()方法获取实例方法列表。但是在class_rw_t结构体中,我们并没有看到结构体里由存储方法列表相关的属性。反而是在methods()方法里看到读取方法列表是从class_rw_ext_t或者class_ro_t结构体。其实不只是methods,包括properties和protocols也是一样的。那么他们之间到底有什么关系呢?
接下来我们先来看看class_rw_ext_t和class_ro_t的数据结构,看看为什么class_rw_t可以从他们这里读取类相关信息。
class_rw_ext_t
class_rw_ext_t是个结构体,其数据结构如下:
struct class_rw_ext_t {const class_ro_t *ro;method_array_t methods;property_array_t properties;protocol_array_t protocols;char *demangledName;uint32_t version;
};
由此可知,结构体class_rw_ext_t中的确有方法列表、属性列表和协议等信息,而且还有一个class_ro_t指针,接下来继续看class_ro_t。
class_ro_t
以下是class_ro_t的结构体:
struct class_ro_t {uint32_t flags;uint32_t instanceStart;uint32_t instanceSize;
#ifdef __LP64__uint32_t reserved;
#endifconst uint8_t * ivarLayout;const char * name;method_list_t * baseMethodList;protocol_list_t * baseProtocols;const ivar_list_t * ivars;const uint8_t * weakIvarLayout;property_list_t *baseProperties;// This field exists only when RO_HAS_SWIFT_INITIALIZER is set._objc_swiftMetadataInitializer __ptrauth_objc_method_list_imp _swiftMetadataInitializer_NEVER_USE[0];_objc_swiftMetadataInitializer swiftMetadataInitializer() const {if (flags & RO_HAS_SWIFT_INITIALIZER) {return _swiftMetadataInitializer_NEVER_USE[0];} else {return nil;}}method_list_t *baseMethods() const {return baseMethodList;}class_ro_t *duplicate() const {if (flags & RO_HAS_SWIFT_INITIALIZER) {size_t size = sizeof(*this) + sizeof(_swiftMetadataInitializer_NEVER_USE[0]);class_ro_t *ro = (class_ro_t *)memdup(this, size);ro->_swiftMetadataInitializer_NEVER_USE[0] = this->_swiftMetadataInitializer_NEVER_USE[0];return ro;} else {size_t size = sizeof(*this);class_ro_t *ro = (class_ro_t *)memdup(this, size);return ro;}}
};
这里面我们发现class_ro_t不仅有baseMethodList、 baseProtocols、baseProperties等信息,还有成员变量const ivar_list_t * ivars等其他信息。在了解他们的结构之后,我们从结构上简单做一下它们之间关系的分析:首先class_rw_t有一个指针ro_or_rw_ext,ro_or_rw_ext指向可能是class_rw_ext_t或者是class_ro_t。ro_or_rw_ext指针的作用是,当读取类相关信息时,会优先判断是否指向class_rw_ext_t,如果是class_rw_ext_t就从它读取,没有就是class_ro_t,直接从class_ro_t读取。那么在程序运行中到底ro_or_rw_ext什么时候是class_rw_ext_t,什么时候是class_ro_t呢?其实WWDC2020有过对这三个数据结构的介绍,下面会做简单的总结。
class_ro_t & class_rw_t & class_rw_ext_t & Class之间的关系
要了解它们之间的关系,我们首先要了解几个关键跟它们相关的概念。
- 名词解析
从字面意思解读:class_ro_t中的“ro”代表只读;class_rw_t中的“rw”代表可读可写;class_rw_ext_t中的“rw_ext”代表可读可写的扩展。
干净内存和脏内存:干净内存内存指的是内存一旦加载就不会被改变。反之,脏内存就是在运行时会被修改的。
- 关系释疑
Class本身是运行时加载的,在运行时会被改变,所以本身Class就是属于脏内存。那么如果想要获取Class的干净内存,也就是编译时确定的数据结构包括方法列表、成员变量等的,该怎么办?
这其实就是class_ro_t的作用。因为class_ro_t是只读,意味着 class_ro_t是从mach-o读取类的数据之后,就不会被改变。那如果我们想在运行时修改类的信息,比如添加方法,比如加载category怎么办呢?那这时候就有一个与之对应的class_rw_t结构,class_rw_t可以在运行时存储类的信息,可读可写的,可以在运行时修改。
说到这里,好像还漏掉一个结构class_rw_ext_t,这个东西又是干什么用的呢?存在的意义是什么?其实还是跟运行时有关。实际上在我们的app运行中,需要运行时修改的类是非常少的,据统计平均大概就10%左右。那也就是说大部分只需要读取class_ro_t中的数据就够了,少部分才需要修改。因此才会有class_rw_ext_t这个扩展的结构体。class_rw_ext_t的作用是这样的:当我们需要修改类结构时,比如添加方法、加载category等时,class_rw_t回去开辟一个额外的空间rwe(class_rw_ext_t),用于存储新的方法和class_ro_t中的方法等信息。这样做的目的有一个好处就是,对于绝大部分类是不需要这个开辟class_rw_ext_t这个结构体,节省内存。
- 创建时机
那这几个结构分别是什么时候创建的呢?这里就设计到类的加载流程。首先类在app启动的时候会被映射到内存,这时候会先创建Class(objc_class)结构,然后把类编译时的类数据映射到class_ro_t,class_ro_t结构体指针存储到Class的bits指针中,我们前面提到类的bits中存储的是class_rw_t指针,实际上在类初始化之前这里存储的是class_ro_t,等到类初始化的时候会创建一个class_rw_t结构,然后通过data()从bits中读取class_ro_t,然后class_rw_t通过set_ro(const class_ro_t *ro)把指针ro_or_rw_ext指向这个class_ro_t,然后Class通过setData()把class_rw_t指针存储到bits里面。然后在运行时根据需要,根据extAllocIfNeeded或extAlloc创建class_rw_ext_t,然后把class_ro_t关联到class_rw_ext_t,这就是大概流程。
category不能添加成员变量的原因
因为category是运行时添加的,他只能操作class_rw_t结构,但是class_rw_t结构没有添加成员变量的入口。成员变量是存储在class_ro_t中的,是无法被修改的,所以category就不能添加成员变量。
类数据的存储
通过上述的源码解析,都应该清楚了类数据的存储结构,大概总结总结一下结构关系,如下图所示:
相关文章:

【iOS】--对象的底层结构
源码 先转一下源码 //#import <Foundation/Foundation.h> #import <objc/runtime.h>interface LGPerson : NSObject property (nonatomic, strong) NSString *KCName; endimplementation LGPersonendint main(int argc, const char * argv[]) {autoreleasepool {…...

高并发内存池设计_内存池
高并发内存池设计 1. 常用的内存操作函数2. 高性能内存池设计_弊端解决之道弊端一弊端二弊端三弊端四3. 弊端解决之道内存管理维度分析内存管理组件选型4. 高并发内存管理最佳实践内存池技术内存池如何解决弊端?高并发时内存池如何实现?5. 高效内存池设计和实现实现思路 (分而…...

给编程初学者的一封信
提醒:以下内容仅做参考,具体请自行设计。 随着信息技术的快速发展,编程已经成为一个越来越重要的技能。那么,我们该如何入门编程呢?欢迎大家积极讨论 一、自学编程需要注意什么? 要有足够的时间、精力等…...

【无功优化】基于改进教与学算法的配电网无功优化【IEEE33节点】(Matlab代码时候)
💥💥💞💞欢迎来到本博客❤️❤️💥💥 🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。 ⛳️座右铭&a…...

数据在内存中的存储(超详细讲解)
目录 浮点数家族 浮点数类型在内存中的存储 一.为什么说整型和浮点数在内存中存储方式不同(证明) 二.浮点数的存储规则 浮点数在计算机内部的表示方法 1.对于M的存储和取出规则 2.对于E的存储和取出时的规则 对前面代码结果进行解释: …...

log4cplus使用示例
1、l4jlog.h封装头文件 #pragma once#include <iostream> #include <log4cplus/logger.h> #include <log4cplus/loggingmacros.h> #include <log4cplus/fileappender.h> #include <log4cplus/layout.h> #include <log4cplus/configurator.h&…...

人工智能学习07--pytorch20--目标检测:COCO数据集介绍+pycocotools简单使用
如:天空 coco包含pascal voc 的所有类别,并且对每个类别的标注目标个数也比pascal voc的多。 一般使用coco数据集预训练好的权重来迁移学习。 如果仅仅针对目标检测object80类而言,有些图片并没有标注信息,或者有错误标注信息。…...

learnOpenGL-深度测试
深度测试:OpenGL将一个片段的深度值与深度缓冲的内容进行对比。执行一个深度测试,测试通过则深度缓冲将会更新为新的深度值。测试失败则片段被丢弃。 深度测试片段着色器及模版测试之后执行。 片段着色器中内置变量gl_FragCoord的z值即为深度值。 提前深…...

阿里云服务器数据盘是什么?系统盘和数据盘区别
阿里云服务器系统盘和数据盘有什么区别?系统盘类似Windows电脑的C盘,数据盘相当于其他盘符,数据盘可以有多个而系统盘只能有一个,数据盘可有可无而云服务器系统盘是必须要有的。阿里云服务器网来详细说下阿里云服务器数据盘和系统…...

linux常用命令精选
参考文章: Top 60 Linux Interview Questions and Answers - howtouselinux 在管理和维护Linux系统时,有一些常用的命令可以帮助您进行系统初始化和配置。这些命令涵盖了各种任务,包括系统设置、用户管理、软件安装和网络配置等。 本文将为…...

人体行为足力特征分析及其应用研究_kaic
第一章 绪论 随着社会现代化的发展和科技的不断进步,我国航天事业蓬勃发展,与此同时产生了很多亟待解决的难题,康复医疗成为航天医学和康复领域的重要课题之一。载人航天实践证明,失重对航天员生理功能有很大影响,这不…...

javascript基础二十七:说说 JavaScript 数字精度丢失的问题,解决方案?
一、场景复现 一个经典的面试题 0.1 0.2 0.3 // false 为什么是false呢? 先看下面这个比喻 比如一个数 130.33333333… 这是一个除不尽的运算,3会一直无限循环,数学可以表示,但是计算机要存储,方便下次再使用,但…...

重塑工作场所:后疫情时代组织韧性的8个策略
经济寒冬来临,倒挂的收益率曲线、持续上升的利率以及层出不穷的裁员公告等等,让经济学家们得出一个结论:全球经济正在衰退。然而,经济下行周期可能是卓越公司改变其命运的最佳时机。有研究表明,相对于非经济衰退时期&a…...

TCP协议为什么要三次握手而不是两次?
TCP(Transmission Control Protocol,传输控制协议)的历史可以追溯到1970年代初期,最初的版本是RFC 793,后来经过多次更新和改进,包括RFC 1122、RFC 1323、RFC 2018、RFC 2581、RFC 2873、RFC 3168和RFC 461…...

使用Vuex进行状态管理
在Vue.js应用程序中,状态管理是一个重要的主题。当应用程序变得复杂,组件之间的状态共享和通信变得困难,这时候使用Vuex就会变得十分有用。Vuex是一个专门为Vue.js设计的状态管理库,它提供了一个集中式的状态管理方案,…...

【优化调度】基于改进遗传算法的公交车调度排班优化的研究与实现(Matlab代码实现)
目录 1 概述 2 运行结果 3 参考文献 4 Matlab代码 1 概述 本文对当前公交企业调度系统进行了分析,建立了公交排班的数学模型。本文基于数据挖掘分析的结果上,使用截面客流量数据对模型进行约束,得出了公交客流出行的空间分布规律。再以…...

IMX6ULL裸机篇之I2C实验-硬件原理图
一. I2C 实验简介 I2C实验,我们就来学习如何使用 I.MX6U 的 I2C 接口来驱动 AP3216C,读取 AP3216C 的传感器数据。 AP3216C是一个三合一的环境光传感器,ALSPSIRLED,ALS是环境光,PS是接近传感器,IR是红外L…...

华为OD机试真题 Java 实现【获取字符串中连续出现次数第k多的字母的次数】【2023Q1 100分】,附详细解题思路
一、题目描述 给定一个字符串,只包含大写字母,求在包含同一字母的子串中,长度第 k 长的子串的长度,相同字母只取最长的那个子串。 二、输入描述 第一行有一个子串(1<长度<100),只包含大写字母;第二…...

充分统计量和因子分解定理
充分统计量 定义: 设样本 X X X的服从分布 f ( X ∣ θ ) f(X|\theta) f(X∣θ), θ ∈ Θ \theta\in\Theta θ∈Θ,设 T T ( X ) TT(X) TT(X)为一统计量,若在已知 T T T的条件下,样本 X X X的条件分布与参数 θ \the…...

M1 PD安装arm ubuntu及Docker
M1 PD安装arm ubuntu 下载 Ubuntu 22.04.2 LTS https://cn.ubuntu.com/download/server/arm 参考视频安装 https://www.bilibili.com/video/BV1Mu4y1f74v/?spm_id_from333.999.0.0&vd_source9056c6d3c91a117baaceb663957daa08 PD Ubuntu安装docker 删除现有的docker安装…...

TCP协议的RST标志
下文中的内容多数来自【参考】中的文章,这边进行一个整理和总结,后续会慢慢增加出现各个 RST 包的测试代码,便于理解。 TCP的 “断开连接” 标志 RST 标志 Reset,复位标志,用于非正常地关闭连接。它是 TCP 协议首部里…...

【软件质量与软件测试 白盒测试与黑盒测试】
第十章 黑盒测试 10.1 等价类划分: 10.1.1 划分等价类 等价类是指所有数据中的一组,它们具有相同的测试结果或相同的响应。等价类划分是将输入数据分为多个等价类的过程。 10.1.2 划分等价类的方法 划分等价类方法主要包括以下几种: 特…...

JavaScript教程(高级)
面向对象编程介绍 两大编程思想 (1)、 面向过程编程: (缩写 POP)( Process-oriented programming)面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现&am…...

C++进阶 —— 范围for(C++11新特性)
目录 一,范围for介绍 二,范围for注意事项 一,范围for介绍 范围for(range-based for loop)是C11新引入的特性,可遍历各种序列结构的容器(如数组、vector、list等);每次循…...

ELK +Filebeat日志分析系统
一、 ELK日志分析系统概述 1、ELK简介 ELK是三个开源软件的缩写,分别表示:Elasticsearch , Logstash, Kibana , 它们都是开源软件。新增了一个FileBeat,它是一个轻量级的日志收集处理工具(Agent),Filebeat占用资源少,…...

万字解析PELT算法!
Linux是一个通用操作系统的内核,她的目标是星辰大海,上到网络服务器,下至嵌入式设备都能运行良好。做一款好的linux进程调度器是一项非常具有挑战性的任务,因为设计约束太多了: 它必须是公平的快速响应系统的throughp…...

腾讯云服务器端口怎么全开?教程来了
腾讯云服务器端口怎么全开?云服务器CVM在安全组中设置开通,轻量应用服务器在防火墙中设置,腾讯云百科来详细说下腾讯云服务器端口全开放教程: 目录 腾讯云服务器端口全部开通教程 云服务器CVM端口全开放教程 轻量应用服务器开…...

深入理解Java虚拟机:JVM高级特性与最佳实践-总结-13
深入理解Java虚拟机:JVM高级特性与最佳实践-总结-13 Java内存模型与线程Java内存模型原子性、可见性与有序性先行发生原则 Java内存模型与线程 Java内存模型 原子性、可见性与有序性 Java内存模型是围绕着在并发过程中如何处理原子性、可见性和有序性这三个特征来…...

租售keysight E8257D 50G模拟信号发生器 销售/回收
是德(Keysight) E8257D 模拟信号发生器 Keysight E8257D (Agilent) PSG 模拟信号发生器提供业界领先的输出功率、电平精度和高达 67 GHz 的相位噪声性能(工作频率可达 70 GHz)。Agilent PSG 模拟信号发生器的高输出功率和卓越的电…...

【C++】什么是函数模板/类模板?
文章目录 一、函数模板1.什么是函数模板?2.函数模板格式3.函数模板原理4.函数模板实例化(1)隐式实例化(2)显示实例化 二.类模板1.类模板定义格式2.类模板的实例化 总结 一、函数模板 1.什么是函数模板? 函…...