has-a关系
-
包含对象成员的类
- 易于理解及使用
- 可包含多个基类,易于区分。
- 构造函数初始化的是成员对象,而不是继承的对象,所以在初始化列表中使用的是成员名,而不是类名。
- 当初始化列表包含多个项目时,这些项目被初始化的顺序为它们被声明的顺序,而不是它们在初始化列表中的顺序。一般来说,初始化顺序并不重要,但如果代码使用一个成员的值作为另一个成员的初始化表达式的一部分时,初始化顺序就非常重要了。
-
私有继承
-
提供的特性比包含多,如基类的保护成员在派生类中可以使用,但包含(继承层级结构外)不可用。
-
需要重新定义虚函数,包含类不能。使用私有继承,重新定义的函数只能在类中使用,而不是公有的。
-
通常,应使用包含来建立
has-a
关系,如果新类需要访问原有类的保护成员,或者需要重新定义虚函数,则应该使用私有继承。 -
基类的公有成员和保护成员都将成为派生类的私有成员。这意味着基类方法将不会成为派生对象公有接口的一部分,但可以在派生类的成员函数调用它。
-
私有继承使用类名和作用域解析运算符来调用基类的方法
double Student::Average()const { if(ArrayDb::size()>0) return ArrayDb::sum()/ArrayDb::size(); else return 0; }
-
访问基类对象时可使用强制类型转换
const string & Student::Name()const { return (const string &)*this; }
-
-
保护继承
-
基类的公有成员和保护成员都将成为派生类的保护成员
-
在第三代继承时,保护继承和私有继承的区别就呈现出来了。使用私有继承,基类的成员都将成为第二代的私有成员,所以第三代派生类无法使用基类的公有成员和保护成员。但使用保护继承则可以。
扫描二维码关注公众号,回复: 3983520 查看本文章 -
重新定义访问权限
- 如果在类外面使用基类方法,有两种选择。
- 在派生类中重新声明一个方法。
double Student::sum() const { return std::valarray<double>::sum(); }
- 使用一个using声明,只使用成员名——没有圆括号,函数特征标和返回类型。
class Student:private std::string,private std::valarray<double> { ... public: using std::valarray<double>::min; using std::valarray<double>::max; }
-
-
各种继承方式
特征 | 公有继承 | 保护继承 | 私有继承 |
---|---|---|---|
公有成员变成 | 派生类的公有成员 | 派生类的保护成员 | 派生类的私有成员 |
保护成员变成 | 派生类的保护成员 | 派生类的保护成员 | 派生类的私有成员 |
私有成员变成 | 只能通过基类接口访问 | 只能通过基类接口访问 | 只能通过基类接口访问 |
能否隐式向上转换 | 是 | 是(但只能在派生类中) | 否 |
多重继承(MI)
-
一个例子(以公有继承举例)
class Worker { ... }; class Waiter:public Worker { ... }; class Singer:public Worker { ... }; class SingerWaiter:public Waiter,public Singer { ... }; int main() { SingerWaiter ed; Worker *pw=&ed;//invalid }
-
通常,这种赋值将把基类指针设置为派生对象中的基类对象地址。但ed中包含两个Worker对象,有两个地址可供选择,故会有二义性。
-
正确写法
Worker *pw1=(Waiter *)&ed; Worker *pw2=(Singer *)&ed;
-
当只需要一个Worker对象时,C++引入了一个新技术——虚基类。
-
虚基类
-
虚基类的声明
class Singer : virtual public Worker{...}; class Waiter : public virtual Worker{...};//public和virtual的顺序无关紧要 class SingerWaiter : public Singer, public Waiter{...};
-
新构造函数规则
SingerWaiter(const Worker &wk,int p=0,int v=0) : Waiter(wk,p),Singer(wk,v){}//invalid SingerWaiter(const Worker &wk,int p=0,int v=0) :Worker(wk),Waiter(wk,p),Singer(wk,v){}//valid
注意:上述代码显示调用了构造函数Worker(const Worker &).请注意,这种用法对于虚基类来说,这种用法是合法的,必须这样做。但对于非虚基类来说,则是非法的。
- 使用基类方法
-
按照普通的使用的话,那么会造成二义性。所以这里得使用作用域解析运算符来澄清编程者的意图:
SingerWaiter newhire("Elise Hawks",2005,6,3); newhire.Singer::Show();
-
重新定义函数
void SingerWaiter::Show() { Singer::Show(); }
-
-
-
MI的其他问题
-
混合使用虚基类和非虚基类
当类通过多条虚途径和非虚途径继承某个特定的基类时,该类将包含一个表示所有的虚途径的基类子对象和分别表示各条非虚途径的多个基类子对象。 -
虚基类和支配
-
一般情况下,如果类从不同的类那里继承了两个或多个的同名成员(数据或方法),则使用该成员名时,如果没有用类名进行限定,将导致二义性。
-
使用虚基类后,如果某个名称优先于其他名称,则使用它时,即使不使用限定符,也不会导致二义性。优先级:派生类中的名称优先于直接或间接祖先类中的相同名称。
class B { public: short q(); ... }; class C : virtual public B { public: long q(); int omg(); ... }; class D : public C { ... }; class E : virtual public B { private: int omg(); ... }; class F : public D,public E { ... };
-
类C中的q()定义优先于类B的q()定义,因此F中的方法可以使用q()来表示C::q().另一方面,任何一个omg定义都不优先于其他omg()定义。因为C和E都不是对方的基类。所以在F中使用非限定的omg()将导致二义性。
-
虚二义性规则与访问规则无关。也就是说,即使E::omg()是私有的,不能在F类中直接访问,但使用omg()仍将导致二义性。同样,即使C::q()是私有的,它也将优先于D::q()。在这种情况下,可以在类F中调用B::q(),但如果不限定q(),则将意味着要调用不可访问的C::q()
-