爬爬爬之路:OC语言(二) 类

前言

类 是具有相同特征和行为的事物的抽象
万事万物皆对象

  • 对象是类的实例
  • 类是对象的类型

面向对象的特点分为:

  • 封装
  • 继承
  • 多态

如何看懂OC中的一个类

在OC中
一个类中的成员, 分为实例变量和方法两种
写在.h文件中的方法在类外是可以调用的.
而实例变量虽然写在.h文件中, 但是它们能否在类外可以调用需要根据他们的修饰符定义的:
实例变量的可见度

可见度特点
@public实例变量可以在类的外部和内部操作
@private实例变量只能在该类内访问
@protected(默认的)实例变量只能在该类和其子类内操作

@private 和@public 在实际开发中基本不使用

  • 对于private和protected修饰的实例变量, 不能在类外进行调用 而public可以在类外对实例变量进行赋值, 但是这样破坏了面向对象的封装性.

如果我们需要对实例变量进行取值, 赋值操作. 可以在类内定义方法. 通过调用方法来获得实例变量的值.
注意: 类方法不能直接调用实例变量. 因为调用类方法的时候, 没有创建对象, 这时候没有在堆中开辟空间, 也没有进行赋值. 也就是说实例变量此时在内存中并不存在, 所以调用不了.

自定义初始化方法可以对实例变量进行赋值, 但是不能简单有效的对某个成员变量进行赋值.

要方便有效准确的对某一个实例变量进行取值, 赋值的操作, 通过以下方法:

  1. 先定义一个类:

    @interface Person : NSObject
    {
        @protected          // 声明实例变量都是protected修饰
        NSString *_name;    // 姓名
        NSString *_gender;  // 性别
        NSString *_age;     // 年龄
    }
  2. 定义一个对象方法, 取出指定的实例变量的值(在类中封装一个取值方法)

    - (NSString *)getName;   // 在.h文件中声明
    - (NSString *)getName {  // 在.m文件中实现
        return _name;
    }
  3. 定义一个对象方法, 更改指定的实例变量的值(在类中封装一个赋值方法)

    - (void)setName:(NSString *)name;  // 在.h文件中声明
    - (void)setName:(NSString *)name { // 在.m文件中实现
        _name = name;
    }   

getName, setName方法称为实例变量name的setter getter方法
既然有专门的名称, 自然有专门的命名规范
setter方法: set+实例变量的名字(忽略下划线 首字母大写) 参数名=实例变量名(忽略下划线)
如:
- (void)setName:(NSString *)name;
getter方法:方法名就等于实例变量名(忽略下划线)
如:
- (NSString *)name;


关于方法名

例如自定义方法:

- (instancetype)initWithName:(NSString *)name age:(NSString *)age sex:(NSString *)sex;

它的方法名是:
initWithName:age:sex:
值得注意的是, 冒号也是方法名的一部分, 不可缺少, 冒号是标识有参数.
setter getter方法名:
如以上定义的setter getter方法, 它们的名字分别是:
setName:name

同时修改两个成员变量的方法:

- (void)setName:(NSString *)name gender:(NSString *)gender; // 在.h文件中声明
- (void)setName:(NSString *)name gender:(NSString *)gender { // 在.m文件中实现
    _name = name;
    _gender = gender;
}

p.s. OC是根据:来识别参数的个数的


关于创建对象

Person *p = [[Person alloc] init];
使用NSLog(@"%@", p); 打印p的信息
通过占位符%@打印对象p的信息, 是调用了继承自父类的方法(继承随后的文章会介绍)
- (NSString *)description;
可以通过重写该方法, 使程序调用
NSLog(@"%@", p);的时候打印出想要显示的结果

如想要根据自己的意愿打印Person类对象的所有信息, 可以重写description方法如下:

- (NSString *)description {
    NSString *str = [NSString stringWithFormat:@"姓名:%@,性别:%@,年龄:%@",_name, _gender, _age];
    return str;
}

附:stringWithFormat: 是格式化拼接字符串方法,是NSString类的类方法. 是OC中非常常用的方法


互相引用会导致错误

定义两个类 Man 和Woman
如在Man类中
#import "Woman.h"
在Woman类中
#import "Man.h"

