9-类构造方法和成员变量作用域、以及变量
最后更新于:2022-04-01 00:17:14
#### 构造方法
出于初始化类中的成员变量的需要, 可以提供一个方法用于此目的,
这个方法就叫构造方法或构造方法(Constructor)。
与C++和Java不同, Objective-C命名是没有限制的, 并且有返回值本身类型指针。
以音乐类举例:
Song.h文件
~~~
@interface Song : NSObject {
NSString *title;
NSString *artist;
long int duration;
}
//操作方法
- (void)start;
- (void)stop;
- (void)seek:(long int)time;
//访问成员变量方法
@property(nonatomic,retain) NSString *title;
@property(nonatomic,retain) NSString *artist;
@property(readwrite) long int duration;
//构造方法
-(Song*) initWithTitle: (NSString *) newTitle
andArtist: (NSString *) newArtist
andDuration:( long int ) newDuration;
@end
~~~
在Song类的定义中添加了一个方法, 它一般用 init开头命名,
它的返回值很特殊, 是返回值本身类型指针。 并且有返回值本身类型指针。
实现代码如下:
~~~
@implementation Song
@synthesize title;
@synthesize artist;
@synthesize duration;
//构造方法
-(Song*) initWithTitle: (NSString *) newTitle
andArtist: (NSString *) newArtist
andDuration:(long int) newDuration {
self = [super init];
if ( self ) {
self.title = newTitle;
self.artist = newArtist;
self.duration = newDuration;
}
return self;
}
... ...
@end
~~~
#### 代码说明:
1、头文件引用规则
导入的文件要用一对引号引起来,而不是中的“"字符。双引号适用于本地文件(你自己创建的文件),而不是系统文件,这样就通知编译器在哪里能够找到指定的文件。
2、构造方法的实现代码几乎就是模式代码, 基本上都是如下写法:
~~~
-(id)init
{
self = [super init];
if (self) {
//初始化代码
}
return self;
}
~~~
其中使用 [super init] 来调用父类默认构造方法。
对象的初始化的常见的编程习惯是类中所有的初始化方法都以init开头。
如果希望在类对象初始化时做一些事情。可以通过覆写init方法达到这个目的。
这个方法返回的实例对象指派给另一新个关键词: self。
self很像 C++ 和 Java 的 this。
3、还有if ( self ) 跟 ( self != nil ) 一样, 是为了确定调用父类构造方法成功返回了一个新对象。
当初始化变量以后, 用返回self 的方式来获得自己的地址。
4、父类默认构造方法 -(id) init。
技术上来说, Objective-C中的构造方法就是一个 "init" 开头的方法,
而不像 C++ 与Java 有特殊的结构。
5、必须将父类init方法的执行结果赋值给self,
因为初始化过程改变了对象在内存中的位置(意味着引用将要改变)。
如果父类的初始化过程成功,返回的值是非空的,通过if语句可以验证,
注释说明可以在这个代码块的位置放入自定义的初始化代码。
通常可以在这个位置创建并初始化实例变量。
注意,
init被定义为返回id类型,这是编写可能会被继承的类init方法的一般规则。
程序开始执行时,它向所有的类发送initialize调用方法。
如果存在一个类及相关的子类,则父类首先得到这条消息。
该消息只向每个类发送一次,并且向该类发送其它任何消息之前,保证向其发送初始化消息。
实例成员变量作用域限定符
在接口中声明的实例变量可以通过子类进行继承。
可以把下面指令放在实例变量之前,以便更精确地控制其作用域:
即便从封装的角度出发, 实例成员变量应该定义为@private,
但作为一种面向对象的语言, Objective-C支持@public、 @private和@protected作用域限定。
如果一个实例变量没有任何的作用域限定的话, 那么缺省就是@protected。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-12-31_5684a3f06aa20.jpg)
•@public作用域限定的实例变量,可以被本类、其他类或者模块中定义的方法直接访问,即可在任何情况下访问;
•@private作用域限定的实例变量,只能在这个类里面才可以访问;
在实现部分定义的实例变量默认属于这种作用域。
•@protected作用域限定的实例变量, 可以在这个类里面和这个类的派生子类里面可以访问这个变量,
在类外的访问是不推荐的, 但也可以访问。在接口部分定义的实例变量默认是这种作用域。
•@package,对于64位映像,可以在实现该类的映像中的任何地方访问这个实例变量。
@public指令使得其他方法或函数可以通过使用指针运算符(->)访问实例变量。
但实例变量声明为public并不是良好的编程习惯,
因为这违背了数据封装的思想(即一个类需要隐藏它的实例变量)。
以一个简单的例子说明作用域:
访问定义如下:
~~~
#import
@interface Access: NSObject {
@public
int publicVar;
@private
int privateVar;
@protected
int protectedVar;
}
@end
~~~
调用的main函数如下:
~~~
#import
#import "Access.h"
int main (int argc, const charchar * argv[]) {
Access *a = [[Access alloc] init];
a->publicVar = 5;
NSLog(@"public var: %i\n", a->publicVar);
a->protectedVar = 6;
NSLog(@"protectedVar var: %i\n", a->protectedVar);
//不能编译
//a->privateVar = 10;
//NSLog(@"private var: %i\n", a->privateVar);
return 0;
}
~~~
注意:
@public、 @private和@protected作用域限定只能修饰的实例成员变量, 不能修饰类变量, 更不能修饰方法。
什么是类变量和类方法
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-12-31_5684a3f084072.jpg)
再以一个简单的例子说明下:
ClassA.h文件
~~~
#import
static int count;
@interface ClassA: NSObject {
int aaa;
}
+(int) initCount;
+(void) initialize;
@end
~~~
ClassA.m文件
~~~
#import "ClassA.h"
@implementation ClassA
-(id) init {
self = [super init];
count++;
return self;
}
+(int) initCount {
return count;
}
+(void) initialize {
count = 0;
}
@end
~~~
#### 代码说明:
init方法是默认构造方法,
在这个构造方法中累计类变量count,
在实例方法中可以访问类变量的, 但是类方法不能访问实例变量。
initCount 方法是一个普通的类方法, 用于返回类变量count,
initialize方法是非常特殊的类方法,它是在类第一次访问时候被自动调用 ,
因此它一般用来初始化类变量的,
类似于C#中的静态构造方法。
调用的main函数
~~~
#import
#import "ClassA.h"
int main( int argc, const charchar *argv[] ) {
ClassA *c1 = [[ClassA alloc] init];
ClassA *c2 = [[ClassA alloc] init];
// print count
NSLog(@"ClassA count: %i", [ClassA initCount] );
ClassA *c3 = [[ClassA alloc] init];
NSLog(@"ClassA count: %i", [ClassA initCount] );
[c1 release];
[c2 release];
[c3 release];
return 0;
}
~~~
#### 代码说明:
在第一次实例化ClassA时候会调用两个方法:
initialize类方法和实例构造方法init,
然后再次实例化ClassA时候只是调用实例构造方法init, 而没有调用 initialize类方法。
这样类变量count被一直累加, 它隶属类;
因此c1 实例可以访问, c2和c3都可以访问。
关于属性、存储方法和实例变量:
编码规范(Xcode4已经采用的)目前的趋势是使用下划线(_)作为实例变量名的起始字符。
@synthesize window = _window;
表明合成(synthesize)属性window的取值方法和设置方法,
并将属性与实例变量_window(实例变量并没有显性声明)关联起来。
这对区别属性和实例变量的使用是有帮助的。
[window makeKeyAndVisible]; //错误
[_window makeKeyAndVisible];//正确
[self.window makeKeyAndVisible];//正确
#### 全局变量:
在程序的开始处(所有的方法、类定义)编写一下语句:
int gMoveNumber = 0;
那么这个模块中的任何位置都可以引用这个变量的值。
这种情况下,我们说gMoveNumber被定义为全局变量。
按照惯例,用小写的g作为全局变量的首字母。
外部变量是可被其他任何方法或函数访问和更改其值的变量。
在需要访问外部变量的模块中,变量声明和普通方式一样,只是需要在声明前加上关键字extern。
使用外部变量时,必须遵循下面这条重要的原则:变量必须定义在源文件中的某个位置。
即在所有的方法和函数之外声明变量,并且前面不加关键字extern,如:int gMoveNumber;
确定外部变量的第二种方式是在所有的函数之外声明变量,
在声明前面加上关键字extern,同时显式地为变量指派初始值。
记住,声明不会引起分配变量的内存空间,而定义会引起变量内存空间分配。
处理外部变量时,变量可以在许多地方声明为extern,但是只能定义一次。
注意,如果变量定义在包含访问该变量的文件中,那么不需要单独进行extern声明。
#### 静态变量:
在方法之外定义的变量不仅是全局变量,而且是外部变量。
如果希望定义全局变量且只在特定模块(文件)中是全局的,就可以使用static来修饰。
注意,重载alloc并不是好的编程实践,因为这个方法处理内存的物理分配。
#### 枚举数据类型:
枚举数据类型的定义以关键字enum开头,之后是枚举数据类型的名称,然后是标识符序列(包含在一对花括号内),它们定义了可以给该类型指派的所以的允许值。
在代码中定义的枚举类型的作用域限于块的内部。
另外,在程序的开始及所有块之外定义的枚举数据类型对于该文件是全局的。
定义枚举数据类型时,必须确保枚举标识符与定义在相同作用域之内的变量名和其他标识符不同。