⑤--面向对象三大特性:封装、继承、多态

本文详细介绍了面向对象编程的三大特性——封装、继承和多态。封装强调隐藏对象细节,提高安全性和重用性,具体表现为合理设定成员变量权限和提供getter/setter方法。继承允许抽取重复代码,建立类间关系,但也可能导致耦合性增强。多态则是通过父类类型的引用指向子类对象,实现不同对象对同一方法的响应,提高了代码的灵活性和可扩展性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.封装的概念以及优点

封装:隐藏对象的属性和实现细节,仅对外提供访问方式
优点:
  1、将变化隔离
  2、便于使用
  3、提高重用性
  4、提高安全性
封装原则:
  1、将不需要对外提供的内容都隐藏起来。
  2、把属性都隐藏,提供公共方法对其访问

2.封装的具体应用

    在之前的代码中,使用了@public关键字来标识类的属性,然而这样使用经常导致一些不合理的结果

如人(Person类)的年龄(age)属性,有可能被错误的直接赋值为负数:

Person p = [Person new];
p->age = -10;

这样的情况显然是不合理的,而且数据在外部可以随意的被访问和修改,带来了很多不安全的因素。

面向对象的封装思想,就是将数据或者类属性信息进行封装,将类的方法实现进行封装,只在类的内部进行修改,而对外提供统一的访问接口。

在创建一个类的过程中,应该采用封装的思想进行编程,具体表现在几个方面:

当成员变量赋值时,要对不合理的赋值进行过滤
1.成员变量的权限不能为public
2.添加set方法,在方法内部进行过滤、赋值等操作//提供一个方法给外界设置age属性值
set方法
提供一个方法给外界设置age属性值
set方法
1.作用:提供一个方法给外界设置成员变量值,可以在方法里面对参数进行相应过滤
2.命名规范
 1>.方法名必须以set开头
 2>set后面跟上成员变量的名称,成员变量的首字母必须大写
 3>返回值一定是void
 4>一定要接受一个参数,而且参数类型跟成员变量类型一致
 5>形参名称不能跟成员变量名一样

若要访问成员变量,需提供get方法

get方法
 1.作用:返回对象内部的成员变量
 2.命名规范:
 1>肯定有返回值,返回值类型肯定与成员变量类型一致
 2>方法名跟成员变量名一样
 3>不需要接受任何参数

如果有些成员变量外界是只读的(只能访问不能修改) ,可以不设置set方法、只设置get

下面的例子是一个完整的封装数据的例子:

#import <Foundation/Foundation.h>

@interface Student : NSObject
{
    //@public
    int age;
}
- (void)setAge:(int)newAge;
- (int)age;
@end



@implementation Student
//set方法实现
- (void)setAge:(int)newAge
{
    if (newAge <= 0)
    {
        newAge = 1;
    }
    age = newAge;
}
//get方法实现
- (int)age
{
    return age;
}
@end



int main()
{
    Student *stu = [Student new];
    [stu setAge:10];
    
    NSLog(@"学生的年龄是%d", [stu age]);
    return 0;
}

3.封装的细节和注意点

   成员变量名的命名规范:一定要以下划线开头

作用:

     1.让成员变量和get方法的名称区分开

     2.可以跟局部变量区分开,一看到下划线开头的变量,一般都是成员变量

规范的成员变量定义和封装示例:

#import <Foundation/Foundation.h>

typedef enum {
    SexMan,
    SexWomen
} Sex;
@interface Student : NSObject
{
    int _no;
    Sex _sex;
}
//sex的set和get方法
- (void)setSex:(Sex)newSex;
- (Sex)sex;

//no的set和get方法
- (void)setNo:(int)no;
- (int)no;
@end

@implementation Student
- (void)setSex:(Sex)newSex
{
    _sex = newSex;
}
- (Sex)sex
{
    return _sex;
}

- (void)setNo:(int)no
{
    _no = no;
}
- (int)no
{
    return _no;
}
@end


int main()
{
    
    Student *stu = [Student new];
    
    [stu setSex:SexMan];
    [stu setNo:10];
    
    NSLog(@"学号是:%d性别是%d", [stu no], [stu sex]);
    return 0;
}

4.OC的弱语法

情况一:只有方法的声明,而没有方法的实现在编译阶段不会报错

OC在运行过程中才会检测对象有没有实现相应的方法

#import <Foundation/Foundation.h>

@interface Person : NSObject
- (void)test;
@end


@implementation Person

@end

int main()
{
    Person *p = [Person new];
    
    //OC在运行过程中才会检测对象有没有实现相应的方法
    [p test];
    return 0;
}
上面的代码在编译阶段不会报错(会产生警告没有实现这个方法),而在链接时没有任何错误,运行时会产生

error:unrecognized selector sent to instance


未声明的函数在C语言中,编译通过,但在链接时是不能通过的

情况二:没有声明方法,直接在@implementation内部声明并实现了方法

#import <Foundation/Foundation.h>

@interface Person : NSObject

@end


@implementation Person
- (void)test
{
    NSLog(@"哈哈哈");
}
@end

