OC基础DAY04 - 继承与多态

static关键字
  1. C语言的static 可以修饰局部变量,将局部变量变为静态变量 修饰全局变量,函数
  2. OC中static
    1. static 不能修饰属性和方法
    2. 方法中的局部变量可以修饰,在方法执行完后不会被回收.下次直接用,和c语言修饰局部变量一样
    3. 如果希望方法无论执行多少次,变量只有一个,那么就搞成静态的

self关键字
  • 考虑的问题
    1. 在方法中是可以定义一个和属性名相同的局部变量,这个时候,如果直接使用同名的变量,访问的是局部变量
    2. 如果我们就是要访问属性怎么办呢?
    3. 就是要在1个对象方法中调用当前对象的另外一个对象方法呢?
self关键字
  1. 可以用在对象方法和类方法中
  2. self是一个指针.在对象方法中self指向当前对象.在类方法中self指向当前类.
    • 在对象方法中 使用self
谁调用这个对象方法,谁就是这个对象方法的当前对象.
[p1 sayHi]; 
1. p1 就是当前对象
2. self的值是当前对象的地址
3. %p 的p1 和 self 是相等的
  1. 可以使用self关键字显示的访问当前对象的属性.
  2. 可以使用self关键字来显示的调用当前对象的方法.
  3. 必用场景
{
    1. 有和属性同名的局部变量,要调用属性就必须用self->去调用属性.
    2. 如果想在对象方法中调用当前对象的其他的对象方法必须是用self->.
    3. 如果你重新创建对象,调用的就不是当前对象的方法而是新对象的.
}
4. 选用场景
5. 在方法中如果没有和属性同名的局部变量,这个时候要访问属性名和
6. 属性的名字才以下划线开头,局部变量不要以下划线开头.这样写是不会出现重名的.
  • 在类方法中使用self
    1. 类加载
      当类第一次被访问的时候,就将这个类储存在代码段.一旦存储,直到程序解释才会被释放
    2. 在类方法中self也是一个指针,这个指针指向当前这个类在代码段中的地址
    3. 如何拿到类在代码段中的地址呢?
      1. 查看对象的isa指针
      2. 在类方法中打印self的值
      3. 调用对象的方法class. 也可以得到这个对象所属的类的地址
      4. 调用类的类方法class 也可以得到这个类所在的地址
[p1 class];
//返回p1方法的类在代码段中的值.
[HMPerson class];
//类在代码段中的地址
  1. 我们现在知道:在类方法中, self确实指向当前这个类的.然而有什么用??
    在类方法中,self代表当前这个类.self == HMPerson
    在类方法中,可以使用当前类的地方,完全可以用self代替
在类方法中可以使用self来代替当前类. 比如调用当前类的其他类方法.

声明变量的时候不能用self 他不是类型. HMPerson *p1 的是类型 self是指针不能代替.
[HMPerson new]可以用self代替

继承

  • 多个类具有相同的成员.
    1. 使用复制粘贴缺点:代码冗余.后期维护不好维护.
    2. 继承的目的就是让子类继承父类的成员,而不用自己定义
    3. 步骤
@interface 类名 : 父类名

@end

@implementation 类名

@end

在声明类的时候.在类名的后面,冒号后面写上父类的名字

子类就拥有所有父类的成员和.而不用自己去定义

几个专业术语:
1. 子类从父类 "继承" . 
2. 子类从父类 "派生" . 父类是子类的 "基类".
使用继承的注意
  1. 继承是类在继承,而不是对象在继承,对象与对象之间是毫无关系的
  2. 继承: 子类模板有了父类模板中的成员.
  3. 不要为了继承而继承.不要为了得到某个类的成员去继承.比如学生继承狗.猪
    1. 只有满足is a关系的两个类才可以继承
    2. 当A类是一个B类的时候 那么这个时候A类才可以从B类继承
    3. 学生是不是一个人 是的 学生是不是狗 不是 学生是不是电脑 不是
    4. 继承强调的是:家族
    5. 只要有一个成员不是所有的子类都拥有的.就不应该把这个成员定义在父类当中

总结
  1. 多个类都有相同的成员就可以定义父类
  2. 单根性:只能继承一个父类
  3. 传递性:A从B继承 B从C继承.A就爽歪歪拥有B,C所有成员.
  4. 4.
