[Effective Objective] 对象、消息、运行期

news2024/11/19 6:29:27

对象:“对象”(object)就是“基本构造单元”(building block),开发者可以通过对象来储存并传递数据。

消息:在对象之间传递数据并执行任务的过程就叫做“消息传递”(Messaging)。

运行期:当应用程序运行起来以后,为其提供相关支持代码叫做“Objectivec- C运行期环境”(Objective-C runtime),它提供了一些使得对象之间能够传递消息的重要函数,并且包含创建类实例所用的全部逻辑。

属性

“属性”(property)是Objective-C的一项特性,用于封装对象中的数据。Objective-C对象通常会把其所需要的数据保存为各种实例变量。

偏移量

“偏移量”(offset)是“硬编码”(hardcode),表示该变量距离存放对象的内存区域的起始地址有多远。

例:

@interface EOCPerson : NSObject {
    @public
    NSString *_firstName;
    NSString *_lastName;
    @private
    NSString *_someInternalData;
}
@end

假设指针为4个字节:
在这里插入图片描述

应用程序二进制接口(Application Binary Interface, ABI)

不兼容现象(incompatibility)

在Java或C++语言里可以定义实例的作用域,然而编写Objective-C代码时却很少这么做。这种写法的问题是:对象布局在编译期(compile time)就已经固定了。此时,如果又加了一个实例变量,那就麻烦了。比如说,假设在_firstName之前又多了一个实例变量:

@interface EOCPerson : NSObject {
    @public
    NSDate *_dateOfBirth;
    NSString *_firstName;
    NSString *_lastName;
    @private
    NSString *_someInternalData;
}
@end

数据布局图就会变成这样:
在这里插入图片描述

原来表示_firstName的偏移量现在却指向_dateOfBirth了。把偏移量硬编码于其中的那些代码都会读取到了错误的值。

如果代码使用了编译期计算出来的偏移量,那么在修改类定义之后必须重新编译,否则就会出错。某个代码库中的代码使用了一份旧的类定义。如果和其相链接的代码使用了新的类定义,那么运行时就会出现不兼容现象(incompatibility)。

ABI定义

Objective-C的做法是,把实例变量当作一种储存偏移量所用的“特殊变量”(special variable),交由“类对象”(class object)保管。偏移量会在运行期查找,如果类的定义变了,那么储存的偏移量也就变了,这样的话,无论何时访问实例变量,总能使用正确的偏移量。这就是稳固的应用程序二进制接口(Application Binary Interface, ABI)。

存取方法

Objective-C对象通常会把所需要的数据保存为各种实例变量。实例变量一般通过“存取方法”(access method)来访问。

  • 获取方法:getter,用于读取变量值。
  • 设置方法:setter,用于写入变量值。

自动创建存取方法

编译器可以自动写出一套存取方法,用以访问给定类型中具有给定名称的变量。例如下面这个类:

@interface EOCPerson : NSObject
@property NSString *firstName;
@property NSString *lastName;
@end

对于该类的使用者来说,上述代码写出来的类于下面这种写法等效:

@interface EOCPerson : NSObject
- (NSString*)firstName;
- (void)setFirstName:(NSString*)firstName;
- (NSString*)lastName;
- (void)setLastName:(NSString*)lastName;
@end

点语法

此特性引入了一种新的“点语法”(dot syntax),使开发者可以更为容易地依照类对象来访问存放与其中的数据。编译器会把“点语法”转换为对存取方法的调用,使用“点语法”的效果与直接调用存取方法相同。

例:

	EOCPerson* aPerson = [[EOCPerson alloc] init];
    
    aPerson.firstName = @"Bob"; //Same as:
    [aPerson setFirstName:@"Bob"];
    
    NSString* lastName = aPerson.lastName; // Same as:
    NSString* lastName = [aPerson lastName];

属性特质

使用属性时,各种特质(attribute)设定也会影响编译器所生成的存取方法。

详情可参考:属性关键字

要点

  • 可以通过@property语法来定义对象中所封装的数据。
  • 通过“特质”来指定储存数据所需的正确语义。
  • 在设置属性所对应的实例变量时,一定要遵传该属性所声明的语义。
  • 开发iOS程序时应该使用nonatomic属性,因为atomic属性会严重影响性能。

