面向对象——继承与派生

继承与派生

继承与派生的概念

区分继承和组合

​ 从模块累积的角度来说,大模块是由无数个小模块构成的。构建一个模块的方式有两种,一是小模块架构组合成为大模块,二是小模块继承生成大模块。

​ 模块可以通过调用模块成员使用模块成员的性质,这就是组合。

​ 组合的代码理解如下:

#include<bits/stdc++.h>
using namespace std;
class point{
    private:
        int x,y;
    public:
        point(int xx,int yy):x(xx),y(yy){}
        void showpoint(){
            cout<<"("<<x<<","<<y<<")"<<endl;
        }
    ~point(){}
};
///小模块组合为大模块
class circle{
    public:
        point p;
        double r;
    public:
        circle(int xx,int yy,double rr);
        double getarea(){return 3.14*r*r;}
        void shouarea(){
            cout<<"S="<<getarea()<<endl;
        }
};
///构造函数
circle::circle(int xx,int yy,double rr):p(xx,yy),r(rr){}
int main(){
    circle c(3,4,5.1);
    c.shouarea();
    (c.p).showpoint();
    return 0;
}

​ 模块除了组合关系,还有继承关系,简要地说就是把父模块的功能性质直接作为自己的功能性质。

继承的概念

​ 简单的理解就是在原有类的功能性质上添加新的功能从而产生一个新的类,这就是继承。

​ 一个新类继承了原有类,原有类称为基类或父类,新类称为派生类或子类。子类获得父类的特性是继承,父类产生子类是派生。

​ 如果一个类只有一个基类,称为单继承,如果有多个基类,称为多继承。

(如果一个派生模块只继承了一个基类模块,称为单继承;如果一个派生模块继承了多个基类模块作为自己的子成员,这种继承关系成为多继承。)

​ 派生类模块的构造是在已有模块的基础上发展起来的,即派生类模块继承了基类模块的接口功能。

​ 派生模块继承了基类模块,也就是说基类模块是派生模块的一部分,在构造派生模块时当然要构造一个基类模块作为他的子成员

派生类的声明

语法

(以公有继承关系为例)

(具体的继承方式在下一段介绍)

class 派生类名 : public 基类名{
    成员说明列表
}

说明:

1.派生类名指的是新构造的模块,基类名是基础模块,派生类是以基类为基础发展形成的;

2. public表示的是基类的接口被继承到派生模块后将被提升为派生类的对外接口
3. 成员说明列表指的是派生模块里除了继承模块外新加的成员。
4. 基类声明的公有成员和保护成员在派生类中**保持原有访问属性**;基类声明的私有成员在派生类中不可访问,仍然为基类私有。

代码:

单继承!

#include<bits/stdc++.h>

using namespace std;

class base{
    private:
        int b_num;
    public:
        ///无参构造函数
        base(){}
        ///有参构造函数
        base(int i):b_num(i){}
        int getbnum(){return b_num;}
        int setnum(int x){b_num=x;}
        void showbnum(){cout<<b_num<<endl;}
        ~base(){}
};
///定义的继承派生关系,没有添加新成员
///公有继承,基类的对外接口继承下来成为派生类的对外接口
class derived:public base{
};

int main(){
    cout<<"测试基类性质"<<endl;
    base b;
    cout<<b.getbnum()<<endl;
    b.setnum(500);
    cout<<b.getbnum()<<endl;
    cout<<"基类base的对象大小为"<<sizeof(b)<<endl;

    cout<<"测试派生类性质"<<endl;
    derived d;
    cout<<d.getbnum()<<endl;
    d.setnum(555);
    cout<<d.getbnum()<<endl;
    cout<<"派生类derived的对象大小为"<<sizeof(d)<<endl;
    ///cout<<d.b_num; 没有访问权限
    return 0;
}

多继承!

#include<bits/stdc++.h>

using namespace std;

class A{
    private: int a;
    public: int puba;
    public:
        void showa(){cout<<a<<endl;}
        int geta(){return a;}
};

class B{
    private: int b;
    public: int pubb;
    public:
        void showb(){cout<<b<<endl;}
        int getb(){return b;}
};

///公有继承下类C有6个接口成员
class C: public A, public B{

};

int main(){
    C e;
    cout<<e.geta()<<endl;
    cout<<e.puba<<endl;
    cout<<e.pubb<<endl;
    cout<<e.getb()<<endl;
    ///e.A::getb();
    e.showa();
    e.showb();
    return 0;
}

说明:

​ 1.公有继承下类C有6个接口成员:类A的puba showa() geta() ,类B的pubb showb() getb()

​ 2.可以理解为类C中含有类A的一个无名对象和类B的一个无名对象,所以

e.getb();
e.B::getb();

其实是等价的,但是为了方便一般写第一种。

(如果有二义性的话就要格外讨论了,后续会介绍)

派生模块的构造和析构

派生模块的成员构成和构造规则

以下面的代码为例

class C:public A,public B{
    private:
    	int c;
    	Clock time;
    public:
    	int get_c(){return c;}
}

借助之前生成无名对象的思想,我们可以分析得到派生类C中含有四个数据成员,分别的类A的无名对象,类B的无名对象,c和Clock类的对象time

派生模块的构造

