您的位置:首页 > 编程语言 > C语言/C++

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

2017-12-24 22:32 375 查看
笔记会持续更新,有错误的地方欢迎指正,谢谢!

虚函数

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

抽象基类

纯虚函数

纯虚函数可清晰地告诉用户当前这个纯虚函数(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是朋友,所以能被访问。
};


总结:

友元关系不能传递、不能继承;

每个类负责控制各自成员的访问权限。

继承中的类作用域

概述:

每个类定义自己的作用域,在这个作用域内我们定义类的成员。

当存在继承关系时,派生类的作用域嵌套在其基类的作用域内。

若一个名字在派生类的作用域内无法正确解析,则编译器将继续在外层的基类作用域中寻找该名字的定义。

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

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


我们来看看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;
}


能说出上面6个结果,说明你的虚函数、动态绑定、重载、作用域都OK了,加油!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  c++ c++primer 读书笔记
相关文章推荐