OC语法之-OC三大特性
面向对象的三大特性:封装/继承/多态
封装
- 又叫做成员变量的封装,也就是说是针对成员变量的
- 为什么要对成员变量进行封装?
为了防止像成员变量传入不合理的值,比如age这个成员变量,p->age = -10;
这就不合理 - @public作用
- 使一个对象在不是当前类的实现@implementation中(即外部)能够访问自己的成员变量(p->age),默认的情况下是不能访问,受保护的(@protected);
- 注意:一个类是可以直接在其实现内部(@implementation –@end)访问它的成员变量的,跟@public无关!!!
#import <Foundation/Foundation.h> @interface Person : NSObject { //是该成员变量能被外界(即不是在当前类中时!!!)访问 @public int age; } - (void)walk; @end // 2.类的实现 @implementation Person // 可以直接使用当前类的成员变量的!!!,因为这是在当前类的实现中 - (void)walk { NSLog(@"%d岁的人走了一段路", age; } @end //这里已经不是Person类了,属于外界了 int main() { Person *p2 = [Person new]; //没有@public是不可以直接访问该对象的成员变量的!!! p2->age = 30; [p2 walk]; return 0; }
set方法和get方法
- set方法和get方法的使用场合
- @public的成员可以被随意赋值,应该使用set方法和get方法来管理成员的访问(类似机场的安检、水龙头过滤,过滤掉不合理的东西),比如僵尸的生命值不能为负数
- set方法
- 作用: 提供一个方法给外界设置成员变量值,可以在方法里面对参数进行相应过滤
- 命名规范:
- 方法名必须以set开头
- set后面跟上成员变量的名称,成员变量的首字母必须大写
- 返回值一定是void
- 一定要接收一个参数,而且参数类型跟成员变量类型一致
- 形参的名称不能跟成员变量名一样!!!(注意哦)
- get方法
- 作用:返回对象内部的成员变量
- 命名规范:
- 肯定有返回值,返回值类型肯定与成员变量类型一致
- 方法名跟成员变量名一样
- 不需要接收任何参数
-
代码举例:
#import <Foundation/Foundation.h> @interface Student : NSObject { // 成员变量尽量不要用@public // @public int age; // 只读(readonly):只允许外界访问我的no,不允许外界修改我的no // 只需要提供get方法 int no; } // set方法 - (void)setAge:(int)newAge; // get方法 - (int)age; - (void)study; @end @implementation Student // set方法的实现 - (void)setAge:(int)newAge { // 对传进来的参数进行过滤 if (newAge <= 0) { newAge = 1; } //如果newAge也用age,那么就会混淆了 age = newAge; } - (int)age { return age; } - (void)study { NSLog(@"%d岁的学生在学习", age); } @end int main() { Student *stu = [Student new]; //stu->age = -10; //stu->age = 10; [stu setAge:10]; NSLog(@"学生的年龄是%d岁", [stu age]); //[stu study]; return 0; }
成员变量/局部变量/全局变量命名规范
- 成员变量的命名规范
_age
- 一定要以下划线
_
开头 - 让成员变量和get方法的名称区分开
- 可以跟局部变量区分开,一看到下划线开头的变量,一般都是成员变量
- 一定要以下划线
- 全局变量通常这样命名:
age_
- 局部变量:
age
#import <Foundation/Foundation.h>
@interface Student : NSObject
{
//成员变量
int _no;
}
// no的set和get方法
- (void)setNo:(int)no;
- (int)no;
@end
@implementation Student
- (void)setNo:(int)no {
_no = no;
}
//get方法与成员变量区别
- (int)no {
return _no;
}
@end
int main() {
Student *stu = [Student new];
[stu setNo:10];
[stu no];
return 0;
}
类方法
- 直接可以用类名来执行的方法(类本身会在内存中占据存储空间,里面有类\对象方法列表)
- 类方法和对象方法对比:
- 类方法:
- 加号 + 开头
- 只能由类(名)来调用
- 类方法中不能访问成员变量(实例变量)
- 对象方法
- 减号 - 开头
- 只能由对象来调用
- 对象方法中能访问当前对象的成员变量(实例变量)
- 类方法:
- 类方法的好处和使用场合
- 不依赖于对象,执行效率高
- 能用类方法,尽量用类方法
- 场合:当方法内部不需要使用到成员变量时,就可以改为类方法
- 注意: 可以允许类方法和对象方法同名
-
代码举例:
#import <Foundation/Foundation.h> @interface Person : NSObject { int age; } // 类方法都是以+开头 + (void)printClassName; - (void)test; + (void)test; @end @implementation Person + (void)printClassName { // error:instance variable 'age' accessed in class method // 实例变量age不能在类方法中访问 //NSLog(@"这个类叫做Person-%d", age); } - (void)test { NSLog(@"111-%d", age); //[Person test]; } + (void)test { // 会引发死循环 //[Person test]; NSLog(@"333"); } @end int main() { //类方法调用 [Person printClassName]; [Person test]; //对象方法调用 Person *p = [Person new]; [p test]; //对象误调用类方法 /* -[Person printClassName]: unrecognized selector sent to instance 0x7fa520c0b370 */ // 系统会认为现在调用的printClassName是个对象方法 //[p printClassName]; return 0; }
self关键字
- 一句话总结:self始终指向当前方法的调用者
- 谁调用了当前方法,self就代表谁
- self出现在对象方法中,self就代表对象
- self出现在类方法中,self就代表类
- 在对象方法利用”self->成员变量名”访问当前对象内部的成员变量,可以用于区分同名的局部变量
[self 方法名]
可以调用其他对象方法\类方法- 代码举例:
#import <Foundation/Foundation.h>
@interface Dog : NSObject
{
int _age;
}
- (void)bark;
- (void)run;
+ (void)run;
- (void)test;
+ (void)test;
@end
@implementation Dog
- (void)bark {
NSLog(@"汪汪汪");
}
- (void)run {
[self bark];
//此时的self是当前对象
//调用的是对象方法-test
[self test];
NSLog(@"跑跑跑");
}
+ (void)run {
//调用的是对象方法+test,此时的self是当前类
[self test];
NSLog(@"调用了+run方法");
}
- (void)test {
// 会引发死循环 此时的self是当前对象
//[self test];
NSLog(@"调用了-test方法");
//成员变量和局部变量同名时,默认采取就近原则,访问的是局部变量,但是我非要访问成员变量
//用self访问成员变量,区分同名的局部变量
int _age = 20;
// self:指向了方法调用者,代表着当前对象.直接写_age,那么指的就是局部变量_age(20)了
NSLog(@"Dog的年龄是%d岁", self->_age);
}
+ (void)test {
NSLog(@"调用了+test方法");
// 会引发死循环 ,此时的self是当前类
//[self test];
}
@end
int main() {
Dog *d = [Dog new];
[d run];
return 0;
}
继承
- 继承的好处
- 抽取重复代码
- 建立了类之间的关系
- 子类可以拥有父类中的所有成员变量和方法
- 坏处
- 耦合性太强
- 重写:
- 子类重新实现父类中的某个方法,覆盖父类以前的做法
- 注意
- 父类必须声明在子类的前面
- 子类不能拥有和父类相同的成员变量
- 调用某个方法时,优先去当前类中找,如果找不到,去父类中找
- 只能单继承,即只能继承一个父类,不能多重继承
- 基本上所有类的根类是NSObject,里面包含了很多基本方法(+new…等)
- 继承的使用场合
- 当两个类拥有相同属性和方法的时候,就可以将相同的东西抽取到一个父类中
- 当A类完全拥有B类中的部分属性和方法时,可以考虑让B类继承A类
- 继承与组合
- 概念:让A类成为B类的成员变量,这样B类仍然可以拥有A类的成员变量跟方法
- 继承与组合的区别:
1.继承 A { int _age; int _no; } B : A { int _weight; } // 继承:xx 是 xxx // 组合:xxx 拥有 xxx 2.组合 A { int _age; int _no; } B { A *_a; int _weight; }
- 类与父类之间的关系
- 每个对象内部都有一个isa指针指向所属类,每个类里面又有一个superclass指针指向其父类
- isa 是nsobject的成员变量
- 如图:
-
继承代码举例:
#import <Foundation/Foundation.h> // 父类:Person @interface Person : NSObject { int _age; } - (void)setAge:(int)age; - (int)age; - (void)run; + (void)test; @end @implementation Person + (void)test { NSLog(@"Person+test"); } - (void)run { NSLog(@"person---跑"); } - (void)setAge:(int)age { _age = age; } - (int)age { return _age; } @end // 不允许子类和父类拥有相同名称的成员变量 // 子类:Student @interface Student : Person { int _no; // int _age; } + (void)test2; @end @implementation Student // 重写:子类重新实现父类中的某个方法,覆盖父类以前的做法 - (void)run { NSLog(@"student---跑"); } + (void)test2 { //当前类方法中没有,就去父类中找 [self test]; } @end int main() { [Student test2]; Student *s = [Student new]; [s run]; return 0; }
-
组合代码举例:
@interface Score : NSObject { int _cScore; int _ocScore; } @end @implementation Score @end @interface Student : NSObject { // 组合 Score *_score; // int _cScore; // int _ocScore; int _age; } @end @implementation Student @end
super关键字
- 一句话总结:super始终指向当前方法调用者的父类(类方法中)/父类对象(对象方法中)
- 作用:
- 直接调用父类中的某个方法
- super处在对象方法中,那么就会调用父类的对象方法
- super处在类方法中,那么就会调用父类的类方法
- 使用场合:子类重写父类的方法时想保留父类的一些行为
-
代码举例
#import <Foundation/Foundation.h> // 僵尸 @interface Zoombie : NSObject - (void)walk; + (void)test; - (void)test; @end @implementation Zoombie - (void)walk { NSLog(@"往前挪两步******"); } + (void)test { NSLog(@"Zoombie+test"); } - (void)test { NSLog(@"Zoombie-test"); } @end // 跳跃僵尸 @interface JumpZoombie : Zoombie + (void)haha; - (void)haha2; @end @implementation JumpZoombie + (void)haha { //调用父类的+test [super test]; } - (void)haha2 { //调用父类的-test方法 [super test]; } - (void)walk { // 跳两下 NSLog(@"跳两下"); // 走两下(直接调用父类的walk方法) [super walk]; //NSLog(@"往前挪两步----"); } @end int main() { [JumpZoombie haha]; JumpZoombie *jz = [JumpZoombie new]; [jz haha2]; return 0; }
多态
- 没有继承就没有多态
- 代码的体现:父类类型的指针指向子类对象
- 好处:如果函数\方法参数中使用的是父类类型,可以传入父类、子类对象
- 局限性:
- 父类类型的变量 不能 直接调用子类特有的方法。必须强转为子类类型变量后,才能直接调用子类特有的方法
-
代码举例:
// 动物 @interface Animal : NSObject - (void)eat; @end @implementation Animal - (void)eat { NSLog(@"Animal-吃东西----"); } @end // 狗 @interface Dog : Animal - (void)run; @end @implementation Dog - (void)run { NSLog(@"Dog---跑起来"); } - (void)eat { NSLog(@"Dog-吃东西----"); } @end // 猫 @interface Cat : Animal @end @implementation Cat - (void)eat { NSLog(@"Cat-吃东西----"); } @end // 如果参数中使用的是父类类型,可以传入父类、子类对象 void feed(Animal *a) { [a eat]; } int main() { //场合1 // 多种形态 Dog *d = [Dog new]; // Dog类型 // 多态:父类指针指向子类对象 Animal *a = [Dog new]; // 调用方法时会检测对象的真实形象,调用Dog的eat [a eat]; //场合2 Animal *aa = [Dog new]; // 多态的局限性:父类类型的变量 不能 用来调用子类的方法 // [aa run]; // 将aa转为Dog *类型的变量 Dog *dd = (Dog *)aa; [dd run]; //场合3 Animal *aa2 = [Animal new]; //调用Animal的 feed(aa2); //调用Dog的 Dog *dd = [Dog new]; feed(dd); //调用Cat的 Cat *cc = [Cat new]; feed(cc); return 0; }