超详细的继承和派生讲解

1、继承中的构造和析构的顺序

在这里插入图片描述
#千锋教育#
学习必须如蜜蜂一样,采过许多花,这才能酿出蜜来。

class Base
{
public:
    Base()
    {
        cout<<"父类的无参构造函数"<<endl;
    }
    ~Base()
    {
        cout<<"父类中的析构函数"<<endl;
    }
};
class Son:public Base
{
public:
    Son()
    {
        cout<<"子类的无参构造"<<endl;
    }
    ~Son()
    {
        cout<<"子类中的析构函数"<<endl;
    }
};
void test01()
{
    Son ob1;
}

运行结果:
在这里插入图片描述

总结:

构造顺序: 父类(基类)构造 ------> 子类(派生类)构造
析构顺序:子类(派生类)析构------> 父类 (基类) 析构
在这里插入图片描述
在这里插入图片描述

2、子类中 有父类、对象成员 构造和析构的顺序

父类的构造和析构 对象成员的构造和析构 子类自身的构造和析构

class Other
{
public:
    Other()
    {
        cout<<"对象成员的构造函数"<<endl;
    }
    ~Other()
    {
        cout<<"对象成员的析构函数"<<endl;
    }
};
class Base
{
public:
    Base()
    {
        cout<<"父类的无参构造函数"<<endl;
    }
    ~Base()
    {
        cout<<"父类中的析构函数"<<endl;
    }
};
class Son:public Base
{
public:
    Son()
    {
        cout<<"子类的无参构造"<<endl;
    }
    ~Son()
    {
        cout<<"子类中的析构函数"<<endl;
    }

    Other ob;//对象成员

};
void test01()
{
    Son ob1;
}

运行结果:
在这里插入图片描述

总结:(重要)

在这里插入图片描述

3、详解 子类中的构造

1、子类会默认调用  父类的 无参构造
2、子类 必须显示 使用初始化列表  调用 父类的有参构造

调用形式:父类名称。

Son(int a,int b):Base(a),b(b)
{
    //this->b = b;
}
class Base
{
private:
    int a;
public:

    Base()
    {
        cout<<"父类的无参构造函数"<<endl;
    }
    Base(int a)
    {
        this->a = a;
        cout<<"父类的有参构造函数"<<endl;
    }
    ~Base()
    {
        cout<<"父类中的析构函数"<<endl;
    }
};
class Son:public Base
{
private:
    int b;
public:
    Son()
    {
        cout<<"子类的无参构造"<<endl;
    }
    Son(int b)
    {
        this->b = b;
        cout<<"子类的有参构造函数int"<<endl;
    }

    //子类必须用 初始化列表 显示的调用父类的有参构造
    //父类名称(参数)
    Son(int a,int b):Base(a)//显示的调用父类的有参构造
    {
        this->b = b;
        cout<<"子类的有参构造函数 int int"<<endl;
    }
    ~Son()
    {
        cout<<"子类中的析构函数"<<endl;
    }
};
void test01()
{
    //子类 默认 会调用 父类的无参构造
    //Son ob1(10);

    //子类必须用 初始化列表 显示的调用父类的有参构造
    //父类名称+()
    Son ob2(10,20);

}

运行结果:
在这里插入图片描述

案例提高:

如果父类有参构造:

 Base(int a, int data)
{
    this->a = a;
    this->data = data;
    cout<<"父类的有参构造函数"<<endl;
}

子类想调用 父类有参构造:

//子类必须用 初始化列表 显示的调用父类的有参构造
//父类名称(参数)
Son(int a,int b, int c):Base(a,c),b(b)//显示的调用父类的有参构造
{
    //this->b = b;
    cout<<"子类的有参构造函数 int int"<<endl;
}

4、父类和子类的同名 成员变量 处理

4.1、当 父类和子类 成员变量同名时 在子类就近原则 选择本作用域的子类成员

4.2、如果在子类中 必须使用父类中的同名成员 必须加上父类的作用域。

class Base
{
    //父类的私有数据 一旦涉及继承 在子类中不可见
public:
    int num;
public:
    Base(int num)
    {
        this->num = num;
        cout<<"Base有参构造int"<<endl;
    }
    ~Base()
    {
        cout<<"析构函数"<<endl;
    }
};

class Son:public Base
{
private:
    int num;
public:
    Son(int num1,int num2):Base(num1)
    {
        this->num = num2;
        cout<<"有参构造int int"<<endl;
    }