NSObject类 是什么
  • 是foundation框架中的一个类
    1. 在这个类中有一个类方法叫做new.
    2. 这个方法的作用是来创建一个对象并初始化这个对象,将这个对象的地址返回.
    3. 如果我们的类,想要创建对象,就必须调用类方法new才可以创建对象
    4. new方法是定义在NSObjct类中的
    5. 所以我们自定义的类如果想要创建对象就必须要有new方法.
    6. 为了让我们的类有创建对象的能力,就必须直接或者间接的从NSObject类继承new方法.
    7. NSObject类是所有类的==基类==

因为OC中所有类都直接或者间接从NSObject类继承.
  • 如果我们在写类的时候,你不确定这个类的父类是谁,就指定NSObject为父类.
  • 在NSObject中有一个属性叫isa,而所有的OC类都是从NSObject类继承的,所以每个对象都有isa.
  • 子类不能定义和父类中同名的属性.因为子类可以继承父类,就相当于自己有这个成员.再定义一个同名的自然就重复了.
super关键字
  1. 可以使用在对象方法和类方法中
  2. 可以显示的掉用当前子类对象从父类中继承过来的对象方法
    1. 要在子类的对象方法中调用从父类继承过来的对象方法,可以使用self来使用,因为父类的就是子类的.
    2. 如果我们想要在子类的对象方法中调用从父类继承过来的对象方法,也可以使用super
    3. 虽然这个时候,使用self和super效果一样,但是我们建议:如果你调用的方法是从父类继承过来的,建议使用super调用.这样代码的可读性很高.
    4. 在类方法中也可以使用super
    5. 在类方法中可以使用super
    6. 显示的调用父类的类方法,可以使用super调用
    7. 调用类方法有很多种方式.父类名,子类名,self,super.
    8. super关键字
    9. 在对象方法中显示的调用从父类继承过来的对象方法
    10. 在类方法中显示的调用从父类继承过来的类方法
    11. super不能访问属性

属性的访问修饰符.
  1. 可以限定属性被访问的范围
  2. 访问修饰符的种类
@private 
1. 被private修饰的属性,叫做私有属性,私有属性只能在本类中访问,除了这个类的内部,其他的地方都是无权访问的
2. 内部就是指在定义方法的地方.
3. 父类的私有成员,子类可以继承,但是子类不能访问.
4. ==但是可以间接访问?==

@protected
1. 受保护的.只能在本类和本类的子类中访问.
2. 外部还是不能访问的.

@package
1. 只能在当前taget中访问.
2. 也可以说只能在当前框架中访问

@public: 
1. 公共的 任意地方都能访问这个属性,前提还是要创建对象去访问.

默认的是protected
作用域是从定义的位置往下知道遇到另一个访问修饰符或者大括号结束
  • 使用建议
    1. 无论什么情况都不要用public,如果希望外界访问就封装geeter和setter.
    2. 如果父类属性不希望子类访问,那么就是用private
    3. 如果父类有属性想给予子类访问,就用@protected
    4. 使用默认的@protected.
真私有属性


  • 私有属性

  1. 被@private修饰的叫做私有属性.但这种不够彻底.因为Xcode仍然会提示这个对象有这个属性只不过无权访问.
  2. 我们想要真正的私有属性,连提示都没有.
  3. 定义在@implementation中.要加个大括号哦.写在这里的属性任何访问修饰符无效.都是私有的.
  4. 唯一的区别就是Xcode不提示了.其他都一样,也能继承.
  5. 私有方法
  6. 只希望这个方法在类的内部调用,不希望在外部调用.
  7. 访问修饰符只能修饰属性,不能修饰方法
  8. 方法只写实现,不写声明.就是私有方法,只能在类的内部调用.
  9. 应用场景
  10. 只是因为内部需要调用,所以才有的属性和方法.
  11. 外部不需要调用类的方法或属性,就设置为私有属性,方法.
  12. 放在.m文件中的@implementation中,别人没有包含.m文件所以不知道.

里氏替换原则LSP
HMPerson *p1 = [HMStudent new];
子类对象可以替换父类对象的位置,并且程序的功能不受影响.叫做里氏替换原则


  • 为什么?

  1. 指针是一个父类类型,但我们给了一个子类对象的地址,是可以的.因为子类就是一个父类.
  2. 为什么没影响,因为父类中的成员子类都有.只会多不会少.所以程序的功能不受影响.
  3. 就算有私有的,在外部也访问不到私有的,所以不收影响.
  4. 表现形式.

