继承是面向对象的三大特性之一,继承是类复用的一个手段,如果要定义重复的成员变量或者函数,就可以继承。
class A{
};
class B : public A
{
};
一般像这样使用,类名加冒号继承方式加继承对象。一般被继承的叫父类或者基类,继承的叫子类或者派生类,一般情况,子类是具有父类全部的成员变量和函数,并且自己可以扩展。
继承的访问方式变化:
子类继承父类的私有成员是不可见的。不可见即不可访问,如类里的私有成员对类外就是不可见的。
因此保护成员和私有成员在不继承的情况下是一样的,但继承后,保护成员对子类可见,私有成员对子类不可见。
父类的其他成员,被子类继承时,以小的为标准,则访问权限,以继承方式和成员的访问限定符中小的那个,公有大于保护大于私有。
class的默认继承方式是私有,struct是公有,不过我们一般是写公有继承方式,很少写私有或者保护。
父类与子类的赋值兼容规则:
子类对象可以赋值给父类对象,指针,或者引用。这种称为切片或者切割的方式。父类的成员是小于等于子类的,所以就如同将父类那部分成员切过去,所以叫切割。反过来,即父类给子类赋值,在指针强转的下,是可以,但一般情况不行。
作用域:
父类与子类有着不同的作用域,所以在有同名的变量的时候,优先从近的类访问,这样就会让子类隐藏了父类的成员变量,这种被称为隐藏(重定义)因此如果要访问父类则需要域作用符。如果是从父类继承,并在子类中存在相同的函数名,便是隐藏,因为重载是在同一作用域下的。
子类的默认成员函数:
构造函数,不能直接初始化父类成员,需要调用父类的构造函数,来初始化成员函数,如果不初始化,则会调用父类的默认构造函数,调用方式是匿名调用。
class person
{}:
class student : public person
{
student()
:person();
};
拷贝构造,同样,不能初始化父类的成员,只能调用父类的拷贝构造,使用方式和构造函数差不多。
Student(const Student& s)
: Person(s)
, _num(s ._num)
虽然是子类的对象,但是因为子类能给父类对象赋值,所以可以使用。
赋值,赋值不能直接调用父类的赋值函数,因为会导致隐藏,因此需要指定域。
Student& operator = (const Student& s )
{
cout<<"Student& operator= (const Student& s)"<< endl;
if (this != &s)
{
Person::operator =(s);
_num = s ._num;
}
return *this ;
}
以上三个是需要显示调用的,但是析构函数则不需要显示调用,首先我们要了解,析构函数在编译时候会被统一解析成destructor的函数名,因此父类会被子类隐藏,但是设计时候,当子类的析构结束时候,父类的析构也会被系统自动调用,因此我们不需要写显示的析构函数,这样也是为了保证先析构子类,在析构父类,因为构造时候,是先构造父类后构造子类,这样满足栈的先进后出。
所以当父类的构造函数被私有化的时候,那么这个类就不能被继承了。
友元的关系不能被继承,静态成员在一个继承族内只存在一个。
多继承,一个子类可以继承多个父类,但是如果父类又继承了同一个父类,那么就会产生菱形继承的问题。显而易见,菱形继承,会导致数据的冗余,且都会继承同一个成员变量,那么就会产生二义性,虽然我们可以通过域作用符来解决二义性,但是数组冗余却不好解决。
系统自身会用虚继承,virtual来解决。
class Person
{
public :
string _name ; // 姓名
};
class Student : virtual public Person
{
protected :
int _num ; //学号
};
class Teacher : virtual public Person
{
protected :
int _id ; // 职工编号
};
class Assistant : public Student, public Teacher
{
protected :
string _majorCourse ; // 主修课程
};
使用方式是在继承方式前面加个virtual关键字,那么底层是如何实现的呢。
虚继承会产生一个虚基表,并在继承时候产生一个指向虚基表的指针,在多继承中,继承了几个类会产生几个指针,这个指针指向的空间,会有偏移量,这个偏移量是指这个父类和公共成员变量的偏移量,这样做的意思是将冗余的数据全部放在了一个公共部分,且记录了每个类和这个公共部分的距离,这样在赋值的时候,也可以找到这个变量,进行切割。
因此一般我们不使用多继承,更不用说是菱形继承了。
继承和组合:
组合也是一种类的复用手段,意思为这个类是另一个类的成员变量,这样便可以产生类的复用。
而继承是一种白箱复用,父类对子类基本透明,一定程度上破坏了父类的封装。
而组合是一种黑箱复用,内部类成员不会透明,所以会保证封装性。
一般来说我们认为组合更好,因为组合的耦合度低,而继承的耦合度高,软件工程要求高内聚,低耦合。
但是具体问题具体分析:
组合是一种has a的关系即内部类是外部类的子件,如轮胎是车的一部分,轮胎不能说是车,所以一般用组合
而继承是一种is a的关系,即父类是子类的总称类别,如宝马是一辆车,但是是车的一个品牌,所以用继承。
但是如果两者都可以使用的情形,优先使用组合。