继承(多态和虚析构函数)

派生类不能直接访问基类的私有成员,必须通过基类方法进行访问。创建派生类对象时,首先创建基类对象。也就是说基类对象在程序进入派生类构造函数之前被创建。C++使用成员初始化列表来完成。派生类构造函数初始化基类私有成员,采用成员初始化列表。
总结:
首先,若基类函数需要被派生类重定义,则需要将其在基类声明为虚函数。
虚函数必须完全一致,否则派生类将重定义函数,隐藏基类虚函数。返回值除外
析构函数须使用虚析构函数,防止因指针或引用类型导致析构错误对象。
虚函数采用动态联编,不同位置虚函数存在不同地址,每个对象包含不同函数地址。

Class_name::Class_name(int a,int b, int c):Base_class(a,b) {    }

派生类构造函数要点:

  1. 首先创建基类对象
  2. 派生类构造函数应通过成员初始化列表将基类信息传递给基类构造函数
  3. 派生类构造函数应初始化派生类新增的成员变量
    注意: 成员初始化列表只能用于构造函数

派生类和基类的关系

1.1 派生类可使用基类的方法(非私有)

基类指针可以在不进行显示类型转换情况下指向派生类对象;基类引用可在不进行显示类型转换的情况下引用派生类对象。同样也可以将派生类对象赋给基类对象。
基类指针或引用只能用于调用基类方法,因此不能使用其调用派生类方法。 通常要求引用和指针类型与赋给的类型匹配,但这一规则对继承来说是例外。不可以将基类对象和地址赋给派生类引用和指针。

1.2 多态公有继承

概念: 同一个方法在派生类和基类中的行为是不同的,那么方法的行为应取决于调用该方法的对象,称为多态。
方法:

  1. 派生类中重新定义基类的方法
  2. 使用虚函数
    要点: 若要在派生类重新定义基类方法,通常将基类方法声明 (定义不需要)为虚函数。为基类声明一个虚析构函数也是一种惯例。
重定义基类方法
  1. 在基类和派生类中分别定义一个同名函数,则根据对象类型来分别调用相应的函数。
class Student {
	string name;
	int age;
public:
	Student(string a,int b):name(a),age(b){}
	void Print() {
		cout << "调用Student: "<<name << endl;
	}
};
class Grade :public Student{
	int grade;
public:
	Grade(int a, string name, int b) :Student(name, b) {
		grade = a;
	}
	void Print() {
		cout << "调用grade: " << grade << endl;
	}

};

可见函数Print()在基类和派生类分别定义了,若对象调用 如下:

Student S("liming", 13);
	S.Print();
	Grade G1(10, "lama", 20);
	G1.Print();

结果为:

调用Student: liming
调用grade: 10
  1. 方法通过引用或者指针而不是对象调用的,没有使用virtual 定义,则根据引用类型或指针类型选择方法。
    调用如下:
Student S("liming", 13);
	S.Print();
	Student *S1;
	Grade G1(10, "lama", 20);
	Student &S2 = G1;
	S2.Print();
	S1 = &G1;
	S1->Print();
	G1.Print();

结果为:

调用Student: liming
调用Student: lama
调用Student: lama
调用grade: 10
  1. 如果使用virtual 程序将根据引用或指针指向的对象类型来选择方法。
    将方法定义设置为:
virtual void Print() {
		cout << "调用Student: "<<name << endl;
	}

Student S("liming", 13);
	S.Print();
	Student *S1;
	Grade G1(10, "lama", 20);
	Student &S2 = G1;
	S2.Print();
	S1 = &G1;
	S1->Print();
	G1.Print();

结果为:

调用Student: liming
调用grade: 10
调用grade: 10
调用grade: 10

虚析构函数的作用

在使用newdelete的对象中,如果析构函数不是虚的,则只调用对应于指针类型的析构函数。


	Student *S1 = new Grade(12, "ll", 17);
	delete S1;

结果为:

调用Student构造函数:
调用grade构造函数:
调用student析构函数

由此可见,并没有调用派生类的析构函数。
若将基类析构函数设为vritual,则结果如下:

调用Student构造函数:
调用grade构造函数:
调用Grade析构函数
调用student析构函数

所以应将析构函数设为虚析构函数。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在C++中,如果一个类中有虚函数,我们通常都会将它的析构函数设为虚析构函数虚析构函数是指在基类中将析构函数声明为虚函数,这样在删除指向派生类对象的基类指针时,会调用派生类的析构函数。 需要虚析构函数的主要原因是防止内存泄漏。当我们在使用多态时,通常会使用基类指针来指向派生类对象,这时如果析构函数不是虚函数,删除指向派生类对象的基类指针时,只会调用基类的析构函数,而不会调用派生类的析构函数。这就会导致派生类中申请的动态内存无法被释放,从而造成内存泄漏。 使用虚析构函数可以保证在删除指向派生类对象的基类指针时,会先调用派生类的析构函数,从而保证所有动态内存都能正确释放。 举个例子,假设我们有一个基类 Animal 和一个派生类 Cat。Animal 类中有一个指针类型的成员变量,指向一个动态分配的字符串。Cat 类继承自 Animal 类,并且重载了析构函数。如果 Animal 类的析构函数不是虚函数,那么在删除指向 Cat 对象的 Animal 指针时,只会调用 Animal 类的析构函数,从而导致 Cat 类中申请的动态内存无法被释放,造成内存泄漏。 ```c++ class Animal { public: Animal() { name = new char[20]; strcpy(name, "Animal"); } ~Animal() { delete[] name; cout << "Animal destructor" << endl; } protected: char* name; }; class Cat : public Animal { public: Cat() { name = new char[20]; strcpy(name, "Cat"); } ~Cat() { delete[] name; cout << "Cat destructor" << endl; } }; int main() { Animal* p = new Cat(); delete p; // Animal destructor,没有调用 Cat 的析构函数,造成内存泄漏 return 0; } ``` 如果将 Animal 类的析构函数声明为虚析构函数,那么在删除指向 Cat 对象的 Animal 指针时,就会先调用 Cat 类的析构函数,从而正确释放动态内存。 ```c++ class Animal { public: Animal() { name = new char[20]; strcpy(name, "Animal"); } virtual ~Animal() { // 声明为虚析构函数 delete[] name; cout << "Animal destructor" << endl; } protected: char* name; }; class Cat : public Animal { public: Cat() { name = new char[20]; strcpy(name, "Cat"); } ~Cat() { delete[] name; cout << "Cat destructor" << endl; } }; int main() { Animal* p = new Cat(); delete p; // Cat destructor,然后 Animal destructor return 0; } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值