在对象内部尽量直接访问实例变量

在对象之外访问实例变量时,总是应该通过属性来做,然而在对象内部访问实例变量时又该如何呢?

在实例变量的获取方法与设置方法中,通过存取方法和不经由存取方法访问实例变量有这几个区别:

  • 由于不经过Objective-C的“方法派发”(method dispatch)步骤,所以直接访问实例变量的速度当然比较快。在这种情况下,编译器所生成的代码会直接访问保存对象实例变量的那块内存。
  • 直接访问实例变量时,不会调用其“设置方法”,这就绕过了为相关属性所定义的“内存管理语义”。比方说,如果直接访问一个声明为copy的属性,那么并不会拷贝该属性只会保留新值并释放旧值。
  • 如果直接访问实例变量,那么不会触发“键值观测”(Key-Value Observing, KVO)通知。这样做是否会产生问题,还取决于具体的对象行为。
  • 通过属性来访问有助于排查与之相关的错误,因为可以给“获取方法”和/或“设置方法”中新增“断点”(breakpoint),监控该属性的调用者及其访问时机。

折中方案

在写入实例变量时,通过“设置方法”来做,而在读取实例变量时,则直接访问之。

优点:既能提高读取操作的速度,又能控制对属性的写入操作。

需要注意的问题

在初始化方法中应该如何设置属性值

这种情况下总是应该直接访问实例变量,因为子类可能会“覆写”(override)设置方法。

假设EOCPerson有一个子类叫做EOCSmithPerson,这个字类专门表示那些性“Smith”的人。该字类可能会覆写lastName属性所对应的设置方法:

- (void) setFirstName:(NSString *)firstName {
    if (![firstName isEqualToString:@"Smith"]) {
        NSLog(@"llllll");
    }
    self.firstName = firstName;
}

在基类EOCPerson的默认初始化中,可能会将姓氏设为空字符串。此时若是通过“设置方法”来做,那么调用的将会是字类的设置方法,从而抛出异常。

惰性初始化

对某些复杂又不常用,而且创建成本高的属性,我们可能会在“获取方法”中对其执行惰性初始化:

- (EOCBrain*)brain {
	if (!_brain) {
		_brain = [Brain new];
	}
	return _brain;
}

在这种情况下,必须使用存取方法来访问brain属性。

要点

  • 在对象内部读取数据时,应该直接通过实例变量来读,而写入数据时,则应通过属性来写。
  • 在初始化方法及dealloc方法中,总是应该直接通过实例变量来读写数据。
  • 有时会使用惰性初始化技术配置某份数据,这种情况下,需要通过属性来读取数据。

理解“对象等同性”这一概念

根据“等同性”(equality)来比较对象是一个非常有用的功能。此时,我们需要使用NSObject协议中声明的“isEqual”:方法来判断两个对象的等同性。一般来说,两个类型不同的对象总是不相等的(unequal)。

以下述代码为例:

NSString* foo = @"Badger 123";
    NSString* bar = [NSString stringWithFormat:@"Badger %i", 123];
    BOOL equalA = (foo == bar); // < equalA = NO
    BOOL equalB = [foo isEqual:bar]; // < equalB = YES
    BOOL equalC = [foo isEqualToString:bar]; // < equalC = YES

大家可以看到 == 与等同性判断方法之间的差别。

用于判断等同性的关键方法

- (BOOL)isEqual:(id)object;
- (NSUInteger)hash;

NSObject类对这两个方法的默认实现是:当且仅当其“指针值”(pointer value)完全相等时,这两个对象才相等。

约定(contract)

  1. 如果“isEqual:”方法判断两个对象相等,那么其hash方法也必须返回同一个值。
  2. 如果两个hash方法返回同一个值,那么“isEqual:”方法未必会认为两者相等。

例:

@interface EOCPerson : NSObject
@property (nonatomic, copy) NSString *firstName;
@property (nonatomic, copy) NSString *lastName;
@property (nonatomic, assign) NSUInteger age;
@end