HMPerson *p0 = [HMPerson new];
//这个不叫里氏替换.
HMPerson *p1 = [HMStudent new];
1. 当一个父类指针指向一个子类对象,就是里氏替换原则


  • LSP的好处是什么

  1. 一个指针不仅仅可以存储本类对象的地址,还可以存储子类对象的地址!
  2. 如果1个指针的类型是NSObect类型的,所有的OC对象都能存!. 甚至可以存@”JACK”.
  3. C语言中学习到的基本数据类型不是OC对象,是不能存的.
  4. NSObject类型的指针叫做万能指针.

    > HMPerson *ps[4];
    > ps[0] = [HMPerson new];
    > ps[1] = [HMStudent new];
    > ps[2] = [HMTeacher new];
    >
    > NSObject *objs[4];
    > objs[0] = @"jack";
    > objs[1] = [HMStudent new];
    > objs[2] = {NSArray new];
    >

  5. 如果一个数组的元素类型是NSObject*类型的,那么久体位置这个数组可以存储任何OC对象.
  6. ==如果一个方法的参数是父类,那么实参即可以是一个父类对象,也可以是一个子类对象.==
  7. 那么在实现一个方法的时候如果需要的参数正好是父类中有的,我们就可以用一个父类指针作为形参,接收不同的子类的实参,扩大了这个方法的应用范围.
  8. LSP的局限性

当一个父类指针指向一个子类对象的时候只能通过这个父类指针去访问子类对象当中的父类成员,==子类独有的成员无法访问==.
方法的重写
子类虽然拥有父类的行为(方法),但是子类的行为的实现和父类是不一样的.


  • 这个时候怎么办呢?那就让子类重写这个方法就可以了.

  1. 在@implementation 中再定义一次.
  2. 这个时候调用子类对象的这个方法,调用的就是子类重写的方法.
  3. 什么时候重写
  4. 子类有这个方法,但是子类的这个方法的实现和父类不一样.==全盘否定==
  5. 子类认为父类的方法做的事情还不够.想在基础上==多做一些事情==.
  6. 如何重写?
    子类在类的实现中,按照自己的方式重新实现这个方法就可以了
  7. 创建子类对象,调用子类对象的方法如果被重写了,调用的就是重写的方法
    ==父类指针调用子类对象的方法,调用的是子类重写的方法.==

当想在父类基础上多做一些事情的时候.
- (void)sport
{
    [super sport]//调用父类的sport方法执行一遍,如果全盘否定就不写这条.
    补充(重写)认为还要做的事情;
}
多态
同一种行为,对于不同的事物而言,具有完全不同的表现形式.

演员 医生 理发师 对于cut这个方法的执行.同一种行为具有不同的形态.
- 父类指针指向子类对象
实现多态的3个前提条件
1. 继承关系
2. 方法的重写
3. 父类的指针指向子类对象.

description
  1. NSLog一个对象的时候,可以用%p的值,是对象的地址
  2. 也可以用%@,答应的是指针指向的对象,我们答打印的是<对象所属的类名:对象地址>.
  3. %@的原理:在使用%@打印一个对象的时候,内部是如何做的
    1. 先调用传入的对象的description方法,这个方法有一个返回值是NSString.
    2. 将拿到的符串数据输出在控制台
    3. 是定义在NSObject中的一个方法.
    4. 方法在NSBoject中的实现是返回一个格式的字符串,

@"<对象所属的类名:对象地址>"
  1. 如果你想要自定义打印对象的格式.那么就重写description方法
- (NSString *)description
{
    return [NSString stringWithFormat:@"姓名:%@ 年龄:%@",_name,_age]
}
继承的本质


  • 创建了一个子类对象,这个子类对象中到底有什么?

子类对象中有子类以及它的所有的父类的所有的属性
  • 访问子类那么肯定要访问父类
    所以,将子类加载到代码区,肯定也会将父类加载到代码区.每一个类都有一个isa指针指向它的父类
  • 搜索方法的步骤
    1. 通过对象名调用一个方法的时候
      [p1 sayHi];
    2. 先通过p1指针找到对象
      1. 通过这个对象的isa指针找到本身这个类,搜索这个类中是否有这个方法.
      2. 如果没有这个方法就会到这个类的父类中找,以此类推
      3. 如果到NSObject类还没找到就报错
      ==逐级搜索,找到就停止==
类是以Class对象的形式存储在代码段中的(类的本质)
  1. 回忆内存中的五大区域
