C++杂讲 继承 与 派生

目录

知识点1【继承和派生的概述】

知识点2【继承的格式】

知识点3【继承的内层结构】

知识点4【继承中的构造和析构的顺序】

知识点5【子类中 有父类、对象成员 构造和析构的顺序】

知识点6【子类中的构造】

知识点7【父类和子类的 同名成员变量 处理】

知识点8【父类和子类的 同名成员函数 处理】

知识点9【继承中的静态成员特性】

知识点10【多继承】(了解)

知识点11【菱形继承】 具有公共祖先 的多继承

知识点12【虚继承】

知识点1【继承和派生的概述】

A类继承于B类,A就直接拥有B类的资源

继承的优点:减少代码的冗余 提高代码的重用性

知识点2【继承的格式】

派生类定义格式:
   Class 派生类名 :  继承方式 基类名{
         //派生类新增的数据成员和成员函数
   };
   class 子类: 继承方式  父类名{
       //子类新增的数据成员和成员函数
   };

继承方式分类:

        public : 公有继承  

        private : 私有继承   

        protected : 保护继承

父类个数分类:

        单继承:指每个派生类只直接继承了一个基类的特征   (一个父类 派生出 一个子类)

        多继承:指多个基类派生出一个派生类的继承关系,多继承的派生类直接继承了不止一个基类的特征(多个父类 派生出 一个子类)

注意:

        子类继承父类,子类拥有父类中全部成员变量成员方法除了构造析构之外的成员方法),但是在子类中,继承的成员并不一定能直接访问,不同的继承方式会导致不同的访问权限

案例1:公有继承 public

//设置一个父类
class Base
{
public:
    int a;
private:
    int b;
protected:
    int c;
};

//设置一个子类
class Son:public Base
{
public:
    //父类中的public数据  在子类中 也是public
    //父类中的private数据 在子类中 是不可见的
    //父类中的protected数据 在子类中 是protected的
    //子类的内部
    void showSon()
    {
        //b = 200;//不能直接访问
        c =300;//在子类 内部是可以访问的
    }
};

void test01()
{
    //子类的外部
    Son ob;
    ob.a = 100;
    cout<<"父类中的public数据a = "<<ob.a<<endl;

    //ob.b = 200;//在子类外 访问不了
    //ob.c = 200;//在子类外 访问不了

}

总结:

        父类中的public数据 在子类中 也是public

        父类中的private数据 在子类中 是不可见

        父类中的protected数据 在子类中 是protected

        (public 继承 父类中的私有数据 在子类 不可见 其他保持原样)

案例2:保护继承protected

//保护继承
class Son1:protected Base
{
private:

public:
    //父类中的public数据  在子类中 也是protected
    //父类中的private数据 在子类中 是不可见的
    //父类中的protected数据 在子类中 是protected的
    //子类的内部
    void showbase(){
        a = 100;//子类内部可访问
        //b = 200;//不能直接访问
        c = 300;//子类内部可访问
    }
};
void test02()
{
    Son1 ob;
    //ob.a;//子类外不可访问
    //ob.b;//子类外不可访问
    //ob.c;//子类外不可访问
}

总结: protected继承

        父类中的public数据 在子类中 也是protected

        父类中的private数据 在子类中 是不可见

        父类中的protected数据 在子类中 是protected的

        (保护继承 父类的私有数据 在子类中 不可见 其他数据 都变保护)

案例3:私有继承 private

//保护继承
class Son2:private Base
{
private:

public:
    //父类中的public数据  在子类中 也是private
    //父类中的private数据 在子类中 是不可见的
    //父类中的protected数据 在子类中 是private的
    //子类的内部
    void showbase(){
        a = 100;//子类内部可访问
        //b = 200;//不能直接访问
        c = 300;//子类内部可访问
    }
};
void test03()
{
    Son2 ob;
    //ob.a;//子类外不可访问
    //ob.b;//子类外不可访问
    //ob.c;//子类外不可访问
}

总结:private继承

        父类中的public数据 在子类中 也是private

        父类中的private数据 在子类中 是不可见

        父类中的protected数据 在子类中 是private的

(私有继承方式:父类的权限 在子类中 变私有)

继承方式总结:public(主要)、protected、private

 不管啥继承方式:父类中的私有数据在 子类中不可见

 知识点3【继承的内层结构】

class Base
{
public:
    int a;
protected:
    int b;
private:
    int c;
};

class Son :public Base
{
public:
    int d;
    int e;
};
int main(int argc, char* argv[])
{
    cout << sizeof(Son) << endl;
    return 0;
}

Son类的布局:

 知识点4【继承中的构造和析构的顺序】

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

运行结果:

 总结:

        构造顺序: 父类(基类)构造 ------> 子类(派生类)构造

        析构顺序:子类(派生类)析构------> 父类 (基类) 析构

 

 知识点5【子类中 有父类、对象成员 构造和析构的顺序】

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

总结:(重要)

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;
}

运行结果:

 知识点6【子类中的构造】

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;
}

知识点7【父类和子类的 同名成员变量 处理】

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

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();
}

运行结果:

 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();
}

运行结果:

 知识点8【父类和子类的 同名成员函数 处理】

案例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;
}

运行结果:

 知识点9【继承中的静态成员特性】

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();
}

运行结果:

知识点10【多继承】(了解)

多继承的格式:

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;
}

多继承容易产生二义性: (解决办法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;
}

知识点11【菱形继承】 具有公共祖先 的多继承

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;
}

结构:

class Animal
{
public:
    int data;
};

class Sheep:public Animal
{
public:
};

class Tuo:public Animal
{
public:
};

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

 知识点12【虚继承】

为解决上述问题,引出虚继承

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:
};

 

 案例:

#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 2;
    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;
}

注意:  

        虚继承解决具备公共祖先的多继承所带来的二义性问题,不能解决没有公共祖先的多继承的。

虚继承:不管继承多少次 虚基类 只有一份数据。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值