我们认为,如果两个EOCPerson的所有字段均相等,那么这两个对象就相等。于是“isEqual:”方法可以写成:

- (BOOL)isEqual:(id)object {
    if (self == object) return YES;
    if ([self class] != [object class]) return NO;
    
    EOCPerson* otherPerson = (EOCPerson*)object;
    if (![_firstName isEqualToString:otherPerson.firstName]) return NO;
    if (![_lastName isEqualToString:otherPerson.lastName]) return NO;
    if (_age != otherPerson.age) return No;
    
    return YES;
}

hash方法:

- (NSUIngeter)hash {
	return 1337;
}

collection性能问题

如果在collection中使用上面的对象将产生性能问题,因为collection在检索哈希表(hash table)时,会用对象的哈希码做索引。假如某个collection是用set实现的,那么set可能会根据哈希码把对象分装到不同的数组中。在向set中添加新对象时,要根据其哈希码找到与之相关的那个数组,依次检查其中各个元素。由此可知,如果每个对象都返回相同的哈希码,那么在set中已有1000000个对象的情况下,若是继续向其中添加对象,则需将这个1000000个对象全部扫描一遍。

hash方法也可以这样来实现:

- (NSUInteger)hash {
    NSString* stringToHash = [NSString stringWithFormat:@"%@:%@:%lu", _firstName, _lastName, _age];
    
    return [stringToHash hash];
}

这个方法是将NSString对象中的属性都塞入另一个字符串中,然后令hash方法返回该字符串的哈希码。但是这样做还需负担创建字符串的开销,所以返回单一值要慢。因为必须先计算其哈希码,把这种对象添加到collection中也会产生性能问题。

最后一种方法:

- (NSUInteger)hash {
    NSUInteger firstNameHash = [_firstName hash];
    NSUInteger lastNameHash = [_lastName hash];
    NSUInteger ageHash = _age;
    return firstNameHash ^ lastNameHash ^ ageHash;
}

优点:这种做法既能保持较高效率,又能使生成的哈希码至少位于一定范围之内,而不会过于频繁地重复。

缺点:此算法生成的哈希码还是会碰撞(collision)。

编写hash方法时,应该用当前的对象做做实验,以便在减少碰撞频度与降低运算复杂程度之间取舍。

特定类所具有的等同性判定方法

如果经常需要判断等同性,那么可能会自己来创建等同性判定方法,因为无须检测参数类型,所以能大大提升检测速度。

在编写判定方法时,也应一并覆写“isEqual:”方法。

例:

- (BOOL)isEqualToPerson:(EOCPerson *)otherPerson {
    if (self == otherPerson) return YES;
    
    if (![_firstName isEqualToString:otherPerson.firstName]) return NO;
    if (![_lastName isEqualToString:otherPerson.lastName]) return NO;
    if (_age != otherPerson.age) return NO;
    
    return YES;
}

- (BOOL)isEqual:(id)object {
    if ([self class] == [object class]) {
        return [self isEqualToPerson:(EOCPerson*)object];
    } else {
        return [super isEqual:object];
    }
}

等同性判定的执行深度

创建等同性判定方法时,需要决定是根据整个对象来判断等同性,还是仅根据其中几个字段来判断。

深度等同性判定(deep equality)

例如,NSArray的检测方式为先看两个数组所含对象个数是否相同,若相同,则在每个对应位置地两个对象身上调用其“isEqual:”方法。如果对应位置上的对象均相等,那么这两个数组就相等,这就叫“深度等同性判定”。

唯一标识符(unique identifier)

有些类可能会含有另外一个属性,此属性是“唯一标识符”,在数据库中用作“主键”(primary key):

@property NSUInteger identifier;

在这种情况下,我们也许只会根据标识符来判断等同性。因为只要两者标识符相同,就肯定表示同一个对象,因而必然相等。

容器中可变类的等同性

在容器中放入可变类对象的时候,就不应在改变其哈希值了。

用一个NSMutableSet与几个NSMutableArray对象测试一下,就能发现这个问题。

  1. 先把一个数组加入set中。
	NSMutableSet* set = [NSMutableSet set];
    
    NSMutableArray* arrayA = [@[@1, @2] mutableCopy];
    [set addObject:arrayA];
    NSLog(@"set = %@", set);
    // Output : set = {((1, 2))}

