一、什么是继承
继承是面向对象编程的一种特性,它允许一个类(称为子类或派生类)继承另一个类(称为父类或基类)的属性和方法。子类可以扩展父类的功能,也可以重写父类的方法。
继承的语法:
class 子类 : public 父类
{
// 子类的成员
};
例子:父类Animal,子类Cat
#include<iostream>
using namespace std;
class Animal
{
public:
int _age;
string _name;
Animal(){}
Animal(int age, string name):_age(age),_name(name){}
void SetAge(int age)
{
_age = age;
}
void SetName(string name)
{
_name = name;
}
};
class Cat:public Animal
{
public:
void EatFish()
{
cout << "cat eat fish" << endl;
}
void Sleep()
{
cout << "cat sleep" << endl;
}
void Show()
{
cout << "age:" <<_age << " name:" << _name << endl;
}
};
int main()
{
Cat cat;
cat.SetAge(5);
cat.SetName("Tom");
cat.Show();
return 0;
}
二、继承方式
分为以下三种:
- public继承:子类可以访问父类的public和protected成员,但不能访问父类的private成员。
- protected继承:子类可以访问父类的public和protected成员,但不能访问父类的private成员。同时,子类的成员在父类外部是不可见的。
- private继承:子类可以访问父类的public和protected成员,但不能访问父类的private成员。同时,子类的成员在父类外部是不可见的,并且子类的成员在子类外部也是不可见的。
如果不考虑继承,protected 和 private是一样的。但是当存在继承的时候,基类中的protected成员可以再派生类中访问,而基类中的private成员不能在派生类中访问。
如图理解:
派生类B中的继承方式是public,那么基类A中的public成员在派生类B中仍然是public的,protected成员在派生类B中仍然是protected的,private成员在派生类B中是不可见的。
派生类C中的继承方式是protected,那么基类A中的public成员在派生类C中仍然是protected的,protected成员在派生类C中仍然是protected的,private成员在派生类C中是不可见的。
派生类D中的继承方式是private,那么基类A中的public成员在派生类D中仍然是private的,protected成员在派生类D中仍然是private的,private成员在派生类D中是不可见的。
使用准则
-
继承方式中的public、protected、private,是用来指明基类成员在派生类中最高的访问权限。
-
不管如何继承,基类中的private成员始终不能使用
-
如果需要基类成员能够被派生类继承并且毫无障碍地使用,那么这些成员只能声明为public或protected。不希望使用的声明为private。
-
如果不希望基类成员在类外通过对象访问,还能在派生类中使用,只能声明为protected。
-
实际开发中,一般使用public。
-
在派生类中,可以通过基类的公有成员访问基类的私有成员。
class A { public: A(){} A(int a, int b, int c):A_a(a), A_b(b), A_c(c){} int A_a = 10; void Show() { cout << "_b = " << A_b << endl; cout << "_c = " << A_c << endl; } protected: int A_b = 20; private: int A_c = 30; }; class B:public A { public: B(){} }; int main() { B b; b.Show(); //_b = 20 _c = 30 return 0; }
-
使用using 关键字,可以改变基类成员在派生类中的访问权限。(只能改变基类中public 和 protected成员的访问权限,不能改变private的访问权限,因为基类中private成员在派生类中是不可见的,根本不能使用)。
class B:public A { public: using A::A_b; //将A中的被保护类型更改为公有类型 protected: using A::A_a; //将A中的公有类型更改为被保护类型 };
三、继承的对象模型
-
创建派生类对象时,先调用基类的构造函数,再调用派生类的构造函数。
-
销毁派生类对象时,先调用派生类的析构函数,再调用基类的析构函数。
-
创建派生类对象时只会申请一次内存,派生类对象包含了基类对象的内存空间,this指针也是同样。
-
创建派生类对象时,先初始化基类对象,再初始化派生类对象。
-
对派生类对象用sizeof得到的是基类所有成员(包括私有成员)+ 派生类所有成员的大小。
#include <iostream> using namespace std; void* operator new(size_t size) { void *ptr = malloc(size); cout << "申请到的内存的地址为:" << ptr << " 大小为:" << size <<endl; return ptr; } void operator delete(void *ptr) { if(ptr == nullptr) return; cout << "释放的内存的地址为:" << ptr <<endl; free(ptr); } class Base { public: Base() { cout << "A的this指针是:" << this << endl; cout << "A中的_a的地址为:" << &_a << endl; cout << "A中的_b的地址为:" << &_b << endl; cout << "A中的_c的地址为:" << &_c << endl; } ~Base() { cout << "~Base()" << endl; } int _a = 10; protected: int _b = 20; private: int _c = 30; }; class Derived : public Base { public: int _d = 40; Derived() { cout << "B的this指针是:" << this << endl; cout << "B中的_a的地址为:" << &_a << endl; cout << "B中的_b的地址为:" << &_b << endl; //cout << "B中的_c的地址为:" << &_c << endl; cout << "B中的_d的地址为:" << &_d << endl; } ~Derived() { cout << "~Derived()" << endl; } }; int main() { cout << "基类的大小:" << sizeof(Base) << endl; cout << "派生类的大小:" << sizeof(Derived) << endl; Derived *d = new Derived(); delete d; return 0; }
从申请的内存大小可以得知,当创建派生类对象时,系统会预先分配一块 基类 + 派生类 大小的内存空间。然后先调用基类的构造函数初始化基类成员,再调用派生类的构造函数初始化派生类成员。
所以this指针指向的是同一个地址,其他的成员变量也是一个地址。
-
在C++中,不同的继承方式的访问权限只是语法上的处理。使用指针可以绕过继承方式,直接访问基类成员。
void* operator new(size_t size) { void *ptr = malloc(size); return ptr; } void operator delete(void *ptr) { if(ptr == nullptr) return; free(ptr); } class Base { public: Base() { } ~Base() { } void fun() { cout << "_a = " << _a << " _b = " << _b << " _c = " << _c << endl; } int _a = 10; protected: int _b = 20; private: int _c = 30; }; class Derived : public Base { public: int _d = 40; Derived() { } ~Derived() { } void fun1() { cout << "_d = " << _d << endl; } }; int main() { Derived *d = new Derived(); d->fun(); //_a = 10 _b = 20 _c = 30 d->fun1(); //_d = 40 memset(d, 0, sizeof(Derived)); d->fun(); //_a = 0 _b = 0 _c = 0 d->fun1(); //_d = 0 return 0; }
此处将基类中private变量_c也被清0,我们直接操作内存,可以绕过继承方式,直接访问基类成员。memset()函数一般不用于类对象,因为类对象中可能存在虚函数,虚函数表指针等,如果直接使用memset()函数,可能会破坏这些内容,导致程序崩溃。
还可以使用指针来修改基类中的私有成员:
int main() { Derived *d = new Derived(); d->fun(); d->fun1(); *((int*)d + 2) = 31; //相当于基类中的_c d->fun(); d->fun1(); return 0; }
四、如何构造基类和派生类
-
如果没有指定基类的构造函数,将使用基类默认构造函数。
-
可以使用初始化列表指明要使用的基类构造函数。
-
基类构造函数负责初始化被继承的数据成员;派生类构造函数主要用于初始化新增的数据成员。
class A { public: int _a; private: int _b; public: A(): _a(0), _b(0) { cout << "调用了基类的无参构造函数" << endl; } A(int a, int b): _a(a), _b(b) { cout << "调用了基类的带参构造函数" << endl; } A(const A &a): _a(a._a), _b(a._b) { cout << "调用了基类的拷贝构造函数" << endl; } void showA() { cout << "_a = " << _a << "_b = " << _b << endl; } }; class B : public A { public: int _c; B(): _c(0) ,A() //派生类的默认构造函数,指明使用基类的构造函数 { cout << "调用了派生类的无参构造函数" << endl; } B(int a, int b, int c): A(a, b), _c(c) { cout << "调用了派生类的带参构造函数" << endl; } B(const A &a, int c): A(a), _c(c) { cout << "调用了派生类的拷贝构造函数" << endl; } void ShowB() { cout << "_c = " << _c << endl; } }; int main() { //基类的成员变量由基类的构造函数初始化 //派生类新增的成员变量由派生类的构造函数初始化 //为什么不能由派生类初始化全部的成员变量? //因为派生类无法看见基类私有的成员变量 //如果将初始化基类成员的代码写在派生类 //当基类被多个派生类继承的时候,每个派生类都需要初始化基类的成员。 B b1; //创建对象b1,调用基类的构造函数 b1.showA(); b1.ShowB(); B b2(1, 2, 3); //创建对象b2,调用基类的带参构造函数 b2.showA(); A a(10,20); B b3(a, 30); //创建对象b3,调用基类的拷贝构造函数 b3.showA(); b3.ShowB(); return 0; }
-
基类
五、名字遮蔽和类作用域
1、名字遮蔽
-
在派生类中,如果基类和派生类中有同名的成员(包括成员变量和成员函数),那么在派生类中访问该成员时,默认访问的是派生类中的成员。
#include<iostream> using namespace std; class A { public: int _a = 10; void func() { cout << "funcA: " << _a << endl; } }; class B : public A { public: int _a = 88; void func() { cout << "funcB: " << _a << endl; } }; int main() { B b; b.func(); //调用类B的成员函数,输出:funcB: 88 return 0; }
此处派生类B的大小为8,里面既有基类A中的成员变量_a,也有派生类B中的成员变量_a,但是派生类B中的成员变量_a(仅从语法上)覆盖了A中的_a。
2)注意:基类的成员函数和派生类的成员函数不会构成重载,如果派生类有同名函数,那么就会遮蔽基类中的所有同名函数。
class A { public: int _a = 10; void func() { cout << "funcA: " << _a << endl; } void func(int a) { cout << "funcA(int a): " << _a << endl; } }; class B: public A { public: int _a = 88; //void func() //{ // cout << "funcB: " << _a << endl; //} }; int main() { B b; b.func(); //调用类B的成员函数,输出:funcB: 88 b.func(99); //调用失败,函数func()不接受一个参数,当将B类中的func()函数注释掉,通过 return 0; }
此处派生类中的func(),将基类中同名的所有func()全部都隐藏。
2、类作用域
- 在类作用域外,普通成员只能通过对象(对象本身,对象指针,对象引用)来访问,
静态成员可以通过对象访问,也可以通过类访问。class A { public: int _a = 10; void func() { cout << "funcA: " << _a << endl; } void func(int a) { cout << "funcA(int a): " << _a << endl; } static void func2() { cout << "func2A: " << endl; } }; int main() { //A::func(); //报错,普通成员函数只能通过对象来访问 A::func2(); //通过,静态成员函数可以通过对象也可以通过类来访问 }
- 当存在继承关系时,如果成员在派生类的作用域中找到了,就不会去基类的作用域中查找;
如果没有找到,再去基类的作用域中寻找。#include<iostream> using namespace std; class A { public: int _a = 0; void func() { cout << "A::func()" << endl; } }; class B : public A { public: int _a = 10; void func() { cout << "B::func()" << endl; } }; class C : public B { public: int _a = 20; void func() { cout << "C::func()" << endl; } }; int main() { C c; cout << c._a << endl; //0 cout << c.B::_a << endl; //10 cout << c.B::A::_a << endl; //20 c.func(); //C::func() c.B::func(); //B::func() c.B::A::func(); //A::func() }
六、继承的特殊关系
派生类和基类之间有一些特殊的关系
- 如果继承方式是公有的,派生类对象可以使用基类成员。
- 可以把派生类对象赋值给基类对象(包括私有成员),但是,会舍弃非基类成员。
#include<iostream> using namespace std; class A { public: int _a = 0; void fun() { cout << "A::fun():" << "_a = "<< _a << " _b = " << _b <<endl; } void set(int b) { _b = b; } private: int _b = 0; }; class B : public A { public: int _c = 3; void fun() { cout << "B::fun():" << "_a = " << _a << " _c = " << _c << endl; } }; int main() { A a; B b; b._a = 10; b.set(20); //设置_b的值 b._c = 30; a.fun(); //A::fun():_a = 0 _b = 0 a = b; //将派生类对象赋值给基类对象 a.fun(); //A::fun():_a = 10 _b = 20 return 0; }
- 基类指针可以在不进行显式转换的情况下指向派生类对象。
//基类指针指向派生类对象
//基类指针 只能调用基类的成员函数,访问基类的成员变量 - 基类引用可以在不进行显式转换的情况下引用派生类对象。
七、多继承和虚继承
不提倡使用多继承,只有比较简单和不出现二义性的时候才使用,能用单一继承切记不要使用多继承。
1、多继承的语法:
class 派生类名 :[继承方式] 基类名1, [继承方式] 基类名2, ...
{
派生类新增的成员
};
class A
{
public:
int _a = 10;
};
class B
{
public:
int _b = 20;
};
class C : public A, public B
{
public:
int _c = 30;
};
int main()
{
C c;
cout <<"_a = " << c._a << " _b = " << c._b << " _c = " << c._c << endl;
return 0;
}
2、菱形继承
派生类D的内存:
可以发现有两个A类成员,两个A类成员的内存是重叠的,所以菱形继承会导致二义性
(可以加类名和作用域解析符解决)和数据冗余
。
int main()
{
D d;
cout << d._a << endl; //二义性,编译器不知道访问的是哪个A类的_a
//cout << d.A::_a << endl; //访问A类的_a
//cout << d.B::_a << endl; //访问B类的_a
return 0;
}
3、虚继承
虚继承的作用:解决菱形继承问题,使得在派生类中只保留一份基类的成员,解决二义性。
虚继承的语法:
class 派生类名 : virtual 继承方式 基类名
{
派生类新增的成员
};
D类通过虚继承从B类和C类中继承了A类,确保了A类在D类中只有一个实例。
int main()
{
D d;
cout << "B中的_a地址:" << &(d.B::_a) << endl;
cout << "C中的_a地址:" << &(d.C::_a) << endl;
return 0;
}
他们的地址也是同一个地址:
由于消除了二义性,可以直接写成这样
D d;
cout << d._a << endl;
类D的内存分布,发现只分配了一次类A,B和C中都有vbptr。
A在被虚继承后,就变成了虚基类,虚基类在派生类中只会存在一份,且在派生类中只存在一份。vbptr指向虚基类表vbtable。
虚基类表存放了数据成员的相对位置。
在D中,B的虚基类指针相对位置是0,所以下面的8+0 = 8,就找到了_a。 C的虚指针相对位置是4,虚基类表中的数据成员位置是4,所以也找到了_a。
八、类的多态
1、什么是多态
基类指针只能调用基类的成员函数,不能调用派生类的成员函数。
如果将基类 成员函数 声明为虚函数virtual,基类指针就可以调用派生类中同名的成员函数,通过派生类的同名成员函数,
可以访问派生对象的成员变量。
有了虚函数,基类指针指向基类对象就使用基类的成员函数和数据,指向派生类对象的时候就使用派生类的成员和数据,
基类指针表现出了多种形式,这种情况被称为多态。
基类引用也可以使用多态。
#include<iostream>
using namespace std;
class Animal
{
public:
int _age;
Animal(int age):_age(age){}
virtual void show()
{
cout << "Animal age:" << _age << endl;
}
};
class Dog : public Animal
{
public:
int _weight;
Dog(int age, int weight):Animal(age),_weight(weight){}
void show()
{
cout << "Dog age:" << _age << endl;
cout << "Dog weight:" << _weight << endl;
}
};
int main()
{
Dog dog(3, 10);
dog.show();
//基类指针指向派生类对象
//基类指针 只能调用基类的成员函数,访问基类的成员变量
Animal* animal = &dog;
animal->show();
//再将基类成员show函数改为虚函数,当用基类指针指向派生类对象,会调用派生类中的同名成员函数
return 0;
}
int main()
{
Animal a; a._age = 100; //创建基类对象并对成员赋值
Dog d; d._age = 10; d._weight = 20; //创建派生类对象并对成员赋值
Animal* p; //声明基类指针
p = &a; p->show(); //让基类指针指向基类对象,并调用虚函数
p = &d; p->show(); //让基类指针指向派生类对象,并调用虚函数
//证明了:有了虚函数,
//基类指针指向基类对象的时候就使用基类的成员函数和数据
//基类指针指向派生类的时候就使用派生类的成员函数和数据
}
//将基类中的成员函数show函数改为虚函数
int main()
{
Dog dog(3, 10);
Animal a(100);
cout << "基类引用指向基类对象" << endl;
Animal &ra = a;
ra.show();
cout << "基类引用指向派生类对象" << endl;
Animal &ra2 = dog;
ra2.show();
}
2、注意:
- 只需在基类的函数声明中加上virtual关键字,函数定义时不能加。
- 在派生类中重定义虚函数时,函数特征要相同。
- 在基类中定义了虚函数时,如果派生类没有重定义该函数,那么将使用基类的虚函数。
仅给派生类重载了一个show函数,基类没有重载。
//2
int main()
{
Dog dog(3, 10);
Animal a;
Animal *pa = &dog; //基类指针指向派生类
pa->show(); //调用派生类重写的函数
//当将派生类的show函数在类中加上参数
//void show(int)...
//pa->show(); //调用基类的show函数
}
给派生类重载一个带参数的show函数,将基类的show函数改为虚函数。
class Animal
{
public:
int _age;
Animal(){}
Animal(int age):_age(age){}
virtual void show()
{
cout << "Animal age:" << _age << endl;
}
};
class Dog : public Animal
{
public:
int _weight;
Dog(){}
Dog(int age, int weight):Animal(age),_weight(weight){}
void show()
{
cout << "Dog age:" << _age << endl;
cout << "Dog weight:" << _weight << endl;
}
void show(int a)
{
cout << "带参数的show函数" << endl;
}
};
int main()
{
Dog dog(3, 10);
Animal a;
Animal *pa = &dog; //基类指针指向派生类
pa->show(); //调用派生类重写的函数
//pa->show(1); //报错
//对于基类指针,不认识派生类中的重载函数。
}
再给基类也重载一个show,并修改为虚函数。
class Animal
{
public:
int _age;
Animal(){}
Animal(int age):_age(age){}
virtual void show()
{
cout << "Animal age:" << _age << endl;
}
virtual void show(int a)
{
cout << "基类带参数的show函数" << endl;
}
};
class Dog : public Animal
{
public:
int _weight;
Dog(){}
Dog(int age, int weight):Animal(age),_weight(weight){}
void show()
{
cout << "Dog age:" << _age << endl;
cout << "Dog weight:" << _weight << endl;
}
void show(int a)
{
cout << "派生类带参数的show函数" << endl;
}
};
int main()
{
Dog dog(3, 10);
Animal a;
Animal *pa = &dog; //基类指针指向派生类
pa->show(); //调用派生类重写的函数
//给基类也重载一个show的虚函数
pa->show(1);
//调用派生类的重载show函数。
}
- 在派生类中重定义了虚函数的情况下,如果想使用基类的虚函数,可以加类名和作用域解析符。
//和上面的类不变
int main()
{
Dog dog(3, 10);
Animal a;
Animal *pa = &dog; //基类指针指向派生类
pa->show(); //调用派生类重写的函数
//给基类也重载一个show的虚函数
pa->show(1);
//调用派生类的重载show函数。
pa->Animal::show(1); //调用基类的show函数
}
- 如果要在派生类中重新定义基类的函数,则将它设置为虚函数;
否则不要再设置虚函数。
九、多态的应用场景
现实:
网盘的基础功能免费,高级功能需要收费
程序员:
基类的虚函数实现基本功能
派生类重定义虚函数,扩展功能、提升性能
实现个性化功能
一个简单的例子,当不适用多态和虚函数时候:
#include<iostream>
using namespace std;
class Hero
{
public:
int viability;
int attack;
void skill1()
{
cout << "Hero skill1" << endl;
}
void skill2()
{
cout << "Hero skill2" << endl;
}
void useSkill()
{
cout << "Hero useSkill" << endl;
}
};
class JS: public Hero //剑圣英雄
{
public:
void skill1()
{
cout << "JS skill1" << endl;
}
void skill2()
{
cout << "JS skill2" << endl;
}
void useSkill()
{
cout << "JS useSkill" << endl;
}
};
class DM : public Hero
{
public:
void skill1()
{
cout << "DM skill1" << endl;
}
void skill2()
{
cout << "DM skill2" << endl;
}
void useSkill()
{
cout << "DM useSkill" << endl;
}
};
int main()
{
int id = 0;
cout << "请输入选择英雄:1、剑圣;2、德玛" << endl;
cin >> id;
if(id == 1)
{
JS js;
js.skill1();
js.skill2();
js.useSkill();
}
else if(id == 2)
{
DM dm;
dm.skill1();
dm.skill2();
dm.useSkill();
}
return 0;
}
引入多态和虚函数,将基类三个函数设置为虚函数,创建基类指针指向派生类即可
class Hero
{
public:
int viability;
int attack;
virtual void skill1()
{
cout << "Hero skill1" << endl;
}
virtual void skill2()
{
cout << "Hero skill2" << endl;
}
virtual void useSkill()
{
cout << "Hero useSkill" << endl;
}
};
...
int main()
{
int id = 0;
cout << "请输入选择英雄:1、剑圣;2、德玛" << endl;
cin >> id;
Hero *hero = nullptr; //创建一个基类指针
if(id == 1)
{
hero = new JS(); //需要谁就new谁
}
else if(id == 2)
{
hero = new DM(); //需要谁就new谁
}
if(hero != nullptr) //如果创建成功,则调用函数
{
hero->skill1(); //基类指针指向派生类,并且派生类成员函数是虚函数,会调用派生类函数
hero->skill2();
hero->useSkill();
}
return 0;
}
十、多态的对象模型
cl main.cpp /d1 reportSingleClassLayoutHero
基类内存空间:
程序在运行的时候,如果创建了对象,除了给对象的成员分配空间,还会创建一个虚函数表。用虚函数指针指向虚函数表。在程序中,如果调用的是普通成员函数,则直接调用;如果调用的是虚函数,要先通过虚函数指针找到虚函数表,查找虚函数表得到函数的地址,再调用函数。
派生类内存空间:
cl main.cpp /d1 reportSingleClassLayoutJS
派生类也有虚函数指针,是从基类继承过来的。如果基类有虚函数,派生类会从基类
多继承一个虚函数指针和虚函数表。这样设计师让基类和派生类保持相同的内存模型。
在运行时,如果创建了派生类对象,在虚函数表中,会用派生类成员函数取代基类成员函数的地址。
当基类指针指向派生类对象时,用基类指针调用虚函数的时候,
会调用的基类函数,但是函数地址已经被派生类替换了,所以会调用派生类函数。
十一、如何析构派生类
当用基类指针指向派生类对象时,delete基类指针只会调用基类的析构函数,不会调用派生类的析构函数。如果希望调用派生类的析构函数,就要把基类的析构函数设置为虚函数,这时派生类的析构函数被调用之后,会自动调用基类的析构函数。
如果手工调用派生类的析构函数,也会自动调用基类的析构函数。
构造函数、析构函数、赋值运算符函数、友元函数都不能继承。
析构函数可以手工调用,delete空指针安全,但是delete野指针可能会程序崩溃,如果对象有对空间需要加上ptr = nullptr;
对于基类来说,即使不需要析构函数,也应该提供一个空虚析构函数。
delete ptr;
ptr= nullptr
1、
#include<iostream>
using namespace std;
class Base
{
public:
Base(){cout << "Base()" << endl;}
virtual void show(){cout << "Base::show()" << endl;}
~Base(){cout << "~Base()" << endl;}
};
class Derived:public Base
{
public:
Derived(){cout << "Derived()" << endl;}
void show(){cout << "Derived::show()" << endl;}
~Derived(){cout << "~Derived()" << endl;}
};
int main()
{
Derived d;
return 0;
}
创建派生类对象,先调用Base的构造,在调用Derived的构造。析构的时候,先调用Derived的析构,再调用Base的析构。
2、创建一个派生类指针,new一个派生类对象。
int main()
{
Derived *d = new Derived();
return 0;
}
这样的代码会导致内存泄漏,因为您使用了 new 关键字动态分配了内存,但没有使用 delete 关键字来释放这块内存。
int main()
{
Derived *d = new Derived();
delete d;
return 0;
}
要将new 和 delete 结合起来使用,以确保在不再需要对象时正确释放内存。
3、手动析构
int main()
{
Derived *d = new Derived();
d->~Derived();
delete d;
return 0;
}
通过派生类指针调用派生类的析构函数。
C++规定派生类的析构函数调用完毕,会自动执行基类的析构函数。
3.1 基类指针指向派生类对象:
int main()
{
Base *d = new Derived();
delete d;
}
发现只调用了基类的析构函数,没有调用派生类的构造函数。
基类内存模型: 只有一个虚指针
派生类内存模型:有一个虚指针,虚函数表中有一个show函数
解决方式:将基类的析构函数设置为虚函数。
class Base
{
public:
Base(){cout << "Base()" << endl;}
virtual void show(){cout << "Base::show()" << endl;}
virtual ~Base(){cout << "~Base()" << endl;}
};
class Derived:public Base
{
public:
Derived(){cout << "Derived()" << endl;}
void show(){cout << "Derived::show()" << endl;}
~Derived(){cout << "~Derived()" << endl;}
};
int main()
{
Base *d = new Derived();
delete d;
}
基类内存模型:
虚函数表中多了一个函数,这个是析构函数(destructor)的缩写
派生类内存模型:
派生类中重定义基类的虚构函数,它们的函数名和参数列表要相同。但是基类的析构函数和派生类的析构函数不可能名字相同。这时C++编译器会做特殊的处理。