《Objective-c》-(三大特性:封装、继承、多态)
一、封装(set方法和get方法)
-
封装的好处:
过滤掉不合理的值,屏蔽内部的赋值细节,让外界不比关心内部的细节。
-
set方法:
1.作用:提供一个方法给外界设置成员变量的值
2.命名规范:
1> 方法名必须是set开头
2> set后面跟上成员变量的名称,并且成员变量的首字母必须是大写
3> 返回值一定是void
4> 一定要接受一个参数,而且参数类型跟成员变量的类型一致
5> 形参名称不能和成员变量名一样
例:
- (void)setAge : (int) newAge; // 方法声明
- (void)setAge : (int) newAge // 方法实现
{
if (newAge <= 0)
{
newAge = 1; // 对传入的参数进行过滤
}
age = newAge;
}
-
get方法:
1.作用:返回对象内部的成员变量值
2.命名规范:
1> 肯定有返回值,并且返回值类型肯定和成员变量类型一致
2> 方法名和成员变量名一样
3> 不需要接受任何参数
例:
- (int) age; // 方法的声明
- (int) age // 方法的实现
{
return age;
}
-
成员变量的命名规范
成员变量的命名规范:一定要以下划线“_”开头;
作用:1.让成员变量和get方法的名称区分开
2.可以跟局部变量区分开,一看到下划线开头的变量,一般都是成员变量
代码练习:
#import <Foundation/Foundation.h>
@interface Student : NSObject
{
// 成员变量尽量不要用@public保证数据的安全性,没有@public就不能通过 对象->成员变量 来赋值(访问),这是可以提供一个set方法给外界设置成员变量的值(虽然没有了@public,但是在对象方法内还是能直接访问成员变量的)
// @public
int age;
}
- (void)setAge:(int)newAge;
- (int)age;
- (void)study;
@end
@implementation Student
// set方法的实现
- (void)setAge:(int)newAge
{
// 对传进来的参数进行相应的过滤
if(newAge <= 0)
{
newAge = 1;
}
age = newAge;
}
// get方法的实现
- (int)age
{
return age;
}
- (void)study
{
NSLog(@"%d岁的学生在学习", age); // 这里的使用age即使在声明成员变量时没有写@public也可以直接访问(对象方法内部可以直接访问成员变量)
}
@end
int main()
{
Student *stu = [Student new];
// 通过调用set方法对对象内部的成员变量进行赋值
[stu setAge:-10];
[stu study];
// 由于没有了@public这是调用get方法获取对象内部的成员变量值
NSLog(@"这个学生的年龄是%d", [stu age]);
return 0;
}
二、继承
1.继承的好处:
-
抽取重复代码
-
建立了类与类之间的关系
-
子类拥有了父类中的所有成员变量和方法
2.继承的坏处:
-
耦合性太强,子类与父类必须相互依存(如果将父类的代码 删除了,子类也不能正常使用);
3.继承注意点:
-
基本所有类的根类是NSObject;
-
父类的声明必须写在子类前面,实现无所谓;
-
不允许子类有和父类相同的成员变量;
-
调用某个方法时,优先在当前类中找,找不到就会去父类中找(内存分析见下图);
-
允许子类中有和父类相同的方法,即:重写(子类重新实现父类中的某个方法,这是在子类中调用该方法就会优先执行子类中重新实现的方法,覆盖父类以前的方法);
4.继承的使用场合:
-
当两个类拥有相同属性和方法时,就可以将相同的东西抽取到一个父类中;
-
当A类中完全拥有B类中的部分属性和方法时,可以考虑让B类继承A类;
-
注意:1.并不是只要有相同的属性和方法就可以使用继承;
2.使用继承时要考虑这两个类是否存在逻辑上的关系;
3.如果不存在逻辑关系,就用组合;
代码一:
#import <Foundation/Foundation.h> /*********Animal类的声明**********/ @interface Animal : NSObject { int _age; double _weight; } - (void)setAge:(int)age; - (int)age; @end /*********Animal类的实现**********/ @implementation Animal - (void)setAge:(int)age { _age = age; } - (int)age { return _age; } @end // : Animal 代表Dog类继承了Animal类,那么Dog类中就拥有了Animal类中的所有成员变量及方法 // Animal类就是Dog类的父类 // Dog类就是Animal的子类 /*********Dog类的声明**********/ @interface Dog : Animal @end /*********Dog类的实现**********/ @implementation Dog @end int main() { Dog *d = [Dog new]; [d setAge:10]; NSLog(@"age=%d", [d age]); return 0; }
代码二:
#import <Foundation/Foundation.h> @interface Person : NSObject { int _age; } - (void)run; @end @implementation Person - (void)run { NSLog(@"person---跑"); } @end // 注意点1:子类的声明必须写在父类的声明后面 @interface Student :Person { int _no; // 注意点2:不允许在子类中有和父类相同的成员变量 // int _age; // 报错:重复定义_age(error: duplicate member '_age') } // - (void)run; // 这里可以不写,因为在其父类中已经有声明,如果子类实现中有该方法的实现,就是重写(子类重新实现父类中的某个方法) @end @implementation Student // 重写:子类重新实现父类中的某个方法,这是在子类中调用该方法就会优先执行子类中重新实现的方法,覆盖父类以前的方法 - (void)run { NSLog(@"student---跑"); } @end int main() { Student *s = [Student new]; [s run]; // 会现在子类中找run方法,找到了就优先执行当前类中run方法,没有找到就会去其父类中找 return 0; }
代码三:
#import <Foundation/Foundation.h>
@interface Score : NSObject
{
int _cScore;
int _ocScore;
}
@end
@implementation Score
@end
// 继承:xx 是 xxx
// 组合:xx 拥有 xxx
@interface Student :NSObject
{
int _no;
int _age;
Score *_score; // 这里用的就是组合
}
@end
@implementation Student
@end
int main()
{
return 0;
}
三、多态(父类指针指向子类对象)
1.代码体现:父类类型的指针指向子类对象;
2.好处:如果函数、方法形参中使用的是父类类型,就可以传入父类、子类对象;
3.局限性:父类类型的变量不能直接调用子类特有的方法,必须强转为子类类型后才能直接调用子类特有方法
4.代码练习:
#import <Foundation/Foundation.h> @interface Animal : NSObject - (void)eat; @end @implementation Animal - (void)eat { NSLog(@"Animal---吃东西"); } @end @interface Dog : Animal - (void)run; @end @implementation Dog - (void)eat { NSLog(@"Dog---吃东西"); } - (void)run { NSLog(@"狗跑起来了"); } @end @interface Cat : Animal @end @implementation Cat - (void)eat { NSLog(@"Cat---吃东西"); } @end // 喂狗 void feed(Dog *d) { [d eat]; } // 喂猫 void feed2(Cat *c) { [c eat]; } // 由于喂狗和喂猫的代码基本一致,所以考虑将其抽取到一个函数内 // 多态的好处:如果形参中使用的是父类类型,就可以传入父类、子类对象 void feed3(Animal *a) { [a eat]; } int main() { /***************1.多态的基本使用******************/ // 多态:父类指针指向子类对象 Animal *a = [Dog new]; // 调用方法时会动态检测对象的真实类型,这里对象a的真实类型是Dog [a eat]; // 输出:Dog---吃东西 /***************2.OC的弱语法******************/ /* 注意点: OC的弱语法:这里仅仅是一个警告(warning: incompatible pointer types initializing 'Dog *' with an expression of type 'Animal *' [-Wincompatible-pointer-types]) */ // Dog *d = [Animal new]; // 虽然仅仅是一个警告,但是在意思上不合理(动物是狗) // [d eat]; // 输出:Animal---吃东西 /***************3.多态的好处******************/ // 多态的好处:如果形参中使用的是父类类型,就可以传入父类、子类对象 Dog *d2 = [Dog new]; feed(d2); Cat *c2 = [Cat new]; feed2(c2); Dog *d3 = [Dog new]; feed3(d3); Cat *c3 = [Cat new]; feed3(c3); /***************4.多态的局限性******************/ Dog *dd = [Dog new]; // Dog类型 Animal *aa = [Dog new]; // Animal类型 // OC的弱语法: warning: 'Animal' may not respond to 'run' // 对于编译器来讲,编译器认为aa的类型是Animal,这里[aa run]就会去Animal中找是否有run的具体实现,没有找到就会警告,但是
还是会运行成功,因为在运行是会动态检测aa的真实类型(Dog),Dog类中有run方法的具体实现,就能正常运行 // 多态的局限性:父类类型的变量不能用来调用子类特有的方法 // [aa run]; // 对于上面的问题,可以将aa强制转换成Dog类型,编译器就不会警告 Dog *dd = (Dog *)aa; // 将aa强制转换成Dog *类型 [dd run]; return 0; }