1 继承的概念
通过一种机制表达出类型之间的共性和特性的方式,利用已有的数据类型定义新的数据类型,这种机制称为继承。
eg:
人类 :姓名 年龄 吃饭 睡觉
学生类 :姓名 年龄 学号 吃饭 睡觉 学习
教师类 :姓名 年龄 工资 吃饭 睡觉 讲课
人类 :姓名 年龄 吃饭 睡觉
学生类继承人类 :学号 学习
教师类继承人类 :工资 讲课
人类(基类/父类)
/ \
学生类 教师类(派生类/子类)
2 继承语法
class 子类:继承方式 基类{...}
继承方式:
公有继承:public
保护继承:protected
私有继承:private
eg:
class Human{
string name;
int age;
};
class Student:public Human{
//Student类继承Human类
//在Student中存在一份Human类中的成员
};
3 公有继承的特性
1)子类对象会继承基类的属性和行为,通过子类对象访问基类中的成员,如同是基类对象在访问它们一样。
子类对象中包含基类的部分称为"基类子对象"
2)向上造型(重点掌握)
将子类类型的指针或引用转换为基类类型指针或引用。
这种操作性缩小的类型转换,在编译器看来是安全的,可以隐士完成。
eg:
void func(Human& h){...}
void func2(Human* h){...}
int main(void){
Student s(..);
func(s);//向上造型
func2(&s);//向上造型
}
3)向下造型
将基类类型的指针或引用转换为子类类型的指针或引用。
这种操作性放大的类型转换,在编译器看来是危险的,因为必须显示完成。
4)子类继承基类的成员
在子类中,可以直接访问基类中的公有成员和保护成员,如果是子类自己的成员一样。
基类的私有成员在子类中存在但是不可见,所以无法直接访问,但是通过基类提供的公有或者保护的借口函数可以间接访问。
5)基类的构造函数和析构函数,子类是无法继承的,但是可以在子类自己的构造函数中,通过初始化表显示说明基类子对象的初始化方式。
6)子类隐藏基类的成员
子类和基类中定义同名的成员函数,作用域不同,不会构成重载关系,而是一种隐藏关系。如果需要在子类中访问所隐藏的成员,可以使用"类名::"方式显示指明。
如果同名的成员函数符合同名不同参两个重载的条件,可以使用using声明的方式将基类的成员函数引入子类的作用域,让其在子类中形成重载关系。
4 继承方式和访问控制属性
访问控制 访问控制 内部 子类 外部 友元
限定符 属性
public 公有成员 ok ok ok ok
protected 保护成员 ok ok no ok
private 私有成员 ok no no ok
===============================================
基类中的 在公有子 在保护子 在私有子
类中变成 类中变成 类中变成
公有成员 公有成员 保护成员 私有成员
保护成员 保护成员 保护成员 私有成员
私有成员 私有成员 私有成员 私有成员
注:私有继承和保护继承不能向上造型
5 子类构造函数
1)如果子类的构造函数没有显示指明基类子对象的初始化方式,那么编译器会调用基类的无参构造函数初始化基类子对象。
2)如果希望基类子对象以有参的方式被初始化,必须在子类构造函数的初始化表中显示指明其初始化方式。
3)子类对象的构造过程
--》分配内存
--》构造基类子对象(按继承表的顺序)
--》构造成员子对象(按声明的顺序)
--》执行子类构造函数的代码
6 子类的析构函数
1)子类的析构函数,无论是自己定义的还是编译缺省提供的,都会调用基类的析构函数,析构基类子对象。
2)子类对象的析构过程
--》执行子类析构函数的代码
--》析构成员子对象(按声明逆序)
--》析构基类子对象(按继承表逆序)
--》释放内存
3)基类的析构函数不会调用子类的析构函数,delete一个指向子类对象的基类指针,实际被调用的仅仅是基类的析构函数,子类中的析构函数不会被调用,有内存泄露的风险。
eg:
class A{..};
class B:public A{};
int main(void){
A* pa = new B;//pa :指向子类对象的基类指针
delete pa;//实际只调用基类的析构,内存泄露
}
明天:多重继承