抽象基类
一个基类是抽象基类,它的虚函数必须是一个纯虚函数。纯虚函数是虚函数后加 =0 来定义的, =0 只能出现在虚函数的类内声明之后 :
class Disc_quote : public Quote { public: Disc_quote() = default; Disc_quote(const std::string& book, doule price, size_t qty, double disc) : Quote(book, price), quantity(qty), discount(disc) {} // 纯虚函数 double net_price(size_t) const = 0; protected: size_t quantity = 0; double discount = 0.0; };
抽象基类的定义:含有或者继承了纯虚函数(未覆盖)的类是一个抽象基类。
1. 抽象基类只负责定义接口;
2. 不能定义抽象基类的对象;
3. 一般来说,为纯虚函数提供定义是没有意义的, 毕竟在派生类中可能要覆盖该虚函数。也可以为这个纯虚函数提供定义,不过定义不能出现在类内;
4. 抽象基类的派生类必须将纯虚函数override, 否则该派生类也是一个抽象基类。
访问控制与继承
受保护的成员的访问控制
protected可以看做是private和public的中和产物:
1. 和私有成员类似, 受保护成员对于类的用户来说是不可访问的;
2. 和公有成员类似, 受保护成员对于派生类额成员和友元是可访问的。
除了这两条外,protected还有自己的一条重要的性质:
1. 派生类的成员或友元只能通过派生类对象来访问基类的受保护成员
2. 派生类对于一个基类对象中的受保护成员没有任何访问权 。
class Base { protected: int prot_mem; }; class Sneaky : public Base { friend void clobber(Sneaky&); friend void clobber(Base&); s.j; }; // 正确,clobber能访问Sneaky对象的private和protected成员 void clobber(Sneaky& s) { s.j = s.prot_mem = 0; } // 错误, clobber不能访问Base对象中的protected成员 void clobber(Base& b) { b.prot_mem = 0; }
public、protected和private访问控制继承
有如下的继承体系 :
class base { public: std::string pub_string {""}; protected: std::string prot_string {""}; private: std::string priv_string {""}; }; class pub_derived : public base { public: void change(){ ; } void memfun1(base& b){ b = *this; } protected: int j = 1; private: int i = 0; }; class pro_derived : protected base { public: void change(){ ; } void memfun1(base& b){ b = *this; } protected: int j = 1; private: int i = 0; }; class pri_derived : private base { public: void change(){ ; } void memfun1(base& b){ b = *this; } protected: int j = 1; private: int i = 0; };
定义了一个基类,又分别定义了三个派生类,而且派生访问说明符分别为public、protected和private的。
在继承体系中:
1. 派生类说明符对于派生类的成员及其友元能否直接访问基类的成员没什么影响。
2. 对于基类成员的访问权限只与基类的访问说明符有关。
3. 派生类的访问说明符的目的是控制派生类用户以及派生类的派生类的用户对于基类成员的访问权限。
对于pub_derived:它的用户可以访问基类中的pub_string, 但不能访问prot_string和priv_string, 它的成员以及友元可以访问pub_string和prot_string。
对于pro_derived: 它的用户不能访问基类中任何成员, 但是它的成员和友元可以访问pub_string和prot_string
对于pri_derived : 它的用户不能访问基类中任何成员, 它的成员和友元可以访问pub_string和prot_string
派生类向基类转换的可访问性:
假设D继承自B:
- 只有当D公有继承B时, 用户的代码才能使用D向B的转换。
- 不论D是以哪种方式继承 B, D的成员和友元都能使用D向B的转换。 派生类向其直接基类的类型转换对于派生类的成员和友元来说是永远可以访问的。
- 如果D继承B的方式是公有的或受保护的,则D的派生类(三种继承方式都可以)的成员和友元可以使用从D向B的转换,否则不可以。
通俗的说,如果基类的公有部分是可访问的,则派生类向基类的转换也是可访问的。
友元和继承
友元关系不可传递,同时友元的关系也不能继承。基类的友元不能访问其派生类的成员, 派生类的友元也不能访问基类的成员
对于基类的友元来说, 它的友元可以通过它的派生类来访问它,因此这种可访问性包括了内嵌在其派生类对象中的情况。
我们可以通过using声明将直接基类或间接基类中的可访问 成员标记出来,标记出来的成员的可访问性与using所在的访问控制符有关。
派生类只能为那些它可以访问的名字提供using声明。
默认的继承保护级别:
对于struct来说, 默认以public方式继承,而class默认以private继承。
继承中的类作用域 ##
派生类的作用域嵌套在基类的作用域中,因此,派生类才能像使用自己的成员一样使用基类成员。
- 在编译时进行名字查找
一个普通对象、引用或指针的静态类型决定了该对象的哪些成员是可见的,即使这个对象的动态类型和静态类型可能不一样。 - 名字冲突与继承
派生类能重用定义在直接基类或间接基类中的名字。 此时在派生类中的名字将隐藏定义在直接基类或间接基类中的名字。
struct Base { Base() : mem(0){}; protected: int mem; }; struct Derived : Base { Derived(int i) : mem(i) {} int getMem() const { return mem; } protected: int mem; // 隐藏基类中的mem };
如果需要使用基类中的名字,我们可以使用类作用域符来显示要求。
- 名字查找先于类型检查
声明在内层作用域的(派生类)名字并不会覆盖掉外层(基类)中的名字, 对于函数是一样的。如果派生类中的函数和基类中的函数同名,即使形参列表不一致,基类成员函数仍然被隐藏掉。
一旦名字被找打,编译器就不再继续查找了!
因此对于虚函数来说, 在继承体系中必须保持相同的形参列表。
实际运用中,一个类可能覆盖它的基类的部分函数而非全部,如果必须每个函数都覆盖,操作会十分繁琐。
解决方案就是使用using标识出我们不需要覆盖的函数,而需要覆盖的我们才进行重写。
关键概念: 名字查找与继承
假设我们调用p->mem(),则执行的步骤为:
- 先确定p的静态类型
- 在p的静态类型中查找mem,如果找不到,则依次在其直接基类中继续查找直到继承体系的顶端。如果还没找到,编译器报错
- 找到了mem,进行类型检查,确认是否调用合法
- 如果调用合法,判断mem是否是虚函数而产生不同的代码:
(1)—— 如果mem是虚函数且我们是通过引用或指针进行的调用,则编译器在运行时确定虚函数属于哪个版本
(2)—— 如果mem不是虚函数或者调用mem不是通过引用或指针,则编译器产生一个常规函数调用。
来源:https://www.cnblogs.com/averson/p/5150003.html