定义类
- 接口部分:定义该类包含的成员变量和方法。
- 实现部分:为该类的方法提供实现。
成员变量:用于描述该类的对象的状态数据。
方法:用于描述该类的行为。
- 类型:可以是OC允许的任何数据类型。
- 成员变量名:建议以下划线开头,如:_name。
- 方法类型标识符:+代表类方法,直接用类名调用,-代表实例方法,必须用对象调用。
- 方法返回值类型:可以是OC允许的任何数据类型。
- 方法签名关键字:由方法名、形参标签和冒号组成。
方法类似于我们之前熟悉的函数,区别之处在于所有类型应该用圆括号括起来。另外,要注意冒号,有 : 代表方法有参数。
- 类实现部分的类名必须与类接口部分的类名相同。
- 类实现部分也可以声明自己的成员变量,但只能在当前类里访问。
- 类实现部分必须为类声明部分的每个方法提供方法定义。
分离接口和实现文件
通常,类的声明(@interface部分)放在它名为 class.h 的文件中,而类的定义(@implementation部分)放在相同名称的文件中,但拓展名要使用.m。
示例:
Fraction.h接口文件
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface Fraction : NSObject
-(void) print;
-(void) setNumerater: (int) n;
-(void) setDenominator: (int) d;
-(int) numerator;
-(int) denominator;
-(double) convertToNum;
@end
NS_ASSUME_NONNULL_END
Fraction.m实现文件
#import "Fraction.h"
@implementation Fraction {
int numerator;
int denominator;
}
-(void) print {
NSLog(@"%i/%i",numerator,denominator);
}
-(void) setNumerater: (int) n {
numerator = n;
}
-(void) setDenominator: (int) d {
denominator = d;
}
-(int) numerator {
return numerator;
}
-(int) denominator {
return denominator;
}
-(double) convertToNum {
if(denominator != 0) {
return (double) numerator / denominator;
} else {
return NAN;
}
}
@end
main.m主测试程序
#import "Fraction.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
Fraction *myFraction = [[Fraction alloc] init];
[myFraction setNumerater: 1];
[myFraction setDenominator: 3];
NSLog(@"The value of myFraction is:");
[myFraction print];
NSLog(@"The value of myFraction is: %lf",[myFraction convertToNum]);
}
return 0;
}
对象和指针
在上面的Fraction *myFraction = [[Fraction alloc] init];这段代码中,创建了一个Fraction对象,这个对象被赋给myFraction变量。其实,myFraction变量仅仅保存了Fraction对象在内存中的首地址。注意:如果堆内存里的对象没有任何变量指向它,那么程序将无法再访问该对象。
static关键字
在变量声明前加上关键字static,可以使局部变量保留多次调用一个方法所得的值。
例如:static int hitCount = 0;
self关键字
self关键字总是指向该方法的调用者(对象或类),当self出现在实例方法中时,self代表调用该方法的对象;当self出现在类方法中时,self代表调用该方法的类。
self关键字最大的作用是让类中的一个方法访问该类的另一个方法或成员变量。
例子:
// FKDog.h
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface FKDog : NSObject
-(void) jump;
-(void) run;
@end
NS_ASSUME_NONNULL_END
// FKDog.m
#import "FKDog.h"
@implementation FKDog
-(void) jump {
NSLog(@"正在执行jump方法");
}
/*
-(void) run {
FKDog* d = [[FKDog alloc] init];
[d jump];
NSLog(@"正在执行run方法");
}
如果不使用self关键字,则要用以上程序来实现。
*/
-(void) run {
[self jump];
NSLog(@"正在执行run方法");
}
@end
// main.m
#import "FKDog.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
FKDog* dog = [[FKDog alloc] init];
[dog run];
}
return 0;
}
大家可以自己尝试一下程序的输出结果。
在局部变量与成员变量重名时也可以使用self关键字进行区分。
示例:
// FKWolf.h
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface FKWolf : NSObject {
NSString* _name;
int _age;
}
-(void) setName: (NSString*) _name andAge: (int) _age;
-(void) info;
@end
NS_ASSUME_NONNULL_END
// FKWolf.m
#import "FKWolf.h"
@implementation FKWolf
-(void) setName:(NSString *)_name andAge:(int)_age{
self->_name = _name;
self->_age = _age;
}
-(void) info {
NSLog(@"我的名字是%@,年龄是%d岁",_name,_age);
}
@end
//main.m
#import "FKWolf.h"
int main(int argc, const char* argv[]) {
@autoreleasepool {
FKWolf *w = [[FKWolf alloc] init];
[w setName:@"小灰" andAge:8];
[w info];
}
return 0;
}
id类型
OC提供了一个id类型,任意类的对象都可以赋值给id类型的变量。当通过id类型的变量来调用方法时,OC将会执行动态绑定,它会在运行时判断该对象所属的类,并在运行时确定需要动态调用的方法。
示例:
将上一个程序稍作修改。
//main.m
#import "FKWolf.h"
int main(int argc, const char* argv[]) {
@autoreleasepool {
//使用id
id w = [[FKWolf alloc] init];
[w setName:@"小灰" andAge:8];
[w info];
}
return 0;
}
方法详解
方法是由传统的函数发展而来的,它们都可以接受定义形参,调用时都可以传入实参。但方法不能独立存在,方法必须属于类或对象。
所有的方法必须使用 [类 方法] 或 [对象 方法] 的形式调用。
具有多个参数的方法:
通过列出每个连续的参数并用冒号将其连起来,就可以定义一个接收多个参数的方法。
示例:
-(void) setNumerato: (int) n andDenominator: (int) d;
不带参数名的方法:
创建方法名时,参数名实际上是可选的。示例:
-(int) set: (int) n: (int) d;
这个方法的第二个参数没有命名,这个方法名为 set:: ,两个冒号表示这个方法依然有两个参数。调用示例:
[aFraction set: 1 : 3];
形参个数可变的方法:
如果在定义方法时,在最后一个形参名后增加逗号和三点(,…),则表明该形参可以接受多个参数值。
示例:
@interface VarArgs : NSObject
//定义形参可变的方法
-(void) test: (NSString*) name, ...;
@end
为了在程序中获取个数可变的形参,需要用到如下关键字:
- va_list:一个类型,用于定义指向可变参数列表的指针变量。
- va_start:一个函数,指定开始处理可变形参的列表,并让指针变量指向可变形参列表的第一个参数。
- va_end:结束处理可变形参,释放指针变量。
- va_arg:返回获取指针当前指向的参数的值,并将指针移动到下一个参数。
实现部分:
#import "VarArgs.h"
@implementation VarArgs
-(void) test: (NSString*) name, ...{
va_list argList;
//如果第一个name参数存在,才处理后面的参数
if (name) {
//name不在可变参数列表中,先处理
NSLog(@"%@",name);
//让argList指向第一个可变参数列表的第一个参数,开始提取可变参数列表的参数
va_start(argList, name);
//va_arg用于提取argList指针当前指向的参数,并将指针移动到指向下一个参数
//arg变量用于保存当前获取的参数,如果该参数不为nil,则进入循环体
NSString* arg = va_arg(argList, id);
while (arg) {
//打印出每一个参数
NSLog(@"%@",arg);
//再提取下一个,并把指针指向下一个参数
arg = va_arg(argList, id);
}
//释放argList指针,结束提取
va_end(argList);
}
}
@end
main.m:
#import "VarArgs.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
VarArgs *va = [[VarArgs alloc] init];
[va test:@"学习OC",@"努力学习OC",@"疯狂学习OC",nil];
}
return 0;
}
结果:
学习OC
努力学习OC
疯狂学习OC
本质上,可变参数也是一个类似于数组的结构,调用test: 方法时,为了明确告诉程序可变形参的结束点,可以将最后一个参数设为nil。此外,个数可变的形参只能处于形参列表的最后,也就是说,一个方法中最多只能有一个个数可变的形参。
单例模式
在某些时候,程序多次创建某个类的对象没有任何意义,此时程序需要保证该类只有一个实例。
如果一个类始终只能创建一个实例,则这个类被称为单例类。
单例类可以通过static全局变量来实现,每次程序需要获取该实例时,都要先判断该static全局变量是否为nil,如果该全局变量为nil,则初始化一个实例并赋值给static全局变量;如果该全局变量不为nil,那么程序直接返回该全局变量指向的实例即可。
示例:
// FKSingleton.h
// 单例问题
#import <Foundation/Foundation.h>
@interface FKSingleton : NSObject
+(id) instance;
@end
// FKSingleton.m
// 单例问题
#import "FKSingleton.h"
@implementation FKSingleton
static id instance = nil;
+(id) instance {
//如果instance全局变量为nil
if (!instance) {
//创建一个Singleton实例,并将该实例赋给instance全局变量
instance = [[super alloc] init];
}
return instance;
}
@end
// main.m
// 单例问题
#import "FKSingleton.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
//判断两次获取的实例是否相等,将返回1代表真
NSLog(@"%d", [FKSingleton instance] == [FKSingleton instance]);
}
return 0;
}
最终程序会输出1,代表两次创建的instance是一个变量。