【Objective-C】4 继承与多态

第四节 继承与多态


01 Xcode文档的安装

  • apple 提供了很多框架,框架中有很多类 / 函数 / 数据类型

    ---> 问题:

    1. 如何知道有哪些框架
    2. 框架中有哪些类
    3. 类中有什么方法
    4. 如何调用

    ---> 答案均在 Xcode 文档中

Xcode 文档:window --> developer documentation


02 static 关键字

  • C 语言中的 static

    修饰局部变量 / 全局变量 / 函数

在 OC 中,static 不能修饰属性,也不能修饰方法,但可以修饰方法中的局部变量
---> 若 static 修饰了方法中的局部变量,则该变量变为静态变量,存储在常量区,在方法执行完毕后不会被回收,下次执行方法时不再重新创建,而是直接使用

举例:学生编号自动 + 1,不由用户输入
--> 令编号为创建对象的类方法中的一个静态变量,每调用一次该变量的值 + 1

  • 注意:当类方法返回值为类的对象时,返回值类型建议不要写类名,写 instancetype

    @interface Person : NSObject
    {
      NSString *_name;
    }
    + (instancetype)person;
    @end
    

03 self 关键字

在方法内部可以定义一个与属性名相同的局部变量。
此时在方法中该名称指代方法的局部变量,无法访问对象的属性。

3.1 self 使用情景

self 是一个指针,可以在对象方法(指向当前对象) & 类方法(指向当前类 --> 存储代码段中类加载后的地址)中使用

  1. 方法内部存在与属性名相同的局部变量,若想访问同名属性,使用 self->_xxx

    规避该情景的方法:属性名以 _ 下划线开头,方法中的局部变量名直接写名词

  2. 在方法内想要调用另一个方法,使用 [self xxx]

3.2 self 在类方法中的使用

可以在本类中调用其他类方法(其实可以使用类名调用,但建议使用self)

  • 取到类在代码段中的地址的方法

    1. 通过调试查看对象中 isa 指针的值(isa 指针禁止访问,只能查看,无法实际“取到”)

    2. 在类方法中查看 self 的值

      + (void)test{
        NSLog(@"self = %p", self);
      }
      
    3. 调用对象的对象方法 class ,可以返回类的地址

      Person *p = [Person new];
      NSLog(@"%p", [p class]); // 输出 p 所属的类的地址
      
    4. 调用类的类方法 class,也可以返回地址

      NSLog(@"%p", [Person class]); // 输出 Person 类的地址
      

      注意:对象方法 & 类方法可以重名(调用方式不同),但对象方法之间 & 类方法之间不可重名


04 继承

---> 当多个类具有相同的成员 --> 大量复制粘贴,造成代码冗余,后期维护难

现实中的继承:子代无条件拥有父代的资产,非子代不能拥有;父代的资产由父代创建

代码中的继承:子类拥有父类中所有的成员,想直接继承,不必重新定义

4.1 继承语法

// Person 类已创建
// 想让Chinese 类继承 Person 的全部成员(“人”的属性 & 行为“中国人”都有)

@interface Chinese : Person
  // Chinese 类从 Person 类 继承 / 派生
  // Chinese 是 Person 的子类 / 派生类,Person 是 Chinese 的父类 / 基类
@end

4.2 使用注意

  • 在新创建时可以在弹窗中的 subclass of 中指定父类,Xcode 自动生成相应代码

  • 继承是类继承,而不是对象继承,对象与对象之间毫无关系

  • 继承是有条件的,乱继承往往不符合逻辑要求

    ---> 判断是否满足继承关系 :... is a ...

    A is a B --> A 可以继承 B (学生是人,车是交通工具,...)

  • 如果有一个成员并不是所有的子类都有,那么就不应该定义在父类中

    例:学生有学号,但老师没有 ---> 学号属性不应定义在 Person 类中;

    交通工具不是都能在地面行驶(船,飞机。。)---> 在地面上行驶的方法不应定义在父类中

  • 子类中不可存在和父类同名的属性 & 方法

4.3 继承的特点

  1. 单根性:一个类只能有一个父类
  2. 传递性:A从B继承,B从C继承 ---> A同时拥有B C 的成员

4.4 NSObject 类

是 Foundation 框架中的类,其中包含类方法 new。
该方法用于创建一个新对象,返回值为这个对象的指针

---> 所有类必须直接或间接的从 NSObject 继承,否则将无法创建对象

NSObject 类中还包含属性 isa,因此所有类的对象都包含一个 isa 指针

4.5 super 关键字

  1. 在对象方法中使用

    [super xxxx]:用于调用当前对象从父类继承来的对象方法

    ---> 其实使用 [self xxxx] 也可以实现,因为子类继承了父类的全部

  2. 在类方法中使用

    [super xxxx]:用于调用当前类从父类继承来的类方法

    ---> 其实使用 [self xxxx] 或 [子类名 xxxx] 或 [父类名 xxxx] 也可以实现

