RE0_OC_1

361 阅读13分钟

类的本质是我们自定义的一个数据类型

(1)类与对象

Objective-C 的类: 类包含了两个部分:定义(interface)与实现(implementation)。定义(interface)部分包含了类声明和实例变量的定义,以及类相关的方法。实现(implementation)部分包含了类方法的实际代码。

img

#import <Foundation/Foundation.h>
//类接口声明
@interface Animal : NSObject 
@property (nonatomic, strong) NSString *name; //属性(类的特征)
- (instancetype)initWithName:(NSString *)name;  //单个参数的方法声明
- (NSString *)introduce:(NSString *)name :(int)age :(NSString *)gender; //多个参数的方法声明
- (void)makeSound;  //实例方法(无返回值无传参的方法声明)  (类的功能)
+ (void)wolfing;  //类方法
@end
//预处理指令
#import <Foundation/Foundation.h>

#import:OC特有的头文件引入指令,eg:#import <Foundation/Foundation.h>

<Foundation/Foundation.h>: Cocoa 和 Cocoa Touch框架的核心组件,有点类似于C语言的studio.h,包含NSObject、NSString等基础类,为IOS开发提供运行环境,以及需要遵从的协议;

//类接口声明
@interface Animal : NSObject

@interface:类/接口的声明符,结尾需使用 @end;(类名遵循大驼峰命名规范)

//属性声明
@property (nonatomic, strong) NSString *name;

@property:属性声明方法,可以自动生成getter和setter

nonatomic:OC在定义时有原子属性(atomic)和非原子属性(nonatomic)两种属性、atomic是线程安全,需要消耗大量的资源;nonatomic是非线程安全,适合内存较小的移动设备;因此在IOS APP开发时,建议所有的属性都适用nonatomic,避免多线程抢占资源,将加锁、资源抢夺的逻辑交给服务端处理,减轻客户端的压力;

strong:一种属性声明,表示指向并拥有该对象,表示浅拷贝,多个指针指向同一个地址;

与之对应的常用属性声明是copy,表示深拷贝,copy的属性会在内存中拷贝一份新的对象,指向新的地址;

//方法声明
- (instancetype)initWithName:(NSString *)name;
- (void)makeSound;

:实例方法标识符,类方法用 + 标识

instancetype:返回类型声明,表示不确定类型,运行时才能确定,即调用该方法的类实例对象指针;因为不确定性,因此不能作为对象的定义类型,只能用户成员方法返回类型;与之相关的是id类型,也是未知类型,但是是确定的(确定未知),因此可以用作定义对象类型,eg:id Animal;

initWithName: 方法名(遵循小驼峰命名规范)

*(NSString ) :参数类型声明(中间有一个空格)、明确参数为字符串指针,NSString表示字符串类型;

(void) :无返回值方法声明

@end:协议与内存管理

//类接口实现
@implementation Animal;
- (instancetype)initWithName:(NSString *)name{
    self = [super init]; 
    if(self){
        _name = name;
    }
    return self;
}
- (void)makeSound{
  NSLog(@"I am %@,I can wolf",slef.name)
}
+ (void)wolfing{
  NSLog(@"I can wolfing");
}
@end    //必须以end闭合
//实现实例方法
- (instancetype)initWithName:(NSString *)name{
  self = [super init];  //确保父类初始化完毕后再初始化子类
  if(self){ // 确认self初始化成功,不为nil
    _name = name //使用_name的方式访问变量来设置属性值,避免不必要的setter;
  }
  return self
}
//多传参方法实现
- (NSString *)introduce:(NSString *)name :(int)age :(NSString *)gender{
    NSString *result = [NSString stringWithFormat:@"my name is %@,i am %d years old, i am %@",name,age,gender];
    NSLog(@"%@",result);
    return result;
}
- (void)makeSound{
  NSLog(@"I am %@,I can wolf",slef.name)
}
//实现类方法
+ (void)wolfing{
  NSLog(@"I can wolfing");
}

self = [super init] :确保父类初始化完毕后再初始化子类

已经声明和实现了一个接口,那么应该如何使用它?

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Animal *animal = [[Animal alloc] initWithName:@"gggg"]; 
        NSLog(@"init name is %@",animal.name);  //因为你初始化的时候给了name属性一个默认值,因此这个地方打印出来是gggg
        animal.name = @"Dog"; //你也可以重写这个属性的值,打印出来就是你重写的值
        [animal makeSound]; //实例化方法需要实例化对象进行调用
        [Animal wolfing]; //类方法,可以不经过实例化,直接被类调用;
        [animal introduce:@"小花" :9 :@"男"];
    }
    return 0;
}

