OC语法之-OC三大特性

面向对象的三大特性:封装/继承/多态

封装

  1. 又叫做成员变量的封装,也就是说是针对成员变量的
  2. 为什么要对成员变量进行封装?
    为了防止像成员变量传入不合理的值,比如age这个成员变量,p->age = -10;这就不合理
  3. @public作用
    • 使一个对象在不是当前类的实现@implementation中(即外部)能够访问自己的成员变量(p->age),默认的情况下是不能访问,受保护的(@protected);
    • 注意:一个类是可以直接在其实现内部(@implementation –@end)访问它的成员变量的,跟@public无关!!!
     #import <Foundation/Foundation.h>
     @interface Person : NSObject
     {   //是该成员变量能被外界(即不是在当前类中时!!!)访问
         @public
         int age;
     }
     - (void)walk;
     @end
        
     // 2.类的实现
     @implementation Person
     // 可以直接使用当前类的成员变量的!!!,因为这是在当前类的实现中
     - (void)walk {
         NSLog(@"%d岁的人走了一段路", age;
     }
     @end
     //这里已经不是Person类了,属于外界了
     int main() {
         Person *p2 = [Person new];
         //没有@public是不可以直接访问该对象的成员变量的!!!
         p2->age = 30;
         [p2 walk];
         return 0;
     }
    

set方法和get方法

  1. set方法和get方法的使用场合
    • @public的成员可以被随意赋值,应该使用set方法和get方法来管理成员的访问(类似机场的安检、水龙头过滤,过滤掉不合理的东西),比如僵尸的生命值不能为负数
  2. set方法
    1. 作用: 提供一个方法给外界设置成员变量值,可以在方法里面对参数进行相应过滤
    2. 命名规范:
      1. 方法名必须以set开头
      2. set后面跟上成员变量的名称,成员变量的首字母必须大写
      3. 返回值一定是void
      4. 一定要接收一个参数,而且参数类型跟成员变量类型一致
      5. 形参的名称不能跟成员变量名一样!!!(注意哦)
  3. get方法
    1. 作用:返回对象内部的成员变量
    2. 命名规范:
      1. 肯定有返回值,返回值类型肯定与成员变量类型一致
      2. 方法名跟成员变量名一样
      3. 不需要接收任何参数
  4. 代码举例:

     #import <Foundation/Foundation.h>
     @interface Student : NSObject
     {
         // 成员变量尽量不要用@public
         // @public
         int age;
         // 只读(readonly):只允许外界访问我的no,不允许外界修改我的no
         // 只需要提供get方法
         int no;
     }
        
     // set方法
     - (void)setAge:(int)newAge;
     // get方法
     - (int)age;
     - (void)study;
     @end
        
     @implementation Student
        
     // set方法的实现
     - (void)setAge:(int)newAge {
         // 对传进来的参数进行过滤
         if (newAge <= 0) {
             newAge = 1;
         }
         //如果newAge也用age,那么就会混淆了
         age = newAge;
     }
        
     - (int)age {
         return age;
     }
        
     - (void)study {
         NSLog(@"%d岁的学生在学习", age);
     }
        
     @end
        
     int main() {
         Student *stu = [Student new];
         //stu->age = -10;
         //stu->age = 10;
         [stu setAge:10];
            
         NSLog(@"学生的年龄是%d岁", [stu age]);
            
         //[stu study];
         return 0;
     }
    

成员变量/局部变量/全局变量命名规范

  1. 成员变量的命名规范_age
    1. 一定要以下划线 _ 开头
    2. 让成员变量和get方法的名称区分开
    3. 可以跟局部变量区分开,一看到下划线开头的变量,一般都是成员变量
  2. 全局变量通常这样命名:age_
  3. 局部变量:age
#import <Foundation/Foundation.h>
@interface Student : NSObject
{   
   //成员变量
    int _no;
}

// no的set和get方法
- (void)setNo:(int)no;
- (int)no;
@end

@implementation Student

- (void)setNo:(int)no {
    _no = no;
}
//get方法与成员变量区别
- (int)no {
    return _no;
}
@end

int main() {
    Student *stu = [Student new];
    [stu setNo:10];
    [stu no];
    return 0;
}

类方法

  1. 直接可以用类名来执行的方法(类本身会在内存中占据存储空间,里面有类\对象方法列表)
  2. 类方法和对象方法对比:
    1. 类方法:
      1. 加号 + 开头
      2. 只能由类(名)来调用
      3. 类方法中不能访问成员变量(实例变量)
    2. 对象方法
      1. 减号 - 开头
      2. 只能由对象来调用
      3. 对象方法中能访问当前对象的成员变量(实例变量)
  3. 类方法的好处和使用场合
    1. 不依赖于对象,执行效率高
    2. 能用类方法,尽量用类方法
    3. 场合:当方法内部不需要使用到成员变量时,就可以改为类方法
  4. 注意: 可以允许类方法和对象方法同名
  5. 代码举例:

     #import <Foundation/Foundation.h>
     @interface Person : NSObject
     {
         int age;
     }
     // 类方法都是以+开头
     + (void)printClassName;
     - (void)test;
     + (void)test;
     @end
        
     @implementation Person
    
     + (void)printClassName {
         // error:instance variable 'age' accessed in class method
         // 实例变量age不能在类方法中访问
         //NSLog(@"这个类叫做Person-%d", age);
     }
        
     - (void)test {
         NSLog(@"111-%d", age);    
         //[Person test];
     }
     + (void)test {
         // 会引发死循环
         //[Person test];
         NSLog(@"333");
     }
     @end
        
     int main() {
         //类方法调用
         [Person printClassName];
         [Person test];
         //对象方法调用
         Person *p = [Person new];
         [p test];
         //对象误调用类方法
         /*
          -[Person printClassName]: unrecognized selector sent to instance 0x7fa520c0b370
          */
         // 系统会认为现在调用的printClassName是个对象方法
         //[p printClassName];
            
         return 0;
     }
    

self关键字

  1. 一句话总结:self始终指向当前方法的调用者
  2. 谁调用了当前方法,self就代表谁
    1. self出现在对象方法中,self就代表对象
    2. self出现在类方法中,self就代表类
  3. 在对象方法利用”self->成员变量名”访问当前对象内部的成员变量,可以用于区分同名的局部变量
  4. [self 方法名]可以调用其他对象方法\类方法
  5. 代码举例:
#import <Foundation/Foundation.h>
@interface Dog : NSObject
{
    int _age;
}
- (void)bark;
- (void)run;
+ (void)run;
- (void)test;
+ (void)test;

@end

@implementation Dog
- (void)bark {
    NSLog(@"汪汪汪");
}
- (void)run {
    [self bark];
    //此时的self是当前对象
    //调用的是对象方法-test
    [self test];
    NSLog(@"跑跑跑");
}
+ (void)run {
    //调用的是对象方法+test,此时的self是当前类
    [self test];
    NSLog(@"调用了+run方法");
}
- (void)test {
    // 会引发死循环 此时的self是当前对象
    //[self test];
    NSLog(@"调用了-test方法");
    
    //成员变量和局部变量同名时,默认采取就近原则,访问的是局部变量,但是我非要访问成员变量
    //用self访问成员变量,区分同名的局部变量
    int _age = 20;
    // self:指向了方法调用者,代表着当前对象.直接写_age,那么指的就是局部变量_age(20)了
    NSLog(@"Dog的年龄是%d岁", self->_age);
}
+ (void)test {
    NSLog(@"调用了+test方法");
    
    // 会引发死循环 ,此时的self是当前类
    //[self test];
}

@end

int main() {
    Dog *d = [Dog new];
    [d run];
    return 0;
}

继承

  1. 继承的好处
    1. 抽取重复代码
    2. 建立了类之间的关系
    3. 子类可以拥有父类中的所有成员变量和方法
  2. 坏处
    1. 耦合性太强
  3. 重写:
    1. 子类重新实现父类中的某个方法,覆盖父类以前的做法
  4. 注意
    1. 父类必须声明在子类的前面
    2. 子类不能拥有和父类相同的成员变量
    3. 调用某个方法时,优先去当前类中找,如果找不到,去父类中找
    4. 只能单继承,即只能继承一个父类,不能多重继承
    5. 基本上所有类的根类是NSObject,里面包含了很多基本方法(+new…等)
  5. 继承的使用场合
    1. 当两个类拥有相同属性和方法的时候,就可以将相同的东西抽取到一个父类中
    2. 当A类完全拥有B类中的部分属性和方法时,可以考虑让B类继承A类
  6. 继承与组合
    1. 概念:让A类成为B类的成员变量,这样B类仍然可以拥有A类的成员变量跟方法
    2. 继承与组合的区别:
     1.继承
      A
      {
         int _age;
         int _no;
      }
         
      B : A
      {
         int _weight;
      }
         
      // 继承:xx 是 xxx
      // 组合:xxx 拥有 xxx
         
      2.组合
      A
      {
          int _age;
          int _no;
      }
         
      B
      {
          A *_a;
          int _weight;
      }
    
  7. 类与父类之间的关系
    1. 每个对象内部都有一个isa指针指向所属,每个里面又有一个superclass指针指向其父类
    2. isa 是nsobject的成员变量
    3. 如图:

  8. 继承代码举例:

        
      #import <Foundation/Foundation.h>
     // 父类:Person
     @interface Person : NSObject
     {
         int _age;
     }
        
     - (void)setAge:(int)age;
     - (int)age;
        
     - (void)run;
        
     + (void)test;
        
     @end
        
     @implementation Person
        
     + (void)test {
         NSLog(@"Person+test");
     }
        
     - (void)run {
         NSLog(@"person---跑");
     }
        
     - (void)setAge:(int)age {
         _age = age;
     }
     - (int)age {
         return _age;
     }
     @end
        
     // 不允许子类和父类拥有相同名称的成员变量
     // 子类:Student
     @interface Student : Person
     {
         int _no;
         // int _age;
     }
        
     + (void)test2;
        
     @end
        
     @implementation Student
     // 重写:子类重新实现父类中的某个方法,覆盖父类以前的做法
     - (void)run {
         NSLog(@"student---跑");
     }
        
     + (void)test2 {
         //当前类方法中没有,就去父类中找
         [self test];
     }
     @end
        
        
     int main() {
         [Student test2];
            
         Student *s = [Student new];
            
         [s run];
            
         return 0;
     }
        
    
  9. 组合代码举例:

     @interface Score : NSObject
     {
         int _cScore;
         int _ocScore;
     }
     @end
        
     @implementation Score
     @end
        
     @interface Student : NSObject
     {
         // 组合
         Score *_score;
     //    int _cScore;
     //    int _ocScore;
         int _age;
     }
     @end
        
     @implementation Student
        
     @end
    

super关键字

  1. 一句话总结:super始终指向当前方法调用者的父类(类方法中)/父类对象(对象方法中)
  2. 作用:
    • 直接调用父类中的某个方法
    • super处在对象方法中,那么就会调用父类的对象方法
    • super处在类方法中,那么就会调用父类的类方法
    • 使用场合:子类重写父类的方法时想保留父类的一些行为
  3. 代码举例

    #import <Foundation/Foundation.h>
    // 僵尸
    @interface Zoombie : NSObject
    - (void)walk;
        
    + (void)test;
    - (void)test;
        
    @end
        
    @implementation Zoombie
    - (void)walk {
        NSLog(@"往前挪两步******");
    }
        
    + (void)test {
        NSLog(@"Zoombie+test");
    }
        
    - (void)test {
        NSLog(@"Zoombie-test");
    }
    @end
        
    // 跳跃僵尸
    @interface JumpZoombie : Zoombie
    + (void)haha;
    - (void)haha2;
    @end
        
        
    @implementation JumpZoombie
        
    + (void)haha {   //调用父类的+test
        [super test];
    }
        
    - (void)haha2 {   //调用父类的-test方法
        [super test];
    }
        
    - (void)walk {
        // 跳两下
        NSLog(@"跳两下");
            
        // 走两下(直接调用父类的walk方法)
        [super walk];
        //NSLog(@"往前挪两步----");
        
    }
    @end
        
    int main() {
        [JumpZoombie haha];
        JumpZoombie *jz = [JumpZoombie new];
        [jz haha2];
        return 0;
    }
    

多态

  1. 没有继承就没有多态
  2. 代码的体现:父类类型的指针指向子类对象
  3. 好处:如果函数\方法参数中使用的是父类类型,可以传入父类、子类对象
  4. 局限性:
    1. 父类类型的变量 不能 直接调用子类特有的方法。必须强转为子类类型变量后,才能直接调用子类特有的方法
  5. 代码举例:

     // 动物
     @interface Animal : NSObject
     - (void)eat;
     @end
        
     @implementation Animal
     - (void)eat {
         NSLog(@"Animal-吃东西----");
     }
     @end
        
     // 狗
     @interface Dog : Animal
     - (void)run;
     @end
        
     @implementation  Dog
     - (void)run {
         NSLog(@"Dog---跑起来");
     }
     - (void)eat {
         NSLog(@"Dog-吃东西----");
     }
     @end
        
     // 猫
     @interface Cat : Animal
        
     @end
        
     @implementation Cat
     - (void)eat {
         NSLog(@"Cat-吃东西----");
     }
     @end
        
     // 如果参数中使用的是父类类型,可以传入父类、子类对象
     void feed(Animal *a) {
         [a eat];
     }
        
     int main() {
         //场合1
        // 多种形态
         Dog *d = [Dog new]; // Dog类型
         // 多态:父类指针指向子类对象
         Animal *a = [Dog new];
         // 调用方法时会检测对象的真实形象,调用Dog的eat
         [a eat];
            
         //场合2
         Animal *aa = [Dog new];
         // 多态的局限性:父类类型的变量 不能 用来调用子类的方法
         // [aa run];
         // 将aa转为Dog *类型的变量
         Dog *dd = (Dog *)aa;
         [dd run];
          
         //场合3
         Animal *aa2 = [Animal new];
         //调用Animal的
         feed(aa2);
         //调用Dog的
         Dog *dd = [Dog new];
         feed(dd);
         //调用Cat的
         Cat *cc = [Cat new];
         feed(cc);
            
           
         return 0;
     }
    
Table of Contents