【Objective-C】——面向对象(上)


前言

本周学习了Objective-C中的面向对象的上半部分,下面我将总结一下本周的学习成果。


一、类与对象

类可以当作是一种自定义数据类型,我们可以使用类来定义变量而这种变量是指针类型的变量。

1.定义类

定义类分为两步个部分,第一个部分是接口部分:用于定义所包含的成员变量及方法。第二个部分是实现部分:为该类的方法提供实现。
定义接口部分的语法如下图所示:在这里插入图片描述
@interface用于声明定义类的接口部分,其下面的花括号中为用于声明该类的成员变量,花括号下面跟的是两个用于声明该类的方法,@end表示定义结束。
成员变量:用于描述该类的对象的具体数据。例如一个人的年龄,身高之类的。并且
方法:用于描述该类的行为。例如睡觉,吃饭等行为。
我们一般会将接口声明的部分放在头文件(*.h)中。

定义成员变量名的语法如下:

类型 成员变量名

类型可以是任何类型,成员变量名以下划线(_)开头。

声明方法的语法如下:
在这里插入图片描述
方法类型标识符:只有 + 和 - ,其中 + 代表该方法是类方法,可直接用类名调用; -代表该方法是实例方法,需要通过对象调用。
方法返回值类型:可以是任何数据类型,包括基本类型,构造类型和各种指针类型。
方法签名关键字:由方法名,冒号,形参标签三部分构成。例如图中是由inserObject:atIndex:这三部分构成。
在OC的方法声明中,所有的类型都需要用小括号“()”括起来其中包括void类型。 类的接口部分只是声明方法并没有具体实现,因此需要在方法声明的结尾添加分号,这一点与C语言中的函数声明类似。

定义类实现部分的语法如下图所示:
在这里插入图片描述
1.类实现部分的类名必须与接口部分的类名相同,以表示这是同一个类的接口部分以及实现部分。
2.类实现部分也可声明自己的成员变量,但是成员变量只能在当前的类中访问,相当于对外隐藏的成员变量。
3.类实现部分必须为类声明部分的每个方法提供方法定义。

OC中,在接口部分的内容是可以直接暴露且可供用户调用的部分,实现部分对于外界是隐藏的,不能给外界调用。

下面定义一个FKPerson类的接口部分:
FKPerson.h

#import <Foundation/Foundation.h>

@interface FKPerson : NSObject {
	//下面定义两个成员变量
	NSString* _name;
	int _age;
}
//下面定义一个setName : andAge :方法
-void) setName : (NSString*) name andAge : (int) age;
//下面定义一个say:方法,并不提供实现
- (void) say : (NSString*) content;
//下面定义一个不带形参的info方法
- (NSString*) info;
//定义一个类方法
+ (void) foo;
@end

下面定义一个FKPerson实现部分:
FKPerson.m

#import "FKPerson.h"

@implementation FKPerson {
	//定义一个只能在实现部分使用的成员变量(被隐藏的成员变量)
	int _testAttr;
}
//下面实现了一个setName : andAge :方法
- (void) setName : (NSString*) n andAge : (int) a {
	_name = n;
	_age = a;
}
//下面实现一个say办法
- (void) say : (NSString*) content {
	NSLog (@"%@", content);
}
//下面实现一个不带形参的into方法
- (NSString*) into {
	[self test];
	return [NSString stringWithFormat: @"我是一个人,名字为:%@, 年龄为:%d。", _name, _age];
}
//下面定义一个只能在实现部分使用的方法(被隐藏的方法)
- (void) test {
	NSLog (@"--只在实现部分定义的test方法--");
}
//实现类方法
+ (void) foo {
	NSLog (@"FKPerson类的类方法,通过类名调用");
}
@end
}

2、对象的产生和使用

可从三方面来使用类:
1.定义变量。语法格式为:类名* 变量名;
2.创建对象。语法格式为:[ [类名 alloc] 初始化方法];例如[ [Person alloc] init];
3.调用类方法。语法格式为:[调用者 方法名: 参数 形参标签: 参数值 …];如果方法声明中有多个形参,调用该方法时需要为每个形参传入参数值。

下面示范了FKPerson类的用法:
FKPersonTest.m

