Objective-C中的继承与多态, Category, Extension

2023-11-13

1、继承与多态
先要理解实例变量的作用域:
这里写图片描述
再看继承:

这里写图片描述

即:子类的方法和属性 = 从父类继承得到的方法和属性 + 子类新增的方法和属性

例子:

//  Person.h
#import <Foundation/Foundation.h>
@interface Person : NSObject    //其父类是NSObject
{
    NSString *_name;            //实例变量默认是@protected的
    @private
    int _age;                   //父类的私有实例变量
}
-(void)run;                     //声明run方法
@end
//  Person.m
#import "Person.h"
float _salary;                  //定义一个父类的隐藏实例变量

@implementation Person
-(void)run{                     //实现run方法
    NSLog(@"person run!");
}
@end

//  Student.h  
#import "Person.h"
@interface Student : Person     //Student类继承于Person
-(void)printStu;
@end
//  Student.m
#import "Student.h"
@implementation Student
-(void)printStu{
    NSLog(@"student run!");
    NSLog(@"name: %@", _name);  //使用继承自父类的实例变量
}
-(void)work{
    NSLog(@"s w");
}
@end

注意:
(1)父类的私有实例变量虽然能被子类继承,但不能被子类使用。如在Student类的printStu方法使用 Person类的private属性 _age则报错:

这里写图片描述
(2)父类的隐藏实例变量(在 .m文件定义的,也有的地方叫作私有变量,但觉得跟 private 的属性搞混)不能被子类继承。如如在Student类的printStu方法使用 Person类的隐藏实例变量_salary则报错:
这里写图片描述
(3)OC中的继承是单继承。即一个类至多只能有一个父类。
(4)子类可以重写父类的方法。当子类对从父类继承得到方法不满意时,可以改写之,只需要在子类的 .m文件定义一个返回类型、方法名、参数都与父类原方法相同的方法。如上例中父类Person类已经定义了run方法,Student类可以在其.m文件中添加定义:

-(void)run{                 //重写run方法,覆盖父类的run方法
    NSLog(@"student run!");
}

调用时,父类的对象调用父类的方法,子类的对象调用子类的方法,不引起冲突:

//  main.m
#import <Foundation/Foundation.h>
#import "Person.h"
#import "Student.h"
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *person = [Person new];      //定义一个父类Person的对象
        Student *student = [Student new];   //定义一个子类Student的对象
        [person run];                       //父类的对象调用父类的run方法
        [student run];                      //子类的对象调用子类的run方法
    }
    return 0;
}

//运行结果:

这里写图片描述
(5)继承体系中方法调用寻找顺序:
当前类 —(没找到)—> 父类 —(没找到)—> 父类的父类 —(没找到)—> … —-(没找到)–> NSObject —(没找到)—> 报错

多态

使用多态的条件:(1)有继承;(2)有方法的重写;(3)父类指针指向子类对象
如对于上面例子,Student类继承了Person类,现在再创建一个Teacher类也继承Person类,然后两个子类都重写了run方法:

//  Teacher.h
#import "Person.h"
@interface Teacher : Person     //继承了Person
@end
//  Teacher.m
#import "Teacher.h"
@implementation Teacher
-(void)run{                     //重写run方法,覆盖父类的run方法
    NSLog(@"teacher run!");
}
@end
//  main.m
#import <Foundation/Foundation.h>
#import "Person.h"
#import "Student.h"
#import "Person+doSomething.h"
#import "Teacher.h"
int main(int argc, const char * argv[]) {
    @autoreleasepool {

        Person *person = nil;       //定义一个父类Person的对象
        person = [Student new];     //此时person指针指向一个子类Student的对象
        [person run];               //调用的是Student中的run方法

        person = [Teacher new];     //此时person指针指向一个子类Teacher的对象
        [person run];               //调用的是Teacher中的run方法
    }
    return 0;
}

多态的原理:
动态绑定:动态类型能使程序直到运行时才确定对象所属的类型。动态类型绑定能使程序直到运行时才确定要对象调用的方法。
注意:
(1)存在多态时,父类可以调用子类特有的方法,但要经过强制类型转换
这里写图片描述

这里写图片描述

(2)不存在多态时,父类不可以调用子类特有的方法,即使是强制类型转换也不行:
这里写图片描述