    ~Son()
    {
        cout<<"析构函数"<<endl;
    }
    void showNum(void)
    {
        //如果在子类中 必须使用父类中的同名成员  必须加上父类的作用域
        cout<<"父类中的num = "<<Base::num<<endl;

        //当 父类和子类 成员变量同名时  在子类就近原则 选择本作用域的子类成员
        cout<<"子类中的num = "<<num<<endl;
    }
};

void test01()
{
    Son ob1(10,20);
    ob1.showNum();
}

运行结果:
在这里插入图片描述

4.3、子类可以借助 父类的公有方法 间接的操作 父类的私有数据(不可见的数据)

class Base
{

private:
    int num;//父类的私有数据 一旦涉及继承 在子类中不可见
public:
    Base(int num)
    {
        this->num = num;
        cout<<"Base有参构造int"<<endl;
    }
    ~Base()
    {
        cout<<"析构函数"<<endl;
    }
    int getNum(void)
    {
        return num;
    }
};

class Son:public Base
{
private:
    int num;
public:
    Son(int num1,int num2):Base(num1)
    {
        this->num = num2;
        cout<<"有参构造int int"<<endl;
    }

    ~Son()
    {
        cout<<"析构函数"<<endl;
    }
    void showNum(void)
    {
        //如果在子类中 必须使用父类中的同名成员  必须加上父类的作用域
        cout<<"父类中的num = "<<getNum()<<endl;

        //当 父类和子类 成员变量同名时  在子类就近原则 选择本作用域的子类成员
        cout<<"子类中的num = "<<num<<endl;
    }
};

void test01()
{
    Son ob1(10,20);
    ob1.showNum();
}

运行结果:
在这里插入图片描述

5、父类和子类的同名 成员函数 处理

案例:1子类继承父类所有成员函数 和成员变量

class Base
{
public:
    void func(void)
    {
        cout<<"父类中的void func"<<endl;
    }
    void func(int a)
    {
        cout<<"父类中的int func a = "<<a<<endl;
    }
};

class Son:public Base
{
public:
    
};

void test01()
{
    //为啥构造和析构除外?父类的构造和析构 只有父类自己知道该怎么做(构造和析构 系统自动调用)
    //子类会继承父类所有成员函数(构造和析构函数除外) 和成员变量
    Son ob1;
    ob1.func();//访问的是父类的void func(void)
    ob1.func(10);//访问的是父类的func(int a)
}

案例:2子类和父类 同名成员函数

class Base
{
public:
    void func(void)
    {
        cout<<"父类中的void func"<<endl;
    }
    void func(int a)
    {
        cout<<"父类中的int func a = "<<a<<endl;
    }
};

class Son:public Base
{
public:
    //一旦子类 实现了 父类的同名成员函数 将屏蔽所有父类同名成员函数
    void func(void)
    {
        cout<<"子类中voidfunc"<<endl;
    }
};

void test01()
{
    //为啥构造和析构除外?父类的构造和析构 只有父类自己知道该怎么做(构造和析构 系统自动调用)
    //子类会继承父类所有成员函数(构造和析构函数除外) 和成员变量
    Son ob1;
    ob1.func();
    //ob1.func(10);//err //一旦子类 实现了 父类的同名成员函数 将屏蔽所有父类同名成员函数 

    //如果用户 必须要调用父类 的同名成员函数 必须加作用域
    ob1.Base::func();//调用父类的void func
    ob1.Base::func(10);//调用父类的int func
}
int main(int argc, char *argv[])
{
    test01();
    return 0;
}

运行结果:
在这里插入图片描述

6、继承中的静态成员特性(了解)

class Base
{
public:
    //静态成员属于类 而不属于对象
    static int num;
    static int data;

    static void showData(void);

};
int Base::num  = 100;
int Base::data = 200;

class Son:public Base
{
public:
    static int data;//父和子类 静态成员 同名
    static void showData(void);
};
int Son::data = 300;

void test01()
{
    //从Base类中访问
    cout<<Base::num<<endl;

    // Son 也拥有了静态成员num
    cout<<Son::num<<endl;

    //父和子类 静态成员 同名 在子类中 访问子类中的成员
    cout<<Son::data<<endl;//200

    //父和子类 静态成员 同名 访问父类中的成员 必须加 Base::
    cout<<Son::Base::data<<endl;//200


    //父和子类 同名静态成员函数 子类默认访问子类的静态成员函数
    Son::showData();
    //父和子类 同名静态成员函数 子类访问父类的静态成员函数 必须加 Base::
    Son::Base::showData();
}

