[C++学习笔记12]继承
- 代码重用
C++代码重用的方式
继承
组合:在一个类中定义另一个类的成员变量。 - 继承简介
继承是使用已经编写好的类来创建新类,新的类具有原有类的所有属性和操作,也可以在原有类的基础上作一些修改和增补;
新类称为派生类或子类,原有类称为基类或父类;
派生类是基类的具体化。
派生类的声明语法:
class 派生类名 : 继承方式 基类名 {
...
}; - 公有、私有、保护继承
- 默认继承保护级别
class Base {};
struct D1 : Base {}; // 公有继承
class D2 : Base {}; // 私有继承 - 接口继承与实现继承
接口:类的公有成员函数。
接口继承:公有继承,基类的公有成员(接口)成了派生类的公有成员(接口)。
实现继承:私有、保护继承,派生类不再支持基类的公有接口,它希望能重用基类的实现而已。 - 继承与重定义
对基类的数据成员的重定义
对基类成员函数的重定义分为两种
overwrite
与基类完全相同
与基类成员函数名相同,参数不同(无法再调用基类的成员函数)
override(覆盖)
与虚函数相关,后面介绍
#include <iostream> using namespace std; class Base { public: Base() : x_(0) { } void show() { cout << "Base::show ..." << endl; } int x_; }; class Derive : public Base{ public: Derive() : x_(0) { } void show(int n) { cout << "Derive::show(n) ..." << endl; } void show() { cout << "Derive::show() ..." << endl; } int x_; // 重定义基类的成员变量 }; int main(void) { Derive d; cout << d.Base::x_ << endl; // 可以通过这样的方式来调用基类的成员 //d.show(); // 派生类没有定义无参的show函数之前,error C2660: “Derive::show”: 函数不接受 0 个参数 // 但是可以直接通过d.Base::show() 去调用基类的 d.show(); d.Base::show(); return 0; }
- 继承与组合
无论是继承还是组合本质上都是把子对象放在新类型中,两者都是使用构造函数的初始化列表去构造这些子对象;
组合通常是在希望新类内部具有已存在的类的功能时使用,而不是希望已经存在的类作为它的接口,组合通过嵌入一个对象实现新类的功能,而新类用户看到的是新类的接口,而不是来自老类的接口。(has-a)
如果希望新类与已存在的类有相同的接口(在这基础上可以增加自己的成员),这时候就需要用继承,也称为子类型化。(is-a) - 不能自动继承的成员函数
构造函数
析构函数
=运算符 - 继承与构造函数
基类的构造函数不能被继承,派生类中需要声明自己的构造函数;
声明构造函数时,只需要对本类新增成员进行初始化,对继承来的基类成员的初始化(调用基类构造函数完成);
派生类的构造函数需要给基类的构造函数传递参数。
#include <iostream> using namespace std; /** 1.先调用基类构造函数,再调用派生类构造函数; 2.派生类中包含了类的成员变量,先调用调用基类构造,再调用派生类成员的构造函数,最后调用派生类构造; 3.在2的基础上,基类中也包含了类的成员变量,先调用基类成员的构造函数,然后调用基类构造函数,再调用派生类成员的构造函数,最后调用派生类构造; */ class ObjectB { public: ObjectB(int objb) : objb_(objb) { cout << "ObjectB() ..." << endl; } ~ObjectB() { cout << "~ObjectB() ..." << endl; } int objb_; }; class ObjectD { public: ObjectD(int objd) : objd_(objd) { cout << "ObjectD() ..." << endl; } ~ObjectD() { cout << "~ObjectD() ..." << endl; } int objd_; }; class Base { public: Base(int b) : b_(b), objb_(b) // 类自己负责自己的成员变量的初始化 { cout << "Base() ..." << endl; } Base(const Base &other) : objb_(other.objb_), b_(other.b_) { } ~Base() { cout << "~Base() ..." << endl; } int b_; ObjectB objb_; }; class Derived : public Base { public: // 当Base基类没有默认构造函数,需要在派生类指定基类的构造函数 Derived(int d) : d_(d), Base(d), objd_(d) { cout << "Derived() ..." << endl; } Derived(const Derived &other) : objd_(other.objd_), Base(other), d_(other.d_) { } ~Derived() { cout << "~Derived() ..." << endl; } int d_; ObjectD objd_; // 2派生类中包含了类的成员变量 }; int main(void) { Derived d(10); cout << d.b_ << " " << d.d_ << endl; Base b1(100); Base b2(b1); cout << b2.b_ << endl; // 基础类型可以不用在(拷贝)构造函数初始化,但是结果是不确定的 Derived d1(100); Derived d2(d1); cout << d2.b_ << " " << d2.d_ << endl; return 0; }
- 有元关系与继承
有元关系不能被继承。 - 静态成员与继承
静态成员无所谓的继承。 - 转换与继承
派生类对象也是基类对象,这意味着在使用基类的地方可以用派生类来替换。 - 派生类到基类的转换
当派生类以public方式继承基类时,编译器可自动执行的转换(向上转型upcasting安全转换)
派生类对象指针自动转换成基类对象指针;
派生类对象引用自动转换成基类对象引用;
派生类对象自动转换成基类对象,拥有的对象成员消失。
当派生类一private/protectd方式继承基类时
派生类对象指针(引用)转换为基类对象指针(引用)需用强制类型转换,但不能用static_cast,要用reinterpret_cast;
不能把派生类对象强制转换成基类对象。
派生类到基类的转换实例
/** 派生类指针(引用),对象转换成基类指针(引用),对象的演示 **/ #include <iostream> #include <string> using namespace std; class Employee { public: Employee(string name, int age) : name_(name), age_(age) { } private: string name_; int age_; }; class Manager : /*public*/private Employee { public: Manager(string name, int age, int level) : Employee(name, age), level_(level) { } private: int level_; }; int main() { Employee e1("zhangsan", 20); Manager m1("lisi", 38, 10); Employee *pe; Manager *pm; pe = &e1; pm = &m1; //pe = &m1; // OK, 1.public继承,基类指针指向派生类对象 //e1 = m1; // OK, 1.public继承,派生类对象赋值给基类对象 //pe = &m1; // Error,2.private/protected继承,不能自动转换 //pe = (Employee *)&m1; // OK, 2.private/protected继承,使用传统C语言转换 //pe = static_cast<Employee *>(&m1);// Error, 2.private/protected继承,不能使用static_cast转换 //pe = reinterpret_cast<Employee *>(&m1);// OK,2.private/protected继承, 可以使用reinterpret_cast转换 // private/protected继承,只能完成对象指针(引用)的转换,不能完成对象的转换 //e1 = reinterpret_cast<Employee>(m1); // Error, 2.private/protected继承,即使是reinterpret_cast也不能完成对象的转换 return 0; }
- 基类到派生类的转换
基类对象指针(引用)可用强制类型转换转换成派生类对象指针(引用),而基类对象无法强制转换成派生类对象;
向下转型不安全,没有自动转换的机制(可以通过在基类中实现类型转换运算符或者在派生类中实现拷贝构造函数)。
基类到派生类的转换实例
/** 基类指针(引用),对象转换成派生类指针(引用),对象的演示 **/ #include <iostream> #include <string> using namespace std; class Employee { public: Employee(string name, int age) : name_(name), age_(age) { } private: string name_; int age_; }; class Manager : public/*private*/ Employee { public: Manager(string name, int age, int level) : Employee(name, age), level_(level) { } private: int level_; }; int main() { Employee e1("zhangsan", 20); Manager m1("lisi", 38, 10); Employee *pe; Manager *pm; pe = &e1; pm = &m1; // 基类转换成派生类,无private,public继承区别 //pm = &e1; // Error, 不能自动转换 //pm = (Manager *)&e1; // OK, C语言的强制转换 //pm = static_cast<Manager *>(&e1); // OK, static_cast转换 //pm = reinterpret_cast<Manager *>(&e1); // OK, reinterpret_cast转换 //m1 = (Manager)e1; // 即便是强制转换也不能完成对象的转换 // m1 = reinterpret_cast<Manager>(e1); // 即便是强制转换也不能完成对象的转换 return 0; }
- 多重继承
一个派生类可以有多个基类
class 类名 : 继承方式 基类1, 继承方式 基类2, ... {
...
};
派生类同时继承多个基类,更好的软件利用;
可能会有大量的二义性,多个基类中可能包含同名变量或函数。
多重继承解决访问歧义的方法
基类名::数据成员名(或成员函数(参数表))
明确指明要访问定义于哪个基类中的成员。
多重继承类图(主要为后面虚继承类图区别开)
实例演示
class Sofa { public: int weight_; }; class Bed { public: int weight_; }; class SofaBed : public Sofa, public Bed { public: SofaBed() { }// 不提供,error C4700: 使用了未初始化的局部变量“sofaBed” }; int main(void) { SofaBed sofaBed; // 必须提供默认构造函数初始化sofaBed,不然报错 // cout << sofaBed.weight_ << endl; // Error,访问不明确 cout << sofaBed.Bed::weight_ << endl; // OK cout << sofaBed.Sofa::weight_ << endl; // OK return 0; }
- 虚继承与虚基类
当派生类从多个基类派生,而这些基类又从同一个基类派生,则在访问此共同基类中的成员时,将产生二义性---采用虚基类来解决;
虚基类的引入
用于有共同基类的场合
声明
以virtual修饰说明基类
class B1 : virtual public class BB { ... }; // 其中virtual与public可能互换位置
作用
主要用来解决多继承时可能发生的对同一基类继承多次而产生的二义性的问题;
为最远的派生类提供唯一的基类成员,而不重复产生多次拷贝。
虚继承类图(主要是为前面的简单多继承区分)
歧义性样例
class Furniture { public: int weight_; }; class Sofa : public Furniture { }; class Bed : public Furniture { }; class SofaBed : public Sofa, public Bed { }; int main(void) { SofaBed sofaBed; sofaBed.weight_ = 10; // Error, error C2385: 对“weight_”的访问不明确 return 0; }
class Furniture { public: int weight_; }; class Sofa : virtual public Furniture { }; class Bed : virtual public Furniture { }; class SofaBed : public Sofa, public Bed { };
- 虚基类及其派生类构造函数
虚基类的成员是由最远派生类的构造函数通过调用虚基类的构造函数进行初始化的;
在整个继承结构中,直接或间接继承虚基类的所有派生类都必须在构造函数的成员初始化列表中给出对虚基类的构造函数的调用,如果未列出,则表示调用该虚基类的默认构造函数;
在建立对象时,只有最远派生类的构造函数调用的虚基类的构造函数有效,该派生类的其他基类对虚基类的构造函数的调用被忽略。
实例演示
class Furniture { public: Furniture(int weight) : weight_(weight) { cout << "Furniture()..." << endl; } ~Furniture() { cout << "~Furniture()..." << endl; } int weight_; }; class Sofa : virtual public Furniture { public: Sofa() : Furniture(10){ cout << "Sofa()..." << endl; } ~Sofa() { cout << "~Sofa()..." << endl; } }; class Bed : virtual public Furniture { public: Bed() : Furniture(20) { cout << "Bed()..." << endl; } ~Bed() { cout << "~Bed()..." << endl; } }; // Sofa 与 Bed 构造函数的调用顺序与此处继承顺序的先后有关系 class SofaBed : public Sofa, public Bed/*, public Sofa*/ { public: SofaBed() : Furniture(0){ cout << "SofaBed()..." << endl; } ~SofaBed() { cout << "~SofaBed()..." << endl; } }; int main(void) { SofaBed sofaBed; /** Furniture()... // 只有最远派生类构造函数调用虚基类的构造函数有效 Sofa()... // Sofa,Bed顺序与继承顺序有关 Bed()... SofaBed()... ~SofaBed()... ~Bed()... ~Sofa()... ~Furniture()... */ sofaBed.weight_ = 100; // OK 不会出现二义性 return 0; }
- 虚继承对类的大小的影响
虚基类表
virtual base table
本类地址与虚基类表指针地址的差;
虚基类地址与虚基类表指针地址的差;
virtual base table pointer(vbptr)
指向虚基类表的指针
一个简单的案例分析
代码
class BB { public: int bb_; }; class B1 : virtual public BB{ public: int b1_; }; class B2 : virtual public BB{ public: int b2_; }; class DD : public B1, public B2 { public: int dd_; }; int main() { cout << sizeof(BB) << endl; // 4 cout << sizeof(B1) << endl; // 12 cout << sizeof(DD) << endl; // 24 B1 b1; cout << &b1 << endl; cout << &b1.bb_ << endl; cout << &b1.b1_ << endl; long **p = NULL; p = (long **)&b1; cout << p[0][0] << endl; // 0 cout << p[0][1] << endl; // 8 DD dd; cout << &dd << endl; cout << &dd.bb_ << endl; cout << &dd.b1_ << endl; cout << &dd.b2_ << endl; cout << &dd.dd_ << endl; p = (long **)ⅆ cout << p[0][0] << endl; // 0 cout << p[0][1] << endl; // 20 cout << endl; cout << p[2][0] << endl; // 0 cout << p[2][1] << endl; // 12 BB *pp; pp = ⅆ pp->bb_; // 间接访问,需要运行时支持 return 0; }