c++继承与多态

继承

c++最重要的特征是代码重用,通过继承机制可以利用已有的数据类型来定义新的数据类型,新的类不仅拥有旧类的成员,还拥有新定义的成员。

一个B类继承于A类,或称从类A派生类B。这样的话,类A成为基类(父类)类B成为派生类(子类)。 派生类中的成员,包含两大部分: 一类是从基类继承过来的一类是自己增加的成员。 从基类继承过过来的表现其共性,而新增的成员体现了其个性

继承的形式:

class 父类{};
class 子类:继承方式 父类名
{
//新增子类数据
};

继承方式:private protected public(推荐)

所有父类私有在子类中不可访问,

  • 公共继承,父类 的保护属性成员在子类中还是保护属性,公共属性成员还是公共属性
  • 保护继承,父类的保护属性成员,公共属性成员在子类中都是保护属性
  • 私有继承,父类的保护属性成员,公共属性成员在子类中都是私有属性
#include <iostream>
using namespace std;
class fat
{
private:
    int a;
protected:
    int b;
public:
    int c;
};
class son:public fat
{
public:
    void fun(){
        //cout<<a<<endl;a在父类是私有属性不可访问
        //a=10;
        b=12;
        cout<<b<<endl;
        cout<<c<<endl;
    }
};
int main(int argc, char *argv[])
{
    son caicai;
    caicai.c=100;
    //caicai.a=10;a在父类是私有属性不可访问
    //caicai.b=1;b是保护属性
    caicai.fun();
}

子类的构造析构顺序

#include <iostream>
using namespace std;
class fat
{
public:
    int c;
    fat()
    {cout<<"fat的构造函数"<<endl;}
    ~fat()
    {cout<<"fat的析构函数"<<endl;}
};
class son:public fat
{
public:
    son()
    {cout<<"son的构造函数"<<endl;}
    ~son()
    {cout<<"son的析构函数"<<endl;}

};
int main(int argc, char *argv[])
{
    son caicai;
}

fat的构造函数
son的构造函数
son的析构函数
fat的析构函数

构造是从父类向下构造,析构是子类向上依次构造,注意复合语句

子类调用成员对象、父类的有参构造

子类实例化对象时 会自动调用 成员对象、父类的默认构造。
子类实例对象时 必须使用初始化列表 调用成员对象、父类的有参构造。
初始化列表时:父类写类名称 成员对象用对象名

#include <iostream>
using namespace std;
class gra
{
public:
    int a;
    gra()
    {cout<<"gra的构造函数"<<endl;}
    gra(int &a)
    {this->a=a;cout<<"gra的构造函数"<<endl;}
    ~gra()
    {cout<<"gra的析构函数"<<endl;}
};

class fat
{
public:
    int b;
    fat()
    {cout<<"fat的构造函数"<<endl;}
    fat(int &b)
    {this->b=b;cout<<"fat的构造函数"<<endl;}
    ~fat()
    {cout<<"fat的析构函数"<<endl;}
};
class son:public fat   //父类是fat
{
public:
    gra ob;   //包含了成员对象
    int c;

    son()
    {cout<<"son的构造函数"<<endl;}
    son(int a,int b,int c):ob(a),fat(b)  //调用有参构造必须初始化列表,父类写类名称 成员对象用对象名
    {   this->c=c;
        cout<<"son的构造函数"<<endl;
        cout<<a<<"  "<<b<<"  "<<c<<endl;
    }
    ~son()
    {cout<<"son的析构函数"<<endl;}

};
int main(int argc, char *argv[])
{
    son caicai(23,45,67);
}

子类和父类的同名处理

同名成员 最简单 最安全的处理方式:加作用域

不加作用域

子类默认优先访问 子类的同名成员

必须加父类作用域 访问父类的同名成员

#include <iostream>
using namespace std;
class fat
{
public:
    int c;
    fat()
    {cout<<"fat的构造函数"<<endl;}
    fat(int a)
    {   c=a;
        cout<<"fat的有参函数"<<endl;}
    ~fat()
    {cout<<"fat的析构函数"<<endl;}
};
class son:public fat
{
public:
    int c;
    son()
    {cout<<"son的构造函数"<<endl;}
    son(int a,int b):fat(a)
    {   c=b;
        cout<<"son的有参构造"<<endl;}
    ~son()
    {cout<<"son的析构函数"<<endl;}

};
int main(int argc, char *argv[])
{
    son caicai(12,34);
    cout<<caicai.c<<endl;  //子类默认优先访问 子类的同名成员
    cout<<caicai.fat::c<<endl;//必须加父类作用域 访问父类的同名成员
}

