继承是什么?
在原有的类的基础上建立的新的类,称为继承。就像家产的继承一样,被继承的类称为基类(父类),新生成的类称为派生类(子类)。继承是类设计层次的代码复用。通过已有的数据类型来定义新的数据类型。所定义的新的数据类型不仅拥有新定义的成员,而且还拥有旧的成员。
基类和派生类的概念是相对的,一个基类可以派生出许多派生类,派生类又可以派生出新的派生类,所以基类和派生类的概念是相对的。
一个派生类可以继承自一个基类,这叫单继承,一个派生类也可以继承自多个基类,这叫做多继承。
继承的定义:
继承的方式:
继承的方式有三种:公有继承(public)、私有继承(private)、保护继承(protected);
对于 class 来说:如果不写继承方式则为私有继承
对于 struct 来说:如果不写继承方式则为公有继承
关于继承自父类的成员,他们在子类中的访问权限与他们在父类的访问权限以及子类的继承方式有关.
小结:
1、如果非要在子类种访问父类的 private 成员,那么可以通过父类的公有的方法来访问父类的私有成员变量。
2、如果基类成员不想在类外直接被访问,但可以在派生类中访问,就在基类中定义为 protected 。
3、出了私有成员外,其他成员在子类中的访问方式为:min(成员在基类的访问限定符 ,继承方式)。public > protected > private
4、关键字 class 默认的继承方式是私有的,而 struct 是默认公有的继承方式
5、尽量使用公有继承,因为 protected继承或 private继承下来的成员只能在派生的类里使用,实际扩展维护性不强。
基类和派生类赋值转换
对于基类对象和派生类对象之间的赋值转换:
- 派生类对象 可以赋值给 基类的对象 / 指针 / 引用。这可以理解为一个切片的操作
- 基类的指针可以通过强制类型转换赋值给派生类的指针。但是必须是基类的指针指向派生类的对象时才是安全的。如果基类是多态类型,可以使用dynamic_cast来识别后进行安全转换。
举个例子:
class Person
{
protected :
string _name; // 姓名
string _sex; // 性别
int _age; // 年龄
};
class Student : public Person
{
public :
int _No ; // 学号
};
void Test ()
{
Student sobj ;
// 1.子类对象可以赋值给父类对象/指针/引用
Person pobj = sobj ;
Person* pp = &sobj;
Person& rp = sobj;
//2.基类对象不能赋值给派生类对象
sobj = pobj;
// 3.基类的指针可以通过强制类型转换赋值给派生类的指针
pp = &sobj
Student* ps1 = (Student*)pp; // 这种情况转换时可以的。
ps1->_No = 10;
pp = &pobj;
Student* ps2 = (Student*)pp; // 这种情况转换时虽然可以,但是会存在越界访问的问题
ps2->_No = 10;
}
在子类和父类中如果有同名函数,则构成隐藏(重定义),可以在子类中使用基类::基类成员来访问。所以最好不要在继承体系中使用同名函数。
派生类中的默认成员函数
对于派生类中默认成员函数:
1、构造函数:派生类的构造函数必须调用基类的构造函数初始化基类的成员,如果基类没有默认构造函数,则需要在派生类的构造函数中对基类的成员进行显式初始化。
2、拷贝构造:调用基类的拷贝构造完成对基类成员的初始化。
3、析构函数:派生类的析构必须先对派生类的成员进行清理,清理完然后调用基类的析构函数对基类的成员进行清理。
4、赋值运算符重载:必须调用基类的赋值运算符重载对基类的成员进行赋值。
5、构造函数的顺序是先调用激烈的构造初始化基类的成员,再初始化派生类的成员
6、析构的顺序是先清理派生类中的成员,再去调用基类的析构函数清理基类中的成员。这样就能防止内存泄漏。
注意析构函数是在我们释放子类对象时,当子类对象清理完,会自动调用父类的析构函数,以保证清理的顺序和构造刚好相反。所以我们最好不要自己显式调用析构函数。
继承与友元
友元关系不能继承,也就是基类的友元不能访问子类的私有和保护成员。
换句话说也就是你父亲的朋友不是你的朋友,不能访问你的私人空间。
继承与静态成员
如果基类成员定义了一个静态成员,那么在整个继承体系中只有一个静态成员,就是不论继承了多少个子类都只有这么一个static成员实例。(关于static的用法可以点击?static详解)