C++快速讲解(七):继承和多态

本文深入介绍了C++中的继承和多态概念,包括继承入门、公共/保护/私有继承、构造与析构、重写同名函数、多重继承、类的前置声明、静态与动态多态、联编机制、虚函数、覆盖关键字、最终关键字、删除与默认操作符、纯虚函数和抽象类。通过实例解析了各种特性及其在实际编程中的应用。
摘要由CSDN通过智能技术生成


前言:主要讲解了继承和多态的用法。


1.继承入门

#include <iostream>
#include <string>

using namespace std;

//父类
class Father{
public:
    string name;
    int age;
};

//子类
class Son: public Father{

};

int main() {
    //子类虽然没有声明name 和 age ,但是继承了Father类,等同于自己定义的效果一样
    Son s;
    s.name = "张三";
    s.age = 18;

    cout << s.name << " = " << s.age << endl;
    return 0;
}

在这里插入图片描述

2.public、protected、private继承

  • public:表示公有成员,该成员不仅可以在类内可以被访问,在类外也是可以被访问的,是类对外提供的可访问接口;

  • private:表示私有成员,该成员仅在类内可以被访问,在类的外面无法访问;

  • protected:表示保护成员,保护成员在类的外面同样是隐藏状态,无法访问。但是可以在子类中访问。

3.构造和析构

3.1 继承状态

  • 子类对象在创建时会首先调用父类的构造函数;

  • 父类构造函数执行完毕后,执行子类的构造函数;

  • 当父类的构造函数中有参数时,必须在子类的初始化列表中显示调用;

  • 析构函数执行的顺序是先调用子类的析构函数,再调用父类的析构函数

#include <iostream>
#include <string>
//构造和析构  继承状态
using namespace std;

class Father{
public:
    string name;
    int age;

    Father(){
        cout<<"调用了父类构造"<<endl;
    }

    ~Father(){
        cout<<"调用了父类析构"<<endl;
    }
};

class Son: public Father{
public:
    Son(){
        cout<<"调用了子类构造"<<endl;
    }

    ~Son(){
        cout<<"调用了子类析构"<<endl;
    }
};

int main() {
    Son s;
    return 0;
}

在这里插入图片描述

3.2 继承和组合

  • 先调用父类的构造函数,再调用组合对象的构造函数,最后调用自己的构造函数;
  • 先调用自己的析构函数,再调用组合对象的析构函数,最后调用父类的析构函数。
#include <iostream>
#include <string>
//构造和析构  继承和组合
using namespace std;

class Father{
public:
    string name;
    int age;

    Father(){
        cout<<"调用了父类构造"<<endl;
    }

    ~Father(){
        cout<<"调用了父类析构"<<endl;
    }
};

class Other{
public:
    Other(){
        cout<<"调用了Other构造"<<endl;
    }

    ~Other(){
        cout<<"调用了Other析构"<<endl;
    }
};

class Son: public Father{
public:
    Son(){
        cout<<"调用了子类构造"<<endl;
    }

    ~Son(){
        cout<<"调用了子类析构"<<endl;
    }
    Other o;
};

int main() {
    Son s;
    return 0;
}

在这里插入图片描述

3.3 调用父类有参构造

  • 继承关系下,子类的默认构造函数会隐式调用父类的默认构造函数,假设父类没有默认的无参构造函数,那么子类需要使用参数初始化列表方式手动调用父类有参构造函数。
  • 一般来说在创建子类对象前,就必须完成父类对象的创建工作,也就是在执行子类构造函数之前,必须先执行父类的构造函数。
#include <iostream>
#include <string>
//构造和析构  调用父类有参构造
using namespace std;

class Father{
private:
    string name;
    int age;
public:
    Father(int age,string name){
        cout<<"调用了父类构造"<<endl;
        this->name = name;
        this->age = age;
    }

};


class Son: public Father{

    //子类只能使用初始化列表的方式来访问父类构造
public:
    Son(int age,string name):Father(age,name){
        cout<<"调用了子类构造"<<endl;
    }
};

int main() {
    Son s(20,"fly");
    return 0;
}

在这里插入图片描述

3.4 初始化列表

常量和引用的情况

#include <iostream>
#include <string>
//常量和引用的情况
using namespace std;

class Father{
public:
    const string name;
    int &age;

    Father(string name,int age):name(name),age(age){
        cout<<"调用了构造"<<endl;
    }

};



int main() {
    Father f("fly",20);
    cout << f.name << " = " << f.age << endl;
    return 0;
}

初始化对象成员

#include <iostream>
#include <string>

using namespace std;

