继承是C++中的三大特性之一。
继承机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能。
这样产生新的类,叫做派生类。
继承定义的格式:class DeriveClassName(派生类名):asecc-label(继承类型) BaseClassName(基类名)
我们已知的继承类型有三种,分别为:public(共有继承),protected(保护继承)和private(私有继承)
这三种继承在三种继承方式之下产生的结果如下图:
继承方式 | 基类的public成员 | 基类的protected成员 | 基类的private成员 | 继承引起的访问控制关系 |
public继承 | 仍为public成员 | 仍为protected成员 | 不可见 | 基类的非私有成员在子类的访问属性都不变 |
protected继承 | 变为protected成员 | 变为protected成员 | 不可见 | 基类的非私有成员都成为子类的保护成员 |
private继承 | 变为private成员 | 变为private成员 | 不可见 | 基类的非私有成员都成为子类的私有成员 |
总结:
1. 基类的private成员在派生类中是不能被访问的,如果基类成员不想在类外直接被访问,但需要
在派生类中能访问,就定义为protected。可以看出保护成员限定符是因继承才出现的。
2. public继承是一个接口继承,保持is-a原则,每个父类可用的成员对子类也可用,因为每个子类
对象也都是一个父类对象。
3. protetced/private继承是一个实现继承,基类的部分成员并非完全成为子类接口的一部分,
是 has-a 的关系原则,所以非特殊情况下不会使用这两种继承关系,在绝大多数的场景下使用的
都是公有继承。
4. 不管是哪种继承方式,在派生类内部都可以访问基类的公有成员和保护成员,基类的私有成员存
在但是在子类中不可见(不能访问)。
5. 使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public,不过最
好显示的写出继承方式。
6. 在实际运用中一般使用都是public继承,极少场景下才会使用protetced/private继承.
我们已知类的六个默认成员函数分别为:构造函数,析构函数,拷贝构造函数,赋值操作符重载,取地址操作符重载,const修饰的取地址操作符重载。
那么在继承关系中,当派生类中如果没有显示这六个成员函数,编译系统会默认合成这六个成员函数。
需要注意的是:
1、基类没有缺省构造函数,派生类必须要在初始化列表中显式给出基类名和参数列表。
2、基类没有定义构造函数,则派生类也可以不用定义,全部使用缺省构造函数。
3、基类定义了带有形参表构造函数,派生类就一定定义构造函数。
那么对于以下代码:
class Test1
{
public:
Test1( int data)
{
cout <<"Test1()"<<endl;
}
~Test1 ()
{
cout<< "~Test1()"<<endl ;
}
};
class Test2
{
public:
Test2( int data)
{
cout <<"Test2()"<<endl;
}
~Test2 ()
{
cout<< "~Test2()"<<endl ;
}
};
class Base1
{
public:
Base1( int data)
: _data(data )
{
cout <<"Base1()"<<endl;
}
~Base1 ()
{
cout<< "~Base1()"<<endl ;
}
protected:
int _data;
};
class Base2
{
public:
Base2( int data):
_data2(data )
{
cout <<"Base2()"<<endl;
}
~Base2 ()
{
cout<< "~Base2()"<<endl ;
}
protected:
int _data2;
};
class Derive: public Base1, public Base2
{
public:
//Derive(): Base1(0), Base2(1),t1(3), t2(4)
//Derive(): Base2(0), Base1(1),t2(3), t1(4)
//Derive(): t1(3), t2(4), Base1(0), Base2(1)
Derive()
: t2 (3)
, t1 (4)
, Base2 (0)
, Base1(1 )
{
cout <<"Derive()"<<endl;
}
~Derive ()
{
cout<< "~Derive()"<<endl ;
}
protected:
Test1 t1;
Test2 t2;
};
我们运行得到的结果如下图所示:
由结果我们可以得到类和派生类以及基类它们的构造函数以及析构函数的调用顺序。
我们可以知道派生类继承了Base1以及Base2,而且派生类中定义了两个类对象分别为T1和T2;
我们看到编译器先调用类两个基类的构造函数,之后调用了两个Test类对象的构造函数,最后调用了派生类的构造函数,而析构函数的调用与之相反,从先到后析构;
我们发现T1和T2在调用构造函数的时候与派生类构造函数参数列表中给出的顺序无关,而是按照派生类中定义类的顺序调用。
我们用了不同的参数列表验证,结果和图中所示相同。
注意同名隐藏问题:
1. 在继承体系中基类和派生类是两个不同作用域。
2. 子类和父类中有同名成员,子类成员将屏蔽父类对成员的直接访问。(在子类成员函数中,可以使用 基类::基类成员 访问)--隐藏 --重定义
3. 注意在实际中在继承体系里面最好不要定义同名的成员。
继承与转换--赋值兼容规则--public继承
1. 子类对象可以赋值给父类对象
2. 父类对象不能赋值给子类对象
3. 父类的指针/引用可以指向子类对象
4. 子类的指针/引用不能指向父类对象(可以通过强制类型转换完成)
友元关系不能继承,也就是说基类友元不能访问子类私有和保护成员。
基类定义了static成员,则整个继承体系里面只有一个这样的成员。无论派生出多少个子类,都只有
一个static成员实例。
例如:
class Person
{
public :
Person()
{
++ _count;
}
protected :
string _name ;
public :
static int _count;
};
int Person::_count = 0;
class Student : public Person
{
protected :
int _stuNum ;
};
class Graduate :public Student
{
protected:
string _seminarCourse;
};
void TestPerson1()
{
Student s1;
Student s2;
Student s3;
Graduate s4;
cout<<"人数:"<<Person::_count<<endl;
Student ::_count = 0;
cout<<"人数:"<<Person::_count<<endl;
结果如下图所示
}
我们可以看到,自始至终我们定义的静态成员变量_count都是公用的,每一次调用派生类从而调用基类的构造函数时,_count都会++。
单继承&多继承&菱形继承
一个子类只有一个直接父类时称这个继承关系为单继承。
一个子类有两个或以上直接父类时称这个继承关系为多继承。
而菱形继承基于多继承的基础。它使两个派生类继承自一个基类,又使这两个派生类作为基类,有另一个派生类继承它们。
菱形继承存在二义性以及数据冗余的问题,我们可以通过虚继承来解决。
而有关虚继承以及虚函数的知识我会在下一章有关C++多态的博客中写到。