现在set里含有一个数组对象,数组中包含两个对象。

  1. 在向set里加入一个与前一个数组相同的数组。
	NSMutableSet* set = [NSMutableSet set];
    
    NSMutableArray* arrayA = [@[@1, @2] mutableCopy];
    [set addObject:arrayA];
    NSLog(@"set = %@", set);
    // Output : set = {((1, 2))}
    
    NSMutableArray* arrayB = [@[@1, @2] mutableCopy];
    [set addObject:arrayB];
    NSLog(@"set = %@", set);
    // Output : set = {((1, 2))}

此时set里仍然只有一个对象:因为数组B与数组A相同,所以set不会改变。
3. 加入一个和set中已有对象不同的数组。

	NSMutableArray* arrayC = [@[@1] mutableCopy];
    [set addObject:arrayC];
    NSLog(@"set = %@", set);
    // Output : set = {((1), (1, 2))}

现在set里有两个数组。
4. 改变数组C的内容,令其和最早加入的那个数组相等。

    [arrayC addObject:@2];
    NSLog(@"set = %@", set);
    // Output : set = {((1, 2), (1, 2))}

set中居然包含了两个相等的数组!这是不允许的。

所以把某对象放入set之后又修改其内容。那么后来的行为将很难预测。

要点

  • 若想检测对象的等同性,请提供“isEqual:”方法与hash方法。
  • 相同的对象必须具有相同的哈希码,但是两个哈希码相同的对象却未必相同。
  • 不要盲目地逐个检测每条属性,而是应该依照具体需求来制定检测方案。
  • 编写hash方法时,应该使用计算速度快而且哈希码碰撞机率低的算法。

以“类族模式”隐藏实现细节

“类族”(class cluster)是一种很有用的模式,可以隐藏“抽象基类”背后的实现细节。Objective-C的系统框架中普遍使用此模式。

创建类族

现在举例来演示如何创建类族。假设有一个处理雇员的类,每个雇员都有“名字”和“薪水”这两个属性,管理者可以命令其执行日常1工作。但是,各类雇员的工作内容不同。经理在带领雇员做项目时,无须关心每个人如何完成其工作,仅需指示其开工即可。

  1. 定义抽象基类。
typedef NS_ENUM(NSUInteger, EOCEmplyeeType) {
    EOCEmplyeeTypeDeveloper,
    EOCEmplyeeTypeDesigner,
    EOCEmplyeeTypeFinance,
};

@interface EOCEmplyee : NSObject
@property (nonatomic, copy) NSString* name;
@property (nonatomic, assign) NSUInteger salary;

// Helper for creating Employee
+ (EOCEmplyee*)employeeWithType:(EOCEmplyeeType)type;

// Make Employees do their respective day's work
- (void)doADaysWork;

@end

@implementation EOCEmplyee

+ (EOCEmplyee*)employeeWithType:(EOCEmplyeeType)type {
    switch (type) {
        case EOCEmplyeeTypeDeveloper:
            return [EOCEmplyeeDeveloper new];
            break;
        case EOCEmplyeeTypeDesigner:
            return [EOCEmplyeeDesigner new];
            break;
        case EOCEmplyeeTypeFinance:
            return [EOCEmplyeeFinance new];
            break;
    }
}

- (void)doADaysWork {
    // Subclasses implement this.
}

@end
  1. 每个实体子类都从基类基础而来。
    例:
@interface EOCEmplyeeDeveloper : EOCEmplyee
@end

@implementation EOCEmplyeeDeveloper

- (void)doADaysWork {
    // ......
}

@end

这种根据待创建的雇员类别分配好对应的雇员类实例叫“工厂模式”。

Coacoa里的类族

系统框架中有许多类族。大部分collection类都是类族,例如NSArray与其可变版本NSMutableArray。这两个类共属于一个类族,这意味着二者在实现各自类型的数组时可以共用实现代码,此外,还能够把可变数组复制为不可变数组,反之亦然。