子类和父类 同名成员函数

#include <iostream>
using namespace std;
class fat
{
public:
        void fun(){
        cout<<"hello im fat"<<endl;
        }

};
class son:public fat
{
public:
    void fun(){
    cout<<"hello im son"<<endl;
    }

};
int main(int argc, char *argv[])
{
    son caicai;
    caicai.fun();//子类默认优先访问 子类的同名函数
    caicai.fat::fun();//必须加父类作用域 访问父类的同名函数
}

子类 重定义 父类的同名函数

重载:无继承,同一作用域,参数的个数不同、顺序不同、类型不同 都可重载
重定义:有继承, 子类 重定义 父类的同名函数(参数可以不同)(非虚函数)子类一旦 重定义了父类的同名函数(不管参数是否一致),子类中都将屏蔽父类所有的同名函数。

#include <iostream>
using namespace std;
class fat
{
public:
    void fun(){
    cout<<"hello im fat"<<endl;
    }
    void fun(int a){
    cout<<a<<"hello im fat"<<endl;
    }
    void fun(int a,int b){
    cout<<a*b<<"hello im fat"<<endl;
    }
    void fun2(int a,int b){
    cout<<a*b<<"hello im fat"<<endl;
    }
};
class son:public fat
{
public:
    void fun(){
    cout<<"hello im son"<<endl;
    }

};
int main(int argc, char *argv[])
{
    son caicai;
    caicai.fun();//子类默认优先访问 子类的同名函数
    //caicai.fun(10,20);不识别父类的同名函数
    caicai.fun2(10,20);//不同名的函数可以识别
    caicai.fat::fun(2,5);//必须加父类作用域 才能父类的同名函数
    caicai.fat::fun();//必须加父类作用域 访问父类的同名函数
}

子类不能继承父类的成员

不是所有的函数都能自动从基类继承到派生类中。比如构造函数和析构函数,operator=也不能被继承。

多继承

我们可以从一个类继承,我们也可以能同时从多个类继承,这就是多继承。但是由于多继承是非常受争议的,从多个类继承可能会导致函数、变量等同名导致较多的歧义。C++实际开发中不建议用多继承

格式:

class 父类1{};
class 父类2{};
class 子类:继承方式1 父类1, 继承方式2 父类2
{
//新增子类数据
};

多继承中的同名成员处理

如果多继承中 遇到同名成员 需要加父类作用域解决。

#include <iostream>
using namespace std;
class gra
{
public:
        void fun(void)
        {cout<<"gra"<<endl;}
};
class fat
{
public:
    void fun(void)
    {cout<<"fat"<<endl;}
};
class son:public fat,public gra  //son有两个父类
{
public:
    void fun(void)
    {cout<<"son"<<endl;}
};
int main(int argc, char *argv[])
{
    son caicai;
    caicai.fun();//先访问 子类的同名函数
    caicai.fat::fun();//加父类作用域 才能父类的同名函数
    caicai.gra::fun();//加父类作用域 才能父类的同名函数
}

菱形继承

菱形继承:有公共祖先的继承 叫菱形继承。
最底层的子类 数据 会包含多份(公共祖先的数据)

#include <iostream>
using namespace std;
class gra
{public:int a;};
class fat:public gra{};
class son:public gra{};
class cai:public son,public fat{};
int main(int argc, char *argv[])
{
    cai kunkun;
    //kunkun.a=10;ambiguous,有歧义,因为两个父类中都有a
    kunkun.fat::a=10;   
    cout<<kunkun.fat::a<<endl;
}

虚继承

虚继承 解决 菱形继承中 多份公共祖先数据的问题。

在继承方式 前加virtual修饰,子类虚继承父类 子类只会保存一份公共数据。

#include <iostream>
using namespace std;
class gra
{public:int a;};
class fat:virtual public gra{};//前加virtual修饰,子类虚继承父类 子类只会保存一份公共数据。
class son:virtual public gra{};//继承前加virtual关键字后,变为虚继承,此时公共的父类称为虚基类
class cai:public son,public fat{};
int main(int argc, char *argv[])
{
    cai kunkun;
    kunkun.a=10;
    cout<<kunkun.a<<endl;
}

多态