int main()
{
    Person *p = [Person new];
    
    //OC在运行过程中才会检测对象有没有实现相应的方法
    [p test];
    return 0;
}
编译、链接和执行阶段都没有任何错误。

再次验证了:

OC在运行过程中才会检测对象有没有实现相应的方法
 类方法实现的内部不能使用对象成员变量 类方法实现的内部不能使用对象成员变量

5.类方法(使用类名调用的方法)

类方法都是以加好开头+,直接使用类名调用

#import <Foundation/Foundation.h>

@interface Person : NSObject
{
    int age;
}
//类方法都是以加好开头+
+ (void)printClassName;
@end

@implementation Person

+ (void)printClassName
{
    NSLog(@"这个类叫做Person");
}

@end

int main()
{
    [Person printClassName];
    return 0;
}

类方法可以和对象方法同名,当使用类调用方法时,会调用带+号的该方法;而使用对象调用方法时,会调用带-号的方法。

但是类不能调用对象方法,对象也不能调用类方法(除非使用类名),例如

- (void)test
{
    NSLog(@"调用了test方法");
    [Person test];
}


类方法实现的内部不能使用对象成员变量,例如下面的例子中:

#import <Foundation/Foundation.h>

@interface Person : NSObject
{
    int age;
}
+ (void)test;
@end

@implementation Person
+ (void)test
{
    NSLog(@"年龄是%d", age);
}
@end

int main()
{
    [Person test];
    return 0;
}

当执行代码[Person test]时会出错,因为代码使用了对象的属性。

 类方法的好处:
 1.不依赖于对象,执行效率高
 2.能用类方法尽量使用类方法
 3.场合:当方法内部不需要使用到成员变量时,就可以改为类方法


类方法的使用场合:设计工具类 如计算器等

6.self关键字

self的作用
1.self是个指针,返回当前调用这个方法的调用者(可以是类也可以是对象,调用者是类返回类,调用者是对象返回对象)
2.可以利用self->成员变量名,来访问当前对象内部的成员变量
3.[self 方法名];直接调用方法

使用示例1:

#import <Foundation/Foundation.h>

@interface Person : NSObject
{
    int _age;
    
}
- (void)setAge:(int)age;
- (int)age;

- (void)test;
@end

@implementation Person
- (void)setAge:(int)age
{
    _age =age;
}
- (int)age
{
    return _age;
}

- (void)test
{//self是个指针,返回调用这个函数的对象
    int _age = 20;
    NSLog(@"Person年龄是%d", self->_age);
}
@end

int main()
{
    
    Person *p = [Person new];
    
    [p setAge:10];
    [p test];
    return 0;
}
self使用示例2:

#import <Foundation/Foundation.h>

@interface Dog : NSObject
- (void)bark;
- (void)run;
@end

@implementation Dog

- (void)bark
{
    NSLog(@"汪汪汪");
}
- (void)run
{
    [self bark];
    NSLog(@"跑跑跑");
}

@end
int main()
{
    Dog *d = [Dog new];
    
    [d run];
    return 0;
}
self使用的注意事项:容易引发死循环

#import <Foundation/Foundation.h>

@interface Person : NSObject
- (void)test;
//+ (void)test;
@end


@implementation Person
- (void)test
{
    NSLog(@"调用了test方法");
//    [self test];
//    [Person test];
}


@end

int main()
{
    Person *p = [Person new];
    [p test];
    return 0;
}

7.继承

继承的好处:
1.抽取重复代码
2.建立了类之间的关系
3.子类可以拥有父类的所有成员变量和方法

注意点
1.基本上所有的根类都是NSObject,父类的声明要写在子类之前
2.不允许子类和父类拥有相同名称的成员变量
3.子类可以实现与父类同名的方法(重写override,覆盖父类以前的做法),此时父类中的同名方法可以不必实现
4.调用某个方法时,优先去当前类中找,如果找不到,去父类中找

坏处:耦合性太强

继承示例:

#import <Foundation/Foundation.h>

/**************Animal类***/
@interface Animal : NSObject
{
    int _age;
    double _weight;
}
- (void)setAge:(int)age;
- (int)age;

- (void)setWeight:(double)weight;
- (double)weight;
@end

@implementation Animal
- (void)setAge:(int)age
{
    _age = age;
}
- (int)age
{
    return _age;
}

- (void)setWeight:(double)weight
{
    _weight = weight;
}
- (double)weight
{
    return _weight;
}

@end
/****************Dog*/
//Dog继承了Animal,相当于拥有了Animal里面所有成员变量和方法 父类必须写前面
@interface Dog : Animal

@end


@implementation Dog

@end

/****************Cat**/
@interface Cat : Animal

@end

@implementation Cat

@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)setAge:(int)age;
- (int)age;

- (void)run;
@end

@implementation Person
- (void)setAge:(int)age
{
    _age = age;
}
- (int)age
{
    return _age;
}

- (void)run
{

    NSLog(@"person-- 跑");
}
@end
/*Student*/
@interface Student : Person
{
    int _no;
}
@end

@implementation Student
- (void)run
{
    
    NSLog(@"student-- 跑");
}
@end

