(十五)面向对象程序设计(Part2)

copy from(https://blog.csdn.net/billcyj/article/details/78888074)

15.3 虚函数

  • 对虚函数的调用可能在运行时才被解析

通过指针或引用调用虚函数时会执行动态绑定,所有虚函数必须有定义。
被调用的函数是与绑定到指针或引用上的对象的动态类型相匹配的那个。

  • C++多态性

通过指针或引用调用虚函数时,直到运行时才知道执行哪个版本;
非虚函数调用,在编译时被绑定;
通过对象进行的(虚或非虚)的调用也在编译时被绑定。

  • 派生类中的虚函数

1.一旦函数被声明为虚函数,则在所有派生类中都是虚函数
2.虚函数若要被覆盖,在派生类中该虚函数必须与基类的形参类型完全一致(不一致则认为是新函数,与原函数独立),返回类型也要匹配(除非是类本身的指针和引用)

override;
final;

既然默认实参可以在基类和派生类中都有,那我们最好让它们一致。实参值由本次调用的静态类型决定
若虚函数使用默认实参,在基类和派生类最好让它们一致

  • 回避虚函数的机制

使用作用域运算符 ::
发生在派生类的虚函数需要调用其在基类的版本时。

15.4 抽象基类

  • 纯虚函数

纯虚函数可清晰地告诉用户当前这个纯虚函数(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;
};

15.5 访问控制与继承

1.每个类分别控制自己成员的初始化过程
2.每个类还分别控制着其成员对于派生类来说是否可访问

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

1.受保护的成员对于类的用户来说不可访问
2.受保护的成员对于派生类的成员和友元来说可访问
3.派生类的成员和友元只能通过派生类对象来访问基类的受保护成员,派生类对于一个基类对象中受保护成员没有任何访问权

//访问权限
class Base
{
protected:
    int prot_mem;
};
class Sneaky : public Base {
    friend void clobber(Sneaky&);//能访问prot_mem
    friend void clobber(Base&);//不能访问prot_mem
    int j;//j默认是private
};

//正确 clobber能访问Sneaky对象的private和protected成员
void clobber(Sneaky& s) { s.j = s.prot_men = 0; }
//错误 clobber不能Base的protected成员
void clobber(Base& b) { b.prot_men = 0; }
  • 公有、私有和受保护继承

struct默认公有继承,class默认私有继承

某个类对继承来的成员访问权限要看:

1.在基类中该成员的访问说明符
2.在派生类的派生列表中的访问说明符

派生类的(派生列表中的)访问说明符不影响派生类成员及友元直接访问基类成员,只会控制 派生用户(包括派生类的派生类) 对基类成员的访问权限。若B公有继承A,C继承B,则对于C而言,B的成员将遵循A中原有的访问控制。
派生类成员及友元能否访问基类成员 只与基类中该成员的访问说明符有关。
所以,在类外,某代派生类对最老的那个基类的某个成员访问权限等于一路继承过来最低的权限。比如原来是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.每个类负责控制各自成员的访问权限

  • 改变个别成员可访问性

using 声明

15.6 继承中的类作用域

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

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

因为我们是通过Bulk_quote的对象来调用isbn的,所以首先在Bulk_quote中查找,没找到,又因为Bulk_quote是Disc_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;
}
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容