1.尽管派生类中含有基类继承来的成员,但派生类初始化这部分变量需要调用基类的构造函数。
1 class A 2 { 3 private: 4 int x; 5 virtual void f(){cout<<"A f"<<endl;} 6 public: 7 A(/* args */){x=1;cout<<"A构造"<<endl;} 8 ~A(){} 9 friend void p(const A& a){cout<<a.x;} 10 }; 11 class B:A{ 12 public: 13 void f() override {cout<<"B f"<<endl;} 14 operator A()const {} 15 }; 16 int main() 17 { 18 B x; 19 system("pause"); 20 return 0; 21 }
2.如果基类定义了一个静态成员,则整个继承体系中只存在该成员的唯一定义。并且不论基类派生出多少派生类,该静态成员只存在唯一实例。
并且属性也一致。基类中某静态成员是public,派生类中也是public。如果基类中是private,那么派生类也无法调用该静态成员。
1 /* 基类的静态成员 */ 2 3 #include<iostream> 4 5 using namespace std; 6 7 class base 8 { 9 public: 10 static int num; 11 12 base() 13 { 14 num+=1; 15 } 16 17 ~base() 18 { 19 num-=1; 20 } 21 }; 22 23 // 基类定义的静态成员,被所有派生类共享 24 // 起到计数器的作用,自身还有所有派生类对象的个数 25 // 遵循私有 公有 保护的继承机制 访问不同 26 // 可以通过父类 子类对象访问 还可以通过父类 子类类名访问 27 int base::num = 0;// 类的静态成员 28 29 class basenew : public base 30 { 31 32 }; 33 34 class basenewx : public basenew 35 { 36 37 }; 38 39 40 void main() 41 { 42 basenew *p = new basenew[100]; 43 44 base *p1 = new base[40] 45 46 basenewx *p2 = new basenewx[50]; 47 48 cout << p->num << endl;// 190 49 cout << p1->num << endl;// 190 50 cout << p2->num << endl;// 190 51 52 53 cin.get(); 54 }
3.如果不想让某个类被继承,在类名后加final关键字。
final除了可以修饰类外,还可以修饰成员函数。还可以指明某个基类的虚函数不能被其派生类版本覆盖,如下:
首先要明确覆盖(override)与重载(overload)的定义,区别出什么是覆盖和重载:
覆盖就是派生类中虚成员函数覆盖基类中同名且参数相同的成员函数。
1 class A 2 { 3 public: 4 A(/* args */){} 5 virtual void f()final {} 6 virtual ~A(){} 7 }; 8 class B:public A{ 9 public: 10 void f(int x){}//重载(overload) 11 void f(){} //覆盖(override,非法,因为A中的f声明了final) 12 };
4.派生类对象是基类对象,派生类中包含有基类的成员。基类对象不是派生类对象,它不能包含派生类型的成员。
派生类可以向基类转化(仅限指针和引用),基类不能向派生类转化(毕竟派生类有多余的数据,基类没法自己生成)
如果用派生类对象为一个基类对象初始化或者赋值,只有其中的基类部分会被拷贝/移动/赋值,它的派生类部分会被忽略。
1 class A 2 { 3 private: 4 int x; 5 public: 6 A(/* args */){x=1;cout<<"A构造"<<endl;} 7 virtual ~A(){} 8 }; 9 class B:public A{ 10 11 }; 12 int main() 13 { 14 A x; 15 B y; 16 A* p=&y;//正确,将基类指针指向派生类变量,派生类指针隐式转换为基类指针。 17 B* q=&x;//错误 18 system("pause"); 19 return 0; 20 }
5.动态绑定(即dynamic bninding)只有当我们通过指针或者引用调用虚函数时才会发生。
由于继承导致对象的指针和引用具有两种不同的类型:静态类型和动态类型。
静态类型:指针或者是引用声明时的类型。
动态类型:由实际指向的类型确定。
其中静态类型编译时就已经确定,动态类型只有到运行的时候才能知道。所以如果用普通类类型调用虚函数,编译时就会把要调用的虚函数版本确定下来。
1 class A 2 { 3 public: 4 A(/* args */){} 5 virtual void f(){cout<<"基类的f"<<endl;} 6 virtual ~A(){} 7 }; 8 class B:public A{ 9 public: 10 void f(){cout<<"派生类的f"<<endl;} 11 }; 12 void F(A& p){ 13 p.f(); 14 } 15 int main() 16 { 17 A x; 18 B y; 19 F(x); 20 F(y); 21 system("pause"); 22 return 0; 23 }
可以看到同一个函数F,因为调用不同类型实参,最终在内部调用了不同版本的f函数,这就是动态绑定。
如果F的形参改成普通类型,那么两次都会调用基类的f函数,其中F(y)会把y隐式类型转换为一个A类型的临时变量传给形参。