原文地址:
之前介绍过变量和数据类型,那么我们来看看Objective-C中更深层次的变量和数据类型。比如我们要初始化一个分数类,可以使用如下代码:
1 Fraction *fraction = [[Fraction alloc] init];
那么为了简便,我们已经将代码简化为:
1 Fraction *fraction = [[Fraction new];
此时,若想对分数进行赋值,那么必须再写一个方法来完成,而new也是达不到这个效果的,很显然这里我们需要对分数类进行业务初始化,使得其创建好就具备数值,那么这个业务初始化方法可以这么来写:
1 #import2 3 @interface Fraction : NSObject 4 5 @property int numerator,denominator; 6 7 -(void) print; 8 -(double) convertToNum; 9 -(void) setTo:(int) n over: (int) d;10 -(Fraction *)add:(Fraction *)f;11 -(void) reduce;12 -(Fraction *) initWith:(int) n over:(int) d;13 14 @end
首先在分数类的定义中加入initWith...over方法,然后编写实现代码:
1 -(Fraction *) initWith:(int)n over :(int)d2 {3 self=[super init];4 if(self){5 [self setTo:n over:d];6 }7 return self;8 }
在实现中我们调用super的init方法实现对象初始化,之后使用self来进行业务初始化,那么主函数就必须使用alloc来创建空间了,因为我们这里不能再使用new来合并二者了,代码如下:
1 #import "Fraction.h" 2 3 int main(int argc, const char * argv[]) 4 { 5 @autoreleasepool { 6 Fraction *fractionA=[[Fraction alloc] initWith:1 over:3]; 7 Fraction *fractionB=[[Fraction alloc] initWith:2 over:5]; 8 9 Fraction *result=[fractionA add:fractionB];10 [result print];11 }12 return 0;13 }
编译运行,我们可以得到如下效果:
这里我们就是编写了业务初始化的方法,使用一行语句完成对象的创建和初始化,回过头来看看初始化方法,可以过滤出一个模板:1 -(id) init2 {3 self=[super init];4 if(self){5 //业务初始化代码6 }7 return self;8 }
那么为了使用这个初始化规则,我们进一步对分数类进行修改,提供一个可以作为父类方法的init方法,代码如下:
1 #import2 3 @interface Fraction : NSObject 4 5 @property int numerator,denominator; 6 7 -(void) print; 8 -(double) convertToNum; 9 -(void) setTo:(int) n over: (int) d;10 -(Fraction *)add:(Fraction *)f;11 -(void) reduce;12 -(Fraction *) initWith:(int) n over:(int) d;13 -(id) init;14 @end
1 我们覆盖Fraction类的init方法,提供如下实现:
我们人为将分数都初始化为1,那么主函数为:
1 #import "Fraction.h" 2 3 int main(int argc, const char * argv[]) 4 { 5 @autoreleasepool { 6 Fraction *fraction=[Fraction new]; 7 [fraction print]; 8 } 9 return 0;10 }
编译运行,我们得到如下结果:
其实这里的new还是合并调用了alloc和init,alloc还是NSObject中的方法,而init则是调用我们覆盖的方法,因此初始化的效果为1,这个很好理解。 对于变量,有一个概念叫作用域,我们之前没有使用访问控制指令来修饰变量,那么Objective-C提供了以下几种访问控制指令,结合继承我们来看看。 @private指令,该指令修饰的实例变量仅能在该类中访问,不能被子类访问。我们在实现部分定义的变量默认就是这种作用域。 @protected指令,该指令修饰的实例变量可以被该类及其子类访问。那么我们在接口部分定义的变量默认就是这种作用域。 @public指令,该指令修饰的实例变量不仅可以被该类访问,其他类也可以进行访问。 那么如何来使用这些访问控制指令呢,可以使用如下的代码段:1 @interface ClassA 2 { 3 @private 4 int n; 5 int b; 6 @protected 7 int i; 8 int j; 9 //其它实例变量10 }
这种语法结构告诉我们跟在指令之后的变量都是该访问控制类型的,除非遇到下一个访问控制指令来修饰。如上面的代码n和b就是private类型的,而i和j是protected类型的。对于public类型的变量则不推荐使用,因为它会破坏数据封装的原则。
我们来看一下全局变量这个概念。如果在某程序的开始处(所有函数,类定义之外)定义了一个变量,那么在这个模块的任何位置都引用这个变量的值,这个变量也叫做全局变量。 关键字extern可以标识访问其它文件定义的全局变量。使用extern标识外部变量时要保证变量在所有的函数和类定义之外声明,并且不能加关键字。 我们来看下面的示例代码:1 #import2 3 @interface Foo : NSObject4 5 -(void) setGlobalVar:(int)val;6 7 @end
一个简单的类定义,用于设置全局变量,那么其实现代码为:
1 #import "Foo.h" 2 3 @implementation Foo 4 5 -(void) setGlobalVar:(int)val 6 { 7 extern int globalVar; 8 globalVar=val; 9 }10 11 @end
使用extern关键字来标识一个外部的全局变量,注意变量命名要和外部的全局变量一致,那么主函数就必须这么来写:
1 #import "Foo.h" 2 3 int globalVar=10; 4 5 int main(int argc, const char * argv[]) 6 { 7 @autoreleasepool { 8 Foo *foo=[Foo new]; 9 NSLog(@"%i",globalVar);10 11 [foo setGlobalVar:406];12 NSLog(@"%i",globalVar);13 }14 return 0;15 }
这里的全局变量globalVar和方法中使用extern修饰的变量名一致,才可以对其进行修改,那么编译运行后我们就得到如下结果:
当然可以尝试不同命名所带来的错误,从而更好理解这个问题。 下面一个概念是静态变量,全局变量和extern的使用破坏了数据封装原则和面向对象的思想,而有时需要在不同的方法中共享某值,则还需要使用它。上面的例子说明方法之外定义的变量不仅是全局变量而且还可以作为外部变量,那么更多情况是我们需要全局变量而不是外部变量。也就是希望某全局变量仅在特定文件中使用,那么要做到这一点就会使用到static关键字。加上了static关键字,那么外部的extern就不会起作用了。 作为示例,我们继续修改分数类Fraction,比如说我们要记录到底创建了多少个分数对象,那么该如何来实现呢?看下面的代码:1 +(Fraction *) allocF2 +(int) countF
在分数类定义中添加两个类方法,使用+号修饰,表明它们不是实例变量,一个用于申请空间,注意这里不要覆盖alloc方法,因为分配空间涉及物理层面的操作,尽量避开,之后的countF就是记录该类创建了几次,需要使用到全局变量,在实现代码中,首先定义全局变量:static int gCount;之后完善上面两个方法:
1 +(Fraction *) allocF 2 { 3 gCount++; 4 return [Fraction alloc]; 5 } 6 7 +(int) countF 8 { 9 return gCount;10 }
那么主函数中我们可以这么来写:
1 #import "Fraction.h" 2 3 int main(int argc, const char * argv[]) 4 { 5 @autoreleasepool { 6 Fraction *fractionA,*fractionB,*fractionC; 7 8 NSLog(@"%i",[Fraction countF]); 9 10 fractionA=[[Fraction allocF] init];11 fractionB=[[Fraction allocF] init];12 fractionC=[[Fraction allocF] init];13 14 NSLog(@"%i",[Fraction countF]);15 }16 return 0;17 }
编译运行(此时请删除之前的Foo类,因为我们已经去掉全局变量globalVar了,保留它们就会报错,因为方法中有extern修饰的变量定义),我们来看下结果:
说明静态变量gCount保留了方法allocF的调用次数。这里的静态变量初始值为0,那么若要重置这个静态变量的值,可以提供setter方法来实现,而此例中并不需要这么做。