【C++ 2】、继承与多态


前言

       面向对象的程序设计中提供了类的继承机制,允许程序员在保持原有类特性的基础上,进行更具体、更详细的类的定义。 而面向对象程序设计的真正力量不仅仅在于继承,还在于将派生类对象当基类对象一样处理的功能。支持这种功能的机制就是多态和动态绑定。

提示:以下是本篇文章正文内容,下面案例可供参考

一、继承

概念:
       派生类继承基类的全部数据成员和除了构造、析构函数之外的全部函数成员。(同时可以改造基类成员,并添加新的成员)

   C++不同于Java ,可以多继承,即有多个父类

继承方式:控制从基类继承的成员的访问属性,只能向下改变访问控制 即public -> protected -> private

访问控制包括两个方面:
       1、控制派生类中新增成员访问基类成员(以下简称类内部访问);
       2、控制派生类的实例对象访问基类成员(以下简称对象访问)

注意:当分析访问控制时分以下两种情况;
       派生类访问,考虑要访问的基类的成员在基类是什么身份
       派生类的对象访问,考虑要访问的成员在派生类内是什么身份,自然包括继承的和新增的成员。

       很容易理解无论什么继承方式,对于继承的私有成员,类和对象都不可直接访问,下面不再涉及。

以下实例说明三种继承方式:
              公有继承,保护继承,私有继承

现有:
       基类 classA
公有成员:property1
保护成员:property2
私有成员:property3
代码如下(示例):

class classA
{
public:

	int property1;
	
    void initclassA(int p1, int p2, int p3);
    
    int getP1();
    int getP2();
    int getP3();        //classB1、classB2、classB3

protected:
    int property2;

private:
    int property3;
    int a;
};

1、公有继承

(1) 概念
       对继承的基类成员访问控制不发生改变,仍以在基类的中身份出现在派生类中。

(2) 测试
——对继承的基类的三类成员访问控制测试
代码如下(示例):

    //public 继承  访问控制未改变
    classB1 B1;
    B1.initclassB(1,2,3);
    cout<<"B1.properties:"<<endl;
    cout<<B1.property1<<endl;
    //外部可以直接访问继承的public成员
    
    //cout<<B1.property2<<endl
    //error: 'int classA::property2' is protected within this context
    cout<<B1.getP2()<<endl<<B1.getP3()<<endl;
    //对象B1直接访问不到protected、private成员,不同的是
    //classB1类内部可以直接访问protected成员
    //classB1类内部不能访问private成员,但可以间接使用classA的public或proteced的外部接口访问private成员。

(3) 结论

       访问控制不发生改变,继承的protected成员,对象不可直接访问,类内部可以直接访问。

2、保护继承

(1) 概念
       基类的公有成员和保护成员以保护成员的身份出现在派生类中
(2) 测试
——对继承的基类的三类成员访问控制测试
代码如下(示例):

    //protected 继承 public变为protected
    classB2 B2;
    B2.initclassB(2,4,6);
    cout<<"B1.properties"<<endl;
    //cout<<B2.property1<<endl;
    //error: 'int classA::property1' is inaccessible within this context
    cout<<B2.getP1()<<endl<<B2.getP2()<<endl;
    cout<<B2.getP3()<<endl;

(3) 结论
       访问控制发生改变, 公有成员和保护成员在派生类以保护成员身份出现,类可以直接访问继承的公有成员和保护成员,对象不可以直接访问

3、私有继承

(1) 概念
       基类的公有成员和保护成员以私有成员的身份出现在派生类中
(2) 测试
——对继承的基类的三类成员访问控制测试
代码如下(示例):

    //private 继承 public和protected变为private
    classB3 B3;
    B3.initclassB(3,6,9);
    cout<<"B1.properties"<<endl;
    //cout<<B3.property1<<B3.property2<<B3.property3<<endl;
    //error: 'int classA::property1' is inaccessible within this context
    //error: 'int classA::property2' is protected within this context
    cout<<B3.getP1()<<endl<<B3.getP2()<<endl;
    cout<<B3.getP3()<<endl;

