栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

C++ 基础与深度分析 Chapter12 类的细节(类的继承)

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

C++ 基础与深度分析 Chapter12 类的细节(类的继承)

文章目录
  • 概述
  • 类的派生会形成嵌套域
  • 虚函数
  • 继承与特殊成员函数
  • 补充知识

概述


通过类的继承(派生)来引入 是一个 的关系
被继承的类——基类,继承的类——继承类。

#include 
using namespace std;

struct Base
{
};

struct Base2 : Base
{
};


// 这是类的声明,下面继承部分不是类的声明
struct Drive;

// Drive派生自Base基类
// struct不写public,默认也是public继承
// 但是如果是class,那么默认是private继承,所以class经常看到public继承
struct Drive : public Base2
{

};

int main()
{
    Drive d;
    // 使用基类的指针或引用可以指向派生类对象
    // Base2继承自Base,Drive继承自Base2,还是可以的
    Base& ref = d;
    Base* ptr = &d;
}

使用基类的指针或引用可以指向派生类对象

静态类型 V.S. 动态类型
静态类型:编译器就可以确定的类型。
动态类型:在运行期为ref或者指针赋予的实际的类型。

#include 
using namespace std;

struct Base
{
    void fun() {}
};

struct Base2 : public Base
{
};

struct Drive : public Base2
{
    void fun2() {}
};

struct Drive2 : public Base2
{
};

int main()
{
    Drive d;
    Base& ref = d;
    ref.fun(); // 编译器只看静态类型是否可以接收
    // ref.fun2(); // fun2是Drive的函数,是ref的动态类型,ref不能调用动态类型的函数
    Drive d2;
    Base* ptr = &d;
    ptr = &d2; // ptr对应的静态类型没有发生改变,还是Base。但是动态类型从Drive到Drive2了
}

protected 限定符:派生类可访问

#include 
using namespace std;

struct Base
{
protected: // 如果是protected,那么Derive的fun2,就可以调用了
    void fun() {}
    int x;
};


struct Derive : public Base
{
public:
    void fun2() 
    {
        fun(); // fun如果是private,无法访问
        x; // 因为proteted,所以可以访问fun和x
    }
};


int main()
{
    Derive d;
    Base b;
    // b.fun(); // fun private不行
    d.fun2();
    // b.fun2(); // b不能访问Derive的函数
}
类的派生会形成嵌套域




派生类中的名称定义会覆盖基类

#include 
using namespace std;

struct Base
{
    int val = 2;
};


struct Derive : public Base
{
public:
    void fun2() 
    {
        cout <<  val << endl;
    }
    // int val = 3;
};


int main()
{
    Derive d;
    Base b;
    d.fun2(); // 3 因为Derive里的val覆盖了Base的val
    // 根本原因是Derive是嵌套在Base里的

    cout << d.val << endl; // 如果把Derive的val注释掉,那么会去找Base的val。因为他们是嵌套的关系
}

使用域操作符显式访问基类成员

#include 
using namespace std;

struct Base
{
    int val = 2;
};


struct Derive : public Base
{
public:
    void fun2() 
    {
        cout <<  val << endl;
        cout <<  Base::val << endl; // 域操作符
    }
    int val = 3;
};


int main()
{
    Derive d;
    Base b;
    d.fun2(); // 3 因为Derive里的val覆盖了Base的val

}

在派生类中调用基类的构造函数

#include 
using namespace std;

struct Base
{
    Base ()
    {
        cout << "Base constructor is called" << endl;
    }
};


struct Derive : public Base
{
public:
    Derive ()
    {
        cout << "Derive constructor is called" << endl;
    }
};


int main()
{
    Derive d; // Base构造函数先调用,再调用Derive构造函数

}
#include 
using namespace std;

struct Base
{
    Base (int)
    {
        cout << "Base constructor is called" << endl;
    }
};


struct Derive : public Base
{
public:
    Derive (int a)
        : Base(a) // 通过初始化列表来去调用基类Base的构造函数
    {
        cout << "Derive constructor is called" << endl;
    }
};


int main()
{
    Derive d(100); // Base构造函数先调用,再调用Derive构造函数

}
虚函数


通过虚函数与引用(指针)实现动态绑定
使用关键字 virtual 引入
非静态、非构造函数可声明为虚函数
虚函数会引入vtable结构

#include 
using namespace std;

struct Base
{
    virtual void baseMethod() {} // 虚函数
    int baseMember;

};


struct myClassDerive : public Base
{
    virtual void deriveMethod() {} // 虚函数
    int deriveMember;
};

struct myClassDerive2 : public myClassDerive
{
    virtual void deriveMethod2() {} // 虚函数
    int deriveMember2;
};

int main()
{
    myClassDerive2 d;
    Base& b = d;
    Base* ptr = &d;
    myClassDerive2& d2 = dynamic_cast(b);
    myClassDerive2* ptr2 = dynamic_cast(ptr);
}

如果我们想把一个基类的指针转成一个派生类的指针,如果基类的指针确实指向派生类的地址,那么这个转换就是成功的,否则指向空指针。基类的引用转换成派生类的引用,如果基类引用确实绑定在派生类的引用,就是成功的,否则抛出异常。

static_cast是在编译期处理的,但是dynamic_cast是在运行期发生的,判断动态类型能不能发生转变。如果要编写高性能的程序,dynamic_cast慎用。

#include 
using namespace std;

struct Base
{
    virtual void fun() 
    {
        cout << "Base :fun() is called" << endl;
    } 

};

struct Derive : Base
{
    void fun()
    {
        cout << "Derive :fun() is called" << endl;
    }
};

int main()
{
    Derive d;
    d.fun();
    Base& b = d;
    b.fun(); // 注意,使用虚函数,那么就会调用Derive的fun,但是如果没有使用虚函数,就会使用Base里的fun
    // 没有了虚函数,就没有个vtable。所以对象的绑定都会在编译期确定,而编译期只看静态类型,b就是Base,所以只能调用Base中的fun
}

虚函数在派生类中的重写( override )

#include 
using namespace std;

struct Base
{
    virtual void fun() 
    {
        cout << "Base :fun() is called" << endl;
    } 

};

struct Derive : Base
{
    // override 重写
    void fun()
    {
        cout << "Derive :fun() is called" << endl;
    }
};

void proc(Base& b)
{
    b.fun();
}

int main()
{
    // 动态类型实现的运行期的一个多态,也叫运行期多态
    Derive d;
    proc(d); // 这个参数的动态类型是derive,所以调用Derive的fun

    Base b;
    proc(b); // 这个参数动态、静态类型都是Base,所以调用Base的fun
}

函数签名保持不变(返回类型可以是原始返回指针 / 引用类型的派生指针 / 引用类型)

#include 
using namespace std;

struct Base2 {};

struct Derive2 : Base2 {};

struct Base
{
    virtual Base2& fun() 
    {
        cout << "Base :fun() is called" << endl;
        static Base2 b;
        return b;
    } 

};

struct Derive : Base
{
    // override 重写
    Derive2& fun()
    {
        cout << "Derive :fun() is called" << endl;
        static Derive2 inst;
        return inst;
    }
};

void proc(Base& b)
{
    b.fun();
}

int main()
{
    // 动态类型实现的运行期的一个多态,也叫运行期多态
    Derive d;
    proc(d); // 这个参数的动态类型是derive,所以调用Derive的fun

    Base b;
    proc(b); // 这个参数动态、静态类型都是Base,所以调用Base的fun
}

可以通过 = 0 声明纯虚函数,相应地构造抽象基类



虚函数特性保持不变

override 关键字

继承与特殊成员函数 补充知识
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/1004936.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号