多态是C++面向对象三大特性之一

多态分为两类

  • 静态多态: 函数重载 和 运算符重载属于静态多态,重定义
  • 动态多态: 虚函数

静态多态和动态多态区别:

  • 静态多态的函数地址早绑定 - 编译阶段确定函数地址
  • 动态多态的函数地址晚绑定 - 运行阶段确定函数地址

虚函数

父类指针(引用)保存 子类空间地址的目的 就是让算法通用(操作派生出的所有子类)。

父类指针 保存 子类空间地址有一个问题,父类指针调用成员函数实际没有调用子类的成员函数。

#include<iostream>
using namespace std;
class Gra
{
public:
    void fun(void)
    {cout<<"im Gra"<<endl;}
};
class Fat:public Gra
{
public:
    void fun(void)
    {cout<<"im fat"<<endl;}
};
int main()
{   Gra *caicai=new Fat;//父类指针指向子类对象
    caicai->fun();      //但是父类指针调用函数调用的父类函数im Gra
    return 0;
}

虚函数:成员函数前加virtual修饰,解决这一问题。

#include<iostream>
using namespace std;
class Gra
{
public:
    virtual  void fun(void)
    {cout<<"im Gra"<<endl;}
};
class Fat:public Gra
{
public:
    virtual void fun(void)   //子类重写 父类的虚函数:函数名、返回值类型、参数类型个数顺序 必须完全一致
    {cout<<"im fat"<<endl;}
};
class Mot:public Gra
{
public:
    virtual void fun(void)   //子类重写 父类的虚函数:函数名、返回值类型、参数类型个数顺序 必须完全一致
    {cout<<"im mot"<<endl;}
};
int main()
{   Gra *caicai=new Fat;//父类指针指向子类对象
    caicai->fun();//im fat,实际调用了子类的成员函数
    Gra *kunkun=new Mot;
    kunkun->fun();//im mot,实际调用了子类的成员函数
    return 0;
}

多态条件:有继承、子类重写父类的虚函数,父类指针 指向子类空间。

虚函数的动态绑定机制

如果一个类中的成员函数 被virtual修饰,那么这个函数就是虚函数。类就会产生一个虚函数指针(vfptr)指向了一张虚函数表(vftable)。如果这个类 没有涉及到继承, 这时虚函数表中 纪录就是当前虚函数入口地址。

一旦子类重写父类虚地址,就会蒋子类的覆盖虚函数表中原来的入口地址。

重载、重定义、重写的区别

  • 重载:同一作用域,同名函数,参数的顺序、个数、类型不同 都可以重载。函数的返回值类型不能作为,重载条件(函数重载、运算符重载)。
  • 重定义:有继承,子类 重定义 父类的同名函数(非虚函数), 参数顺序、个数、类型可以不同。子类的同名函数会屏蔽父类的所有同名函数(可以通过作用域解决)
  • 重写(覆盖)有继承,子类 重写 父类的虚函数。返回值类型、函数名、参数顺序、个数、类型都必须一致。

纯虚函数

如果基类一定派生出子类,而子类一定会重写父类的虚函数,那么父类的虚函数中的函数体感觉是无意义,可不可以不写父类虚函数的函数体呢?可以的,那就必须了解纯虚函数。

#include<iostream>
using namespace std;
class Gra   //有 纯虚函数的类 为抽象类
{
public:
    virtual  void fun(void)=0;//纯虚函数
};
class Fat:public Gra
{
public:
    virtual void fun(void)   //子类一定要重写 父类的所有纯虚函数
    {cout<<"im fat"<<endl;}
};
class Mot:public Gra
{
public:
    virtual void fun(void)   //子类一定要重写 父类的所有纯虚函数
    {cout<<"im mot"<<endl;}
};
int main()
{   //Gra pb;抽象类不能实例化对象
    Gra *caicai=new Fat;//父类指针指向子类对象
    caicai->fun();//im fat,实际调用了子类的函数
    Gra *kunkun=new Mot;
    kunkun->fun();//im mot,实际调用了子类的函数
    return 0;
}

抽象类主要的目的 是设计 类的接口:

一个例子