---> 还是建议使用 super,增强可读性

  • 注意:super 只能用来调用方法,不可访问属性

05 私有属性与私有方法

5.1 访问修饰符

用来修饰属性,限定了属性的使用范围

@private:私有的,使属性只能在本类的内部访问
---> 只有本类的方法实现可以访问,其他类(包括子类) & 外部函数均不可访问)

@protected:受保护的,使属性只能在本类和其子类的内部访问

@package:使属性可以在当前框架中使用(需要自己写框架时才会用到)

@public:公共的,可以任意访问

  • 注意:

    1. 如果属性未被指定访问修饰符,系统默认为 @protected

    2. 子类可以继承父类的私有属性,但子类内部无法直接访问 --> 可以通过父类的 setter & getter 方法访问

    3. 作用范围:从当前访问修饰符开始,到另一个访问修饰符 或 } 大括号结束

    4. 使用建议:

      @public:不要使用(不要让属性暴露给外部)

      @private:只在本类中使用 ---> 不需要写 getter & setter,如果需要写就不要用(没必要)

      @protected:一般情况下,都推荐使用默认的 @protected

    5. 只能修饰属性,不可修饰方法

5.2 私有属性

  • @private 修饰的属性称为私有属性,只能在类的内部访问。
    但在外部,Xcode 仍会提示这个对象中有这个属性,只是无权访问

    如何“真私有”?让外界不知道内部存在这个属性?

    ---> 将属性定义在 @implementation 的大括号中,此时属性为私有属性,各种访问修饰符均无效,外界不会提示更无法访问

    @implementation Person
    {
      @public
      int _age; // 即使前面缀有 @public 访问修饰符,这个属性始终私有
    }
    @end
    

这两种定义属性的方式都会产生私有属性,唯一的区别在于 Xcode 能否产生提示

5.3 私有方法

方法不想被外界调用,只在内部使用?

---> 只写实现,不写声明 ---> 只能在本类的其他方法内使用


06 多态

6.1 里氏替换原则(LSP)

子类可以替换父类的位置,且程序的功能不受影响。

表现形式:让一个父类指针指针子类对象

Person *p = [Student new]; // 合法

Student *s = [Student new];
// Person 类中的类方法: + (void)person:(Person *)p1 TalkToPerson(Person *)p2;
[Person person:p TalkToPerson:s]; // 合法

为什么?

  • 逻辑上:学生也是人,请求要一个人,当然可以给一个学生
  • 代码上:父类拥有的成员子类中都有,不会影响程序的功能

作用:指针不仅可以当前类的对象在堆区的地址,也可以存储其子类的对象的地址

  • 如果一个指针类型为 NSObject,那么这个指针可以指向任意的 OC 对象
  • 如果一个数组的元素类型为 OC类的指针,那么该数组的元素可以存储这个 OC 类及其任意子类的对象的地址

---> 因此有:

NSObject *ob[3];
ob[0] = [Person new];
ob[1] = [Student new];
ob[2] = @"jack"; // NSString 也是一个 OC 类
// 总结:NSObject 指针类型数组可以存储任意值

注意:当父类指针指向子类的对象时,该指针只能访问子类对象中的父类成员

6.2 方法重写

子类继承了父类的方法 --> 子类通过继承,拥有了相同的功能
问题:子类实现该功能的方式可能和父类不同

---> 在子类中重写方法:在 @implementation 中写出该方法在当前类中的实现

注意:当父类指针指向子类的对象时,若方法在子类中重写了,那么调用的就是重写的方法

6.3 多态

表示同一个行为,在不同事物上具有不同的表现形式。

举例:【 cut】
医生 - 切除 / 理发师 - 理发 / 演员 - 结束表演 ...

多态的优点:方便后期维护修改

6.4 description 方法

  • %p:打印指针变量的值 --> 存储的地址

    %@:打印指针变量指向的对象

---> 如果使用 %@ 打印一个对象,输出的格式为 <对象名所属的类名:对象的地址>

原理:打印对象时,NSLog 函数的底层实现

  1. 调用传入的对象的 description 方法
  2. 拿到方法的返回值,返回值为一个字符串
  3. 将这个字符串输出

description 方法是定义在 NSObject 类中的对象方法 --> 每一个 OC 对象都有这个方法
该方法返回的字符串格式为 @"<对象所属的类名:对象的地址>"

想换一种方式打印对象?---> 在类中重写 description 方法。通过自定义返回的字符串格式来改变打印结果

// 声明省略
@implementation Person
  - (NSString *)description{  
    return [NSString stringWithFormat:@"姓名:%@,年龄:%d", _name, _age];
  }
@end

int main(){  
  Person *p = [Person new];  
  [p setName:@"jack"];  
  [p setAge:18];  
  NSLog(@"%@", p); // 输出【姓名:jack,年龄:18】
}
posted @ 2022-03-07 12:57  Z/z  阅读(166)  评论(0)    收藏  举报