class Father{
public:
    int age;
    Father(int age):age(age){
        cout<<"调用了父类构造"<<endl;
    }

};

class Son{

public:
    Father f;
    Son():f(10){
        cout<<"调用了Son类构造"<<endl;
    }
};

int main() {
    Son s;
    cout<<s.f.age<<endl;
    return 0;
}

4.重写父类同名函数

#include <iostream>
#include <string>
//重写父类同名函数
using namespace std;

class Father{
public:

    void smoke(){
        cout<<"抽普通烟"<<endl;
    }

};


class Son: public Father{

public:
    void smoke(){
        Father::smoke();
        cout<<"抽高级烟"<<endl;
    }
};

int main() {
    Son s;
    s.smoke();
    return 0;
}

在这里插入图片描述

5.多重继承

#include <iostream>
#include <string>
//多重继承
using namespace std;

class Father{
public:
    void makeMoeny(){
        cout << "赚钱" << endl;
    }
};
class Mother{
public:
    void makeHomeWork(){
        cout << "做家务活" << endl;
    }
};

class Son:public Father , public Mother{

};

int main(){

    Son s ;
    s.makeMoeny();
    s.makeHomeWork();

    return 0 ;
}

在这里插入图片描述

6.类的前置声明

#include <iostream>
#include <string>
//类的前置声明
using namespace std;

class father; //所有前置声明的类,在某个类中定义的时候,只能定义成引用或者指针。

class son{
public:
    //father f0; //因为这行代码,单独拿出来说,会执行B类的无参构造,
    //但是编译器到此处的时候,还不知道B这个类的构造长什么样。
    father &f1;
    father *f2;

    son(father &f1 , father *f2):f1(f1),f2(f2){

    }
};


class father{

};


int main(){

    //  father b; //---> 执行B的构造函数。
    father f1;
    father f2;

    son s(f1 ,&f2);

    return 0 ;
}

7.多态

7.1 静态多态

静态多态是编译器在编译期间完成的,编译器会根据实参类型来选择调用合适的函数,如果有合适的函数可以调用就调,没有的话就会发出警告或者报错 。

#include <iostream>
#include <string>
//静态多态
using namespace std;

int Add(int m, int n){
    return m+n;
}

double Add(double  m, double  n){
    return m+n;
}

int main() {
    Add(10,20);
    Add(10.2,20.2);
    return 0;
}

7.2 动态多态

它是在程序运行时根据父类的引用(指针)指向的对象来确定自己具体该调用哪一个类的虚函数。

#include <iostream>
#include <string>

using namespace std;


class Father{

public:
    virtual void smoke(){ //加上virtual
        cout << "father smoke" << endl;
    }
};

class Son : public Father{

public:
    void smoke(){
        cout << "son smoke" << endl;
    }
};


int main() {

    Father f;
    f.smoke(); //父亲的抽烟

    Son s ;
    s.smoke(); //孩子的抽烟

    Father *f2 = new Son();//父类指针指向子类对象
    f2->smoke();

    return 0;
}

在这里插入图片描述

8.联编机制

8.1 静态类型和动态类型

  • 静态类型:不需要运行,编译状态下即可知晓具体的类型
  • 动态类型:只有真正运行代码了,才能知晓具体的类型

8.2 父类指针指向子类对象

#include<iostream>

using namespace std;

class father{
public:
    void show(){
        cout << "father show" << endl;
    }
};

class children : public father{
public:
    void show(){
        cout << "children  show" << endl;
    }
};

int main(){
    father f = children();
    f.show(); // 打印father show

    return 0 ;
}

8.3 静态联编和动态联编

#include<iostream>

using namespace std;

class WashMachine{
public:
    void wash(){
        cout << "洗衣机在洗衣服" << endl;
    }
};


class SmartWashMachine : public WashMachine{
public:
    void wash(){
        cout << "智能洗衣机在洗衣服" << endl;

    }

};

int main(){
    WashMachine *w1= new WashMachine(); //父类指针指向父类对象  打印:洗衣机在洗衣服
    w1->wash();

    SmartWashMachine *s  = new SmartWashMachine();  //子类指针指向子类对象 打印: 智能洗衣机...
    s->wash();

    WashMachine *w2 = new SmartWashMachine(); //父类指针指向子类对象 打印..洗衣机在洗衣服
    w2->wash();

    return 0 ;
}

9.虚函数

9.1 虚函数入门

#include <iostream>
#include <string>
//虚函数
using namespace std;

class WashMachine{
public:
    virtual void wash(){
        cout << "洗衣机在洗衣服" << endl;
    }
};

class SmartWashMachine : public WashMachine{
public:
    virtual  void wash(){
        cout << "智能洗衣机在洗衣服" << endl;

    }
};