这样互相引用会导致循环引用, 因而报错.
原理是:
在头文件A中import 头文件B, 而头文件B中又import了头文件A,这会导致A运行到import语句, 跳转到了头文件B, 头文件B运行到improt语句又跳回了头文件A, 而头文件A再次运行到import语句又跳到了B… 像这样导致编译器无限的在两个头文件中跳转来跳转去, 导致死循环, 直到崩溃.

解决方法是:
在其中一个类中用@class 类名;的方法来解决
如在Man类中
#import "Woman.h"
在Woman类中
@class Man;
此句的意思是声明Man是一个类 如果需要在.m文件中用到Man类中的方法, 还需要在#import “Man.h”(在.m文件中声明)
此时在Man中可以定义一个Woman类的实例变量
在Woman中也可以定义一个Man类的实例变量

在出现循环导入的时候, 注意初始化的时候会出现赋的值未初始化而无法进行赋值的情况.

引用语句中
使用#include引头文件的时候不能重复导入
#import可以重复导入 , 但是不能循环导入

重复导入是在同一个文件中 多次使用import “XXX.h” 语句调用同一个头文件.
循环导入是在A文件中import B文件的头文件, 而B文件的头文件中又import了A文件的头文件, 导致编译器无限的在两个头文件中跳转来跳转去, 导致死循环, 直到崩溃.


例题如下:
创建男人类:
属性有: 姓名, 工作, 钱, 妻子
方法有: 看篮球, 赚钱
女人类: 姓名, 颜值, 丈夫, 孩子
方法有: 购物
宝宝类: 姓名, 性别

// Man.h
#import <Foundation/Foundation.h>
#import "Woman.h"
@interface Man : NSObject
{
    NSString *_name;    // 姓名
    NSString *_job;     // 工作
    NSString *_money;   // 钱
    // 复合: 在本类中 声明了一个其他类的对象作为本类的实例变量
    Woman *_wife;       // 妻子
}
- (instancetype)initWithName:(NSString *)name
                         job:(NSString *)job
                       money:(NSString *)money;

- (void)setName:(NSString *)name;
- (NSString *)name;

- (void)setJob:(NSString *)job;
- (NSString *)job;

- (void)setMoney:(NSString *)money;
- (NSString *)money;

- (void)setWife:(Woman *)wife;
- (Woman *)wife;


- (void)watchBasketBall;
- (void)makeMoney;


- (NSString *)description;
@end

// Man.m
#import "Man.h"

@implementation Man
// 可能在初始化Man的对象的时候, Woman对象尚未创建, 所以先不给Man里的实例变量Woman进行赋值
- (instancetype)initWithName:(NSString *)name
                         job:(NSString *)job
                       money:(NSString *)money {
    _name = name;
    _job = job;
    _money = money;

    return self;
}

- (void)setName:(NSString *)name {
    _name = name;
}
- (NSString *)name {
    return _name;
}

- (void)setJob:(NSString *)job {
    _job = job;
}
- (NSString *)job {
    return _job;
}

- (void)setMoney:(NSString *)money {
    _money = money;
}
- (NSString *)money {
    return _money;
}

- (void)setWife:(Woman *)wife {
    _wife = wife;
}
- (Woman *)wife {
    return _wife;
}


- (void)watchBasketBall {
    NSLog(@"看篮球");
}
- (void)makeMoney {
    NSLog(@"挣钱");
}

- (NSString *)description {                         // 输出妻子的姓名, 而不是妻子的全部信息. 请注意
    return [NSString stringWithFormat:@"姓名:%@,工作:%@,钱:%@,妻子:%@", _name, _job, _money, [_wife name]];
}
@end

// Woman.h
#import <Foundation/Foundation.h>

#import "Baby.h"
@class Man; // 用@class 关键字声明Man是一个类 由于Man.h已经importWoman.h”, 所以不能再在Woman.himportMan.h"
@interface Woman : NSObject
{
    NSString *_name;            // 姓名
    NSString *_beautifulValue;  // 颜值
    Man *_husband;              // 丈夫 在这里将Man仅仅当成一个类名来使用, 在这里虽然husbend声明的是Man的对象 但它没有保存Man类中的实例变量和方法, 若要调用到Man类里的方法, 只能通过import ”Man.h”的形式
    Baby *_baby;                // 孩子
}
        // 由于Woman对象初始化的时候 Man对象和Baby对象可能还未创建, 无法给Woman对象中的实例变量_husband, _baby赋值, 所以先不在初始化中对这两个变量进行初始化.
