继承

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/kai29/article/details/79945899

继承


继承是面向对象的复用手段,通过继承定义一个类,继承是类之间关系建模,共享共有的东西,实现各自本质不同的东西。

三种继承关系:public/protected/private基类成员在派生类的访问关系的变化如下

这里写图片描述

实现最基本的继承如下:

//父类
class Person
{
public:
    Person(const char* name="")
        :_name(name)
    {}
    void Print()
    {
        cout << _name << endl;
    }


protected:
    string _name;
};

//派生类
class Student : public Person
{
protected:
    int _num;

};

注意:

  1. 基类的私有成员在派生类中不能访问,如果想访问定义为protected,由此可以看出,保护成员限定符因为继承出现的。
  2. public是一个接口继承,保持is-a原则,每个父类可用的成员(成员函数及成员变量)对子类也可用,每个子类对象也是一个父类对象。
  3. protected/private是一个实现继承,保持has-a原则,基类的部分成员并非成为子类接口的一部分。
  4. 不可见为存在但不能访问
  5. class默认的继承方式为private,struct默认的继承方式为public

继承与转换–赋值兼容规则(public继承)


  • 子类对象可赋值给父类对象(切片/切割,编译器天然支持,不是隐式类型转换)
  • 父类对象不可赋值给子类对象
  • 父类的指针/引用可指向子类对象
  • 子类的指针不能指向父类对象(强转可以)

代码验证为:


class Person
{
public:
    void Print()
    {
        cout << _name << endl;
    }

protected:
    string _name;
};

class Student : public Person
{
public:
    int _num;

};

int main()
{
    Person p;
    Student s;
    //子类对象赋值给父类
    p = s;
    //父类对象赋值给子类
    //s = p;

    //父类的指针和引用可指向子类对象
    Person *p1 = &s;
    Person &r1 = s;

    //子类指针与引用指向父类
    Student *p2 = (Student*)&p;
    Student &r2 =(Student&)p;
    system("pause");
    return 0;
}

这里写图片描述

隐藏(重定义)


定义:子类与父类中有同名成员,子类成员将屏蔽父类对成员的直接访问,即父类被隐藏。在子类中成员函数中,可通过指定作用域(基类:基类成员)访问


class Base

{

public:

    virtual void f(float x)
    { 
        cout << "Base::f(float) " << x << endl;
    }

    void g(float x)
    {
        cout << "Base::g(float) " << x << endl; 
    }

    void h(float x)
    { 
        cout << "Base::h(float) " << x << endl; 
    }

};



class Derived : public Base

{

public:

    virtual void f(float x)
    {
        cout << "Derived::f(float) " << x << endl; 
    }

    void g(int x)
    {
        cout << "Derived::g(int) " << x << endl;
    }

    void h(float x)
    {
        cout << "Derived::h(float) " << x << endl; 
    }

};



void Test()

{

    Derived d;

    Base *pb = &d;

    Derived *pd = &d;

    // Good : behavior depends solely on type of the object

    pb->f(3.14f);  // Derived::f(float) 3.14

    pd->f(3.14f); // Derived::f(float) 3.14

    // Bad : behavior depends on type of the pointer

    pb->g(3.14f);  // Base::g(float) 3.14

    pd->g(3.14f);  // Derived::g(int) 3 (surprise!)

    // Bad : behavior depends on type of the pointer

    pb->h(3.14f);  // Base::h(float) 3.14 (surprise!)

    pd->h(3.14f);  // Derived::h(float) 3.14

}

接下来做道题吧,复习一下隐藏

class AA
{
public:
    void f()
    {
        cout<<"AA::f()"<<endl;
    }
};

class BB : public AA
{
public:
    void f(int a)
    {
        cout<<"BB::f()"<<endl;
    }
};

int main()
{
    AA aa;
    BB bb;

    aa.f();
    bb.f();//bb.AA::f();正确


    /*A. BB里面有两个f函数,并且构成重载
    B. BB里面有两个f函数,并且构成隐藏
    C.上面代码编译器不过
    D.上面代码可以编译通过,输出AA::f().*/   
正确答案是:B C
解释:重载是两个成员函数在同一个类中,故A错;
     隐藏是两个不同类的成员函数同名。故B对
     父类成员函数被隐藏,故C对,D错


}

派生类的6个默认成员函数


若在派生类中没有显示定义,则系统默认合成这6个默认成员函数。

代码实现:

#include <iostream>
using namespace std;
#include <windows.h>
#include <string>


//默认成员函数
class Person
{
public:
    //构造函数
    Person(const char*name)
        :_name(name)
    {
        cout << "Person()" << endl;
    }

    //拷贝构造
    Person(const Person& p)
        :_name(p._name)
    {
        cout << "Person(const Person& p)" << endl;
    }

    //赋值运算符重载
    Person& operator=(const Person& p)
    {
        if (this != &p)
        {
            _name = p._name;
        }
        return *this;
        cout << "Person& operator=(const Person& p)" << endl;
    }

