2020, Aug.11类的继承

类的继承

  1. 类的继承
  • is a kind of关系

    自然界存在一种关系,A是一种B

    其中,A是一种类型,B是一种类型

  • 由C++构成的抽象世界也存在 is a kind of的关系

    class Tutorial {
        
    };
    class VideoTutorial: public Tutorial {
        
    };
    // 语法:class B : public A {}
    // 表示类B继承于类A,把A称为父类(基类),把B称为子类(派生类)
    
  • 当B继承于A时,则自动的将父类中的所有public成员继承。

    class Tutorial {
    public:
        char name[32];
        char author[16];
    public:
        void ShowInfo()
        {
            printf("Tutorial: %s, %s \n"name,aithor)
        }
    };
    // 则在VideoTutorial类中也具有了这些成员,而不必显式写出。
    
  • Main

    int main()
    {
        VideoTutorial_cpp_guide;
    strcpy(cpp.guide.name,"C/C++学习");
        strcpy(cpp.guide.author,"Jiang Weibin");
        cpp.guide.ShowInfo();
        return 0;
    }
    
  1. 继承
  • 子类只需要把自己的独有的那部分特性写出来

    class VideoTurtorial : public Turtorial {
    public:
        void Play()  // 播放
        {
            printf("Playing...");
        }
    public:
        char url[128]; // 在线观看的URL地址
        int visits; // 播放量
    };
    
  • 访问修饰符protected

    在描述继承关系时,新增一种访问修饰符protected(受保护的)

    当一个类成员被修饰为protected的时候,有以下规则成立:

    (1)该成员不能被外部访问,同private

    (2)该成员可以被子类继承,同public

    所以,public和protected的成员都能够被子类继承

  • 举例:将父类的成员变量声明为protected

    class Turtorial
    {
    protected:
        char name[32];
        char author[16];
    public:
        void ShowInfo();
    }
    
  • 问题1

    在内存上描述父类和子类的关系:子类对象的前半部分就是父类对象

    class Parent {
    public:
        
        int a;
    private:
        int ddd;
    };
    class Child : public Parent {
    public:
        int b;
    };
    int main()
    {
    	Child c;
        c.a = 0x11111111;
        c.b = 0x22222222;
        return 0;
    }
    // (1) 用sizeof验证
    // (2) 在内存窗口中直接观测
    // 结论,内存上面从父类继承的成员变量放在前面,自己的成员变量放在后面
    
  • 问题2:父类的private成员变量也会出现在内存中么?

    回答:是的,父类的所有成员变量都在子类对象中,只是编译器限制了访问。

  • 小结:

    1. 用class B : class A {}表示B继承于A
    2. 当B继承于A后,父类的所有protected/public成员都被继承
    3. 什么叫被继承?就是这些父类的成员就如同直接写在子类里一般。
    4. 继承可以使代码简化
  1. 虚拟继承:虚函数virtual
  • 子类可以重写从父类继承而来的函数(overwriting)

    class Parent {
    public:
        void Test()
        {
            printf("Parent:...\n");
        }
    };
    class Child : public Parent {
    public:
        void Test()
        {
            printf("Child:...\n");
        }
    };
    
  • 如果重写的时候,还是要嵌入调用一下父类的函数,怎么办?

    void Child::Test()
    {
        // 解决方法:显式地调用父类的函数
        Parent::Test();
        printf("Child:...\n");
    }
    
  • 父类指针指向子类对象

    可以将父类指针指向子类的对象,这是完全允许的。

    // 左侧为Tree*,右侧为AppleTree*
    Tree* p = new AppleTree;
    

    从普通的逻辑来讲,苹果树是一种树,因而可以吧AppleTree*视为一种Tree*

    从语法本质上讲,子类对象的前半部分就是父类,因而可以将子类对象的指针直接转化为父类

  • 问题:考虑以下情况

    Parent* p = new Child();
    p->Test();
    

    那么,此时调用的Test()是父类的、还是子类的?以下两种观点:

    (1)指针p的类型是Parent* ->父类Test

    (2)指针p指向的对象是Child*->子类Test

    调用者的初衷:因为p指向的对象是子类对象,所以应该调用子类的Test()。

    结果:观点(1)被实现,调用了父类的函数Test()

  • 虚拟继承:virtual

    当一个成员函数需要子类重写,那么在父类应该将其声明为virtual(子类virtual自动继承,可写可不写)

    virtual本身表明该函数即将被子类重写

  • 加virtual关键字是必要的

    考虑以下情况

    // 语法允许,合乎情理
    Patent* obj = new Child();
    p->Test();
    

    此时,如果Test()在父类中被声明为virtual,是调用的是子类的Test()。

    这解释了virtual的作用,根据对象的实际类型,调用相应类型的函数

  • 注意:

    1. 只需要在父类中将函数声明为vitual,子类自动地就是 virtual了
    2. 即将被重写的函数添加virtual,是一条应该遵守的编码习惯
  • 小结:

    1. 介绍继承关系中,对函数重写后的结果
    2. 介绍virtual关键字的作用和必要性(父类指针指向子类对象)
  1. 继承:构造与析构
  • 有Child类继承于Parent类

    class Child : public Parent {}

    那么,当创建一个子类对象时:(编译器默认动作)

    子类对象构造时,先调用父类的构造函数,再调用子类的构造函数。

    子类对象析构时,先调用子类的析构函数,再调用父类的构造函数。

  • 当父类有多个构造函数时,可以显式地调用其中的一个构造函数

    如果没有显示调用,则调用父类的"默认构造函数"

    记住调用方法:Parent(1,1)

  • virtual析构函数

    当一个类被继承时,应该将父类的析构函数声明为virtual,否则会有潜在的问题

    class Parent {
        // 声明为virtual
        virtual ~Parent() {}
    }int main()
    {
        Parent* p = new Child();
        delete p; //此时,调用的是谁的析构函数
        return 0;
    }
    // 如果析构函数没有标志为virtual,则有潜在的隐患,并有可能直接导致程序崩溃(资源没有被释放,并引申一系列问题)
    
  • 类的大小,与virtual关键字的影响

    1. 类的大小由成员变量决定。(与struct的原理相同)

      类的大小与成员函数的个数无关,即使一个类有10000个成员函数,对它所占的内存空间是没有影响的。

    2. 但是,如果有一个成员函数被声明为virtual,那类的大小会有些微的变化。(这个变化由编译器决定,一般是增加了4个字节,多加了一个指针)

  • 小结:

    1. 继承关系中,父类的构造函数和析构函数将被调用。

    2. 当一个类被别的类继承时,应该将父类的析构函数声明为virtual。

      (注:如果这个类在设计的时候,已经明确它不会被继承,则不需要声明为virtual)

  1. 多重继承
  • 定义这个语法的本意:一个孩子有父有母,可以从父母处各自继承一些特点

  • 语法:

    // 用Father, Mother表示两个类
    class Child : public Father, public Mother {};
    

    表示Child继承于Father,Mother

    在写法上,以冒号引导,每个父类用逗号隔开

  • 多重继承的结果:从所有父类中,继承他们所有可以被继承的成员(public/protected)

  • 多重继承的问题:当多个父类有相同的成员时,会影响冲突。多重继承一般不会被用到,多重继承在纯虚函数中可以发挥作用。

  • 小结:

    1. 多重继承的常见应用场景:纯虚函数

    2. 语法:

    class Child : public Parent1, public Parent2 {};
    ```



5. **纯虚函数与纯虚类**

+ 与设计模式中的接口概念有关

+ 纯虚函数的语法

 1. 将成员函数声明为virtual
 2. 后面加上 = 0 ("纯")
 3. 该函数没有函数体

 ```cpp
 class CmdHandler {
 public:
     virtual void OnCommand(char* cmdline) = 0;
 }
  • 含有纯虚函数的类,称为抽象类(Abstract Class)(或称纯虚类)。

    如:CmdHandler中有一个纯虚函数OnCommand(),因此,它是纯虚类。

    抽象类不能够被实例化,即无法创建该对象。

    CmdHandler ch; // 编译错误!
    CmdHandler* p = new CmdHandler(); // 编译错误!
    // 不能被实例化,还定义这个类有什么用?! 
    
  • 抽象类的实际作用:充当”接口规范“

    (相当于Java中的interface语法)

    (用于替代C中的回调函数的用法)

    接口规范:凡是遵循此规范的类,都必须实现指定的函数接口。通常是一系列接口。

  • 比如,

    class CmdHandler
    
    {
    public:
        virtual void OnCommand(const char* cmdline) = 0;
    };
    
    可以理解为:凡是遵循CmdHandler规范的类,都必须实现指定的函数接口:OnCommand()
    
  • 实例:

    项目需求:用户输入一行命令,按回车完成输入。要求解析命令输入,并且处理。

    设计:

    ​ CmdInput:用于接收用户输入

    ​ CmdHandler:规定一系列函数接口

    ​ MyParser:实际用于解析处理的类

     main.cpp //
    #include"CmdInput.h"
    #include"MyParser.h"
    
    int main()
    {
        CmdInput input; // 实现输入
        MyParser parser;// 实现接口规范
      	// 把接口规范传给input,实现联调
        input.SetHandler(&parser);
        
        input.Run();
        
        return 0;
    }
    
    //CmdInput.h/
    #ifndef _CMD_INPUT_H
    #define _CMD_INPUT_H
    
    #include "CmdHandler.h"
    
    class CmdInput {
    public:
        CmdInput();
        void SetHandler(CmdHandler* h);
        // 开始运行
        int Run();
        
    private:
        CmdHandler* m_handler;
    }
    
    //CmdInput.cpp//
    void CmdInput::SetHandler(CmdHandler* h)
    {
        m_handler = h;
    }
    int CmdInput::Run()
    {
        char cmdline[256];
        while(1)
        {
            // 输入
            printf(">");
            gets(cmdline);
            // 退出
           if(strcmp(cmdline,"exit") == 0)
            {
                break;
            }
            // 解析与执行
            if(m_handler)
            {
                m_handler->OnCommand(cmdline);
            }
        }
        return 0;
    }
    
    //CmdHandler.h//
    #ifndef _CMD_HANDLER_H
    #define _CMD_HANDLER_H
    
    /* CmdHandler:
       接口类
    */
    
    class CmdHandler
    {
    public:
        ~CmdHandler() {} // 析构函数声明为 virtual
        virtual void OnCommand(char* cmdline) = 0; // 纯虚函数
    };
    
    #endif
    
    ///MyParser.h///
    #ifndef _MY_PARSER_H
    #define _MY_PARSER_H
    
    #include "CmdHandler.h"
    
    /* MyParser:
    	一个遵循了CmdHandler接口的类
    */
    class MyParser : public CmdHandler
    {
    public:
        MyParser();
        
    public:
        // 函数接口集
        virtual void OnCommand(char* cmdline);
    
    private:
        // 解析命令
        int Split(char text[],char* parts[])
    };
    ///MyParser.cpp/
    (补全)
    
    void MyParser::OnCommand(char* cmdline)
    {
        char* argv[128];
        int argc = Split(cmdline, argv);
        
        if(argv > 0)
        {
            printf("命令:%s \n", argv[0]);
            printf("参数:");
            
            for(int i=1; i<argc; i++)
            {
                printf("%s",argv[i]);
            }
            printf("\n\n");
        }
    }
    
    
  • 小结:

    1. 如何定义一个纯虚函数

    2. 抽象类的实质作用:接口规范

      因为它只代表了一个规范,并没有具体实现,所以它不能被实例化

    3. 抽象类通常被多重继承

      比如,一个普通的类,实现了多套接口规范,又继承于原有的父类

    4. 抽象类的析构函数应该声明为virtual,因为它是被设计用于继承的

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值