@autoreleasepool:自动释放池,在该释放池内创建的对象在被销毁时会被自动释放;

Animal *animal = [[Animal alloc] initWithName:@"gggg"]; 

上述代码是为Animal类创建了一个实例,名称叫做animal,[Animal alloc]的作用时调用 alloc 方法为 Animal 类分配内存。alloc 方法会返回一个指向 Animal 对象的指针,但此时对象还未初始化。因此后续需要init类进行初始化; 一般情况下,如果不传参数的话,我们用init方法这设置对象的初始状态即可,例如将属性设置为默认值。不会为属性提供一个初始值;

Animal *animal = [[Animal alloc] init]
//在oc2.0中,如果不需要传参,可以直接使用new 方法
Animal *animal = [Animal new]

但,我们在类声明时声明了一个initWithName方法,即

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

这个方法就要求你实现的时候,必须也实现这个方法,初始化一个name 因此,Animal *animal = [[Animal alloc] initWithName:@"gggg"]的作用就是在初始化时为animal实例添加了一个name属性,值为gggg

(2)继承

我们可以把Animal当成父类,然后我们继续创建一个Dog的子类,并继承父类,则代码变成了:

#import <Foundation/Foundation.h>//创建父类接口
@interface Animal : NSObject;   //继承自根Object
@property (nonatomic, strong) NSString *name;   //属性声明
- (instancetype)initWithName:(NSString *)name;  //方法声明
- (void)makeSound;  //实例方法声明,用-修饰
+ (void)wolfing; //类方法声明,用+修饰
@end//父类接口实现
@implementation Animal;
// 实例化方法
- (instancetype)initWithName:(NSString *)name{
    self = [super init]; //防御性措施,确保父类初始化成功后再初始化子类
    //检查self是否为nil
    if(self){
        _name = name;
        // 通过在方法内部使用下划线前缀(如_name = name;),直接访问实例变量来设置属性值。
        // 这是因为在Objective-C中,直接使用属性名进行赋值会自动调用属性的setter方法,
        // 这在某些情况下(如懒加载属性)是有用的,但在初始化时直接使用实例变量可以避免不必要的setter调用开销。
    }
    return self;
}
- (void)makeSound{
    NSLog(@"I am  %@,I can wolf",self.name);
}
+ (void)wolfing{
    NSLog(@"I can wolfing");
}
@end//声明子类接口
@interface Dog : Animal;
@property (nonatomic,strong)NSString *leg;
-(void)makeRun;
@end//子类接口实现
@implementation Dog;
- (void)makeRun{
    NSLog(@"I can RUN");
}
@end//程序入口
int main(int argc, const char * argv[]) {
    //自动释放池
    @autoreleasepool {
        Animal *animal = [[Animal alloc] initWithName:@"gggg"]; //创建一个类的实例
        NSLog(@"init name is %@",animal.name);
        animal.name = @"Dog";  //添加属性,字符串前
        [animal makeSound];     //执行实例方法
        [Animal wolfing];     //类的方法不用实例化,可被类直接调用
        
        // 子类可直接调用父类的属性和方法,包括实例方法和类方法
        Dog *dog = [[Dog alloc] init];
        dog.name= @"petty";
        dog
        [dog makeRun];  //调用自己的实例方法
        [dog makeSound];    //调用父类的实例方法
        [Dog wolfing];  //调用父类的类方法
    }
    return 0;
}
​

参考资料:

blog.csdn.net/m0_57836225…

(3)对象的属性

对象的属性:

@interface Dog : NSObject {
    NSString *age;
    int _a;
    float _b;
}
@end

一般情况下,对象的属性是不是允许被外部访问的,即:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Dog *dog2 = [[Poodle alloc] init];
        dog2->_a = 2; //当前情况下是无法给对象的属性进行赋值的
    }
    return 0;
}

若要在外部访问对象中的属性,则需要@public关键字进行修饰;

@interface Dog : NSObject {
    @public
    NSString *age;
    int _a;
    float _b;
}
@end

访问的语法:

Dog *dog1 = [[Dog alloc] init]  //类的实例化 oc 1.0写法
Dog *dog1 = [Dog new];  //类的实例化 oc 2.0写法(常用)
​
dog1->_a = 2;   //访问并修改类的属性值,写法一(常用)
(*dog1)._a = 4; //访问并修改类的属性值,写法二

(4)方法的实现可以直接访问对象所声明的属性

例如,我们有如下的Person类的声明,其中包含了一些方法:

@interface Person : NSObject{
    NSString *_name;
    int _age;
    NSString *_gender;
}
- (void)run:(NSString *)name;
@end
​
​
@implementation Person;
- (void)run:(NSString *)name{
    _age = 10;  
    NSLog(@"i am %@,i am runing,my age is %d",name,_age);
}
​
@end

在上述代码中,可以在方法的实现中,直接访问对象的属性,不用实例化,而且不要求对象的属性使用@public进行修饰;

(5)分组导航标记

当一个文件中有很多类和类的实现时,不方便查找;可以为类和类的实现加上中文导航,方便快速查找;

1、第一种导航方法:在导航条中添加一个标题

#pragma mark 分组名

image-20250517222748118.png

2、第二种导航方法:在导航条中添加一条横线

#pragma mark -

image-20250517223322872.png

3、第三种导航方法,可直接结合水平线和标题

#pragma mark - 猫猫类

image-20250517225330851.png

(6)类指针作为方法的参数

类,本质上来讲是我们自定义的一个数据类型,因此是可以作为方法的参数的;

首先声明一个Dog类:

@interface Dog : NSObject{
    NSString *_name;
}
-(void)bark;
@end@implementation Dog
- (void)bark{
    NSLog(@"狗会叫");
}
@end

然后声明一个Person类:

@interface Person : NSObject{
    @public
    NSString *_name;
    int age;
}
-(void) eat;
-(void) testDog:(Dog *)dog1;
@end
  
@implementation Person
-(void)eat{
    NSLog(@"我是人,我会吃饭");
}
-(void)testDog:(Dog *)dog1{
    [dog1 bark];
}
@end

在Person类中,我们声明了一个方法testDog,这个方法需要传入一个Dog类指针作为参数,然后这个testDog的实现是调用了Dog类指针中的bark方法;

因此在使用时,我们就可以通过实例话Person指针,并调用testDog方法,同时传入Dog类指针作为参数;且只能传入Dog类,因为声明的是Dog类;

int main(int argc, const char * argv[]){
    Person *p1 = [Person new];
    Dog *dog1 = [Dog new];
    [p1 testDog:dog1];  //传入一个类指针作为方法的参数
}

而且当对象作为方法的参数时,是地址传递;

如:我们在Person类的实现中修改了狗的名称:

@implementation Person
-(void)eat{
    NSLog(@"我是人,我会吃饭");
}
-(void)testDog:(Dog *)dog1{
    dog1->_name = @"大黄";
    [dog1 bark];
}
@end

然后在main中调用的时候也修改了狗的名称;

int main(int argc, const char * argv[]){
    Person *p1 = [Person new];
    Dog *dog1 = [Dog new];
    dog1->_name = @"旺财";
    [p1 testDog:dog1];  //传入一个类指针作为方法的参数
    NSLog(@"狗的名字是:%@",dog1->_name);
}

那么此时狗的名称是:大黄;因为虽然我们先将dog的_name属性修改为了旺财,但是,我们又执行了p1的testDog方法,在这个方法中,又将 _name属性改为了大黄;这就是因为传入的Dog *指针是地址,大家都指向同一个对象,因此改的都是同一个属性;

(7)上帝杀人示例(对象作为方法的参数)

Gog类:

@interface God : NSObject{
    @public
    NSString *_name;
    int _age;
    int _leftlife;
    Gender _gender;
}
-(void)killWithPerson:(Person *)person;
@end@implementation God
-(void)killWithPerson:(Person *)person{
    NSLog(@"我是%@,请喝下这杯毒药,凡人……",_name);
    NSLog(@"要带走的人是:%@,他今年%d岁,性别为:%u,剩余生命为:%d",person->_name,person->_age,person->_gender,person->_leftLife);
    person->_leftLife = 0;
    NSLog(@"%@已经被带走了,他的剩余生命为%d",person->_name,person->_leftLife);
}
@end

Person类:

@interface Person : NSObject{
    @public
    NSString *_name;
    int _age;
    int _leftLife;
    Gender _gender;
}
@end@implementation Person@end