- (instancetype)initWithName:(NSString *)name  
              beautifulValue:(NSString *)beautifulValue;

- (void)setName:(NSString *)name;
- (NSString *)name;

- (void)setBeautifulValue:(NSString *)beautifulValue;
- (NSString *)beautifulValue;

- (void)setHusBand:(Man *)husband;
- (Man *)husband;

- (void)setBaby:(Baby *)baby;
- (Baby *)baby;

- (void)bayBayBay;


- (NSString *)description;
@end

// Woman.m
#import "Woman.h"
#import “Man.h” // 在这里import “Man.h” 可以通过.h中声明的Man变量 调用Man类里的方法.
@implementation Woman
- (instancetype)initWithName:(NSString *)name
              beautifulValue:(NSString *)beautifulValue {
    _name = name;
    _beautifulValue = beautifulValue;

    return self;
}

- (void)setName:(NSString *)name {
    _name = name;
}
- (NSString *)name {
    return _name;
}

- (void)setBeautifulValue:(NSString *)beautifulValue {
    _beautifulValue = beautifulValue;
}
- (NSString *)beautifulValue {
    return _beautifulValue;
}

- (void)setHusBand:(Man *)husband {
    _husband = husband;
}
- (Man *)husband {
    return _husband;
}

- (void)setBaby:(Baby *)baby {
    _baby = baby;
}
- (Baby *)baby {
    return _baby;
}

- (void)bayBayBay {
    NSLog(@"买东西");
}


- (NSString *)description {
    return [NSString stringWithFormat:@"姓名:%@,颜值:%@,丈夫:%@,孩子:%@",_name, _beautifulValue, [_husband name], [_baby name]]; // 在这里显示的是男人的名字和孩子的名字 为的是避免出现循环调用 导致死循环, 下文会进行详细解释
}

@end

// Baby.h
#import <Foundation/Foundation.h>

@interface Baby : NSObject
{
    NSString *_name; // 姓名
    NSString *_sex;  // 性别
}
- (instancetype)initWihtName:(NSString *)name
                         sex:(NSString *)sex;

- (void)setName:(NSString *)name;
- (NSString *)name;

- (void)setSex:(NSString *)sex;
- (NSString *)sex;


- (NSString *)description;
@end

// Baby.m
#import "Baby.h"

@implementation Baby
- (instancetype)initWihtName:(NSString *)name
                         sex:(NSString *)sex {
    _name = name;
    _sex = sex;
    return self;
}
- (void)setName:(NSString *)name {
    _name = name;
}
- (NSString *)name {
    return _name;
}

- (void)setSex:(NSString *)sex {
    _sex = sex;
}
- (NSString *)sex {
    return _sex;
}

- (NSString *)description {
    return [NSString stringWithFormat:@"姓名:%@,性别:%@",_name,_sex];
}
@end


// main.m
#import <Foundation/Foundation.h>
#import "Man.h"
#import "Woman.h"
#import "Baby.h"
int main(int argc, const char * argv[]) {

    Man *man = [[Man alloc] initWithName:@"l" job:@"ios" money:@"12k/m"];
    Woman *women = [[Woman alloc] initWithName:@"y" beautifulValue:@"10"];
    Baby *baby = [[Baby alloc] initWihtName:@"ly" sex:@"女"];
    [women setBaby:baby];
    [women setHusBend:man];
    [man setWife:women];

    NSLog(@"%@", man);

    return 0;
}

要点1:

  • 在Woman.h中用@class Man; 而不是import "Man.h".是为了避免循环引用.

要点2:

  • Woman类中的description调用了Man对象, NSLog(@"%@", man);输出的是man对象, 而输出一个对象的内容是这个对象的description方法, 而若此时man的description又出现NSLog(@"%@", woman);又调用了woman的description方法, 这样就发现两者无限循环调用, 直到内存满了, 程序崩溃. 所以两个对象不能同时输出对方的对象. 只有一方输出另一方的对象是可以接受的, 两者同时输出另一方, 是会崩溃的. 因而程序中, 输出的不是对象本身, 而是对象的实例变量, 这样就可以绕过循环调用.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值