(3) 结论:
       访问控制发生改变,继承的三类成员在派生类以私有成员身份出现,类内部可以访问直接继承的公有和保护成员,对象不可直接访问。类内部不可直接访问三类成员为错误理解。

4、友元关系不能继承

(1)友元关系
       C是A的友元,则C的所有成员函数可以访问可以访问A的私有和保护成员
友元关系三点注意:
       1.不能传递
       2.是单向的
       3.不能被继承

(2) 测试

       设计类A含有私有变量a,在类A中设置类C是A的友元;
       代码如下(示例):

class classA
{
public:

    friend class classC;

    void seta(int a)
    {
        this->a=a;
    }

    int geta()
    {
        return a;
    }
    
private:
    int a;
};

设计类C
       代码如下(示例):

class classC
{
public:
      int getAa(classA &a){

          return a.a;         //B的基类的私有变量a, C可以直接访问,C的派生类D不可直接访问,D要重写getAa
      }

      int getBb(classB1 &b){

          //error: 'int classB1::b' is private within this context
          //return b.b;        //B的基类私有变量b,C及其派生类D都不能直接访问
          return b.getb();
      }

};

设计类B继承A,添加私有变量b;
       代码如下(示例):

class classB1: public classA
{
public:

    void setb(int b)
    {
        this->b=b;
    }

    int getb(){
        return this->b;
    }


private:
    int b;
};

在类C中测试访问类B的成员变量a, b;
       代码如下(示例):

    classA A;
    A.seta(10);
    classB1 B;  //继承A
    B.setb(11);
    B.seta(100);
    classC C;   //C是A的友元
    cout<<"C-A.a,B.a,B.b"<<endl;
    cout<<"A.a: "<<C.getAa(A)<<endl;   //C是A的友元类,可以直接访问A的私有成员。
    
	注意下面访问B继承的私有成员a,是直接访问。
    cout<<"B.a: "<<C.getAa(B)<<endl;   //C是B的基类A的友元类,B从基类A继承的私有成员a, C可以直接访问

    cout<<"B.b: "<<C.getBb(B)<<endl;   //C是B的基类A的友元类,但不是B的友元类,不可直接访问B的私有成员,需调用外部接口

设计类D继承C
       代码如下(示例):

class classD: public classC
{
public:
    int getAa(classA &a){
        //return a.a;      //友元关系不能被继承,D不能直接访问A的私有成员,调用外部接口重写基类的getAa()
        return a.geta();   //同时D不能直接访问A的派生类的私有成员。
    }

//    int getBb(classB1 &b){  //已继承可以也不必重写

//        //error: 'int classB1::b' is private within this context
//        //return b.b;       //C都不能直接访问B的私有变量,D更不可能
//        return b.getb();
//    }

};

在D的成员函数中测试访问类A的成员变量a,类B的成员变量a, b
       代码如下(示例):

classD D;   //继承C
    cout<<"D-A.a,B.a,B.b"<<endl;
    cout<<"A.a: "<<D.getAa(A)<<endl;   //D的基类是A的友元类,但D不是A的友元类,不可直接访问B的私有成员,重写getAa

    cout<<"B.a: "<<D.getAa(B)<<endl;   //D不能直接访问B继承的私有成员a

    cout<<"B.b: "<<D.getBb(B)<<endl;   //D不能直接访问B的私有成员b

    //总结 在友元关系中,友元关系不能被继承,C是A的友元,但C的派生类D不是A的友元
                                   // B是A的派生类,C可以直接访问B继承的A的私有成员

总结 :
       在友元关系中,友元关系不能被继承
       A B C D四个类中,
       C是A的友元,但C的派生类D不是A的友元
       B是A的派生类,C可以直接访问B继承的A的私有成员

4、虚继承

       当派生类的部分或全部直接基类都从一个共同基类派生而来时,在直接基类之间,直接基类继承来的成员用相同名称,在派生类对象中,同名数据成员就会有多个副本,同一个函数名就会有多个映射
可以通过作用域标识符来唯一标识并分别访问他们
也可以将共同基类设置为虚基类,这时从不同的路径继承过来的同名数据成员在内存中只有一个,同一个函数也只有一个映射