2、Category

OC特有,被翻译为:分类、类别、类目
作用:在不修改原有的类、不需要原有的类的代码的基础上增加新的方法(也只能是方法,不能增加实例变量)。

使用条件及好处:
(1)在分模块开发一个庞大的类时,有利于分工合作。
(2)替代子类的继承。因为继承可能影响原有的继承系统,而且只能单继承。而一个类可以有多个Category。
(3)一个Category中可以有多个方法,所以可以利用它来将方法归类,使得更好地更新和维护。

使用方法:声明 ——> 实现 ——> 使用。
如给上面例子的Person类新增Category:

//  Person+doSomething.h            注意文件名为“原类名+Category名”
#import "Person.h"
@interface Person (doSomething)     //Person为原有的类,doSomething为Category名
-(void)work;                        //声明方法
@end

//  Person+doSomething.m
#import "Person+doSomething.h"
@implementation Person (doSomething) //形式与.h文件一样
-(void)work{                         //实现方法
    NSLog(@"person work");
}
@end
//  main.m
#import <Foundation/Foundation.h>
#import "Person.h"
#import "Person+doSomething.h"       //导入声明文件
int main(int argc, const char * argv[]) {
    @autoreleasepool {

        Person *person = [Person new];
        //person可以像使用自身拥有的其他方法一样地调用Category为Person类新增的方法
        [person work];
    }
    return 0;
}

注意:
(1)category只能给类增加方法,不能增加成员变量、@property(可能编译不报错,但运行有问题)。如:

//  Person+doSomething.h
#import "Person.h"
@interface Person (doSomething)
@property int _height;          //试图在category增加成员变量
-(void)work;
@end

这里写图片描述
(2)category 的 @implementation……@end 可以单独放在一个 .m文件,也可以放在对应的 .h文件中 @implementation……@end 之后。也可以两者都放在原有类的 .h文件的 @implementation……@end之后,直接声明、实现category。

(3)category可以使用原有类的私有实例变量,但原有类的隐藏实例变量仍对category不可见。如:

这里写图片描述
(4)当有多个分类和原有类都有同名方法时,原有类的方法被覆盖,而且执行最后编译的category文件的方法。如

//  Person.m
#import "Person.h"
@implementation Person
-(void)run{            //在原有的Person类中实现run方法
    NSLog(@"person run!");
}
@end

//  Person+doSomething.m
#import "Person+doSomething.h"
@implementation Person (doSomething)
-(void)run{            //在第一个category doSomething中实现run方法
    NSLog(@"person run at doSomething!");
}
@end

//  Person+play.m
#import "Person+play.h"
@implementation Person (play)
-(void)run{             //在第二个category play中实现run方法
    NSLog(@"person run at play!");
}
@end

//  main.m
#import <Foundation/Foundation.h>
#import "Person.h"
#import "Person+doSomething.h"      
#import "Person+play.h"
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *person = [Person new];
        [person run];   //在主函数中调用run方法
    }
    return 0;
}
//输出结果:person run at doSomething!

因为在编译文件中,Person+doSomething.m 排在最后:
这里写图片描述
所以在 category 中是可以重写原有类的方法的,但系统会报警告:
这里写图片描述
而且也不推荐在 category 中重写原有类的方法,因为:
(1)一旦在category中重写了方法,则原有类的同名方法被覆盖掉,再也不能访问,所以也不能像子类那样发送消息给super就可以调用父类的同名方法,所以不能在原有方法的基础上增加功能,除非你想重复写那些功能。
(2)从上面的例子我们可以知道当多个类拥有同名的方法时,调用这个方法的结果貌似不是我们能控制的。
(3)在category中重写的方法不仅影响到原有的类,而且会影响原有类的子类,因为那些子类很可能继承并使用了这个方法,后果不言而喻。

了解了category的应用和优缺点,我们再来看下它的原理:
在Objective-C Runtime Reference中的简介:

Category
An opaque type that represents a category.

即Category是一种类型,这个类型代表了一个分类。
而在runtime.h中的定义:

typedef struct objc_category *Category;

所以Category是指向一个objc_category结构体的指针。而objc_category结构体的声明为:

struct objc_category {
    char *category_name                                      OBJC2_UNAVAILABLE;
    char *class_name                                         OBJC2_UNAVAILABLE;
    struct objc_method_list *instance_methods                OBJC2_UNAVAILABLE;
    struct objc_method_list *class_methods                   OBJC2_UNAVAILABLE;
    struct objc_protocol_list *protocols                     OBJC2_UNAVAILABLE;
}                                                            OBJC2_UNAVAILABLE;

分别存储了指向分类名、原有类的名称、对象方法列表的的结构体、类方法列表的结构体、遵守的协议列表的结构体的五个指针。
其中结构体objc_method_list定义如下:

struct objc_method_list {
    struct objc_method_list *obsolete                        OBJC2_UNAVAILABLE;

    int method_count                                         OBJC2_UNAVAILABLE;
#ifdef __LP64__
    int space                                                OBJC2_UNAVAILABLE;
#endif
    /* variable length structure */
    struct objc_method method_list[1]                        OBJC2_UNAVAILABLE;
}  

而我们定义的方法:

Method
An opaque type that represents a method in a class definition.

即也是一个类型,这个类型代表了在类定义中的一个方法。

在runtime.h中的定义:

typedef struct objc_method *Method;

所以Method是指向一个objc_method 结构体的指针。而 objc_method 结构体的声明为:

struct objc_method {
    SEL method_name                                          OBJC2_UNAVAILABLE;
    char *method_types                                       OBJC2_UNAVAILABLE;
    IMP method_imp                                           OBJC2_UNAVAILABLE;
}  

所以一个 objc_method 结构体存储了单个方法的方法名、方法类型、实现方法的地址。其中IMP是“implementation”的缩写,定义和描述如下:

/// A pointer to the function of a method implementation. 
#if !OBJC_OLD_DISPATCH_PROTOTYPES

typedef void (*IMP)(void /* id, SEL, ... */ ); 
#else
typedef id (*IMP)(id, SEL, ...); 
#endif

Discussion
This data type is a pointer to the start of the function that implements the method. This function uses standard C calling conventions as implemented for the current CPU architecture. The first argument is a pointer to self (that is, the memory for the particular instance of this class, or, for a class method, a pointer to the metaclass). The second argument is the method selector. The method arguments follow.
其中:

#define OBJC_OLD_DISPATCH_PROTOTYPES 1

对常量OBJC_OLD_DISPATCH_PROTOTYPES的解释:

Dispatch Function Prototypes
This macro indicates whether dispatch functions must be cast to an appropriate function pointer type.

亦即IMP是一个指针,指向了方法实现的函数的首地址,即类似C语言中的函数指针。这个实现函数有三个参数,第一个参数是对象自己(self),第二个是参数是方法选择器SEL,之后的参数是方法的参数。

所以,和调用类的方法一样,要调用category中的方法,也是要将方法名包装成SEL,然后在category中找含有方法数组列表的结构体,然后找单个方法,寻找的方法是将包装的SEL与每个方法的SEL型名称对比,两者一致为找到方法,然后通过IMP型指针找到实现方法的函数,调用该函数,即可完成调用category的方法。

(3)非正式协议(informal protocol)

是个特殊的category,特殊在于非正式协议是给根类 (root class,如 NSObject)增加方法。非正式协议一般不需要进行实现,一般在子类中进行方法的重写。

3、Extension

也是个特殊的category,特殊在与它没有名字!所以也叫匿名分类。
而它不仅可以给原有类增加方法,还可以增加成员变量。
如:

//  Person_study.h                 extension的头文件,名字为“原有类名_extension名”
#import "Person.h"
@interface Person ()               //括号内没有名字
{
    @private
    int studyTime;                 //在extension中增加了成员变量
}
-(void)studyHard;                  //在extension中增加了study方法
@end

//  Person.m
#import "Person.h"
#import "Person_study.h"           //要导入extension的.h文件才能使用增加的成员变量
@implementation Person
-(void)studyHard{                               //在原有类的.m文件中实现在extension study中声明的方法
    NSLog(@"person study at %d", studyTime);    //可以使用extension study中增加的成员变量
}
@end

//  main.m
#import <Foundation/Foundation.h>
#import "Person.h"
#import "Person_study.h"            //导入声明文件
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *person = [Person new];
        [person studyHard];         //调用在extension中增加的方法
    }
    return 0;
}