对于Cocoa中NSArray这样的类族来说,还是有办法新增子类的,但是需要遵守几条规则。这几条规则如下。

  • 子类应该继承自类族中的抽象基类。
    若要编写NSArray类族的子类,则需令其继承自不可变数组的基类或可变数组的基类。
  • 子类应该定义自己的数据储存方式。
    子类必须用一个实例变量来存放数组中的对象。因为NSArray本身只不过是包在其他隐藏对象外面的壳,它仅仅定义了所有数组都需具备的一些接口。对于这个自定义的数组子类来说,可以用NSArray来保存其实例。
  • 子类应当覆写超类文档中需要覆写的方法。
    在每个抽象基类中,都有一些子类必须覆写的方法。

在类族中实现子类时所需要遵循的规范一般都会定义与基类的文档之中,编码前应该先看看。

要点

  • 类族模式可以把实现细节隐藏在一套简单的公共接口后面。
  • 系统框架中经常使用类族。
  • 从类族的公共抽象基类中继承子类时要当心,若有开发文档,则应首先阅读。

在既有类中使用关联对象存放自定义数据

有时需要在对象中存放相关信息。这时我们通常会从对象所属的类中继承一个子类,然后改用这个子类对象。然而并非所有情况下都能这么做,有时候类的实例可能是由某种机制所创建的,而开发者无法令这种机制创建出自己所写的子类实例。Objective-C中有一项强大的特性可以解决此问题,这就是“关联对象”(Associated Object)。

关联对象

可以给某对象关联许多其他对象,这些对象通过“键”来区分。存储对象值的时候,可以指明“存储策略”(storage policy),用于维护相应的“内存管理语义”。

存储策略

存储策略由名为objc_AssociationPolicy的枚举所定义。

对象关联类型图:
在这里插入图片描述

下列方法可以管理关联对象:

  • void objc_setAssociatedObject (id object, void *key, id value, objc_AssociationPolicy) 此方法 以给定的键和策略为某对象设置关联对象值。
  • id objc_getAssociatedObject (id object, void *key) 此方法根据给定的键从某对象中获取相应的关联对象值。
  • void objc_removeAssociatedObjects (id object) 此方法移除指定对象的全部关联对象。

关联对象与NSDictionary关于键值的不同

NSDictionary:如果在两个键上调用“isEqual:”方法的返回值是YES,那么NSDictionary就认为二者相等。

关联对象:二者必须是完全相同的指针。

**在设置关联对象时,弱小令两个键匹配到同一个值,则二者必须是完全相同的指针才行。

要点

  • 可以通过“关联对象”机制来把两个对象连起来。
  • 定义关联对象时可指定内存管理语义,用以模仿定义属性时所采用的“拥有关系”与“非拥有关系”。
  • 只有在其他做法不可行时才应选用关联对象,因为这种做法通常会引入难以查找的bug。

理解objc_msgSend的作用

在对象上调用方法在Objective-C中叫做“传递消息”。消息有“名称”(name)或“选择子”(selector),可以接受参数,而且可能还有返回值。

C语言的函数调用方式使用“动态绑定”(static binding),也就是说,在编译器就能决定运行时所应调用的函数。

在Objectivec-C中,如果向某对象传递消息,那就会使用动态绑定机制来决定需要调用的方法。即在对象收到消息之后,究竟该调用那个方法完全于运行期决定,甚至可以在程序运行时改变。这些特性使得Objective-C成为一门真正的动态语言。

obj_msgSend函数介绍

obj_msgSend函数是消息传递机制中的核心函数,其原型如下:

void objc_msgSend(id self, SEL cmd, ...)

给对象发宋消息可以这样来写:

id rerurnValue = [someObject messageName:parameter];

在本例中,someObject叫做“接收者”(receiver),messageName叫做“选择子”(selector)。选择子与参数合起来称为消息。编译器看到消息后,将其转化成上面的objc_msgSend函数调用。如下:

id returnValue = objc_msgSend(someObject, @selector(messageName), parameter);

objc_msgSend函数是一个参数可变的函数。第一个参数代表接受者,第二个参数代表选择子(SEL是选择子的类型,指的是方法的名字),后续参数就是消息中的那些参数,其顺序不变。