运行结果:
在这里插入图片描述

7、多继承(了解)

7.1、多继承的格式:

class 子类: 继承方式1 父类名1,继承方式2 父类名2,继承方式3 父类名3,....
{

};
//表示子类 是由 父类名1,父类名2,父类名3...共同派生出来
class Base1
{
public:
    int a;
};
class Base2
{
public:
    int b;
};

class Son:public Base1,public Base2
{
    //Son类 拥有了a  b
};
int main(int argc, char *argv[])
{
    Son ob;
    ob.a = 100;
    ob.b = 200;
    return 0;
}

7.2、多继承容易产生二义性: (解决办法1 使用作用域)

class Base1
{
public:
    int a;
};
class Base2
{
public:
    int a;
};

class Son:public Base1,public Base2
{

};
int main(int argc, char *argv[])
{
    Son ob;
    //ob.a = 100;//err Base1 和 Base2中都有a成员同名
    //解决办法:加作用域
    ob.Base1::a = 100;
    ob.Base2::a = 200;
    return 0;
}

7.3、菱形继承(具有公共祖先 的多继承)

class Animal
{
public:
    int data;
};

class Sheep:public Animal
{
public:
};
class Tuo:public Animal
{
public:
};

class SheepTuo:public Sheep,public Tuo
{
public:
};
int main(int argc, char *argv[])
{
    SheepTuo st;
    //SheepTuo 从Sheep中继承data  从Tuo继承data 就产生二义性
    //st.data = 200;//err
    //第一中方式:加作用域解决
    st.Sheep::data = 200;
    st.Tuo::data = 300;

    return 0;
}

8、普通继承:vs studio分析

class Animal
{
public:
    int data;
};

在这里插入图片描述

class Sheep:public Animal
{
public:
};

在这里插入图片描述

class Tuo:public Animal
{
public:
};

在这里插入图片描述

class SheepTuo:public Sheep,public Tuo
{
public:
};

在这里插入图片描述

9、虚继承(了解) vs studio分析

virtual修饰继承方式
//继承的动作 虚继承
//父类:虚基类
class 子类:virtual public 父类
{
};

虚继承:

class Animal
{
public:
    int data;
};

在这里插入图片描述

class Sheep:virtual public Animal
{
public:
};

在这里插入图片描述
vbptr(虚基类指针) 其中v是virtual 虚 b是base 基类 prt指针
(vbptr指向虚基类表)
vbtable(虚基类表 ) 保存了当前的虚指针相对于虚基类的首地址的偏移量

class Tuo:virtual public Animal
{
public:
};

在这里插入图片描述
总结:之所以 产生 vbptr和vbtable 目的 保证 不管多少个继承 虚基类的数据只有一份。

class SheepTuo:public Sheep,public Tuo
{
public:
};

在这里插入图片描述
在这里插入图片描述

案例1:

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

class Animal
{
public:
    int data;
};

class Sheep :virtual public Animal
{
public:
};
class Tuo :virtual public Animal
{
public:
};

class SheepTuo :public Sheep, public Tuo
{
public:
};
int main(int argc, char* argv[])
{
    SheepTuo st;
    st.data = 200;
    
    //通过Sheep的vbptr 寻找vbptr距离虚基类首地址的偏移量 
    //&st == vbptr
    //*(int *)&st sheep 的虚基类表的起始位置
    int off_set = (int)*((int*)(*(int*)&st) + 1);
    cout << off_set << endl;

    //通过sheep的vbptr 和 off_set定位虚基类的首地址
    cout << ((Animal*)((char*)&st + off_set))->data << endl;

    return 0;
}

注意:vsstudio中运行
在这里插入图片描述
注意: 虚继承只能解决具备公共祖先的多继承所带来的二义性问题,不能解决没有公共祖先的多继承的。
虚继承:不管继承多少次 虚基类 只有一份数据。

各位看官,感觉我写的怎么样呢, 大家有没有看懂呢, 欢迎评论
如果对大家有帮助,不要忘记点个赞哦.

发布了52 篇原创文章 · 获赞 42 · 访问量 4930

猜你喜欢

转载自blog.csdn.net/weixin_43288201/article/details/105078757
今日推荐