类是面向对象的重要内容,可以把类当成一种自定义数据类型,可以使用类来定义变量,这种类型的变量相当于指针类型的变量。也就是说,所有的类都是指针类型的变量。
(1)定义类
面向对象的程序设计过程中有两个重要概念:类和对象(对象也称为实例),其中,类是某一批对象的抽象,可以把类理解为某种概念,对象才是一个具体存在的实体,从这个意义上看,日常所说的人其实都是人的实例,而不是人类。
类和对象是面向对象的核心。
Objective-C中定义类需要分为两个步骤。
在上面的语法格式中,@interface用于声明定义类的接口部分,@end表明定义结束。其中紧跟该类的一堆花括号用于声明该类的成员变量;花括号后面的部分用于声明该类的方法。
对于面向对象编程来说,成员变量和方法都是非常重要的概念,其中:
在上面的语法格式中,定义成员变量的语法格式如下:
类型 成员变量名;
其中:
关于上图所示的声名方法的语法说明如下:
类的接口部分只是声名方法,并没有为方法提供方法体,所以需要在方法声明后添加一个分号,用于表明方法声明结束。
除了接口部分之外,Objective-C的类还需要定义实现部分,定义实现部分的语法格式如图所示:
关于类实现部分的语法说明如下:
下面定义一个类的接口部分和一个类的实现部分:
//类的接口部分
#import <Foundation/Foundation.h>
@interface Person: NSObject {
//下面定义两个成员变量
NSString* _name;
int _age;
}
//下面定义了一个setName:方法
- (void) setName: (NSString*) name andAge:(int) age;
//下面定义了一个say:方法,并不提供实现
- (void) say: (NSString*) content;
//下面定义了一个不带形参的info方法
- (void) info;
//定义一个类方法
+ (void) foo;
@end
在上面的程序中,Person类的接口部分定义了两个成员变量和三个方法:三个方法都只有方法声明,没有方法体。除此之外,该Person类还定义了一个类方法:foo。
根据习惯,Objective-C 把接口部分和实现部分分别使用两个源文件保存(这并不是必需的,只是约定俗成的做法),其中接口部分的源文件通常命名为*.h 文件,实现部分的源文件通常命名为*.m 文件。
//类的实现部分
#import “Person.h”
@implementation Person {
//定义一个只能在实现部分使用的成员变量(被隐藏的成员变量)
int _testAttr;
}
//下面实现了一个setName:andAge:方法
- (void) setName: (NSString*) n andAge: (int) a {
_name = n;
_age = a;
}
//下面实现了一个不带形参的info方法
- (NSString*) info {
[self test];
return [NSString stringWithFormat:@"我是一个人,名字是:%@,年龄是:%d。", _name, _age];
}
//定义一个只能在实现部分定义的test方法(被隐藏的方法)
- (void)test {
NSLog(@"--只在实现部分使用的test方法--");
}
//实现类方法
+ (void) foo {
NSLog(@"Person类的类方法,通过类名调用");
}
@end
(2)对象的产生和使用
定义类之后,接下来就可以使用该类了,可以从如下三方面来使用类。
在上面的格式语法格式中,alloc是Objective-C的关键字,该关键字负责为该类分配内存空间,创建对象。
除此之外,还需要调用初始化方法对该实例执行初始化。由于所有的对象都继承了NSOject类,因此所有的类都有一个默认的初始化的方法:init
同时,为了照顾java程序员的习惯,Objective-C也支持使用new来创建对象,语法格式为:[类名 new];
但是上面这种写法基本等同于[[类名 alloc] init];而且这种写法比较少用,通常还是使用第一种语法来创建对象。
Objective-C调用方法的语法格式为:[调用者 方法名:参数 形参标签:参数值…];
如果方法声明中声明了多个形参,那么调用该方法时需要为每个形参传入想用的参数值。
(3)self关键字
self关键字总是只向该方法的调用者(对象或类),当self出现在实例方法中时,self代表调用该方法的对象;当self吃现在类方法中时,self代表调用该方法的类。
self关键字最大的作用是让类中的一个方法访问该类的另一个方法或成员变量。
接下来,我们定义一个Dog类,这个Dog对象的run方法需要调用它的jump方法,代码如下:
#import <Foundation/Foundation.h>
@interface Dog: NSObject
//定义一个jump方法
- (void) jump;
//定义一个run方法,run方法需要借助jump方法
- (void) run;
@end
//下面是Dog类的实现部分
#import "Dog.h"
@implementation Dog
//实现jump方法
- (void) jump {
NSLog(@"正在执行jump方法");
}
//实现一个run方法,run方法需要借助jump方法
- (void) run a {
[self jump];
NSLog(@"正在执行run方法");
}
@en```
从上面的代码中可以看出run方法使用self关键字调用了jump方法
在局部变量和成员变量重名的情况下,局部变量会隐藏成员变量。为了在方法中强行引用成员变量,也可以使用色咖啡关键字进行区分。例如,如下程序定义了Wolf类的接口部分。
```objectivec
#import <Foundation/Foundation.h>
@interface Wolf: NSOject {
NSString* _name;
int _age;
}
//定义一个setName:abdAge:方法
- (void) setName:(NSString*) _name andAge: (int) _age;
//定义一个info方法
- (void) info;
@end
接下来将会在类实现部分为该方法提供实现。在实现部分故意让形参与成员变量重名,然后通过self强行制定访问成员变量。
#import "Wolf.h"
@implementation Wolf
//定义一个setName:andAge:方法
- (void) setName: (NSString*) _name andAge: (int) _age {
//当局部变量隐藏成员变量时
//可用self代表调用该方法的对象,这样即可为调用该方法的对象的成员变量赋值
self -> _name = _name;
self -> -age = _age;
//定义一个info方法
- (void) info {
NSLog(@"我的名字是%@, 年龄是%d岁", _name, _age);
}
@end
int main(int argc, char* argv[]) {
@autoreleasepool {
Wolf* w = [[Wolf alloc] init];
[w setName: @"灰太狼" andAge:8];
[w info];
}
}
}
运行结果是:
我的名字是灰太狼,年龄是8岁
上面的setName:andAge:方法中,由于_name、_age形参隐藏了_name、_age成员变量,因此编译器会提示警告,但由于程序使用了self -> _name、 self -> _age来指定为调用该方法的Wolf对象的_name、-age成员变量赋值,这样就可以把调用该方法时传入的参数赋值给_name、_age两个成员变量。
(4)id类型
id类型可以代表所有对象的类型。也就是说,任意类的对象都可以赋值给id类型的变量。
当通过id类型父变量来调用方法时,Objective- C将会执行动态绑定。动态绑定是指Objective-C将会跟踪对象所属的类,它会在运行时判断该对象所属的类,并在运行时确定需要动态调用的方法,而不是在编译时确定要调用的方法。
例如:
#import <Foundation/Foundation.h>
#import "Person.h"
int main(int argc, char* argv []) {
@autoreleasepool {
//定义id类型的变量,并将Person对象赋给该变量
id p = [[Person alloc] init];
//使用p变量来调用say:方法
//程序将在运行时动态绑定,因此实际执行Person对象的say:方法
[p say: @"你好,疯狂iOS讲义"];
}
}
运行结果为:
你好,疯狂iOS讲义
上面程序中定义了一个id类型的变量,并将一个Person对象赋给id类型的变量,接下来可以通过该id类型的变量来调用say:方法,程序将会在运行时动态检测该变量所指的对象的实际类型为Person,因此,将会动态绑定到执行Person对象的say:方法。