一、面向对象
OC语言是面向对象的,C语言是面向过程的,面向对象和面向过程知识解决问题的两种思考方式,面向过程关注的是解决问题涉及到的步骤,而面向对象关注的是设计能够实现解决问题所需功能的类。
术语:OC面向对象 OPP面向对象编程
二、类
(一)关于类
1>类是一个抽象的概念,而对象是一个真实存在的物体,一个对象包括两部分:属性跟方法
2>类的设计最重要的三点:类名、 属性 、方法
注意:一般名词都是类,拥有相同属性和行为的对象都可以抽象为一个类,类名是标识符的一种,需要符合规范,通常类名的第一个字母大写,且不能有下划线,如果由多个单词组成则使用驼峰法则。在OC中,对象对方法的调用称为消息机制,即向既定的对象发送了什么消息。
(二)简单的内存分析
1>类创建对象,每个对象在内存中都占据一定的存储空间,每个对象都有一份属于自己的单独的成员变量(属性或实例变量),所有的对象共用类的成员的方法,方法在整个内存中只有一份,类本身在系统中占据一份内存空间,类的方法存在于此。
2>每个对象内部都默认有一个isa指针来指向这个对象所使用的类。
3>[person(对象名) run(对象方法)] 表示:给person所指向的对象发送一条run消息,调用对象的run方法,此时对象会顺着内部的isa指针找到存储于类中的方法并执行。
4>isa是对象中隐藏的指针,指向创建这个对象的类。
(三)类的声明和实现
1> 接口部分:@interface 声明了类于父类 以@interface编译指令为开始,以@end结束
实现部分:@implementation ... @end
2>类方法以+开头,如+(void) run;
类的声明1:
#import <Foundation/Foundation.h> @interface Student : NSObject{ /** 学号*/ NSInteger _no; //勿忽略下划线 /** 姓名*/ NSString * _name; /** 成绩*/ CGFloat _score; } //*********set get 方法************* /** * 学号set get方法 * * @param no 学号 */ -(void) setNo:(NSInteger) no; -(NSInteger) no; /** * 姓名方法 * * @param name 姓名 */ -(void) setName:(NSString *) name; -(NSString *) name; /** * 成绩方法 * * @param score 成绩 */ -(void) setScore:(CGFloat) score; -(CGFloat) score; //*************其他方法*************** /** * 查询成绩方法 */ -(void) showScore; /** * 补考方法 */ -(void) reExam; @end
以上声明可以用@property简化如下:
#import <Foundation/Foundation.h> @interface Student : NSObject /** 学号*/ @property NSInteger no; //无需加下划线 /**姓名*/ @property NSString * name; /** 成绩*/ @property CGFloat score; //*************其他方法*************** /** * 查询成绩方法 */ -(void) showScore; /** * 补考方法 */ -(void) reExam; @end
类的实现1:
#import "Student.h" @implementation Student #pragma mark - 学号 -(void) setNo:(NSInteger) no{ _no=no; } -(NSInteger) no{ return _no; } #pragma mark - 姓名 -(void) setName:(NSString *) name{ _name=name; } -(NSString *) name{ return _name; } #pragma mark - 成绩 -(void) setScore:(CGFloat) score{ _score=score; } -(CGFloat) score{ return _score; } //*************其他方法*************** #pragma mark - 查询成绩 -(void) showScore{ NSLog(@"成绩:%g分",_score); } #pragma mark - 补考 -(void) reExam{ if (_score<60) { NSLog(@"补考"); }else{ NSLog(@"无需补考"); } } @end
类的调用:在主函数创建一个Student类型的对象(先调用alloc分配存储空间,然后调用init方法初始化为0),并定义了一个Student类型的指针指向创建的这个对象,之后用.语法初始化对象变量score的值为59,然手调用对象的reExam方法.
#import <Foundation/Foundation.h> #import "Student.h" int main(int argc, const char * argv[]) { @autoreleasepool { //初始化并赋值 Student * stu=[[Student alloc] init]; stu.score=59; //调用方法 [stu reExam]; } return 0; }
练习题
说明:
属性: 坐席数量 剩余数量 票据信息,其中票据信息又包括电影名称 电影类型 开始时间 价格,实现当前上映电影的简介以及卖票,退票功能
解析:票据信息Ticket需要新建一个类,并且将套入到Cinema类中
Cinema类的声明
#import <Foundation/Foundation.h> #import "Ticket.h" @interface Cinema : NSObject /** 坐席数量*/ @property NSInteger seatNo; /** 剩余数量*/ @property NSInteger leftNo; /** 票据信息*/ @property Ticket * ticket; //************其他方法************ /** * 卖票 */ -(void) sellTickets; /** * 退票 */ -(void) returnTickets; @end
Ticket类的声明
#import <Foundation/Foundation.h> @interface Ticket : NSObject /** 电影名称*/ @property NSString * name; /** 电影类型*/ @property NSString * type; /** 开始时间*/ @property NSString * startTime; /** 价格*/ @property CGFloat price; //***************其他方法************ /** * 展示电影简介 */ -(void) showFilm; @end
Cinema类的实现
#import "Cinema.h" @implementation Cinema #pragma mark - 卖票 -(void) sellTickets{ //嵌套方法 [_ticket showFilm]; //卖票 _leftNo-=1; NSLog(@"现有%ld张票,成功卖票1张,还剩%ld张",_seatNo,_leftNo); } #pragma mark - 退票 -(void) returnTickets{ //嵌套方法 [_ticket showFilm]; //退票 _leftNo+=1; NSLog(@"现有%ld张票,成功退票1张,还剩%ld张",_seatNo,_leftNo); } @end
Ticket类的实现
#import "Ticket.h" @implementation Ticket #pragma mark - 展示电影简介 -(void) showFilm{ NSLog(@" 电影名称:%@ 电影类型:%@ 开始时间:%@ 价格:%g",_name,_type,_startTime,_price); } @end
调用
#import <Foundation/Foundation.h> #import "Cinema.h" #import "Ticket.h" int main(int argc, const char * argv[]) { @autoreleasepool { /* //初始化并赋值 Student * stu=[[Student alloc] init]; stu.score=59; //调用方法 [stu reExam]; */ //初始化并赋值 Cinema * cinema=[[Cinema alloc] init]; cinema.seatNo=50; cinema.leftNo=50; Ticket * ticket=[[Ticket alloc] init]; ticket.name=@"极限挑战"; ticket.type=@"娱乐搞笑类"; ticket.startTime=@"20:35"; ticket.price=65.5; //调用 [cinema sellTickets]; [cinema returnTickets]; } return 0; }
(四)常见错误
1>@interface @end 和 @implementation @end不能嵌套包含
2>只有类的声明,却没有类的实现
3>漏写@end
4>两个类的声明嵌套(可以把顺序打乱)
5>成员变量没有写在{}内
6>方法的声明写在了{}内
7>在对类的成员变量声明的同时就给初始化了,请注意成员变量不能脱离对象而独立存在
8>方法无法像函数那样调用
9>成员变量和方法不能用static等关键字修饰,不要跟C语言混淆
10>类的实现可以写在main函数后面,在使用之前声明即可
4>屏幕输出:NSLog(@“hello”);//相比C语言的printf会增加时间跟进程信息,并且会自动换行
三、OC对象与函数
OC对象与函数有着本质的区别:
1>方法的实现只能写在@implementation ... @end中,对象方法的声明只能写在@interface ... @end内
2>对象方法都已-开头,类方法都以+开头
3>对象方法只能由对象来调用,类方法只能由类来调用,不能当做函数一样调用
4>函数属于整个文件,可以写在文件中的任何位置,包括@implementation ... @end中,但写在@interface ...@end内会无法识别,函数的声明可以在main函数的内部,也可以在main函数的外部
5>函数调用不依赖与对象
6>函数内部不能直接通过成员变量名访问对象的成员变量
四、类和方法的设计
1>工具类:基本没有任何成员变量,里面的方法基本都是类方法
2>类方法以+开头,如+(void) run;
3>类方法只能由类来调用
4>调用格式:[类名 类方法名],直接在主程序中调用,无需像对象方法那样先初始化
5>类方法的好处跟适用场合:
不依赖与对象,执行效率更高
能用类方法解决的问题,尽量使用类方法
场合:当方法内部不需要使用到成员变量时,可以改为类方法
练习题
要求:设计一个工具类:一个计算器类,要求1>返回π 2>计算两个整数的和 3>计算某个整数的平法
计算器类的声明
#import <Foundation/Foundation.h> @interface Calculator : NSObject /** * π的类方法 * * @return π的值 */ +(CGFloat) pi; /** * 两个整数的和的类方法 * * @param num1 第一个整数 * @param num2 第二个整数 * * @return 返回两个整数的和 */ +(NSInteger) sumOfNumber1:(NSInteger) num1 andNumber2:(NSInteger) num2; /** * 计算一个整数的平方 * * @param no 一个整数 * * @return 返回这个整数的平方 */ +(NSInteger) square:(NSInteger) no; @end
计算器类的实现
#import "Calculator.h" @implementation Calculator #pragma mark - π +(CGFloat) pi{ return 3.14; } #pragma mark - 计算两个整数的和 +(NSInteger) sumOfNumber1:(NSInteger) num1andNumber2:(NSInteger) num2{ return num1+num2; } #pragma mark - 计算一个整数的平方 +(NSInteger) square:(NSInteger) no{ return no*no; } @end
主程序:直接使用类名调用类方法
#import <Foundation/Foundation.h> #import "Calculator.h" int main(int argc, const char * argv[]) { @autoreleasepool { /* //初始化并赋值 Student * stu=[[Student alloc] init]; stu.score=59; //调用方法 [stu reExam]; */ /* //初始化并赋值 Cinema * cinema=[[Cinema alloc] init]; cinema.seatNo=50; cinema.leftNo=50; Ticket * ticket=[[Ticket alloc] init]; ticket.name=@"极限挑战"; ticket.type=@"娱乐搞笑类"; ticket.startTime=@"20:35"; ticket.price=65.5; //调用 [cinema sellTickets]; [cinema returnTickets]; */ //类方法直接用类名调用 NSInteger result=[Calculator sumOfNumber1:5 andNumber2:6]; NSLog(@"%ld",result); NSInteger result2=[Calculator square:6]; NSLog(@"%ld",result2); CGFloat result3=[Calculator pi]; NSLog(@"%g",result3); } return 0; }
注意:
1>可以允许类方法跟对象方法同名
2>在对象方法中可以调用类方法
五、方法名
1>不带参数的方法
声明:
调用:
2>带参数的方法
声明:
调用:
注意:冒号也是方法名的一部分
六、文件编译
在工作中,通常把不同类放到不同的文件中,每个类的声明和实现分开,声明写在.h头文件中,实现写在相应的.m文件中,类名是什么,文件名的前缀就是什么.
假设有两个类,分别是Person类和Dog类,则通常有以下五个文件:
1>Person.h Person类的声明文件
2>Person.m Person类的实现文件
3>Dog.h Dog类的声明文件
4>Dog.m Dog类的实现文件
5>Main.m 主函数(程序入口)
在主函数以及类的声明文件中要使用#import包含相应的头文件
补充:import的两个作用:一是和include一样,完全拷贝文件的内容;二是可以自动防止文件内容的重复拷贝(即使文件被多次包含,也只拷贝一份)
在使用命令行进行编译链接文件的时候,通常把.m文件单文件编译,然后再把多有的目标文件链接,但是在Xcode中,是把所有的.m文件都进行编译链接的,若果出现重复定义的错误,那大部分的问题根源应该就是文件内容被重复包含或者是包含.m文件所引起的.
源文件中不论是使用import还是include,都不能包含.m或者.c文件,只能放声明,因此,在OC中通常把类拆分开来,拆分成声明和实现两个部分
提示:这也是编程思想的一种体现,可以说.h跟.m文件是完全独立的,知识为了追求更好的可读性,才要求两个文件的文件名一致,这也是把接口和实现分离,让调用者不必去关心具体的实现细节.
Xcode的写一行编译一行,有简单的修复功能,红色是错误提示,黄色的警告信息,如果在主程序中声明了一个变量,但是这个变量没有被使用,此时也会产生警告信息,在调试程序的时候,如果发现整个页面都没报错,但是一运行就报错,那么一定死链接报错.