int main()
{
    Student *stu = [Student new];
    [stu run];
    return 0;
}

8.继承的使用场合

继承的使用场合:

 1.当两个类拥有相同属性和方法的时候,就可以将相同的东西抽取到一个父类中

 2.A类拥有B类的部分属性和方法时,可以考虑让B类继承A

 

 //继承:A类是B

 组合:A类拥有B(A类含有B类型的成员变量)


扩展知识:

组合(contains-a)
依赖(use-a)
聚合(has-a)
继承(is-a)


聚合与组合的区别


聚合:指的是整体与部分的关系。通常在定义一个整体类后,再去分析这个整体类的组成结构。从而找出一些组成类,该整体类和组成类之间就形成了聚合关系。


组合:也表示类之间整体和部分的关系,但是组合关系中部分和整体具有统一的生存期。一旦整体对象不存在,部分对象也将不存在。部分对象与整体对象之间具有共生死的关系。


聚合和组合的区别在于:
聚合关系是“has-a”关系,组合关系是“contains-a”关系;聚合关系表示整体与部分的关系比较弱,而组合比较强;聚合关系中代表部分事物的对象与代表聚合事物的对象的生存期无关,一旦删除了聚合对象不一定就删除了代表部分事物的对象。组合中一旦删除了组合对象,同时也就删除了代表部分事物的对象。

在聚合关系中,部分可以独立于聚合而存在,部分的所有权也可以由几个聚合来共享
一般来说被组合对象不能脱离组合对象独立存在,而且也只能属于一个组合对象
聚合则不一样,被聚合的对象可以属于多个聚合对象。

9.super关键字

super关键字作用
1.直接调用父类中的某个方法
2.super处在对象方法中,那么就会调用父类的对象方法
super处在类方法中,那么就会调用父类的类方法
3.使用场合:子类重写父类的方法时,想保留父类的一些行为

super关键字使用示例:

#import <Foundation/Foundation.h>

//僵尸
@interface Zoombie : NSObject
- (void)walk;
@end

@implementation Zoombie
- (void)walk
{
    NSLog(@"往前挪两步---");
}

@end
//跳跃僵尸
@interface JumpZoombie : Zoombie
@end

@implementation JumpZoombie
- (void)walk
{
    //跳两下
    NSLog(@"跳两下");
    
    //走两下
    [super walk];
}
@end

int main()
{
    
    JumpZoombie *jz = [JumpZoombie new];
    [jz walk];
    return 0;
}

10.多态

多态性是指相同的操作或函数、过程可作用于多种类型的 对象上并获得不同的结果。不同的 对象,收到同一消息可以产生不同的结果,这种现象称为 多态性
多态性允许每个 对象以适合自身的方式去响应共同的消息。
多态性增强了软件的灵活性和重用性。

多态的具体使用示例:

#import <Foundation/Foundation.h>

@interface Animal : NSObject
- (void)eat;
@end

@implementation Animal
- (void)eat
{
    NSLog(@"Animal吃东西---");
}
@end
Dog
@interface Dog : Animal
- (void)run;
@end

@implementation Dog
- (void)eat
{
    NSLog(@"Dog吃东西---");
}
- (void)run
{
    NSLog(@"dog跑起来");
}
@end
/Cat
@interface Cat : Animal

@end
@implementation Cat
- (void)eat
{
    NSLog(@"Cat吃东西---");
}
@end
void feed(Animal *animal)
{
    [animal eat];
}
int main()
{
    
    //多种形态
    //Dog *dog = [Dog new];//Dog类型
    
    
    //多态:父类指针指向子类对象
    Animal *a = [Dog new];//面向抽象
    [a eat];  // Dog吃东西---
    
    //多态的实现方法:1.子类有重写父类方法
//                 2.需要父类类型参数的地方用子类对象传递(面向抽象)
//                    3.调用相关方法时,运行时实现多态
    //种类:区别于子类父类的多态
    //种类:区别于同级子类的多态
    NSLog(@"********************");
    Dog *d = [Dog new];
    feed(d);
    
    Cat *c = [Cat new];
    feed(c);
    //使用多态时 用具体类(子类)调用抽象方法(父类方法),而避免使用抽象类型对象调用具体实现
    Animal *aa = [Dog new];
//    [aa run];//避免这样
    [(Dog *)aa run];//强制类型转换
    
    return 0;
}

编译时多态
多态的实现方法:1.子类有重写父类方法
                             2.需要父类类型参数的地方用子类对象传递(面向抽象)
                             3.调用相关方法时,运行过程实现多态
运行时多态( self关键字在多态中的使用
1.子类重写或者没有重写父类方法均可
2.父类的方法中使用self关键字 实现对当前调用者的引用
3.运行时,系统根据当前调用者具体判断使用哪个类中的方法


对多态的总结

 1.没有继承就没有多态

 2.代码的体现:父类类型的指针指向子类对象

 3.好处:如果函数\方法参数中使用的是父类类型,可以传入父类、子类对象

 4.局限性:

 1> 父类类型的变量 不能直接调用子类特有的方法。必须强转为子类类型变量后,才能直接调用子类特有的方法


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值