//定义person变量并赋值
FKPeron* person = [ [FKPerson alloc] init];
//调用有参数的方法必须传入参数
[person say : @"Hello, I love iOS"];
//调用无参数的方法,不需要传入参数
//方法有返回值。可以定义一个类型匹配的变量来接收返回值
NSString* info = [person info];
NSLog (@"person的info信息为:%@", info);
//下面调用test方法会错误,因为test在实现部分定义,已被隐藏
//[person test];
//通过类名调用类方法
[FKPerson foo];

3.对象和指针

在前面的FKPerson.m中,有FKPeron* person = [ [FKPerson alloc] init];这行代码创建了一个FKPerson实例,也被称为FKPerson对象,它被赋值给person变量。当创建FKPerson对象时,必须有对应的内存来存储FKPerson对象的成员变量。下图显示了FKPerson对象在内存中的存储示意图:
在这里插入图片描述

4.self关键字

Objective-C中提供了一个self关键字,self关键字总是指向该方法的调用者(对象或类),在实例方法中self代表调用该方法的对象,在类方法中,self代表调用该方法的类。
1.self关键字的最大作用是让类中的一个方法访问该类的另一个方法或成员变量。
示例代码如下:
FKDog.h

#import <Foundation/Foundation.h>

@interface FKDog : NSObject
//定义一个jump方法
-void) jump;
//定义一个run方法,run方法需要借助jump方法
-  (void) run;
@end

FKDog.m

#import "FKDog.h"

@implementation FKDog
//实现一个jump方法
 - (void) jump {
 	NSLog (@"正在执行jump方法");
 }
 //实现一个run方法
 - (void) run{
    [self jump];
 	NSLog (@"正在执行run方法");
 }
 @end

2.在局部变量和成员变量重名时,局部变量会隐藏成员变量。为了强行引用成员变量,也可以使用self关键字进行区分。
3.当self作为对象或类本身的默认引用使用的时候,程序可以像访问普通指针变量一样访问这个self引用。

5.id类型

id类型可以代表所有对象的类型,任意对象可赋值给id类型的变量。

二、方法详讲

方法是类或对象行为特征的抽象。从功能上看,方法完全类似于传统结构化设计里的函数。但是方法不能独立存在,在逻辑上要么属于类,要么属于对象。

1.方法的所属性

1.方法不能独立定义,只能在类体内定义
2.从逻辑意义上,方法要么属于该类本身,要么属于该类的一个对象。
3.不能独立调用方法,调用方法需要使用类或对象作为调用者。使用+只能用类作为调用者,使用-只能用该类中的实例作为调用者。

2.形参可变的方法

如果在定义方法时在最后的形参名后加上逗号和三个点“,…”表明该形参可接受多个参数值。
下面定义一个形参个数可变的方法。
Varargs.h

#import <Foundation/Foundation.h>

@interface VarArgs : NSObject
- (void) test : (NSString*) name, ...;
@end

在这里插入图片描述
Varargs.m

@implementation VarArgs

- (void)test:(NSString *)name, ... {
    va_list arglist;
    if (name) {
    NSLog (@"%@", name);
    }
    va_start(arglist, name);
    for (NSString *arg = name; arg != nil; arg = va_arg(arglist, id)) {
        NSLog(@"%@", arg);
    }
    va_end(args);
}
@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        VarArgs *varArgs = [[VarArgs alloc] init];
        // 调用可变参数方法并传入多个参数
        [varArgs test:@"Hello", @"World", @"!", nil];
    }
    return 0;
}

三.成员变量

1.成员变量及其运行机制

成员变量指在类接口部分或类实现部分定义的变量。需要注意的是OC中的成员变量都是实例变量。实例变量从实例被创建开始存在,直至系统完全销毁这个实例。实例变量可理解为实例成员变量,它作为实例的一个成员,与实例共存亡。其使用语法如下:

实例->实例变量

示例代码如下:
在这里插入图片描述
成员变量无需显式初始化,只要为一个类定义了实例变量,系统会为实例变量执行默认初始化。基本类型的实例变量默认初始化为0,指针类型的成员变量默认初始化为nil。

2.模拟类变量

OC不支持类似于Java的类变量,但是如果需要使用它可以通过内部全局变量来模拟类变量。为了模拟类变量,我们在实现部分定义一个static修饰的全局变量,并提供一个类方法来暴露全局变量。需要注意的是static关键字只能修饰局部变量、全局变量和函数,修饰局部变量表示将该局部变量存储到静态存储区,修饰全局变量用于限制全局变量只能在当前源文件中被调用

3.单例(singleton)模式