obj_msgSend函数作用

objc_msgSend函数会依据接受者与选择子的类型来调用适当的方法。

该方法会在接受者所属的类中搜寻其“方法列表”(list of methods),如果能找到与选择子名称相符的方法,就跳至其实现代码。若是找不到,那就沿着继承体系继续向上查找,等找到合适的方法之后再跳转。如果最终还是找不到相符的方法,那就执行“消息转发”(message forwarding)操作。

objc_msgSend会将匹配结果缓存在“快速映射表”(fast map)里面,每个类都有这样一块缓存,若稍后还向该类发送与选择子相同的消息,那么执行起来就很快了。

其他边界情况时调用的函数

  • objc_msgSend_stret。如果待发送的消息要返回结构体,那么可交由此函数处理。只有当CPU的寄存器能够容纳得下消息返回类型时,这个函数才能处理此消息。若返回值无法容纳与CPU寄存器中(比如说返回的结构体太大了),那么就由另一个函数执行派发。此时,那个函数会通过分配在栈上的某个变量来处理消息所返回的结构体,
  • objc_msgSend_fpret。如果消息返回的是浮点数,那么可交由此函数处理。在某些架构的CPU中调用函数时,需要对“浮点数寄存器”(floating-point register)做特殊处理,也就是说,通常所用的objc_msgSend在这种情况下并不合适。这个函数是为了处理x86等架构CPU中某些令人稍觉惊讶的奇怪状况。
  • obj_msgSendSuper。如果要给超类发消息,例如[super message:parmeter],那么就交由此函数处理。也有另外两个与objc_msgSend_stret和objc_msgSend_fpret等效的函数,用于处理发给super的相应消息。

尾调用优化

Objective-C对象的每个方法都可以视为简单的C函数,其原型如下:

<return _type> Class _selector(id self, SEL _cmd, ...)

每个类里都有一张表格,其中的指针都会指向这种函数,而选择子的名称则是查明时所用的“键”。objc_msgSend等函数正是通过这张表格来寻找应该执行的方法并跳至其实现的。

请注意,原型的样子和objc_msgSend函数很像。这不是巧合,而是为了利用“尾调用优化”技术,令“跳至方式实现”这一操作变得跟简单。

如果某函数的最后一项操作是调用另外一个函数,那么就可以运用“尾调用优化”技术。编译器会生成调转至另一函数所需的指令码,而且不会向调用堆栈中推入新的“栈桢”(frame stack)。只有当某函数的最后一个操作仅仅是调用其他函数而不会将其返回值另作他用时,才能执行“尾调用优化”。这项优化对objc_msgSend非常关键,如果不这么做的话,那么每次调用Objective-C方法之前,都需要为调用objc_msgSend函数准备“栈桢”,大家在“栈踪迹”(stack trace)中可以看到这种“栈桢”。此外,若是不优化,还会过早地发生“栈溢出”(stack overflow)现象。

要点

  • 消息由接收者。选择子及参数构成。给某对象“发生消息”(invoke a message)也就相当于在该对象上“调用方法”(call a method)。
  • 发给某对象的全部消息都要由“动态消息派发系统”(dynamic message dispatch system)来处理,该系统会查出对应的方法,并执行其代码。

理解消息转发机制

当对象接收到无法解读的消息后,就会启动“消息转发”(message forwarding)机制,程序员可经由此过程告诉对象应该如何处理未知消息。

开发者在编写自己的类时,可于转发过程中设置挂钩,用以执行预定的逻辑,而不使应用程序崩溃。

消息转发分为两大阶段

  1. 征询接收者,所属的类,看其是否能动态添加方法,以处理当前这个“未知的选择子”(unknown selector),这叫做“动态方法解析”(dynamic method resolution)。
  2. 如果运行期系统已经把第一阶段执行完了,那么接受者自己就无法再以动态新增方法的手段来响应包含该选择子的消息了。此时,运行期系统会请求接收者以其他手段来处理与消息相关的方法调用。
    这又细分为两小步:
    1. 请看接收者看看有没有其他对象能处理这条消息。
    2. 若有,则运行期系统会把消息转给那个对象。
    3. 若没有,运行期系统会把与消息有关的全部细节都封装到 NSInvocation对象中,再给接收者最后一次机会,令其设法解决当前还未处理的这条消息。

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

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