: 局部变量
堆: OC对象.自己申请的空间:callo malloc realloc
BSS:未初始化的全局变量,静态变量
数据段:已经初始化的全局变量,静态变量,常量数据
代码段L存储代码
  1. 类加载

    当类第一次被访问的时候,就会将这个类加载到代码段中.

  2. 关于类加载
    1. 类什么时候加载
    当类第一次被访问的时候
    2. 类是以什么形式存储到代码区
    这里是重点!!
    3. 什么时候回收存储在代码区中的类
    当程序结束的时候,类就从代码段中回收
  3. 类是以什么形式存储到代码区
    1. 任何存储在内存中的数据都有一个类型.内存中的空间都有类型
    2. 将类加载到代码段中存储,肯定也是需要一块空间来存储的.那么在代码段中存储类的空间是什么类型的呢?
  4. 加载类到代码段中的过程
a.先在代码段中创建一个Class类型的对象.
        Class是一个类,由系统定义的.所以我们就可以创建这个类的对象

        这个Class对象的作用:用来存储一个类的信息.

    b.将要加载的类的信息存储到这个Class对象当中.

        类的信息:
        类名:   HMPerson
        属性s;  年龄,姓名
        方法s;  sayHi;
    所以,类是以Class对象的形式存储在我们对象中的.
    这个Class对象我们也叫类对象.
  1. 如何拿到Class对象
    1. 要声明Class指针不需要加* 了.因为Class类型是一个typedef类型,在定义的时候就已经加 *了.

      > Class c1;
      >

    2. 调用对象的class方法 就可以得到存储这个对象所属的类的Class对象.

      > Class c1 = [p1 class];
      > NSlog(@"%p",c1);
      > c1指向存储HMPerson类的Class对象.
      >

    3. 调用类的类方法class,就可以的到存储这个类的class对象

      > Class c2 = [HMPerson class];
      > c2 得到的东西和 c1 一样.
      >
    4. 如何使用类对象
    5. 类对象中(c1,c2)中存储的是HMPerson类的信息.所以我们可以认为c1对象就代表HMPerson类.c1对象和HMPerson类完全等价.
    6. 所以我们就可以用class对象来调用存储在class对象中的类的类方法.不能调用对象方法
    7. 也可以使用Class对象来创建存储在Class对象中的类的对象.

HMPerson *p1 = [c1 new];
//也就是调用class对象中的类的类方法.
SEL数据
调用一个对象的对象方法有两种方式,通过创建对象用对象名调用.或者手动的为对象发送SEL消息.
[p1 performSelector:@selector(sayHi)];
手动发送SEL消息.
  • SEL

    1. 全称: selector
  • SEL实际上是一个类,这个类的对象是用来存储方法的.是Class类中的一个属性

    因为要存储一个方法,信息有很多.方法名,参数没返回值,方法体

  • 所以Class对象存储细腻的方式如下
    1. Class对象中有一个属性是专门存储类的方法的
    2. 这个属性的类型是SEL指针类型
    3. 创建1个SEL对象.将方法的信息存储在这个对象中
    4. 将这个SEL对象的地址赋值给Class对象的SEL属性
    5. 如何拿到存储方法的SEL对象
      1. 因为SEL是一个typedef定义类型.定义的时候就已经有了.所以声明SEL指针的时候就不需要再加
      2. 拿到存储方法的SEL对象的格式.

SEL s1 = @selector(sayHi);
//这个地址就是存储sayHi方法的SEL对象的地址.
SEL s2 = @selector(eatWith:)
//带了参数的参数名是要带冒号的.
如果给定的方法名不存在,不会报错,只不过拿到一个无效的地址.
  1. SEL中,存的不仅仅是方法的地址,还有这个方法是属于哪一个类的等等..
  2. 调用方法的本质
[p1 sayHi];是怎么来的
1. 先拿到存储sayHi方法的SEL对象的地址等,我们叫做SEL消息,也叫做SEL数据
2. 将这个SEL消息发送给p1对象.
3. 当这个对象收到发送过来的SEL消息的时候,对象就知道你一定是要调用我的方法.
4. 对象再根据isa指针找到存储类的Class对象,在Class对象中搜索是否有匹配的SEL数据.如果有就执行,没有就搜索父类.
  1. 手动的为对象发送SEL消息.
1. 先得到存储方法的SEL消息
SEL s1 = @selector(sayHi);

2. 将SEL消息发送给对象
[p1 performSelector:s1];
这就是间接调用方法.

如果有参数呢?
SEL s1 = @selector(eatWith:);

[p1 performSelector:s1 withObject:@"红烧肉"];
[p1 performSelector:s1 withObject:xx withObject:xx];
如果参数有3个系统自带没有就自己写.传一个对象进去.
{
    int _num1;
    int _num2;
    int _num3;
}


  1. 如果多个方法重名怎么办?

没关系,因为SEL中不只有地址,还有别的可以区分方法的数据.你把s1消息发给谁就是调用谁的.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值