如果一个类始终只能创建一个实例,则这个类被称为单例子类。单例类可通过static全局变量来实现,此处考虑定义一个static全局变量,该变量用于保存已创建的Singleton对象,每次程序获取该实例时,程序会先判断static全局变量是否为nil,如果为nil则初始化一个实例并赋值给全局变量。

四、隐藏和封装

1.理解封装

封装是面向对象的三大特征之一,另外两个是继承与多态。它是指将成员变量隐藏在对象内部,不许外部程序直接访问,而是通过该类所提供的方法对内部信息进行操作与访问。
对一个类或对象实现良好的封装,可实现以下目的:

1.隐藏类的实现细节
2.让使用者只能通过预先设定好的方法对数据进行访问,从而可以在该方法中加入控制逻辑,限制对成员变量的不合理访问。
3.进行数据检查
4.便于修改 5将对象的成员变量以及实现细节隐藏起来,不允许外部直接访问。 6暴露方法,让方法来控制对这些成员变量进行安全的访问与操作。

2.使用访问控制符

OC提供了四个访问控制符:@private、@package、@protected、@public,分别代表四个访问控制级别。
在这里插入图片描述
1.private(当前类访问权限)
被该访问控制符限制的成员变量只能在当前类的内部被访问,用于彻底隐藏成员变量。在类的实现部分定义的成员变量默认使用这种访问权限。

2.package(与映像访问权限相同)
被该访问控制符限制的成员变量可以在当前类以及当前类实现的同一个映像的任意地方进行访问。用于部分隐藏成员变量。

3.protected(子类访问权限)
被该访问控制符限制的成员变量可以在当前类,当前类的子类的任意地方进行访问。用于部分隐藏成员变量。类的接口部分定义的成员变量默认这个访问权限。

4.public(公共访问权限)
被该访问控制符限制的成员变量可以在任意地方进行访问。

关于访问控制符的使用,存在如下几条基本原则:

1.类的绝大部分成员变量都应该用@private限制,另外,有些方法只用于辅助实现该类的其他方法,也应该隐藏在该类的内部。
2.如果某个类主要作为其他类的父类并且希望被子类访问其中的成员变量则可考虑用@protected。
3.暴露出来给其他类自由调用的方法应该先在类接口部分定义再在实现部分实现。

3.理解@package访问控制符

@package让那些受他控制的成员变量不仅可在当前类访问,也可以在同一映像的其它程序中访问。那么同一映像是如何定义的呢?
同一映像简单来说就是编译后生成的同一个框架或同一个执行文件。当我们想开发一个基础框架时,如果用@private则限制的太死了,其他函数可能需要直接访问这个成员变量,但是该框架又不希望外部程序访问我们的成员变量,此时我们可以考虑用@package。
示例代码如下:
FKApple.h

#import <Foundation/Foundation.h>

@interface FKApple : NSObject {
	//使用@package限制成员变量
	@package
	double _weight;
}
@end

FKApple.m

#import "FKApple.h"
int main (int argc, char* argv[]) {
	@autoreleasepool {
		FKApple* apple = [ [FKApple alloc] init];
		//下面代码直接访问@package限制的成员变量
		apple->_weight = 30.4;
		NSLog (@"apple 的重量为:%g", apple->_weight);
	}
}

4.合成存取方法

前面介绍了成员变量为自己实现setter与getter方法,这种做法虽然不难,但是当一个类有多个成员变量时自己实现setter和getter方法就会是重复且枯燥的事情。从OC2.0开始,它自动合成了setter和getter方法。

让系统自动合成letter和getter方法需要两步:

1.在类接口部分使用@property指令定义我们的属性,我们无需将其放在花括号中,只需将其放在我们的@interface与@end的中间。
2.如果程序需要该百年getter和setter方法对应的成员变量的变量名,在类实现部分使用@synthesize声明该属性即可。
其语法格式如下:
@synthesize property 名[ = 成员变量名];

使用上述两个步骤合成存取方法后,不仅会合成成对的方法,还会在类实现部分定义一个与getter方法同名的成员变量。如果为某个类定义了一个成员变量,并提供了相应的getter与setter方法,那么可称定义了一个属性。