main函数:

int main(int argc, const char * argv[]){
    God *god = [God new];
    Person *p1 = [Person new];
    god->_name = @"耶稣";
    god->_age = 9999;
    god->_leftlife = 9999;
    god->_gender = GenderMale;
    
    p1->_name = @"小东";
    p1->_age = 30;
    p1->_gender = GenderFeMale;
    p1->_leftLife=10;
    
    [god killWithPerson:p1];
}

(8)对象作为方法的返回值

返回值是一个对象,那么返回值类型应该是类指针;

按照上帝的那个例子来讲:为上帝新增一个造人的方法(带参和不带参)

@interface God : NSObject{
    @public
    NSString *_name;
    int _age;
    int _leftlife;
    Gender _gender;
}
-(void)killWithPerson:(Person *)person;
-(Person *)makePerson;  //每次都造一样的属性
-(Person *)makePersonWithName:(NSString *)name andGender:(Gender)gender andAge:(int)age andleftLife:(int)leftLife;  //有多个参数传参,可以自定义属性@end
@implementation God
-(void)killWithPerson:(Person *)person{
    NSLog(@"我是%@,请喝下这杯毒药,凡人……",_name);
    NSLog(@"要带走的人是:%@,他今年%d岁,性别为:%u,剩余生命为:%d",person->_name,person->_age,person->_gender,person->_leftLife);
    person->_leftLife = 0;
    NSLog(@"%@已经被带走了,他的剩余生命为%d",person->_name,person->_leftLife);
}
-(Person *)makePerson{
    Person *p1 = [Person new];  //  创建要返回的对象实例
    p1->_name =@"亚当";
    p1->_age = 20;
    p1->_gender = GenderMale;
    p1->_leftLife = 20
    return p1;
}
-(Person *)makePersonWithName:(NSString *)name andGender:(Gender)gender andAge:(int)age andleftLife:(int)leftLife{
    Person *p1  = [Person new];
    p1->_name = name;
    p1->_age = age;
    p1->_gender = gender;
    p1->_leftLife = leftLife;
    
    return p1;
}
@end

在Person类中新增一个展示的方法:

@interface Person : NSObject{
    @public
    NSString *_name;
    int _age;
    int _leftLife;
    Gender _gender;
}
-(void)show;
@end
  
@implementation Person
-(void)show{
    NSLog(@"造的人名字是:%@,年龄是:%d,性别是:%u,还能生存%d年",_name,_age,_gender,_leftLife);
}
@end

main方法调用:

int main(int argc, const char * argv[]){
    Person *p2 = [god makePerson];
    Person *p3 = [god makePersonWithName:@"杏花" andGender:(GenderFeMale) andAge:(24) andleftLife:(70)];
    [p2 show];
    [p3 show];
}

(9)猜拳游戏

需要的类:

  • 玩家(姓名、得分、出拳类型)
  • 机器人(姓名、得分、出拳类型)
  • 裁判(姓名)
拳头类型枚举
typedef enum {
    FistTypeShitou = 1,
    FistTypeJiandao = 2,
    FistTypeBu = 3,
} FistType;
玩家类:
头文件:Player.h
#import <Foundation/Foundation.h>
#import "FistType.h"NS_ASSUME_NONNULL_BEGIN/**
    玩家类
    1、姓名
    2、出的拳头类型
    3、得分?
 */
@interface Player : NSObject{
    @public
    NSString * _userName;
    int _score;
    FistType _userSelect;
}
-(void)showFist;
-(NSString *)numberMapFist:(int)number;
​
@endNS_ASSUME_NONNULL_END
实现类:Player.m
#import "Player.h"
#import "FistType.h"@implementation Player
​
-(void)showFist{
    /**
     提示玩家输入拳头类型
     */
    NSLog(@"你好%@,请选择你要出的拳头:1、石头 2、剪刀 3、布",_userName);
    
    /**
     接收用户输入的拳头
     */
    int userInput = 0;
    scanf("%d",&userInput);
    
    /**
     显示用户输入的拳头
     */
    NSLog(@"你好%@,你选择的拳头类型是:%@",_userName, [self numberMapFist:userInput]);
    
    /**
     将用户选择的拳头存在对象的属性中
     */
    _userSelect = userInput;
}
-(NSString *)numberMapFist:(int)number{
    NSString *type;
    switch (number) {
        case 1:
            type=@"石头";
            break;
        case 2:
            type=@"剪刀";
            break;
        case 3:
            type=@"布";
            break;
        default:
            break;
    }
    return type;
}
​
@end
关键代码解析
[self numberMapFist:userInput]