int main(){

    WashMachine *w2 = new SmartWashMachine(); //父类指针指向子类对象 打印..洗衣机在洗衣服
    w2->wash();
    return 0 ;
}

在这里插入图片描述

9.2 虚函数的工作原理

  • 通常情况下,编译器处理虚函数的方法是: 给每一个对象添加一个隐藏指针成员,它指向一个数组,数组里面存放着对象中所有函数的地址。这个数组称之为虚函数表(virtual function table ) 。表中存储着类对象的虚函数地址。
  • 父类对象包含的指针,指向父类的虚函数表地址,子类对象包含的指针,指向子类的虚函数表地址。
  • 如果子类重新定义了父类的函数,那么函数表中存放的是新的地址,如果子类没有重新定义,那么表中存放的是父类的函数地址。

9.3 构造函数不可以是虚函数

构造函数不能为虚函数 , 因为虚函数的调用,需要虚函数表(指针),而该指针存在于对象开辟的空间中,而对象的空间开辟依赖构造函数的执行。

9.4 析构函数可以是虚函数

#include <iostream>
#include <string>

using namespace std;

class father{

public:
    virtual ~father(){
        cout << "执行父类析构函数" << endl;
    }
};


class son : public father{
    ~son(){
        cout << "执行子类析构函数" << endl;
    }
};

int main(){

    father *f = new son(); //父类指针指向子类对象

    //创建的是子类对象,理应执行子类的析构函数
    delete f;

    return 0 ;
}

10.override 关键字

#include <iostream>
#include <string>

using namespace std;

class father{
public:
    virtual void run(){
        cout << "父亲在跑步" << endl;
    }
};


class son : public father{
public:
    virtual  void run() override{ //表示重写父类的函数
        cout << "孩子在跑步" << endl;
    }
};

int main(){

    father *f = new son(); //父类指针指向子类对象
    return 0 ;
}

11.final 关键字

#include <iostream>
#include <string>

using namespace std;

class person final{ //表示该类是最终类,无法被继承
    virtual void run() final{ //表示该方法时最终方法,无法被重写

    }
};


//编译错误,无法被继承。
class student : public person{
    //编译错误,方法无法被重写。
    void run(){

    }
};

int main(){
    
    return 0 ;
}

12.=delete 和 =default

#include <iostream>
#include <string>

using namespace std;

class stu{

    string name;
    int age;

public:
    stu(string name , int age):name(name) , age(age){
        cout << "执行stu的构造函数" << endl;
    }
    
    //表示禁止调用拷贝构造函数
    stu(stu & s) =delete;


    ~stu(){
        cout << "执行stu的析构函数" << endl;
    }
};

int main(){

    stu s("张三",18) ;

    //编译错误。
    stu s1= s;

    return 0 ;
}
#include <iostream>
#include <string>

using namespace std;

class stu{

    string name;
    int age;

public:

    stu() = default;

    stu(string name , int age):name(name) , age(age){
        cout << "执行stu的构造函数" << endl;
    }

    ~stu(){
        cout << "执行stu的析构函数" << endl;
    }
};

int main(){

    stu s("张三",18) ;

    //编译错误。
    stu s1 = s;

    return 0 ;
}

13.纯虚函数

#include <iostream>
#include <string>
//纯虚函数
using namespace std;

class WashMachine{
public:
    //没有函数体,表示洗衣机能洗衣服,但是具体怎么洗,每个品牌不一样
    virtual void wash() = 0;
};

class HaierMachine:public WashMachine{
public :
    virtual void wash(){
        cout << "海尔牌洗衣机在洗衣服" << endl;
    }
};

class LittleSwanMachine:public WashMachine{
public :
    virtual void wash(){
        cout << "小天鹅洗衣机在洗衣服" << endl;
    }
};

int main(){

    //WashMachine w;  错误,抽象类无法创建对象
    WashMachine *w1 = new HaierMachine() ;
    WashMachine *w2 = new LittleSwanMachine() ;

    return 0 ;
}

14.抽象类和接口

所谓接口,其实就是用于描述行为和功能,并不会给出具体的实现。C++中没有提供类似interface 这样的关键字来定义接口 , 纯虚函数往往承担起了这部分功能,可以看成是对子类的一种约束。

例如:

class Person{
      Person() =default; // 可以用于初始化成员函数
      virtual ~Person()=default; //防止子类析构函数无法被调用问题


      //每个人吃什么,做什么都不一样,,即可声明为纯虚函数
      virtual void eat() = 0 ;
      virtual void work() = 0 ;
      //...

};

结束!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

等待着冬天的风

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值