语法形式:
       class 派生类名: virtual 继承方式 基类名

       如果虚基类声明有非默认形式的构造函数,并且没有声明默认形式的构造函数,这时直接或间接继承虚基类的所有派生类,都必须在构造函数的成员初始化类表中列出对虚基类的初始化。

二、多态性

1、类型兼容规则

(1)概念:
       在需要基类对象的任何地方,都可以使用公有派生类的对象来替代。
替代包括以下情况:
       1.派生类的对象可以隐含转换为基类的对象
       2.派生类的对象可以初始化基类的引用
       3.派生类的指针可以隐含转换为基类的指针

需要注意的是,派生类的对象的地址赋值基类的指针,通过这个指针只能访问到从基类继承的成员

通过下面实例1体会:
代码如下(示例):

#include <iostream>
using namespace std;

class base1{
public:
    void display(){
        cout<<"Base1:display()"<<endl;
    }
};

class derived1 :public base1{
public:
    void display(){
        cout<<"derived1:display()"<<endl;
    }
};

int main()
{
    base1 base;
    base1 &b = base;
    derived1 derived;
    base1 &d = derived;
    
    b.display();
    d.display();
    return 0;
}

发现,结果为
Base1:display()
Base1:display()

2、一般多态性函数

一般多态性: 函数名称、参数、返回值完全相同

       由于上面提到的派生类的对象的地址赋值基类的指针,通过这个指针只能访问到从基类继承的成员

解决方法:基类中有同名成员,再将同名函数声明为虚函数。

虚函数成员的声明语法时:
       virtual 函数类型 函数名(形参表);
注意:虚函数声明只能出现在函数声明中,不能在函数实现时

虚函数是动态绑定的

可以总结运行中的多态的三个条件:
       1.类之间满足赋值兼容规则
       2.声明虚函数
       3.由成员函数调用或者通过指针、引用来访问虚函数

使用虚函数,实例1的程序只需修改以下部分
代码如下(示例):

class base1{
public:
    virtual void display(){
        cout<<"Base1:display()"<<endl;
    }
};

结果为
Base1:display()
derived1:display()

总结:
       要重写与基类函数同名的函数,就应该在基类中将相应的函数声明为虚函数,而继承而来的非虚函数意味着不希望被派生类改编的行为。只有通过基类的指针或引用调用虚函数时,才会发生动态绑定。

3、特殊多态性函数

       当函数的参数为子类或父类的引用或指针时。

在实例1已经设置虚函数的情况下添加以下全局函数
代码如下(示例):

void fun(base1 b){
	b.display();
}

main()添加
代码如下(示例):

derived1 d2;
fun(d2);

结果
Base1:display()

       输入派生类,调用的是基类的函数,这是因为,函数参数为基类,自动将输入的派生类保留基类成员,而丢掉派生类新增成员。

那么如何实现多态呢
参数改为基类的引用
代码如下(示例):

void fun(base1 &b){
	b.display();
}

结果
derived1:display()

代码如下(示例):

4、析构函数的多态性

构造函数不能声明为虚函数,但可以声明虚析构函数

有什么作用呢?

       如果有可能基类指针调用派生类对象的析构函数,就需要设置基类的析构函数为虚函数,否则派生类的析构函数不被调用,只调用基类的析构函数。

通过以下实例说明
代码如下(示例):

#include <iostream>
using namespace std;

class base1{
public:
    ~base1();
};

base1::~base1(){
    cout<<"base1 destructor"<<endl;
}


class derived1 :public base1{
public:
    derived1();
    ~derived1();

private:
    int *p;
};

derived1::derived1(){
    p = new int(0);
}

derived1::~derived1(){
    cout<<"derived1 destructor"<<endl;
    delete p;
}


void fun(base1 *b){
    delete b;
}

int main()
{

    base1 *d = new derived1();
    fun(d);

    return 0;
}

结果
base1 destructor
       可以看到删除派生类时,调用的是基类的析构函数。

       避免以上错误就是将基类的析构函数声明为虚函数
代码如下(示例):

class base1{
public:
    virtual~base1();
};

三、多继承设计组合图形

博客二:多继承设计组合图形

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值