最后对比一下:
这里写图片描述

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Objective-C中的继承与多态, Category, Extension 的相关文章

  • 什么是多态?如何实现?只看这一篇就够了

    1 多态的概念 1 1 概念 多态的概念 通俗来说 就是多种形态 具体点就是去完成某个行为 当不同的对象去完成时会产生出不同的状态 2 多态的定义及实现 2 1多态的构成条件 多态是在不同继承关系的类对象 去调用同一函数 产生了不同的行为
  • 《clean architecture》第二部分编程范式读书笔记

    前言 第二部分的主题内容 Chap3 编程范式总览 三个编程范式 思考和小结 Chap4 结构化编程 可推导性 goto 是有害的 功能分解 测试 小结 Chap5 面向对象编程 封装 继承 多态 依赖反转 小结 Chap6 函数式编程 不
  • 从0开撸C++系列(二)——c++的继承

    从0开撸C 系列 往期地址 c 的封装 本期主题 c 中的继承 文章目录 从0开撸C 系列 1 c 的继承介绍 2 不同类型的继承 2 1 public继承 2 2 private继承 2 3 protected继承 3 关于继承的总结 1
  • 多重继承和多继承, super, __mro__

    继承 父类派生子类 子类继承父类 通过继承 可以让子类去拥有父类中的属性和方法 而不必重新编写相同的代码 并且可以在父类的基础上添加新的属性和功能 在继承的同时 子类还可以重写父类中的方法 从而获取与父类不同的功能 实现多态 在 Pytho
  • c++面向对象三大特征封装、继承和多态知识总结

    面向对象三大特征 封装 继承 多态 一 封装 该公开的就公开话 该私有的就隐藏掉 主要是由public private实现 作用是便于分工和分模块 防止不必要的扩展 二 继承 就是一种传承 可以把父类型中的数据传承到子类中 子类除了传承了父
  • JAVA-面向对象

    面向对象编程与面向过程编程只是一种在思维方式上的划分 面向过程是以分步骤的方式解决问题 而面向对象是以分步骤的方式解决问题 面向对象的三大特性是 封装 继承 多态 封装 就是将客观事物封装成抽象的类 抽象类可以将自己的数据和方法只让自己信任
  • 面试官问,重载(overload)和重写(override)的区别?

    重载 overload 方法重载是指同一个类中的多个方法具有相同的名字 但这些方法具有不同的参数列表 即参数的数量或参数类型不能完全相同 重写 override 方法重写是存在子父类之间的 子类定义的方法与父类中的方法具有相同的方法名字 相
  • 面向对象的编程思想和Python的继承和多态,特殊方法,引用计数

    面向对象的编程思想和Python的类 访问和属性 继承 在上一文中我们了解到了 私有的属性的访问方式 实例名 类名 私有属性名 一 私有的属性如何对外提供公有的取值和赋值方法呢 提供公有的方法作为接口进行取值 例如 class Avg Sc
  • C++进阶:继承

    继承 面向对象的三大特性 封装 继承 多态 继承在实际中用的也不是太多 但必须能熟练使用 1 继承的概念 继承机制是面向对象思想中复用代码的一种手段 它能够在原有类的特性的基础上进行扩展 由此产生新的类 称为派生类 原有类称作基类 继承体现
  • 继承中析构和构造的调用原则

    继承与组合混搭情况下 构造和析构调用原则 先说结论 原则 先构造父类 再构造成员变量 最后构造自己 先析构自己 在析构成员变量 最后析构父类 注 先构造的对象 后释放 class my 创建一个成员类 public int a my int
  • line-height 百分比和数值设置行高

    一 line height的值为百分比 子集元素继承父级元素的距离 1 例如
  • Objective-C中的继承与多态, Category, Extension

    1 继承与多态 先要理解实例变量的作用域 再看继承 即 子类的方法和属性 从父类继承得到的方法和属性 子类新增的方法和属性 例子 Person h import
  • JS继承

    最近在面试中被问到js的继承 当时回答的不太好 所以今天特别总结一下 我们先来看一个基于原型链的继承例子 父类 function Person 子类 function Student 继承 Student prototype new Per
  • 【C++ 进阶】继承

    一 继承的定义格式 基类又叫父类 派生类又叫子类 二 继承方式 继承方式分为三种 1 public继承 2 protected继承 3 private继承 基类成员与继承方式的关系共有9种 见下表 虽然说是有9种 但其实最常用的还是红框里的
  • 三十、纯虚函数、抽象类、多态、简单工厂模式

    一 纯虚函数 虚函数是多态是实现多态的前提 如果我们需要在基类中定义共同的结构 那么接口就需要定义成虚函数 但是很多情况下基类的接口是无法实现的 比如形状类Shape 定义一个Draw方法 很明显这个方法没法实现 因为我们可以画出圆 正方形
  • Java面向对象三大特性:继承、封装、多态

    面向对象编程 一 继承 1 表现形式 A extends B 2 子类继承了父类的什么 BAT 面试 3 this 和 super 关键字的区别 面试 4 Java 中访问权限修饰符 5 重写 与 重载的区别 面试 6 final 的用法
  • java学习笔记——第八章 类和方法(二)

    8 4使用方法 java中的方法不能独立存在 他必须属于一个类或者一个对象 因此方法也不能像函数那样被独立执行 因此必须使用类或对象来调用 总结 方法不能独立定义 方法只能在类体中定义 逻辑上讲 方法要么属于该类本身 要么属于该类的一个对象
  • JavaSE进阶(一)—— 面向对象进阶(static、单例、代码块、继承)

    目录 一 static静态关键字 1 static是什么 static修饰成员变量的用法 2 成员方法的分类 2 1 使用场景 3 static修饰成员方法的内存原理 4 static的注意事项 拓展 二 static应用知识 工具类 1
  • iOS中堆和栈的使用(Swift)

    堆和栈都是一种数据项按序排列的数据结构 只能在一端 称为栈顶 top 对数据项进行插入和删除 堆 队列优先 先进先出 FIFO first in first out 栈 先进后出 FILO First In Last Out 堆栈空间分配
  • Objective-C中的封装、继承、多态、分类

    封装 封装最好理解了 封装是面向对象的特征之一 是对象和类概念的主要特性 封装 也就是把客观事物封装成抽象的类 并且类可以把自己的数据和方法只让可信的类或者对象操作 对不可信的进行信息隐藏 继承 面向对象编程 OOP 语言的一个主要功能就是