相关文章

最粗暴的方法实现一个栈

对于栈和队列是一个很简单的知识&#xff0c;用的感觉也不是很多&#xff0c;但是&#xff0c;我们仍然的学习&#xff01;&#xff01;加油&#xff01;&#xff01;在实现最简单的栈之前&#xff0c;我们需要简单了解一下栈是什么&#xff1f;&#xff1f;栈&#xff08;stac…

iplatform平台简介

前置条件&#xff1a;原则规范一&#xff09;统一技术栈1&#xff09;关于JDK统一使用Open JDK&#xff0c;版本最低1.8&#xff0c;几年后可能会升级到17&#xff1b;避免使用Sun JDK&#xff0c;这是商业软件&#xff0c;而且包含部分私有&#xff08;com.sun&#xff09;类库…

pandas 实战:分析三国志人物

简介 背景 Pandas 是 Python 的一个工具库&#xff0c;用于数据分析。由 AQR Capital Management 于 2008 年 4 月开发&#xff0c;2009 年开源&#xff0c;最初被作为金融数据分析工具而开发出来。Pandas 名称来源于 panel data&#xff08;面板数据&#xff09;和 Python d…

基于SEIR模型的传染病预测软件开发(完整代码+数据集+报告)

1 操作页面及用户使用说明(1) 界面说明App页面主要分为4个区域&#xff0c;分别是&#xff1a;曲线显示区、模型初始化和预防参数设定区、传染病特征参数设定区、绘图控制区。① 曲线显示区&#xff1a;显示模型预测的不同人数量随时间的变化曲线。② 模型初始化和预防参数设定…

泛函分析中的向量空间

一、向量空间背景 (1) 具有如下点内积或标量内积的实数域RRR上的欧式空间RNR^NRN&#xff1a; ⟨u,v⟩uTvu0v0u1v1⋯uN−1vN−1∑i0N−1uivi\langle\boldsymbol{u}, \boldsymbol{v}\rangle\boldsymbol{u}^{\mathrm{T}} \boldsymbol{v}u_{0} v_{0}u_{1} v_{1}\cdotsu_{N-1} v_{…

SpringCloud-Netflix学习笔记——微服务和微服务架构

一、什么是微服务&#xff1f; 什么是微服务&#xff1f;微服务&#xff08;Microservice Architecture&#xff09;是近几年流行的一种架构思想&#xff0c;关于它的概念很 难一言以蔽之。究竟什么是微服务呢&#xff1f;我们在此引用 ThoughtWorks 公司的首席科学家 Martin F…

谷粒商城-高级篇-Day11-商城业务

文章目录整合thymeleaf渲染页面页面修改不重启服务器实时更新渲染二三级数据nginx-搭建域名访问环境一nginx-搭建域名访问环境二整合thymeleaf渲染页面 将index放到product的资源下的static目录&#xff0c;index.html放到templates文件夹下 导入thymeleaf <!-- 模板引擎…

【Acwing寒假2023每日一题】4700. 何以包邮?- 01背包dp至少模板

4700. 何以包邮&#xff1f; - AcWing题库 设满x元包邮&#xff0c;题目要求总价值至少x的最小价值 目录 1、一维 01背包-至少模板 至少模板和至多模板的两大区别 2、二分 二维 01背包-至多模板 &#xff08;1&#xff09;二维dp 3、逆向思维 一维 01背包-至多模板 1…

1.环境搭建 创建spring boot 项目(mac)

1.安装maven 首先你需要在maven官网上下载mac专用的二进制maven压缩包。也就是下图的这个&#xff1a; 然后&#xff0c;开始我们的配置。 1、打开终端&#xff0c;输入这个代码&#xff1a; vim ~/.bash_profile 2、然后点击键盘上的“i”&#xff0c;进入vim编辑模式&…

【BBuf的CUDA笔记】五,解读 PyTorch index_add 操作涉及的优化技术

