1.is-a关系:即派生类对象也是一个基类对象,可以对基类对象执行任何操作,也可以对派生类对象执行任何操作。
通常使用公有继承建立is-a关系。公有继承,类可以继承接口,也可以继承实现(基类的纯虚函数提供接口,但不提供实现)。
(1)公有继承不建立的关系:has-a关系,is-like-a关系(明喻),is-implemented-as-a关系(作为…来实现),uses-a关系(使用)
(2)公有继承要遵循is-a关系。is-a关系表示的方法之一就是无需进行显式类型转换,基类指针就可以指向派生类对象,基类引用就可以引用派生类对象。
(3)不能被继承的函数:
a.构造函数不能被继承。如果能继承调用构造函数的顺序将与继承机制不同。(C++11新增了一种能够继承构造函数的语法,但默认仍为不能继承构造函数)
b.析构函数不能被继承。对于基类,析构函数应该设置为虚的,当通过基类对象的指针或引用来删除派生类的对象时就先调用派生类的析构函数,然后调用基类析构函数,而不是仅调用基类析构函数。
c.赋值函数不能被继承。派生类继承的方法的特征标与基类完全相同,但赋值运算符的特征标随类而异,这是因为它包含一个类型为其所属类的形参。
d.友元函数不能被继承。因为它不是类成员。若要使派生类的友元函数能够使用基类的友元函数,可以通过显式类型转换
ostream & operator<<(ostream & os,const Driver &d)
{
os << (const People &)d;
……
}
2.has-a关系:
has-a关系的实现通过包含,私有继承和保护继承。它可以获得实现,但不能获得接口,也不继承接口。
(1)包含(也叫组合或者层次化):类成员本身是另一个类的对象。包含易于理解,比起继承,不会引起很多的问题,通常使用包含建立has-a关系,其缺点是包含位于继承层次结构之外,不能访问保护成员。
使用初始化被包含的对象:
a.使用成员初始化列表方法。对于成员对象构造函数则使用成员名。因为初始化的是成员对象而不是继承的对象,初始化列表中的每一项都调用与之匹配的构造函数。用初始化列表初始化的项目被初始化的顺序是它们被声明的顺序,而不是在初始化列表中的顺序。
b.不使用成员初始化列表:C++将使用成员对象所属类的默认构造函数。
使用被包含对象的接口:
被包含对象接口不是公邮的,但可以在类方法中使用它,并且使用对象名来调用。
(2)私有继承:各成员属性均变为private.基类private成员被隐藏,派生类成员也只能访问基类中的public/protected成员,派生类对象不能访问基类中的任何成员。私有继承是隐式继承组件而不是成员对象,所以私有继承提供与名称的子对象成员。
class Student:private std::string,private std::valarray<double>
{ public: …… }
另外,私有继承提供的特性要比包含多,私有继承能够访问原有类的保护成员,或者重新定义虚函数。重新定义的函数将只能在类中使用,而不是公有的。
a.初始化基类的组件:使用成员初始化列表,它使用类名而不是成员名来标识构造函数。
Student(const char * str,const double *pd,int n):std::string(str),ArrayDb(pd,n){}
b.访问基类方法:只能在派生类方法中使用基类方法,并且使用类名和作用域解析算符来调用方法。
c.使用基类对象本身:使用强制类型转换,将子对象强转为基类对象。
const std::string & Student::Name() const
{
return (const std::string & )*this;
}
return (const std::string & )*this;
}
d.访问基类的友元函数:通过显式类型转换来调用正确的函数。
ostream & operator<<(ostream & os,const Student & stu)
{
os << "Scores for "<< (const string &)stu << endl;
…
}
无论公有继承还是私有继承,都必须使用显式类型转换。原因:1.不使用类型转换,os << stu;将与友元函数原型匹配,导致递归调用。 2.如果类使用多重继承,编译器将无法确定应转换成那个基类。
(3)保护继承:各属性均变为protected.并且基类private成员被隐藏.派生类成员只能访问基类的public和protected成员,而不能访问private成员,派生类的对象不能访问基类中的任何成员。