之前所接触到的复用更多的都是函数复用,而继承则是对类层次的复用。它允许在保持原有类的特性基础上进行扩展,增加功能,产生新的类,称之为派生类(子类),原本的类则称之为基类(父类)。
继承基类成员后访问方式的变化:
友元关系不能继承,也就是说父母的朋友不一定是我们的朋友,是不能够相互访问的。基类定义了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; // 研究科目 test: Person p; Student s; Graduate g; p._count = 1; s._count = 2; g._count = 3; Person::_count = 4; Student::_count = 5; Graduate::_count = 6; cout << &p._count << endl;//6 cout << &s._count << endl;//6 cout << &g._count << endl;//6 }; 优先使用对象组合,而不是类继承,类继承一定程度破坏了基类的封装,也被人们称之为白箱复用,而对象组合则是黑箱复用,组合类之间没有较强的依赖性,耦合度低,代码的维护性较好。把派生类中父类那部分切割出来进行赋值。
同名隐藏:
父类和子类中有同名的成员,子类只能够直接看到自己的成员如果需要访问父类同名的成员,则需要加上父类的作用域不同的作用域下,含有同名成员的话,当前作用域下的成员就会隐藏其他作用域下的同名成员,这不仅仅是继承体系独有的 成员变量隐藏:成员变量的名称相同 函数隐藏:只要函数名相同,就会构成函数隐藏,和参数无关–>这种情况一般是发生在不同作用域的父类和子类中(因此我们在继承体系中最好不要定义同名成员)函数重载:在同一个作用域中,函数名相同,但参数不同的情况。默认成员函数
派生类对象初始化先调用基类构造,再调派生类构造派生类对象析构清理先调用派生类析构,再调用基类的析构为了更好解决菱形继承的二义性和数据冗余问题,可以使用虚拟继承的方式来进行解决(虚拟继承不能够在其他地方使用)virtual
class A { public: int _a; }; class B :virtual public A { public: int _b; }; class C :virtual public A { public: int _c; }; class D : public B, public C { public: int _d; };不同的对象执行同一种行为时所产生的不同状态,被人们称之为多态。(比如我们抢红包,同样的点击动作,出现的金额是不同的)
如果是非多态的话,则需要看它的类型多态的话,则只需要看实际指向的那个实体重写
重写: 派生类中有一个跟基类完全相同的虚函数(返回值类型,函数名,参数列表完全相同)当然其中也有列外的存在,就是协变函数重写和析构函数的重写 协变: 返回值类型可以不同,但是返回值类型必须是父子关系的指针/引用析构函数的重写:虽然函数名不相同,看似违背重写规则,但在编译后的析构函数却是同一个,因此无论子类析构函数是否加virtual关键字,都与父类析构函数构成重写 虚函数的重写 class Person { public: virtual void BuyTicket() { cout << "买票-全价" << endl; } virtual ~Person() {cout << "~Person()" << endl;} }; class Student : public Person { public: virtual void BuyTicket() { cout << "买票-半价" << endl; } virtual ~Student() { cout << "~Student()" << endl; } } 协变函数重写 virtual B& BuyTicket() { cout << "买票-半价" << endl; return B(); } 析构函数的重写 派生类的虚函数即使是不加virtual关键字,也是可以构成重写,因为继承后基类的虚函数被继承下来了,不建议这样使用如果父类函数加了virtual声明,则子类接口完全一致的函数,即使不加virtual的声明,也具有虚函数的属性,建议一般对于所有虚函数,都加上virtual。C++给我们提供了override和final两个关键字,可以帮助我们检测是否重写
final:修饰虚函数,表示改虚函数不能被继承 class Car { public: virtual void Drive() final {}//使用final定义的函数不能被重写,体现实现继承 }; class Benz :public Car { public: virtual void Drive() {cout << "Benz-舒适" << endl;} }; override:检查派生类虚函数是否重写了基类某个虚函数,强制子类重写父类的某一个虚函数,如果没有则重写编译报错 class Car{ public: virtual void Drive(){} }; class Benz :public Car { public: virtual void Drive() override {cout << "Benz-舒适" << endl;}//体现接口继承 };重载,重写,隐藏的对比: 3. 抽象类(纯虚函数) 在虚函数的后面写上= 0,则整个函数为纯虚函数(纯虚函数没有函数体),包含纯虚函数的类也被称之为抽象类,不能实例化对象。
class Car { public: virtual void Drive() = 0; };这也就意味着在类中,存放着一个_vfptr指针放在对象的前面,我们将这个指针叫做虚函数表指针。一个含有虚函数的类中都至少有一个虚函数表指针,因为虚函数的地址要被放到虚函数表(虚表)中。
class Base { public: virtual void Func1() { cout << "Base::Func1()" << endl; } virtual void Func2() { cout << "Base::Func2()" << endl; } void Func3() { cout << "Base::Func3()" << endl; } private: int _b = 1; }; class Derive : public Base { public: //虚表:存放函数指针的数组 // virtual void Func1() { cout << "Derive::Func1()" << endl; } private: int _d = 2; }; void test() { Base b; Derive d; Base& rb = b; Base& rd = d; rb.Func1(); rd.Func1(); rd.Func2(); d.Func1(); }2. 多继承中的虚函数表
class Base1 { public: virtual void func1() { cout << "Base1::func1" << endl; } virtual void func2() { cout << "Base1::func2" << endl; } private: int b1; }; class Base2 { public: virtual void func1() { cout << "Base2::func1" << endl; } virtual void func2() { cout << "Base2::func2" << endl; } private: int b2; }; class Derive : public Base1, public Base2 { public: virtual void func1() { cout << "Derive::func1" << endl; } virtual void func3() { cout << "Derive::func3" << endl; } private: int d1; }; //定义 函数指针: void 函数() typedef void(*vfPtr)(); void doVF(vfPtr* vftable) { cout << "虚表地址:" << vftable << endl; //nullptr: 结束 for (int i = 0; vftable[i] != nullptr; ++i) { //获取当前虚表位置的函数指针 vfPtr func = vftable[i]; //执行指向的函数 func(); } } void test() { Base1 b; Base2 b2; Derive d; cout << "Base1:" << &b << endl; vfPtr* vftable = (vfPtr*)(*((int*)&b)); doVF(vftable); cout << "Base2:" << &b2 << endl; vftable = (vfPtr*)(*((int*)&b2)); doVF(vftable); //访问Derive的第一个虚表 vftable = (vfPtr*)(*((int*)&d)); cout << "Derive first vftable:" << &d << endl; doVF(vftable); //访问Derive的第二个虚表: 地址偏移 vftable = (vfPtr*)(*((int*)((char*)&d + sizeof(Base1)))); cout << "Derive second vftable:" << (int*)((char*)&d + sizeof(Base1)) << endl; doVF(vftable); }总结: