ObjC: 面向对象基础

本文介绍了Objective-C作为面向对象语言的基本概念,包括类的定义、实例化、继承、接口(协议)、方法覆盖、属性访问等核心内容。通过实例演示了如何在实际编程中应用这些概念,以及如何通过复合(组合)实现更复杂的对象结构。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

ObjC是面向对象语言。因此面向对象的中的很多概念,ObjC都支持,比如:

  • 对象(实例)
  • 实例化
  • 继承
  • 接口(协议)
  • 实现
  • 覆盖(重写)方法
  • 复合(组合)
  • super关键字

下面就分别介绍一下。

 

类的定义和实现

面向对象语言一般都有类的概念,虽然也有特殊的情况,比如javascript只有原型的概念而没有类的概念。ObjC有类的概念。

如下图所示,创建一个类文件:

image

然后是下一步,给出这个类的名称,ObjC的命名规则是类名首字母大写,驼峰方式,这点和Java是一样的。

image

这里强调一点,我把Also create "Book.h"的默认勾选项取消了。勾选该项,将生成两个文件,Book.h和Book.m。前者是头文件,后者是实现文件。一般会将二者分开,这样比较清楚,便于维护。这里的做法是为了说明可直接写m文件而无需写头文件。因为可以在一个头文件中声明多个类,几个m文件共享同一个头文件。

我把Book的头文件类声明部分,放到其他类的头文件中,比如:

#import <Foundation/Foundation.h>

@interface CarInfo : NSObject {

}

-(void)sayCarBrand;

@end

@interface Book : NSObject { 
}

@end

然后,m文件部分要引用该头文件:

#import "CarInfo.h"

@implementation Book

@end

一般情况下,类文件是生成一对文件的,即勾选Also create "Book.h"的情况。那么会生成一个名为Book.h的头文件,和一个名为Book.m的实现文件。

头文件内容:

#import <Foundation/Foundation.h>

@interface Book : NSObject { 
}

@end

 

实现文件:

#import "Book.h"

@implementation Book

@end

这里要提几点:

  • ObjC和Java类似,类是单根继承的,也就是说,有一个最上级的根类,即NSObject,所有其他类都是这个类的直接或者间接的子类。
  • 写法上比Java要啰嗦。类要分声明和实现两部分。其中:
    • 声明部分放在头文件中。使用@interface关键字做类的声明。Book表示是要声明的类名称,:NSObject,表示Book继承自NSObject。花括号内可存放实例变量的声明,后面再讲。@end表示类的声明结束。
    • 实现部分要放在m文件中。使用@implementation关键字,表示实现某类。。@end表示类的实现结束。
  • 实现的这个类是个空的类。既没有属性,也没有方法。没什么用。后面再讲属性和方法的使用。

类的实例化

声明并且实现了一个类,就是为了使用它:

Book *book=[[Book alloc]init];

这里可以拆解来写上面的这行语句:

Book *book; 
book=[[Book alloc]init];

其中第一句,表示声明一个Book类的变量book。book前面加*号,表示该变量是个Book指针类型变量。

ObjC中,所有对象类型的变量都是指针类型的,因此都需要加*号,这里有一个例外:

Book *book; 
book=[[Book alloc]init];

id myBook=book; 

 

斜体加粗这句话编译不会报错。如果写成:

id *myBook=book;

反而会有编译警告。因为id这个数据类型,本身就是指针类型,不需要再增加*号了。在ObjC中,id类型表示任意类型的指针变量。

[[Book alloc] init]这句,类似Java中的:

new Book();

这里还能进一步拆解语句,写成:

book=[Book alloc]; 
book=[book init];

这里要先提到ObjC的一个语法,后面会详细介绍,方法调用。和Java不一样,ObjC使用[]来表示方法调用,[Book alloc]类似Java中的Book.alloc()。alloc是静态方法,如果按照Java的提法来看。在ObjC术语中,叫做类方法。alloc方法可以在NSObject的reference中找到。可以看到该方法是+ alloc,+号表示类方法,-号表示实例方法。这个后面会讲。

alloc将分配内存,创建名为book的Book类实例。alloc还将为该实例创建内存计数。这涉及到ObjC的内存管理机制,在后面再讲解。

init方法,在NSObject的reference中也能找到。是带-号的方法,也就是说是实例方法。该方法的作用,是初始化。即可通过init方法对实例的成员变量做初始化。具体怎么做,要到覆盖方法部分再详细说。

 

类的继承

在ObjC的语法中,可以对任何类继承,如上面已经提到的,要在类的声明中:

