zoukankan      html  css  js  c++  java
  • 23.C++- 继承的多种方式、显示调用父类构造函数、父子之间的同名函数、virtual虚函数

     上章链接: 22.C++- 继承与组合,protected访问级别 


    继承方式

    继承方式位于定义子类的”:”后面,比如:

    class Line : public Object             //继承方式是public
    {
    
    };

    继承方式默认为private

    在C++中,继承方式共有3种:

    public继承

    -指父类的成员(变量和函数)访问级别,在子类中保持不变

    private继承

    -指父类的成员,在子类中变为private私有成员.

    -也就是说子类无法访问父类的所有成员

    protected继承

    -指父类的public成员 ,在子类中变为protected保护成员,其它成员级别保持不变

    如下图所示:

     

    注意: protected继承只针对子类有效

    比如当父类是protected继承时,则子类的子类就无法访问父类的所有成员

    一般而言,C++项目只用到public继承

    显示调用父类构造函数

    • 当我们创建子类对象时,编译器会默认调用父类无参构造函数
    • 若有子类对象,也会默认调用子类对象的无参构造函数。

    比如以下代码:

    class  StrA
    {
    public:
              StrA()
              {
                 cout<<"StrA()"<<endl;
              }
              StrA(string s)
              {
                 cout<<"StrA(string s):"<<s<<endl;
              } 
    };
    
    class  StrB : public StrA
    {
    public:
              StrB(string s)
              {
                 cout<<"StrB(int i):"<<s<<endl;
              }
    };
    
    int main()
    {
           StrB b("123");
           return 0;
    }

     编译运行:

    StrA()                    //父类无参构造函数
    StrB(int i):123

    也可以通过子类构造函数的初始化列表来显示调用

    接下来,修改上面子类的StrB(string s)函数,通过初始化列表调用StrA(string s)父类构造函数

    改为:

    StrB(string s): StrA(s)
    {
      cout<<"StrB(int i):"<<s<<endl;
    }

    运行打印:

    StrA(string s):123
    StrB(int i):123

    父子间的同名成员和同名函数

    • 子类可以定义父类中的同名成员和同名函数
    • 子类中的成员变量和函数将会隐藏父类的同名成员变量和函数
    • 父类中的同名成员变量和函数依然存在子类中
    • 通过作用域分辨符(::)才可以访问父类中的同名成员变量和函数

    比如:

    class Parent{
    
    public:
           int mval;
           Parent()
           {
                  mval=1000;
           }
    void add(int i) { mval+=i; } }; class Child : public Parent { public: int mval; Child() { mval=100; } void add(int i,int j) { mval+=i+j; } };

    在main()函数执行:

           Child c;
    
           //c. add(10);        //该行会报错,由于子类有add函数,所以编译器会默认在子类里寻找add(int i);
    
           c.Parent::add(10);   //该行正确,执行父类的成员函数
    
           c.add(2,3);
    
           cout<<"Child.mval="<<c.mval<<endl;
    
           cout<<"Parent.mval="<<c.Parent::mval<<endl;

    打印:

    Child.mval=105
    Parent.mval=1010

    从打印结果看到,父类和子类之间的作用域是不同的, 所以执行父类的同名成员变量和函数需要作用域分辨符(::)才行

     

    父子间的兼容

    以上示例的Parent父类Child子类为例

    • 子类对象可以直接赋值给父类对象使用,比如: Parent p; Child c;   p=c;
    • 子类对象可以初始化父类对象,比如: Parent p1(c);
    • 父类引用可以直接引用子类对象,比如: Parent& p2 =c;    //p2是c对象的别名
    • 父类指针可以直接指向子类对象,比如: Parent* p3=&c;

    其实是编译器是将子类对象退化为了父类对象, 从而能通过子类来赋值初始化父类

    所以上述的父类对象(包括指针/引用)也只能访问父类中定义的成员.

    如果父类对象想访问子类的成员,只能通过强制转换,将父类对象转为子类类型

    示例1,通过C方式转换:

    Child c;
    Parent* p3=&c;
    Child *c2 = (Child*)p3;         

    示例2,通过static_cast转换:

    Child c;
    Parent* p3=&c;
    Child *c2 = (static_cast*)<Child*>(p3);

    虚函数

    实现多态性,通过指向子类的父类指针或引用,可以访问子类中同名覆盖成员函数

    首先参考下面,没有虚函数的示例:

    class Parent
    {
        int i; 
    public:  
             void example()
            {
                cout<<"class Parent"<<endl;
            }
    
    }; 
     
    class Child : public Parent  
    {
        int j; 
    public:
            void example()
            {
                cout<<"class Child"<<endl;
            }     
    };
    
    
    void print(Parent* p)
    {
         p->example();
    }
    int main()
    {
        Parent t; 
        Child c;
        
        print(&t);
        print(&c);     
        
        cout<<"SIZEOF Parent:"<<sizeof(t)<<endl;
        cout<<"SIZEOF Child:"<<sizeof(c)<<endl; 
    }

    运行打印:

    class Parent
    class Parent
    SIZEOF Parent:4
    SIZEOF Child:8

    从结果看出,即使example函数指针p指向了Child c,也只能调用父类的example(),无法实现多态性.

    所以C++引入了虚函数概念,根据指针指向的对象类型,执行不同类的同名覆盖成员函数,实现不同的形态

    定义: 在父类成员函数的返回值前面,通过virtual关键字声明,这样便能访问子类中的同名成员函数了

    接下来将上个示例的父类成员函数example()改写为虚函数:

    virtual void print()        //将父类的成员函数定为虚函数
    {
    cout<<"class Parent"<<endl;
    }        

    运行打印:

    class Parent
    class Child
    SIZEOF Parent:8
    SIZEOF Child:12

    可以发现,父类和子类的长度都增加了4字节,这4个字节就是用来指向“虚函数表”的指针,编译器便会更据这个指针来执行不同类的虚函数,实现多态性.

    虚析构函数

    -在使用基类指针指向派生类对象时用到

    -通过基类析构函数可以删除派生类对象 

    示例

    #include <iostream>
    
    using namespace std;
    
    class Base
    {
    public:
         Base()
        {
            cout << "Base()" << endl;
        }
    
         virtual ~Base()
        {
            cout << "~Base()" << endl;
        }
    };
    
    class Derived : public Base
    {
    public:
         Derived()
        {
            cout << "Derived()" << endl;
        }
    
         ~Derived()
        {
            cout << "~Derived()" << endl;
        }
    };
    
    int main()
    {
        Base* p = new Derived(); 
        // ...
        delete p;
    
        return 0;
    }

    运行打印:

    Base()
    Derived()
    ~Derived()
    ~Base()

    可以发现,由于基类的析构函数是虚函数,所以我们delete基类指针时,派生类也跟着调用了析构函数,从而避免了内存泄漏,也能满足使用dynamic_cast强制转换

    一般而言,虚构造函数只有在继承下才会被使用,单个类是不会使用虚构函数的,因为虚函数表会产生额外的空间

    注意:构造函数不能成为虚函数,因为虚函数表是在构造函数执行后才会进行初始化

  • 相关阅读:
    Ubuntu安装Oracleclient远程连接数据库
    解决报错:Unable to process Jar entry [org/springframework/jmx/export/annotation/*****]
    解决报错:The import javax.servlet.annotation cannot be resolved
    解决导入MAVEN项目报错Dynamic Web Module 3.1 requires Java 1.7 or newer.
    python批量下载链接图片
    thrax的安装
    cmake下载与使用(含cmake安装包)
    NFA转换为DFA
    杂记
    attention机制
  • 原文地址:https://www.cnblogs.com/lifexy/p/8698293.html
Copyright © 2011-2022 走看看