《C++ Primer》读书笔记第十五章-2-虚函数、继承中的类作用域

本文详细介绍了 C++ 中的继承机制,包括虚函数、纯虚函数及其应用场景。探讨了基类与派生类之间的访问控制、作用域问题,并通过具体实例解释了动态绑定、重载等概念。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

笔记会持续更新,有错误的地方欢迎指正,谢谢!

虚函数

与普通函数一样,有个需要注意的地方是:既然默认实参可以在基类和派生类中都有,那我们最好让它们一致。实参值由本次调用的静态类型决定。

抽象基类

纯虚函数

纯虚函数可清晰地告诉用户当前这个纯虚函数(net_price)是没实际意义的。

在函数体位置写上=0就可以将一个虚函数说明为纯虚函数,只能出现在类内部的虚函数声明语句处:

//书店多种购书优惠政策问题
class Disc_quote : public Quote
{
public:
    Disc_quote() = default;
    //Quote(book, price):父类的变量由父类的构造函数初始化
    Disc_quote(const string& book, double price, size_t qty, double disc) : Quote(book, price), quantity(qty), discount(disc) {}

    double net_price(size_t) const = 0; //虚函数后面加=0是纯虚函数

protected:
    size_t quantity = 0; //折扣适用的购买量
    double discount = 0; //表示折扣的小数值
};

含有纯虚函数的类是抽象基类

抽象基类:

含有或未经覆盖直接继承纯虚函数的类,负责定义接口给后续类覆盖。不能直接创建抽象基类的对象,但可定义抽象基类的派生类的对象,前提是这些派生类覆盖了纯虚函数net_price函数:

//假设Bulk_quote继承Disc_quote并已覆盖了net_price函数
Bulk_quote bulk; //正确:Bulk_quote中没有纯虚函数

Disc_quote的派生类必须给出继承的纯虚函数的定义(net_price),否则仍是抽象基类。

派生类构造函数只初始化它的直接基类

我们重新实现Bulk_quote,让它继承Disc_quote而不是直接继承Quote,于是,在构造函数中只需调用它的直接基类的构造函数就好了,至于再往上的类,自有它儿子负责:

class Bulk_quote : public Disc_quote
{
public: 
    Bulk_quote() = default;
    Bulk_quote(const string& book, double price, size_t qty, double disc) : 
    //为Disc_quote的基类Disc_quote里的变量初始化
    Disc_quote(book, price, qty, disc) {}

    double net_price(size_t) const override;
};

访问控制与继承

由上,每个类分别控制自己成员的初始化过程,与之类似,每个类还分别控制着其成员对于派生类来说是否可访问。

受保护成员:

类使用protected声明那些希望与派生类共享但不想被其他公共访问使用的成员。

访问权限:

受基类中成员和派生列表中访问说明符共同影响;
struct默认公有继承,class默认私有继承
派生类的访问说明符不影响派生类成员及友元直接访问基类成员,只会控制 派生类的派生类 对基类成员的访问权限。

某个类对其继承而来的成员的访问权限受到两个因素影响:
1.在基类中该成员的访问说明符

private:
    char priv_mem;

2.在派生类的派生列表中的访问说明符

struct Pub_Derv : public Base{//省略派生类的代码}; //公有继承

另外,派生访问说明符对子孙也有影响:

struct DfP : public Pub_Derv
{
    int use_base(){return prot_mem;} //正确:在派生类的派生列表中的访问说明符一路public。
};
struct DfPr : public Priv_Derv
{
    int use_base(){return prot_mem;} //错误:DfPr的爷爷是private,连累了它。
};

所以,在类外,某代派生类对最老的那个基类的某个成员访问权限等于一路继承过来最低的权限。比如原来是protected,通过private继承后,那它就是private了。

友元与继承:朋友不能继承或传递

基类的友元在访问派生类成员如其他正常类一样访问,无优待,就像你爸爸的朋友访问你,你对待他像对待陌生人即可。类似的,派生类的友元也不能随意访问基类的成员,你爸爸对待你的朋友也像对待陌生人一样即可。

class Base
{
    //添加友元类,其他跟之前一样
    firend class Pal; //友元类
};
class Pal
{
public:
    int f(Base b) {return b.prot_mem;} //正确:Pal是Base的友元
    int f2(Sneaky s) {return s.j;} //错误:Pal不是Sneaky的友元(没有继承朋友关系)

    int f3(Sneaky s){return s.prot_mem;} //正确:访问基类的成员,
    //能不能被访问,基类说了算,由于基类Base和Pal是朋友,所以能被访问。
};

总结:

  1. 友元关系不能传递、不能继承;
  2. 每个类负责控制各自成员的访问权限。

继承中的类作用域

概述:

  1. 每个类定义自己的作用域,在这个作用域内我们定义类的成员。
  2. 当存在继承关系时,派生类的作用域嵌套在其基类的作用域内。
  3. 若一个名字在派生类的作用域内无法正确解析,则编译器将继续在外层的基类作用域中寻找该名字的定义。

我们看一段之前的书店折扣类的代码:

Bulk_quote bulk;
cout << bulk.isbn();

我们来看看isbn是怎么解析的:
因为我们是通过Bulk_quote的对象来调用isbn的,所以首先在Bulk_quote中查找,没找到,又因为Bulk_quoteDisc_quote的派生类,所以接下来在Disc_quote中找,没找到,接下来在Quote中找,找到了。

名字冲突与继承:局部覆盖整体

隐藏:对于同名变量和同名函数,派生类作用域嵌套在基类作用域内,所以派生类中定义的成员隐藏同名的基类成员。

虚函数与作用域

要求基类与派生类中的虚函数要有相同的形参列表的原因:
加入基类和派生类的虚函数接受的实参不同,那我们就没办法通过基类的引用或指针来调用派生类的虚函数了。

class Base
{
public:
    int fcn(int) //与类中下面的虚函数同名,必须要形参不同才能重载
    {
        cout << "Base不虚" << endl; 
    }
    virtual int fcn()
    {
        cout << "Base虚" << endl;
    }        
};

class D1 : public Base 
{
public:
    int fcn(int) //并不是虚函数,因为参数和基类不同
    {
        cout << "D1不虚" << endl;
    }

    int fcn() //这才是虚函数,虽然没写override,更好的习惯是写上,这里为了迷惑你们就不写了
    {
        cout << "D1虚" << endl;
    }
};


int main()
{
    Base bobj; D1 d1obj;
    Base *bp1 = &bobj; 
    Base *bp2 = &d1obj;
    D1 *bp3 = &d1obj;

    //通过基类的指针来调用派生类的虚函数
    bp2->fcn(); //D1虚

    bp1->fcn(); //Base虚
    bp3->fcn(); //D1虚
    bp1->fcn(1); //Base不虚
    bp2->fcn(1); //Base不虚
    bp3->fcn(1); //D1不虚
    return 0;
}

能说出上面6个结果,说明你的虚函数、动态绑定、重载、作用域都OK了,加油!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值