为了在玩家输入数字后,将数字映射成为对应的中文,我们写了一个匹配方法numberMapFist,接收传过来的数字,然后返回对应的中文,而且这个方法需要showFist调用,但在当前对象中是不能直接调用的,因此,在方法中调用当前类的其他方法时需要通过self关键字来调用:[self 方法名],如果想在方法中调用其他类的方法是,则需要创建一个其他类的对象,通过其他类来调用;

比如在其他类的方法中调用玩家的数字映射拳头方法需要按如下的形式:

Player *p1 = [Player new];
NSString *type = [p1 numberMapFist:random];

当然,如果有需要的话,当前类也可以自己实现这个方法;若觉得实现两次同样的方法较为重复,则可通过继承实现;

机器人类:
头文件:Robot.h
#import <Foundation/Foundation.h>
#import "FistType.h"NS_ASSUME_NONNULL_BEGIN@interface Robot : NSObject{
    @public
    NSString *_robotName;
    int _score;
    FistType _robotSelect;
}
​
-(void)showFist;
-(NSString *)numberMapFist:(int)number;
@endNS_ASSUME_NONNULL_END

实现类:Robot.m

#import "Robot.h"
#import <stdlib.h>
#import "Player.h"
#import "FistType.h"@implementation Robot
-(void)showFist{
    /**
     随机生成1-3的随机数
     */
    int random = arc4random_uniform(3)+1;
    /**
     显示随机出的拳头
     */
    NSString *type = [self numberMapFist:random];
    NSLog(@"机器人随机出的拳头为:%@",type);
    /**
     将随机出的拳头保存在当前对象中
     */
    _robotSelect = random;
    
}
-(NSString *)numberMapFist:(int)number{
    NSString *type;
    switch (number) {
        case 1:
            type=@"石头";
            break;
        case 2:
            type=@"剪刀";
            break;
        case 3:
            type=@"布";
            break;
        default:
            break;
    }
    return type;
}
@end
裁判类:
头文件:Judge.h
#import <Foundation/Foundation.h>
#import "Player.h"
#import "Robot.h"NS_ASSUME_NONNULL_BEGIN@interface Judge : NSObject{
    @public
    NSString *_judgeName;
}
​
-(void)judgeWhoWin:(Player *)player andRobot:(Robot *)robot;
@endNS_ASSUME_NONNULL_END
实现类:Judge.m
#import "Judge.h"
​
@implementation Judge
​
-(void)judgeWhoWin:(Player *)player andRobot:(Robot *)robot{
    /**
     先拿到玩家和机器人的拳头
     */
    FistType playerType = player->_userSelect;
    FistType robotType = robot->_robotSelect;
    
    /**
    判断谁赢谁输,然后加分
     */
    if(playerType - robotType == -1 || playerType - robotType == 2){
        NSLog(@"玩家赢");
        player->_score++;
        NSLog(@"玩家得分:%d-----机器人得分:%d",player->_score,robot->_score);
    }else if(playerType == robotType){
        NSLog(@"平局");
    }else{
        NSLog(@"机器人赢");
        robot->_score++;
        NSLog(@"玩家得分:%d-----机器人得分:%d",player->_score,robot->_score);
    }
}
​
@end
主文件:
#import <Foundation/Foundation.h>
#import "Player.h"
#import "Robot.h"
#import "Judge.h"
​
int main(int argc, const char * argv[]) {
    Player *xiaoMing = [Player new];
    xiaoMing->_userName = @"小明";
    
    Robot *robot = [Robot new];
    robot->_robotName = @"小爱同学";
    
    Judge *judge = [Judge new];
    judge->_judgeName = @"裁判";
    
    while (1) {
        [xiaoMing showFist];
        [robot showFist];
        [judge judgeWhoWin:xiaoMing andRobot:robot];
        char ans = 'a';
        NSLog(@"是否继续 y/n?");
        //清空输入缓冲区
        rewind(stdin);
        scanf("%c",&ans);
        if(ans != 'y'){
            break;
        }
    }
    return  0;
}

其他博主:blog.csdn.net/panjiye82/a…