@interface Book : NSObject {

ObjC语言中,没有限制继承的机制,比如在Java中,可以用关键字final标识一个类,强制规定该类不可继承。比如:

public final class String

这样就无法继承java.lang.String了。

 

方法的声明和使用

在ObjC的类中,可以声明方法。

ObjC语言的方法声明比较特别。不像Java、.net的那么容易识别。

先写个最简单的,帮助读者适应一下。Book类,想声明一个打印书名的方法。这个方法为什么简单呢?因为,既不需要传入参数,也没有返回值。

方法的声明,在头文件中:

#import <Cocoa/Cocoa.h>

@interface Book : NSObject {

}

-(void) printName;

@end

方法的实现,在m文件中:

#import "Book.h"

@implementation Book

-(void) printName{ 
}

@end

怎样使用这个方法,完整代码如下:

Book *book=[[Book alloc] init]; 
[book printName];

我现在没有在printName方法中加入任何可执行语句。读者可考虑调用NSLog函数打印日志。

下面说说,有返回值的情况,这个比较简单。声明方法:

#import <Cocoa/Cocoa.h>

@interface Book : NSObject {

}

-(void) printName;

-(NSString *) getPrice;

@end

实现方法:

#import "Book.h"

@implementation Book

-(void) printName{ 
}

-(NSString *) getPrice{ 
    return @"$17"; 
}

这里返回值是NSString指针类型,因此不能只写(NSString),而是要(NSString *)。

在代码中调用getPrice方法:

NSLog(@"Book price is %@", [book getPrice]);

这里补充说一下NSLog函数的使用,第一个参数是字符串。%@表示,打印后面参数,其中%表示打印的占位符,后面的@表示打印的数据类型是ObjC对象类型。我后面的参数正式ObjC对象类型,是一个NSString类型。

最后,说一下ObjC声明方法比较令人费解的地方,就是传入方法参数的语法了。下面改写printName方法说明这点。假设要传入一个书名,并打印该书名称。

那么方法声明:

#import <Cocoa/Cocoa.h>

@interface Book : NSObject {

}

-(void) printName:(NSString *)bookName;

-(NSString *) getPrice;

@end

实现:

#import "Book.h"

@implementation Book

-(void) printName:(NSString *)bookName{ 
    NSLog(@"书名:%@",bookName); 
}

-(NSString *) getPrice{ 
    return @"$17"; 
}

@end

调用:

Book *book=[[Book alloc] init]; 
[book printName:@"Objective-C入门手册"];

这里需要说明ObjC的一个独特的机制——方法调度程序。ObjC认为对象的方法调用,就是向对象发送消息。消息的内容呢?就是传递的参数。

ObjC在程序运行时,对发送给对象的消息(也就是调用对象方法),做动态的判断,查找该对象是否实现了该方法(包括它的超类和类别中,次序是先类别,然后该类对象本身,然后超类),如果没有找到实现的方法,就会报运行时异常。比如:

2011-05-16 21:30:33.673 SimpleObjcDemo[18967:a0f] -[Book aaa]: unrecognized selector sent to instance 0×100110400

值得注意的是,ObjC在编译时,并不会指出对不存在方法调用的错误。在Xcode中仅仅是警告一下:

image

如果是多个传入参数呢?比如打印书名需要书的名称和作者名称,方法的声明:

#import <Cocoa/Cocoa.h>

@interface Book : NSObject {

}

-(void) printName:(NSString *)bookName authorName:(NSString *)author;

-(NSString *) getPrice;

@end

 

方法的实现:

#import "Book.h"

@implementation Book

-(void) printName:(NSString *)bookName  authorName:(NSString *)author{ 
    NSLog(@"书名:%@, 作者:%@",bookName,author); 
}

-(NSString *) getPrice{ 
    return @"$17"; 
}

@end

调用:

Book *book=[[Book alloc] init]; 
[book printName:@"Objective-C入门手册" authorName:@"Marshal"];

 

方法的覆盖(重写)

覆盖,override,也有翻译为重写的。覆盖是面向对象语言必备的特性,通过该技术来实现多态。覆盖就是子类实现了父类相同的方法。这样,无论通过父类还是通过子类的类型调用,都将调用子类实现的方法。

首先,我编写了一个名为MusicBook的类,继承自上文实现的Book类。头文件:

#import <Cocoa/Cocoa.h> 
#import "Book.h"

@interface MusicBook : Book {

}

@end

 

实现文件:

#import "MusicBook.h"

@implementation MusicBook

@end

 

什么也没做,只是简单的继承。如果这时调用:

MusicBook *musicBook=[[MusicBook alloc] init]; 
NSLog(@"Book price is %@",[musicBook getPrice]);

这样执行的是超类的getPrice方法。下面开始覆盖。覆盖不需要在子类的头文件中再声明方法了。直接在m文件中实现方法就是覆盖:

#import "MusicBook.h"

@implementation MusicBook

-(NSString *) getPrice{ 
    return @"$0.99"; 
}

@end

 

这时再调用,发现执行的是子类的方法了。为了验证多态,在调用时使用超累类型,效果是一样的:

Book *musicBook=[[MusicBook alloc] init]; 
NSLog(@"Book price is %@",[musicBook getPrice]);

 

协议(接口)

面向对象编程语言的语法中,一般都支持接口的概念。比如Java中的interface。但是在ObjC中,interface关键字被类的声明使用了。对应的关键字是@protocol。叫协议也有道理。基于接口的编程又叫基于契约编程嘛。

创建一个协议,和创建类略有不同:

image

比如我想创建一个播放音乐的接口,PlayMusic,用于MusicBook播放声音:

#import <Cocoa/Cocoa.h>

@protocol PlayMusic

@end

 

空的协议没什么用。在里面声明方法:

#import <Cocoa/Cocoa.h>

@protocol PlayMusic

-(void)play;

@end

 

使用协议,比如要让MusicBook类实现协议,需要在MusicBook头文件中:

#import <Cocoa/Cocoa.h> 
#import "Book.h" 
#import "PlayMusic.h"

@interface MusicBook : Book <PlayMusic>{

}

@end

然后在MusicBook类中实现该方法:

#import "MusicBook.h"

@implementation MusicBook

-(NSString *) getPrice{ 
    return @"$0.99"; 
}

-(void)play{ 
    NSLog(@"play music."); 
}

@end

使用该方法,可以通过MusicBook类型调用:

MusicBook *musicBook=[[MusicBook alloc] init]; 
NSLog(@"Book price is %@",[musicBook getPrice]); 
[musicBook play];

 

使用super关键字

在面向对象语言里,如果使用了继承,那么当想调用超类的方法时,需要用到super关键字。ObjC在这点和Java很相似。

比如在MusicBook类中调用超类方法:

#import "MusicBook.h"

@implementation MusicBook

-(NSString *) getPrice{ 
    NSLog(@"super getPrice: %@",[super getPrice]); 
    return @"$0.99"; 
}

 

使用self关键字

ObjC的self关键字,相当于Java中的this指针。可这样使用:

#import "MusicBook.h"

@implementation MusicBook

-(NSString *) getPrice{ 
    NSLog(@"super getPrice: %@",[super getPrice]); 
    [self play]; 
    return @"$0.99"; 
}

-(void)play{ 
    NSLog(@"play music."); 
}

 

面向对象中的复合

上面提到了继承,在自然语义中是is a的意思,比如Music Book is a Book。在语义中除了is a,还有一种是has a,比如Book has an Author。图书有一个作者。这在面向对象中是复合的概念。Java中的复合,比如:

public class Book{ 
    private Author author; 
}

 

这里Author类是隶属于Book的。

这种表达,在Objc中,要用以下的写法。首先,咱们要创建一个Author类。头文件:

#import <Cocoa/Cocoa.h>

@interface Author : NSObject { 
    NSString *name; 
}

@end

 

这里,在{}括号内,为Author类创建了个实例属性,作者姓名。实际上这个属性相对于Author类,就已经是复合关系了。即Author has a name。

简化起见,实现Author的m文件,没做任何事情:

#import "Author.h"

@implementation Author

@end

 

那么下面,我们让Book类和Author建立复合关系,也是在头文件中:

#import <Cocoa/Cocoa.h> 
#import "Author.h"

@interface Book : NSObject { 
    Author *author; 
}

-(void) printName:(NSString *)bookName;

-(NSString *) getPrice;

@end

 

在对象内部使用实例变量

现在,author变量,从对象以外是无法访问的,只能在对象内部被访问。author变量默认是nil,也就是空。需要为该变量赋值。可以通过覆盖init方法(从NSObjcet继承来的初始方法)来为author变量赋值:

#import "Book.h" 
#import "Author.h"

@implementation Book

-(id)init{ 
    self=[super init]; 
    if (self) { 
       author=[[Author alloc] init]; 
    } 
    return self; 
}

 

这里面先不用管init方法为什么这么写,只考虑赋值一行语句即可。

那么可以在其他地方访问该属性了:

-(void) printName:(NSString *)bookName { 
    NSLog(@"书名:%@, 作者:%@",bookName,author); 
}

当然,打印出来的是一个内存地址字符串,还不是友好提示:

2011-05-17 12:13:53.108 SimpleObjcDemo[20018:a0f] print Book instance <Book: 0×100110420>

但是,我们已经能通过对象内部访问到实例变量了。

 

让实例变量能被外界访问

如果用过Java,就应该了解它的setter和getter,比如这样:

public class Book{ 
    private Author author; 
    
    public void setAuthor(Author author){ 
        this.author=author; 
    } 
    
    public Author getAuthor(){ 
        return this.author; 
    } 
}

那么,ObjC中对应的语法是什么呢?首先,要声明使用@property,在头文件中:

#import <Cocoa/Cocoa.h> 
#import "Author.h"

@interface Book : NSObject { 
    Author *author; 
}

@property (nonatomic,retain) Author *author;

这里nonatomic,表示不在多线程环境下使用,因此可提高一点儿性能,这对台式机可能没有多大影响,但是对iOS设备上还是有意义的。retain是保持该变量,在有关内存管理中再详细讲解。

在实现文件中:

#import "Book.h" 
#import "Author.h"

@implementation Book

@synthesize author;

这样就可以这么调用了:

Book *book=[[Book alloc] init]; 
[book printName:@"Objective-C入门手册"]; 
NSLog(@"book author: %@",book.author);

或者,赋值:

book.author=[[Author alloc]init];

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值