派生类可以将基类的所有成员都继承过来,等于说,在派生类的内存空间中也存在了一份基类的成员变量,只不过这些变量都是带作用域的,如下图所示:
class A {
int ma;
};
class B : A {
int mb;
};
class C : B {
int mc;
};
int main() {
A a;
B b;
C c;
cout << sizeof(a) << endl; //4
cout << sizeof(b) << endl; //8
cout << sizeof(c) << endl; //12
return 0;
};
public,protected,private
class Base {
public:
Base(int data = 10) : ma(data) {}
int ma;
};
class Derive : public Base {
public:
// Derive(int data = 10) : ma(), mb(data) {} //会报错,无法直接对继承来的成员进行初始化
Derive(int data = 10) : Base(data), mb(data) {} //必须使用基本的构造函数进行初始化
int mb;
};
class Base {
public:
Base(int data = 10) : ma(data) { cout << __FUNCTION__ << endl; }
~Base() { cout << __FUNCTION__ << endl; }
void show() { cout << this << endl; } //1号函数
void show(int a) { cout << a << endl; } //2号函数
int ma;
};
class Derive : public Base {
public:
Derive(int data = 10) : mb(data) { cout << __FUNCTION__ << endl; }
void show() { cout << __FUNCTION__ << endl; } //3号函数
~Derive() { cout << __FUNCTION__ << endl; }
int mb;
};
int main() {
Derive b(10);
// b.show(100); //报错因为带int参数的show函数被隐藏了
b.Base::show(100); //如果要调用上述函数,需要加作用域
system("pause");
return 0;
};
1号函数和2号函数是重载关系
3号函数隐藏了1号函数和2号函数,所以,如果构造了Derive的对象,并调用show(int a)
函数会出错,因为现在Derive对象只能看到自己的show()
函数。
继承结构是一种从上(基类)向下(派生类)的结构
总结来说,派生类是基类,基类不是派生类,类似于,白马是马,马不是白马。
静态绑定(编译时期的绑定):编译器在编译时期根据对象的类型调用相应作用域下的成员函数,如果该成员函数是非虚函数,就直接调用该类型作用域下的函数生成指令。
动态绑定(运行时期的绑定):编译器在编译时期根据对象的类型调用相应作用域下的成员函数,如果该成员函数是虚函数,就在运行期间调用实际对象的虚函数表中对应的虚函数。
注意:
class Derive: public Base1, public Base2
class Base {
public:
Base() { cout << __FUNCTION__ << endl; }
~Base() { cout << __FUNCTION__ << endl; }
};
class Derive : public Base {
public:
Derive() { cout << __FUNCTION__ << endl; }
~Derive() { cout << __FUNCTION__ << endl; }
};
int main() {
Base *pb = new Derive;
delete pb;
return 0;
};
以上程序输出如下:
Base
Derive
~Base
问题:~Derive
析构函数并没有调用,这样会导致内存泄漏
问题产生原因:delete pb
运行时,会去调用pb指针类型(Base类)的析构函数,编译器发现Base类的析构函数不是virtual,于是发生静态绑定,直接调用Base类的析构函数,而不会调用Derive的析构函数
解决办法:将基类析构函数~Base声明成virtual ~Base()
,如此操作之后输出如下:
Base
Derive
~Derive
~Base
静态(编译时期)多态:
动态(运行时期)多态:
class Animal {
public:
virtual void bark() = 0; //纯虚函数,Animal类成为抽象类,纯虚函数必须被派生类重写
};
如图所示,虚继承的时候,基类的内容会被移到派生类对象内存区域的最后。(以上只是msvc中的对象内存结构,在gcc中,内存结构并没有发生后移的现象)
基类指针指向派生类对象的时候,指针指向的永远是派生类对象中基类内容的开始部分
class Base {
public :
virtual void func() { cout << "Base::func" << endl; }
void operator delete(void *p) {
cout << "operate delete p:" << p << endl;
free(p);
}
};
class Derive : virtual public Base {
public :
void func() { cout << "Derive::func" << endl; }
void *operator new(size_t size) {
void *p = malloc(size);
cout << "operate new p:" << p << endl;
return p;
}
};
int main() {
Base *pb = new Derive();
cout << "main p:" << pb << endl;
pb->func();
delete pb; //msvc编译器和gcc编译器在处理此处处理有所不同
system("pause");
return 0;
};
如上代码所示在delete pb
的时候会出现问题,由于pb指向的并不是Derive对象的内存起始位置,而是Derive对象中从基类继承来的内容的起始部分,所以delete的时候会出错,gcc编译器会自动处理上述问题,而msvc会报错。
菱形继承和圆形继承如下图所示
以上继承方式,最后的派生类会重复继承到最初基类的成员。
解决办法:将最初基类继承的方式变成虚继承。
因为虚继承的时候会把基类的内存转移到派生类对象最后,然后覆盖掉基类重复的变量。