类的构造函数的作用是创建对象时初始化对象。若类的设计者没有定义构造函数,创建
对象时,系统自动生成一个默认构造函数,但该默认构造函数不能将对象初始化。如果设计
者定义了构造函数,系统就不会自动生成默认构造函数,而是调用设计者定义的构造函数。
派生类继承了基类所有的数据成员,自己又添加了新成员,所以派生类对象的构造函数由二
部分构成。一部分用来初始化继承的基类成员,一部分用来初始化新添加的成员。初始化继
承的基类成员是通过调用基类的构造函数实现的。如果用户没有定义派生类的构造函数,在
创建派生类对象时,系统自动生成一个默认构造函数,该构造函数调用基类的无参构造函数。

构造函数就是将其成员全部构造出来并且按照规则和结构进行组装。

简明扼要的语法定义就是:

/**
模块名(参数列表):初始化列表中构造出模块的要素{
	将构造出的模块进行组装
}
**/

代码如下:

C(int aa,int bb,int cc,int hh,int mm,int ss):A(aa),B(bb),c(cc),time(hh,mm,ss){
    	cout<<"类C的构造函数"<<endl;
}
//A(aa),B(bb)可以借助上述提到的无名对象进行理解

派生模块的析构

析构函数是对象消亡时,为进行一些必要的清理工作,系统自动执行的函数。用户一般
不必定义类的析构函数,系统会自动生成一个析构函数。但在某些情况下,用户需要定义析
构函数,完成一些特殊的清理工作。
派生类析构函数的定义与没有继承关系的普通类的析构函数的定义相同。在执行派生类
的析构函数时,系统自动调用基类的析构函数。派生类析构函数的调用顺序与构造函数的调
用顺序正好相反,即:先调用派生类的析构函数,再调用对象成员的析构函数,最后调用基
类的析构函数。

代码如下:

~C(){
	cout<<"类C的析构函数"<<endl;
}

完整代码将不再做赘述。

继承方式和派生模块成员的对外可见性

前言:

需要先明确一点:继承和权限是两回事,先继承,再重新定义对外权限。

访问权限都是定义的可见成员的访问权限,比如基类中的public成员,在派生模块中可以定义为public,也可以定义为private。但是基类中的private成员,是无法定义权限的,因为其本身就不可见,对其进行权限的定义是不符合语法的。

派生模块的新定义成员与同名覆盖:

我们假设类C是由public A,public B派生而来。

如果此时类A和类B里有同名函数,直接用类C的对象e调用该函数是不可行的,这就是二义性,值得注意的是,二义性与函数重载并不相同。

但是,如果我们在函数前加预作用符来说明这是属于类C的哪一个基类,就是可行的。

e.show();///不合法
e.A::show();///合法

还要知道的一点是,如果我们设类A的show函数是public成员,而类B的show函数是private成员,理论上来说这是没有歧义的,因为类B的show成员根本就不可见。但是!编译器并不会区分是public还是private成员,只要是函数名相同的就是二义性。

如果我们在类C里定义一个新的同名show函数,这个新的函数就会覆盖旧的,但是仍然可以通过预作用符来调用旧的同名函数。

比如这样:

class C: public A, public B{
    public:///只要是同名 新的就覆盖旧的
    	void show(){cout<<"CCCCCCC"<<endl;}
};

继承方式和派生模块成员的对外可见性

访问属性:

在这里插入图片描述

(这是偷的图,侵删致歉)

继承方式

类成员有 public(公有)、protected(保护)和 private(私有)三种访问属性。在类的内
部对各种属性的成员均可直接访问。在类的外部通过类的对象,只能直接访问类的公有成员,
类的保护成员与私有成员不可访问。
在类的派生过程中,派生类对基类的继承相应地也有 public(公有)、protected(保护)
和 private(私有)三种继承方式。派生类接收基类成员成为派生类成员时,可以改变基类成
员在派生类中的访问属性。派生类中基类成员的访问属性由基类成员声明的属性和派生方式
共同决定。具体访问规则是:
(1)公有继承方式
基类声明的公有成员和保护成员在派生类中保持原有访问属性;基类声明的私有成员在
派生类中不可访问,仍然为基类私有。
(2)保护继承方式
基类声明的公有成员和保护成员在派生类中都变成保护成员;基类声明的私有成员在派
生类中不可访问,仍然为基类私有。
(3)私有继承方式
基类声明的公有成员和保护成员在派生类中变成私有成员;基类声明的私有成员在派生
类中不可访问,仍然为基类私有。
类的保护成员和私有成员在不涉及继承关系时可认为是等价的,但有继承关系存在时,
二者是有区别的。通过上面的访问规则可以看出私有成员和保护成员的区别。无论哪种继承
方式,基类的私有成员在派生类中仍为基类私有,成为派生类不可访问的成员。基类的保护
成员可成为派生类的保护成员或私有成员,在派生类的内部可以访问。

保护继承

希望某成员能够被继承到子类里,并且还不作为对外访问的接口,这种访问权限就是protected.

protected访问权限的本意就是当前成员可以被继承到派生类里,但是不能作为派生类的对外接口,此访问权限介于public和private中间。

在这里插入图片描述
未完待续,实在是写不下去了

posted @ 2020-05-09 14:05  OvO1  阅读(88)  评论(0编辑  收藏  举报