随机推荐

  • Eclipse导入外部工程运行报错:The selection cannot be run on any server

    一 错误描述 课下复习时导入老师的Demo 发现项目报错 在项目上有红色叉 运行时报如下错误 二 解决步骤 1 在项目上右键 gt Properties 2 打开Java Build Path 发现JDK和Tomcat都未绑定 这是原先项目
  • vue左侧悬浮_vue实现移动端悬浮窗效果

    本文讲述 在使用VUE的移动端实现类似于iPhone的悬浮窗的效果 相关知识点 touchstart当在屏幕上按下手指时触发 touchmove 当在屏幕上移动手指时触发 touchend 当在屏幕上抬起手指时触发 mousedown mo
  • UE4的SpawnActor问题

    1 SpawnActor传参 做项目过程中发现先SpawnActor再传参会先运行 生成的Actor的BeginPlay然后才进行传参 可是我在BeginPlay时就需要用到参数做操作了 发生情况时代码如下 if BaseCharactor
  • 9.21 小米一面面经

    介绍项目经历 测试时用的方式 在公司学到了哪些 有什么积累 将来想发展的方向 手工测试 测试开发 上学时的专业课 手机测试部 通信系统部 和通信关联性比较大 通信协议相关测试 跟手机芯片相关 2G3G4G5G网络 在研产品测试 国际运营商的
  • 计操理论课08 -- openEuler实验第七章文件系统

    文章目录 任务1 为 Ext4 文件系统添加扩展属性 25min 任务描述 任务过程及截图 任务2 注册一个自定义的文件系统类型 15min 任务描述 任务代码 任务截图 任务3 在 proc下创建目录 20min 任务描述 任务代码 任务
  • (LeetCode C++)移动零

    给定一个数组 nums 编写一个函数将所有 0 移动到数组的末尾 同时保持非零元素的相对顺序 请注意 必须在不复制数组的情况下原地对数组进行操作 示例1 输入 nums 0 1 0 3 12 输出 1 3 12 0 0 示例2 输入 num
  • 25道Python练手题(附详细答案),赶紧收藏!python入门

    题目 1 水仙花数 水仙花数 Narcissistic number 也被称为超完全数字不变数 pluperfect digital invariant PPDI 自恋数 自幂数 阿姆斯壮数或阿姆斯特朗数 Armstrong number
  • 启动jar包报错: 找不到或无法加载主类

  • C++ 删除文件夹下所有内容

    介绍一种删除文件夹下所有内容的实现方式 包括删除该文件夹 注 该文件夹下面 无论嵌套多少层文件夹或文件数据 都可以删掉 直接上代码 Tip 以 方式查找所有类型文件 do while方式进行循环遍历 直到满足终止条件 删除文件夹及内容 in
  • web前端开发语言介绍

    web前端开发语言主要包含 html语言 css样式代码 javascript脚本 html5 css3 jQuery ajax Bootstrap Backbone 1 html语言 网页的基本标记语言 也是最基础的语言 掌握起来比较简单
  • 8.cmake常用命令

    前面我们讲到了 cmake 常用的变量 相信 cmake 即编程 的感觉会越来越明显 无论如何 我们仍然可以看到 cmake 比 autotools 要简单很多 接下来我们就要集中的看一看 cmake 所提供的常用指令 在前面的章节我们已经
  • ctfshow-WEB-web11( 利用session绕过登录验证)

    ctf show WEB模块第11关用session中保存的密码进行登录验证 将 session中保存的密码清空即可绕过 页面中直接给了源码 很明显是让我们进行代码审计 源码中将我们输入的密码与 session中保存的密码进行匹配 两个pa
  • stata F值缺失_stata操作问题实录

    一 数据整理 1 新变量带原变量的标签 sysuse auto clear gen m mpg 新建的变量m没有了标签 我想要标签 clonevar Mpg mpg 直接用clonevar命令就好了 2 下载时候就应该直接选合并报表 ege
  • 【Uniapp】base64图片资源转为本地图片,解决canvas不支持base64问题

    通过接口获取到base64类型的二维码 把二维码放到canvas里生成海报 遇到的问题 在微信小程序开发工具中能够正常显示海报 到真机上测试就无法显示二维码 原因 因为canvas不支持base64 其次在使用小程序 canvas 的 dr
  • Logistic回归实战篇之预测病马死亡率(三)

    作 者 崔家华 编 辑 李文臣 四 使用Sklearn构建Logistic回归分类器 开始新一轮的征程 让我们看下Sklearn的Logistic回归分类器 官方英文文档地址 http scikit learn org dev module
  • Python 自动发送邮件详细教程

    自动发送邮件能应用于许多场景中 比如我想要知道股票策略中的股票池是否有实时的更新 这时候如果再拉一遍数据 跑一遍脚本 实在是太浪费时间了 为什么不把这一套流程放到服务器上 然后到点自动运行并发送邮件呢 类似的应用场景还有很多 不仅仅是在股票
  • Cocos2dx中文乱码问题

    最开始在网上找的一个方法 结果在wp8上报错 在windows环境下使用visual studio 开发cocos2d x 由于visual studio 默认编码为GBK 格式 而cocos2d x引擎默认编码为UTF 8 如果有用到中文
  • 从输入网址(URL)到页面加载的全过程

    从输入网址 URL 到页面加载的全过程 简述 输入网址到页面加载的过程涉及知识点众多 所以这里整理一下自己看过的相关文章内容 在浏览器中输入URL 在浏览器中输入URL 也就是网址 URL是Uniform Resource Locator的
  • Queue基本概念

    概念 Queue是一种先进先出的数据结构 他有两个出口 队列容器允许从一段新增元素 从另一端移除元素 队列中只有队头和队尾可以被外界使用 因此队列不允许有遍历行为 队列中进数据称为 入队push 队列中出数据称为 出队pop queue常用
  • Objective-C中的继承与多态, Category, Extension

    1 继承与多态 先要理解实例变量的作用域 再看继承 即 子类的方法和属性 从父类继承得到的方法和属性 子类新增的方法和属性 例子 Person h import