C++中的继承 ** 一.继承的概念及定义 二.基类和派生类对象赋值转换 三.继承中的作用域 四.派生类的默认成员函数 五.继承与友元 六.继承与静态成员 七.复杂的菱形继承及菱形虚拟继承 八.继承和组合 ** 一.继承的概念及定义 1.概念 使代码可以复用的手段(继承是类设计层次的复用) 允许程序员在保持原有类特性的基础上进行扩展,增加功能,产生新的类,称为派生类。 2.定义 1.定义格式 class [派生类]:[继承方式] [基类] class默认的继承方式是privata struct默认的继承方式是public 2.继承关系和访问限定符 1.继承权限 1.概念 不同继承方式下,基类中不同访问权限的成员在子类中的权限。 2.分类 public继承,private继承,protected继承 2.访问权限 1.概念 限定该成员变量是否可以直接在类外进行调用。 2.分类 public访问,private访问,protected访问 3.继承基类成员访问方式的变化 1.基类private成员在派生类中无论以什么方式继承都是不可见的。 不可见是指基类的私有成员还被继承到了派生类对象中,但是在语法上限制派生类对象不管在类里面还是类外面都不能访问它。 2.public继承 3.protected继承 4.private继承 二.基类和派生类对象赋值转换 前提:public继承 1.派生类与基类对象的关系是is-a的关系 is-a:可以将一个子类对象看成是一个基类对象(所有用到基类对象的位置,可以用子类对象来代替) 2.派生类对象可以赋值给 基类的对象/基类的引用/基类的指针。 b = d; B pb = &d; B& rb = d; 3.基类对象不能赋值给派生类对象 4.基类指针可以通过强制转换赋值给派生类的指针。(但是基类的指针是指向派生类对像时才是安全的) Dpd=(D *)&b;
class B { public: void SetB(int b) { _b = b; } public: int _b; }; class D :public B { public: void SetD(int b, int d) { _b = b; _d = d; } public: int _d; }; //赋值兼容规则:前提--》public //如果是public继承方式:派生类与基类对象之间是一个--is-a的关系 //is-a:是一个可以将一个子类对象看成是一个基类对象 //所有用到基类对象的位置都可以使用子类对象进行代替 int main() { B b; D d; //一个基类对象可以指向子类对象 //一个子类的指针不能直接指向一个基类的对象 // 1.子类对象可以赋值给父类对象/指针/引用 b = d; B *pb = &d; B& rb = d; //2.基类对象不能赋值给派生类对象 //d=b; // 3.基类的指针可以通过强制类型转换赋值给派生类的指针 D* pd =(D*) &b; pd->_d = 40; pd-_b=10; return 0; }三.继承中的作用域 1.在继承体系中基类和派生类都有独立的作用域。 2.子类和父类有同名成员,子类成员将屏蔽父类对同名成员的直接访问(不是构成重载,因为不是同一作用域,构成隐藏/重定义)。 与变量类型是否相同无关 3.成员函数的隐藏,只需函数名相同就能构成隐藏。 与成员函数原型是否一样无关 4.在继承体系中最好不要定义同名的成员。(可读性差)
class B { public: void SetB(int b) { _b = b; } void Test(int a) {} char _b; }; class D :public B { public: void SetD(int b, int d) { _b = b; _d = d; } void Test() { } int _b; int _d; }; int main() { cout << sizeof(D) << endl; D d; d._b = '1'; d.B::_b = '2'; d.Test(); d.B::Test(10); //d.Test(10);//也是会调用d.Test函数,即使和d中的函数参数不匹配 d.SetD(1, 2); return 0; }四.派生类的默认成员函数 1.构造函数 1.基类如果没有显示定义构造函数,子类也可以不用定义 2.基类具有无参或者全缺省的构造函数,子类也可以不用定义。除非需要做特定事情,子类可以给出构造函数。 3.基类具有带参数的普通构造函数(不是全缺省),则派生类的构造函数必须显示提供,而且要在其初始列表的位置显示调用基类的构造函数。 2.拷贝构造函数 1.基类如果没有定义,子类也可以不用定义 两个类都采用默认的构造函数(前提:不会涉及到资源管理) 2.基类如果显式调用了自己的拷贝构造函数,派生类也必须显示定义,而且要在其初始化列表的位置显示调用基类的拷贝构造函数。 3.赋值运算符重载 1.基类如果没有定义,子类也可以不用定义。除非派生类非要在赋值期间做其他操作,根据情况给出。 2.基类定义赋值运算符重载,一般情况下子类也要提供,并在其中调用基类的赋值运算符重载。 4.析构函数 派生类的析构函数会在被调用完成后自动调用基类的析构函数清理基类成员。 (构造函数和析构函数的调用顺序和打印顺序的区别?)
class B { public: B(int b) :_b(b) { cout << "B(int)" << endl; } ~B() { cout << "B::~B()" << endl; } B(const B& b) :_b(b._b) {} B& operator=(const B& b) { if (this != &b) { _b = b._b; } return *this; } protected: int _b; }; class D :public B { public: D(int b,int d) :B(b) , _d(d) { cout << "D(int,int)" << endl; } D() { cout << "D::~D()" << endl; } D(const D& d) :B(d) , _d(d._d) {} D& operator=(const D& d) { if (this != &d) { B::operator=(d); _d = d._d; } return *this; } protected: int _d; }; void TestD() { D d(1, 2); } int main() { D d1(1, 2); D d2(d1); D d3(3, 4); d2 = d3; return 0; }注意:打印结果和调用顺序不同 1.运行结束的打印结果 TestD的打印结果 : 2.构造和析构的调用次序:
构造次序: 派生类构造函数() :基类构造函数() {} 析构次序: 派生类析构函数() { 释放派生类资源 //编译器在派生类析构函数最后一条有效语句后插入一条汇编代码: call基类析构函数; }常见的面试题:实现一个不能被继承的类 思路: 1.首先把构造函数设置为私有函数,就会成为不能继承的类 2.不能创建这个类的对象,所以在类中在设置一个共有函数可以调用类中的私有构造函数。 3.为什么设置成静态的公有函数? 可以在类外通过类名访问类中的函数(因为成员函数必须通过对象来访问,静态成员函数可以通过类名访问)。
// C++98中构造函数私有化,派生类中调不到基类的构造函数。则无法继承 class Base { public: static Base GetObject(int b) { return Base(b); } private: Base(int b) :_b(b) {} protected: int _b; }; // C++11给出了新的关键字final禁止继承 class Base final {};五.继承与友元 友元关系不能继承,即基类友元不能访问子类私有和保护成员。
class Base { friend void Print(); public: Base(int b) :_b(b) {} int GetB() { return _b; } protected: int _b; }; class Derived :public Base { public: Derived(int b,int d) :Base(b) , _d(d) {} protected: int _d; }; void Print() { Base b(10); cout << b._b << endl;//友元函数可以访问类中的保护和私有变量 cout << b.GetB() << endl; Derived d(10, 2); //cout << d._d << endl;//类外不能访问类中的保护变量 }六.继承与静态成员 基类定义了static静态成员,则整个继承体系里面只有一个这样的成员。 1.基类中的静态成员可以被子类继承。 2.在整个继承体系中,静态成员变量只有一份。
//统计一个类创建了多少对象 class Person { public: Person(const string& name, const string& gender, int age) :_name(name) , _gender(gender) , _age(age) { ++_count; } Person(const Person& p) :_name(p._name) , _gender(p._gender) , _age(p._age) { ++_count; } ~Person() { --_count; } protected: string _name; string _gender; int _age; public: static size_t _count; }; size_t Person::_count = 0; class Student :public Person { public: Student(const string& name,const string& gender,int age,int stuld) :Person(name,gender,age) , _stuld(stuld) {} Student(const Student& s) :Person(s) , _stuld(s._stuld) {} protected: int _stuld; }; class Teacher :public Person { public: Teacher(const string& name,const string gender,int age,int stuld) :Person(name,gender,age) , _stuld(stuld) {} Teacher(const Teacher& s) :Person(s) , _stuld(s._stuld) {} protected: int _stuld; }; void TestPerson() { Person p("111", "男", 18); Student s("222", "女", 34, 23); cout << Person::_count << endl; cout << Student::_count << endl; Student::_count = 0; cout << Person::_count << endl; cout << &Person::_count << endl; cout << &Student::_count << endl; cout << &Teacher::_count << endl; } int main() { TestPerson(); return 0; }七.复杂的菱形继承及菱形虚拟继承 1.菱形继承<钻石继承> 菱形继承是多继承的一种特殊情况. 单继承:一个子类只有一个直接父类的继承关系。 多继承:一个子类有两个或两个以上直接父类的继承关系。
菱形继承的问题 数据冗余和二义性 解决方法: 1.加上基类的作用域–访问明确(只能通过代码编译,不能从根本上解决问题)。 2.虚拟继承。
class B { public: int _b; }; //class C1 :public B class C1 :virtual public B { public: int _c1; }; //class C2 :public B class C2 :virtual public B { public: int _c2; }; class D :public C1, public C2 { public: int _d; }; int main() { cout << sizeof(D) << endl; //20 D d; #if 0 //d._b = 2; //菱形继承缺陷:会存在二义性 cout << sizeof(D) << endl; //20 d.C1::_b = 1; d._c1 = 4; d.C2::_b = 3; d._c2 = 5; d._d = 5; #endif //虚拟继承 cout << sizeof(D) << endl; //24 d._b = 2; d._c1 = 4; d._c2 = 5; d._d = 5; return 0; }虚拟继承的的内存对象成员模型 : 这里是通过C1和C2的两个指针,指向一张表, 这两个指针叫虚基表指针,这两个表叫虚基表。 虚基表中存的偏移量。通过偏移量可以找到B。 八.继承和组合 1.继承组合的区别 1.public继承是一个is-a的关系。 每个派生类对象都有一个基类对象。 派生类和基类的依赖的关系很强,耦合度高。 2.组合是一种has-a的关系。 假设B组合A,每个B对象中都有一个A对象。 组合类之间没有很强的依赖关系,耦合度低。代码维护性好。 3.类之间关系可以用继承,可以用组合,就优先考虑组合。(实现多态必须用继承)
2.白箱复用和黑箱复用 1.白箱复用 继承允许根据基类的实现来定义派生类的实现。通过生成派生类的复用通常被称为白箱复用。 2.黑箱复用 新的更复杂的功能通过组合或组装对象来获得,要求组合的对象要有良好定义的接口。复用风格称为黑箱复用。
// Car和BMW Car和Benz构成is-a的关系 class Car{ protected: string _colour = "白色"; // 颜色 string _num = "陕ABIT00"; // 车牌号 }; class BMW : public Car{ public: void Drive() { cout << "好开-操控" << endl; } }; class Benz : public Car{ public: void Drive() { cout << "好坐-舒适" << endl; } }; // Tire和Car构成has-a的关系 class Tire{ protected: string _brand = "Michelin"; // 品牌 size_t _size = 17; // 尺寸 }; class Car{ protected: string _colour = "白色"; // 颜色 string _num = "陕ABIT00"; // 车牌号 Tire _t; // 轮胎 };