    //析构函数
    ~Person()
    {
        cout << "~Person()" << endl;
    }

protected:
    string _name;
};

class Student :public Person
{
public:
    Student(const char* name, int num)
        :Person(name)
        , _num(num)
    {
        cout << "Student()" << endl;
    }

    //拷贝构造函数
    Student(const Student& s)
        :Person(s)
        , _num(s._num)
    {
        cout << "Student(const Student& s)" << endl;
    }
    //赋值运算符重载
    Student& operator=(const Student& s)
    {
        if (this != &s)
        {
            Person::operator=(s);
            _num = s._num;
        }
        return *this;

        cout << "Student& operator=(const Student& s)" << endl;

    }

    //析构
    ~Student()
    {
        cout << "~Student()" << endl;
    }
private:
    int _num;

};

int main()
{

    Student s1("林在范",18);
    Student s2(s1);

    Student s3("JB", 21);
    s1 = s3;


    system("pause");
    return 0;
}

结果如图
这里写图片描述

注意:
1. 合成版本:在初始化列表,父类构造初始化父类成员,子类构造初始化子类成员
2. 父类的构造函数不需要在子类显示写出,会自动调用父类的。析构函数经编译后在汇编层看会变为destructor。子类析构函数会隐藏父类。原因如下:

这里写图片描述

单继承与多继承


  • 单继承- -一个子类只有一个直接父类时称这个继承关系为单继承
  • 多继承- -一个子类有两个或以上直接父类时称这个继承关系为多继承

这里写图片描述

菱形继承:由多继承产生


这里写图片描述

菱形继承会存在二义性与数据冗余问题。
那么如何解决呢·?这就引出了新的问题 ,什么是虚继承?
虚继承:在共同继承的类前加上virtual关键字。
虚继承通过对象内存模型解决了二义性与数据冗余问题。

代码实现:

#include <iostream>
using namespace std;
#include <string>
#include <windows.h>


class Person
{
public:
    int _id;//编号
};

class Student:virtual  public Person
{
public:
    int _num;//学号
};

class Teacher:virtual public Person
{
public:
    int _tid;//教工号
};

class Assistant :public Student, public Teacher
{
public:
    int _aid;//助理号
};

int main()
{
    cout << sizeof(Assistant) << endl;//未加virtual之前为20,加了之后为24?
    Assistant a;
    a.Student::_id = 1;
    a.Teacher::_id = 2;
    a._num = 3;
    a._tid = 4;
    a._aid = 5;
    system("pause");
    return 0;
}

菱形继承的虚拟继承对象模型:

这里写图片描述
汇编层查看:
这里写图片描述

虚函数重写(覆盖)


当在子类的定义了一个与父类完全相同的虚函数时,则称子类的这个函数重写(也称覆盖)了父类的这个虚函数。


#include <iostream>
using namespace std;
#include <windows.h>
#include <string>
class   Person
{
public:
    virtual  void  BuyTickets()
    {
        cout << "买票-全价"<< endl;
    }
    protected :
    string  _name;       // 姓名
};
class   Student : public  Person
{
public:
    virtual void  BuyTickets()
    {
        cout << "  买票-半价  "<< endl;
    }
    protected :
    int  _num;       //学号
};
//void  Fun(Person*   p)
void  Fun(Person&  p)
{
    p.BuyTickets();
}
int main()
{
    Person  p;
    Student  s;
    Fun(p);
    Fun(s);
    system("pause");
    return 0;
}

对象调用哪一函数?

  • 与类型有关->常规情况
  • 与指向对象有关->多态(条件:虚函数重写;对象指针或引用)

注意:

  1. 派生类重写基类的虚函数实现多态,要求函数名、参数列表、返回值完全相同。(协变除外,协变是指函数返回值不同,一个为父类指针,一个为子类指针)
  2. 基类中定义了虚函数,在派生类中该函数始终保持虚函数的特性
  3. 只有类的成员函数才能定义为虚函数
  4. 静态成员函数不能定义为虚函数
  5. 如果在类外定义虚函数,只能在声明函数时加virtual,类外定义函数时不能加virtual
  6. 构造函数不能为虚函数,虽然可以将operator= 定义为虚函数,但是最好不要将operator = 定义为虚函数,因为容易使用时容易引起混淆。
  7. 不要在构造函数和析构函数里面调用虚函数,在构造函数和析构函数中,对象是不完整的,可能会发生未定义的行为
  8. 最好把基类的析构函数声明为虚函数。(why ?另外析构函数比较特殊,因为派生类的析构函数跟基类的析构函数名称不一样,但是构成覆盖,这里是因为编译器做了特殊处理)

    对第8条作解释,显示调用一次析构函数并查看其汇编代码
    可以看到编译器对其做了处理。

接下来对比一下隐藏,重载,覆盖?

这里写图片描述

阅读更多
想对作者说点什么?

博主推荐

换一批

没有更多推荐了,返回首页