#include<iostream>
using namespace std;
//抽象制作饮品
class AbstractDrinking {
public:
    //烧水
    virtual void Boil() = 0;
    //冲泡
    virtual void Brew() = 0;
    //倒入杯中
    virtual void PourInCup() = 0;
    //加入辅料
    virtual void PutSomething() = 0;
    //规定流程
    void MakeDrink() {
        Boil();
        Brew();
        PourInCup();
        PutSomething();
    }
};
//制作咖啡
class Coffee : public AbstractDrinking {
public:
    //烧水
    virtual void Boil() {
        cout << "煮农夫山泉!" << endl;
    }
    //冲泡
    virtual void Brew() {
        cout << "冲泡咖啡!" << endl;
    }
    //倒入杯中
    virtual void PourInCup() {
        cout << "将咖啡倒入杯中!" << endl;
    }
    //加入辅料
    virtual void PutSomething() {
        cout << "加入牛奶!" << endl;
    }
};
//制作茶水
class Tea : public AbstractDrinking {
public:
    //烧水
    virtual void Boil() {
        cout << "煮自来水!" << endl;
    }
    //冲泡
    virtual void Brew() {
        cout << "冲泡茶叶!" << endl;
    }
    //倒入杯中
    virtual void PourInCup() {
        cout << "将茶水倒入杯中!" << endl;
    }
    //加入辅料
    virtual void PutSomething() {
        cout << "加入枸杞!" << endl;
    }
};
//业务函数
void DoWork(AbstractDrinking* drink) {
    drink->MakeDrink();
    delete drink;
}

void test01() {
    DoWork(new Coffee);
    cout << "--------------" << endl;
    DoWork(new Tea);
}


int main() {

    test01();

    system("pause");

    return 0;
}

虚函数和纯虚函数的区别

  • 虚函数:virtual修饰 有函数体 不会导致父类为抽象类。
  • 纯虚函数:virtual修饰,=0,没有函数体 导致父类为抽象类。子类必须重写父类的所有纯虚函数。

虚析构函数

  • virtual修饰析构函数
  • 虚析构:通过父类指针 释放整个子类空间
#include<iostream>
using namespace std;
class Gra   //有 纯虚函数的类 为抽象类
{
public:
    virtual  void fun(void)=0;//纯虚函数
    virtual ~Gra()            //虚析构,不加virtual 只会调用父类的析构函数,不会调用子类的析构函数
    {
        cout<<"Gra的析构函数"<<endl;
    }
};
class Fat:public Gra
{
public:
    virtual void fun(void)   //子类一定要重写 父类的所有纯虚函数
    {cout<<"im fat"<<endl;}
    ~Fat()
    {
        cout<<"Fat的析构函数"<<endl;
    }
};
class Mot:public Gra
{
public:
    virtual void fun(void)   //子类一定要重写 父类的所有纯虚函数
    {cout<<"im mot"<<endl;}
    ~Mot()
    {
        cout<<"Mot的析构函数"<<endl;
    }
};
int main()
{
    Gra *caicai=new Fat;//父类指针指向子类对象
    caicai->fun();
    delete caicai;  //释放全部空间,
    return 0;
}
  • 构造的顺序:父类—>成员---->子类
  • 析构的顺序:子类—>成员---->父类

纯虚析构函数

纯虚析构的本质:是析构函数,各个类的回收工作。而且析构函数不能被继承。
必须为纯虚析构函数提供一个函数体。
纯虚析构函数 必须在类外实现.

#include<iostream>
using namespace std;
class Gra   //有 纯虚函数的类 为抽象类
{
public:
    virtual  void fun(void)=0;//纯虚函数
     virtual ~Gra()=0;            //虚析构
};
Gra::~Gra(){    //函数体在类外,必须加作用域
   cout<<"Gra的析构函数"<<endl;
}
class Fat:public Gra
{
public:
    virtual void fun(void)   //子类一定要重写 父类的所有纯虚函数
    {cout<<"im fat"<<endl;}
    ~Fat()
    {
        cout<<"Fat的析构函数"<<endl;
    }
};
class Mot:public Gra
{
public:
    virtual void fun(void)   //子类一定要重写 父类的所有纯虚函数
    {cout<<"im mot"<<endl;}
    ~Mot()
    {
        cout<<"Mot的析构函数"<<endl;
    }
};
int main()
{
    Gra *caicai=new Fat;//父类指针指向子类对象
    caicai->fun();
    delete caicai;
    return 0;
}

虚析构和纯虚析构的区别

  • 虚析构:virtual修饰,有函数体,不会导致父类为抽象类。
  • 纯虚析构:virtual修饰,=0,函数体必须类外实现,导致父类为抽象类。
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值