当我们用@property定义prorerty时,我们可以在@property和类型之间添加一些特殊的指示符。例如:
assgin:只是进行简单的赋值不会改变对所赋的值的引用计数,那么何为引用计数呢?引用次数时OC中内存回收的概念,当一个对象的引用计数大于零时这个对象就不会被系统回收。由于assign用于的都是基本类型和各种C数据类型因此不涉及内存回收的问题。
atomic(nonatomic):指定合成的存取方法是否为线程安全。如果使用了atomic,那么合成存取的方法就是线程安全的,当一个线程进入存取的方法体之后,其它线程就无法进入该方法,避免了多线程并发破坏对象的数据完整性。在大多数单线程的环境下,我们一般会用nonatomic来提高存取方法的访问性能。
copy:在调用setter方法给成员变量赋值时,会将被赋值的对象生成一个副本,然我将该副本赋值给成员变量。
readonly、readwrite : readonly只合成getter方法不合成setter方法,readwrite两个都合成。
retain :使用retain定义属性时,当把某个对象赋给该属性,被赋值对象计数加一,该属性原来引用的对象计数减一。
strong(强引用)、weak(弱引用) :被strong指向的对象不会被回收,被weak指向的对象即使被引用也会被回收。

五.对象初始化

每次创建对象时总需要使用alloc方法为对象分配内存空间。

1.为对象分配空间

当程序调用某个类的alloc类方法时,系统完成如下事情:

1.系统为我们的所有实例变量分配内存空间
2.将每个实例变量的内存空间都置为0。
仅仅分配内存空间的对象还不能使用,还需要执行初始化,也就是init才可以使用它。

2.初始化方法与对象初始化

系统初始化会将每个实例变量的内存空间都置0,为了满足实际需求,在实际编程中可以自定义初始化,也就相当于重写NSObject的init方法。

六.类的继承

1.继承的特点

继承是面向对象的三大特征之一,也是实现软件复用的重要手段。实现继承的类叫子类,被继承的类叫父类。子类是一种特殊的父类,因此父类包含的范围总比子类包含的范围要大,因此可以认为父类是大类,子类是小类。OC的继承具有单继承的特点也就是说每个父类只有一个直接父类,需要注意的是只能有一个直接父类但是可以有多个间接父类,例如生物是动物的直接父类,动物是人类的直接父类,人类是中国人的直接父类,生物是中国人的间接父类也是人类的间接父类。
OC中子类继承父类的语法格式如下:

@interface SubClass : SuperClass {
	//成员变量定义
}
//方法定义部分
@end

2.重写父类的方法

大部分时候,子类总是以父类为基础,额外增加新的成员变量和方法。但是有一种情况除外,那就是重写父类的方法。
下面提供一个示例代码:
FKDingzhen.h

#import <Foundation/Foundation.h>

@interface FKDingzhen : NSObject
- (void) smoke;
- @end

FKDingzhen.m

#import <Foundation/Foundation.h>
#import "FKDingzhen.h"

@implementation FKDingzhen
//FKDingzhen类的smoke方法
- (void) smoke {
	NSLog (@"丁真只抽电子烟");
}
@end

下面再定义一个FKSmoke类,这个类扩展了FKDingzhen类,重写了FKDingzhen类的smoke方法。
示例代码如下:
FKSmoke.h

#import <Foundation/Foundation.h>
#import "FKDingzhen.h"
@interface FKSmoke : FKDingzhen 
@end

当子类重写父类时,子类接口部分不需要重新声明要重写的方法。
FKSmoke.m

#import <Foundation/Foundation.h>
#import "FKSmoke.h"

@implementation FKSmoke 
//重写父类的smoke方法
- (void) smoke {
	NSLog (@"丁真只抽瑞克五代");
}
@end

int main (int argc, char* argv[]) {
	@autoreleasepool {
		//创建FKSmoke对象
		FKSmoke* dz = [ [FKSmoke alloc] init];
		//执行FKSmoke的smoke方法
		[dz smoke];
	}
}

3.super关键字

super关键字可以用来在子类方法中调用父类被覆盖的实例方法。super用于限定该对象调用它从父类继承得到的属性或方法。在类方法中当使用super调用父类的方法时,被调用的父类方法只能是类方法,在实例方法中只能是实例方法。当子类继承父类时,子类可以获得父类中定义的成员变量,因此子类接口部分不允许定义与父类接口重名的成员变量。但是父类在实现部分定义的成员变量对子类没有任何影响。同时反过来也一样。

七.多态

OC中指针类型有两种,一个是编译时类型,一个是运行时类型。编译时类型由声明该变量时使用的类型决定,运行时类型由实际赋给该变量的对象决定。如果编译时类型和运行时的类型不一致,就可能出现多态。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值