C++虚函数

C++虚函数

    • 纯虚函数
    • 虚函数
    • 面试问题

纯虚函数

  • 纯虚函数
	virtual void fun() = 0;

纯虚函数的出现时为了应对一些特殊的情况,即基类无法实现某些抽线行为,交由继承类去实现。
=0告诉编译器此函数为纯虚函数,由此可以引出抽象类。
抽象类指的是包含一个或者一个以上的纯虚函数的类。
派生类继承基类的时候必须实现纯虚函数,否则编译不通过。

虚函数

  • 普通虚函数
	virtual void fun() {}

首先来说普通虚函数是如何实现多态,虚函数实现多态的主要思想是通过基类指针调用基类和派生类中同名、同参数表的虚函数语句,基类指针指向什么对象,运行时就调用哪个派生类的虚函数。

比如在继承Person类的Worker、Student类中都有睡觉这个功能,但是他们由于身份的不同,睡眠时间是不同的。

class Person {
public:
	Person() {
		cout << "Person 构造函数 " << endl;
	}
	void eat() {}//吃饭
	virtual void sleep() {
		cout << "人类睡8小时最佳..." << endl;
	}
	~Person() {
		cout << "Person 析构函数 " << endl;
	}
protected:
	int age; // 年龄
	int height; // 身高
};
class Worker : public Person {
public:
	Worker() {
		cout << "Worker 构造函数 " << endl;
	}
	virtual void sleep() {
		cout << "工人顶多睡7小时" << endl;
	}
	~Worker() {
		cout << "Worker 析构函数 " << endl;
	}
};
class Student : public Person {
public:
	Student() {
		cout << "Student 构造函数 " << endl;
	}
	virtual void sleep() {
		cout << "学生顶多睡6小时" << endl;
	}
	~Student() {
		cout << "Student 析构函数 " << endl;
	}
};
 
void main(void)
{ 
	Worker w;   // 1
	Student s;   // 2
	Person *p = &w;    // 3
	p->sleep();  //4
	p = &s;
	p->sleep();
}

C++虚函数_第1张图片
1-4行:由于Worker和Student继承自Person,所以二者在使用类名对象名创建对象时,都会首先调用Person构造函数,再调用自己的构造函数。
**5-6行:**首先将Worker对象w拷贝给Person基类的指针p,并调用sleep方法。由于sleep方法申明为虚函数,所以基类指针p根据对象w去调用w的sleep方法,输出工人睡觉的时间!Student的对象同理
7-10行:由于使用类名对象名创建对象,所以在结束时会自动调用彼此的析构函数。析构函数的调用顺序和构造函数相关,最先调用构造函数的最后析构。

面试问题

问题一、那么哪些函数不可以申明为虚函数呢?

  1. 构造函数
    首先虚函数的调用需要通过查虚函数表,但是虚函数表需要在构造函数创建实例以后才生成。如果构造函数是虚函数,则无法创建实例,类的成员也不可被访问。
  2. 静态函数
    静态函数属于整个类,不属于任何一个类的对象。本身就是实体,不可被定义为虚函数。
  3. 非成员函数
    非成员函数不属于类,所以无法生成虚函数表。虚函数必须要求是成员函数。
  4. 友元函数
    友元函数定义在类外,虽然有权访问类的private和protected成员,但是友元函数并非成员函数。
  5. 内联成员函数
    内联成员函数在编译时就需要展开,而虚函数在执行时才可动态绑定,所以不适用。

问题二、为什么使用类名对象名创建对象会自动调用析构函数?

面试官经常喜欢问的问题有两个

  1. 类名对象创建对象和使用new创建对象有何区别?
  2. 基类析构函数为何必须定义为虚函数?

问题1:
类名对象创建对象

	Person p;

这种方式是在局部栈区创建对象,栈区随着程序的结束栈区会自动释放,所以对象会自动调用析构函数。这种方式创建对象速度快,毕竟是在栈区,比较适合频繁创建对象和对象比较小的应用场景。
new 创建对象

	Person* p = new Person();
	delete p;

这种创建方式在堆上创建对象,程序结束是不会对堆内存进行释放的,对应的程序中应该手动对其进行释放内存。new一般与delete配合使用。

问题2:
基类析构函数设置为虚函数是为了防止内存泄漏的情况。如果使用类名对象在栈区创建对象的方式,这是没任何问题的。因为栈区会在程序结束时候自动释放栈区内存,自动调用析构函数。
但是如果使用new方式创建对象,可能造成在delete释放的时候发生问题。当然也分为两种情况:

  1. delete 释放的指针是派生类的指针
//	接着上文的代码,修改main函数中对象创建的方式
class Person {
public:
	Person() {
		cout << "Person 构造函数 " << endl;
	}
	virtual void sleep() {
		cout << "人类睡8小时最佳..." << endl;
	}
	~Person() {   //  基类析构函数非虚函数
		cout << "Person 析构函数 " << endl;
	}
protected:
	int age; // 年龄
	int height; // 身高
};
void main(void)
{
	Worker* w = new Worker();
	Person *p = w;
	p->sleep();
	delete w;
}

从执行结果可以看出,如果delete释放的是派生类指针,基类和派生类都会调用析构函数,不存在内存泄漏的情况。
C++虚函数_第2张图片
2. delete 释放的指针是派生类的指针

void main(void)
{
	Worker* w = new Worker();
	Person *p = w;
	p->sleep();
	delete p;
}

从执行结果可以看出,派生类并没有调用析构,这时产生内粗泄漏。
C++虚函数_第3张图片
如果将基类析构函数定义为虚函数,无论delete基类指针还是派生类指针都会自动调用所有析构函数,解决内存泄漏问题

你可能感兴趣的:(C++基础)