本文把pytorch index_add算子的代码抽取出来放在&#xff1a;https://github.com/BBuf/how-to-optim-algorithm-in-cuda/blob/master/indexing/index_add_cuda_pytorch_impl.cu 。如果不太熟悉PyTorch的话也可以直接看这个.cu文件&#xff0c;有问题请在这个repo提issue。 0x0.…

2022,我感受到了CSDN不平凡

最初注册CSDN&#xff0c;只是因为老师的要求&#xff0c;负责教C课程的老师让同学们注册CSDN&#xff0c;并经常更新自己的博客。虽然注册了CSDN的博客&#xff0c;也写了几篇博客文章&#xff0c;可最初我并不理解老师为什么要让我们注册&#xff0c;可是随着在CSDN驻留时间的…

运行时数据区

目录 一、概述 1.1、数据区 1.2、JAVA线程数据区 二、线程 2.1、JVM线程概述 2.2、JVM系统线程 三、PC寄存器 3.1、寄存器概述 3.2、作用 3.3、常见问题 一、概述 1.1、数据区 内存是非常重要的系统资源&#xff0c;是硬盘和CPU的中间仓库及桥梁&#xff0c;承载着操…

vulnhub DC系列 DC-5

总结&#xff1a; 下载地址 DC-5.zip (Size: 521 MB)Download: http://www.five86.com/downloads/DC-5.zipDownload (Mirror): https://download.vulnhub.com/dc/DC-5.zip使用方法:解压后&#xff0c;使用vm直接打开ova文件。 漏洞利用 信息收集 这里还是使用DC-1的方法 1.给靶…

AR Foundation

AR Session 在一个AR应用中有且只允许存在一个AR Session 包括两个组件 &#xff1a;AR Session &#xff08;用于管理 Session&#xff09;、AR Input Manager (用于管理输入的一些信息) AR Session&#xff08;用于管理 Session&#xff09; 作用&#xff1a;管理AR应用状…

01.数据的存储

1. 数据类型介绍 1&#xff09;基本的内置类型&#xff1a;char //字符数据类型 short //短整型 int //整形 long //长整型 long long //更长的整形 float //单精度浮点数 double //双精度浮点数 2&#xff09;类型的意义&#xff1a; 使用这个类型开辟内存空间的大小&#xff…

Docker快速入门自用笔记

1. Docker - 介绍 不同Linux的内核一致。 2. Docker - 与虚拟机的不同 3. Docker - Docker架构 镜像&#xff08;只读&#xff09;&#xff1a;应用程序及其所需依赖、函数库、环境、配置等文件打包在一起&#xff0c;称为镜像。 容器&#xff1a;镜像中的应用程序运行后…

一文看懂人机对话

人机对话概述 人机对话是指&#xff0c;让机器理解和运用自然语言实现人机通信的技术&#xff0c;如图1所示&#xff0c;通过人机对话交互&#xff0c;用户可以查询信息&#xff0c;如示例中的第一轮对话&#xff0c;用户查询天气信息&#xff0c;用户也可以和机器机型聊天&am…

Pytorch深度学习【十三】

LeNet网络 基本结构图 构造思路 先用卷积层来学习图片空间信息池化层降低敏感度全连接层来转换到类别空间 代码实现 import torch from torch import nn from d2l import torch as d2l class Reshape(nn.Module):def forward(self ,x):return x.view(-1, 1, 28, 28) # vie…

ElasticSearch7 Kibana集群安装

文章目录ElasticSearch安装下载安装包基础环境安装JDK安装修改Linux配置安装ES启动报错bootstrap check failure [1] of [1]: memory locking requested for elasticsearch process but memory is not lockedKibana安装Kibana简介Kibana下载Kibana安装ElasticSearch安装 下载安…

RS485接口电路设计

RS485接口是串口的一种&#xff0c;常常用在一些工业控制中&#xff0c;485通信是半双工的通信&#xff0c;一条总线最多可连接256个485设备。RS485信号速率最高可以到10Mbps&#xff0c;传输距离最大能到1000多米485接口包含一对差分信号A和B&#xff0c;如下图所示RS485标准规…