38.【C++ 虚函数 纯虚函数 虚基类 (最全详解)】

(一)、虚函数

1.什么是虚函数:

虚函数的作用是实现动态联编,也就是在程序的运行阶段动态地选择合适的成员函数,在定义了虚函数后,可以在基类的派生类中对虚函数重新定义,在派生类中重新定义的函数应与虚函数具有相同的形参个数和形参类型。以实现统一的接口,不同定义过程。如果在派生类中没有对虚函数重新定义,则它继承其基类的虚函数

2.虚函数的格式:

class Student
{
virtual void show(){}
}
class Student1 :public Student
{
	void show(){}
}

3.关于虚函数的注意事项:

1、 必须把动态联编的行为定义为类的虚函数。  
2、类之间存在父子类型关系,一般表现为一个类从另一个类公有派生而来。 
3、 必须先使用基类指针或者引用指向子类型的对象,然后直接或者间接使用基类指针调用虚函数

4.虚函数的作用:

可以让成员函数操作一般化用基类的指针指向不同的派生类的对象时,基类指针调用其虚成员函数,则会调用其真正指向对象的成员函数
而不是基类中定义的成员函数(只要派生类改写了该成员函数)。
若不是虚函数,则不管基类指针指向的哪个派生类对象,调用时都
会调用基类中定义的那个函数。

5.虚函数访问格式

指针变量->成员函数

6.虚函数的各种疑难杂症

【当指针是基类、但虚函数不在基类时】

代码展示:
#include <iostream>
using namespace std;
class A
{
public:
	 void fun() { cout << "调用了A" << endl; }
};
class B:public A
{
public:
	void fun() { cout << "调用了B" << endl; }

};
class C:public A
{
public:
	virtual void fun() { cout << "调用了C" << endl; }
};
int main()
{
	A a,*p;
	B b;
	C c;
	p = &c;
	p->fun();
}
效果展示:

在这里插入图片描述

【当指针在派生类,虚函数在基类,指向派生类】

代码展示:
#include <iostream>
using namespace std;
class A
{
public:
	 virtual void fun() { cout << "调用了A" << endl; }
};
class B:public A
{
public:
	void fun() { cout << "调用了B" << endl; }

};
class C:public A
{
public:
	 void fun() { cout << "调用了C" << endl; }
};
int main()
{
	A a;
	B b;
	C c,*p;
	p = &c;
	p->fun();

}

效果展示:

在这里插入图片描述

1.【基类和派生类都为虚函数,指针在基类指向派生类】

父类的与子类的同名虚函数,在继承的时候,子类会将父类的覆盖,

代码展示:
#include <iostream>
using namespace std;
class A
{
public:
	 virtual void fun() { cout << "调用了A" << endl; }
};
class B:public A
{
public:
	void fun() { cout << "调用了B" << endl; }
};
class C:public A
{
public:
	virtual void fun() { cout << "调用了C" << endl; }
};
int main()
{
	A a,*p;
	B b;
	C c;
	p = &c;
	p->fun();
}
效果展示:

在这里插入图片描述

2.【基类和派生类都有虚函数,指针在基类指向基类】

代码展示:
#include <iostream>
using namespace std;
class A
{
public:
	virtual void fun() { cout << "调用了A" << endl; }
};
class B :public A
{
public:
	void fun() { cout << "调用了B" << endl; }

};
class C :public A
{
public:
	virtual void fun() { cout << "调用了C" << endl; }
};
int main()
{
	A a, * p;
	B b;
	C c;
	p = &a;
	p->fun();
}

效果展示:

在这里插入图片描述

3.【基类和派生类都有虚函数,指针在派生类指向派生类】

代码展示:
#include <iostream>
using namespace std;
class A
{
public:
	 virtual void fun() { cout << "调用了A" << endl; }
};
class B:public A
{
public:
	void fun() { cout << "调用了B" << endl; }

};
class C:public A
{
public:
	virtual void fun() { cout << "调用了C" << endl; }
};
int main()
{
	A a;
	B b;
	C c,*p;
	p = &c;
	p->fun();

}
效果展示:

在这里插入图片描述

4.【基类和派生类都有虚函数,指针在派生类指向基类】

错误分析:派生类访问不到基类

代码展示:
#include <iostream>
using namespace std;
class A
{
public:
	virtual void fun() { cout << "调用了A" << endl; }
};
class B :public A
{
public:
	void fun() { cout << "调用了B" << endl; }

};
class C :public A
{
public:
	virtual void fun() { cout << "调用了C" << endl; }
};
int main()
{
	A a;
	B b;
	C c,*p;
	p = &a;
	p->fun();
}
效果展示:

在这里插入图片描述

7.虚函数的示范

【非虚函数的调用,会适当其反】

代码展示:
#include <iostream>
using namespace std;
class A
{
private:
 int number;
public:
 A(int nu=21032114) :number(nu) {}
  void show()
 {
  cout << "基类,学号为:" << number<<endl;
 }
};
class B:public A
{
private:
 int score;
public:
 B(int sc = 86) :score(sc) {}
 void show()
 {
  cout << "派生,成绩为:" << score<<endl;
 }
};
int main()
{
 A a;
 a.show();
 B b;
 b.show();
 A* p;          
 p = &b;
 p->show(); //我想调用派生类,但仍然是基类的函数
 A& p1 = b;    
 b.show();
 return 0;
}
效果展示:

在这里插入图片描述

【虚函数的调用正中下怀】

代码展示

#include <iostream>
using namespace std;
class A
{
private:
 int number;
public:
 A(int nu=21032114) :number(nu) {}
  virtual void show()
 {
  cout << "基类,学号为:" << number<<endl;
 }
};
class B:public A
{
private:
 int score;
public:
 B(int sc = 86) :score(sc) {}
 void show()
 {
  cout << "派生,成绩为:" << score<<endl;
 }
};
int main()
{
 A a;
 a.show();
 B b;
 b.show();
 A* p;          
 p = &b;
 p->show(); //我想调用派生类,但仍然是基类的函数
 A& p1 = b;    
 b.show();
 return 0;
}

效果展示:

在这里插入图片描述

#include using namespace std;

class A { public:
virtual int fun() { return 1; } };

class E : public A { public:
virtual int fun() { return 5; } };

class B : virtual public A { public:
virtual int fun() { return 2; } };

class C : virtual public A { public:
virtual int fun() { return 3; } };

class D : public B, public C { public:
virtual int fun() { return 4; } };

int main() {
cout << sizeof(A) << endl; // 4
cout << sizeof(B) << endl; // 4
cout << sizeof© << endl; // 4
cout << sizeof(D) << endl; // 8
cout << sizeof(E) << endl; // 4

return 0; }

在这里插入图片描述

解释:

1.static静态成员变量不需计算

2.成员函数不占用空间

3.虚函数 有一个虚表指针占四个字节

A=4——> 4(A虚表指针)。

4.父类的与子类的同名虚函数,在继承的时候,子类会将父类的覆盖,

B=4------> 4(B虚表指针)

C=4------> 4(C虚表指针)

5.如果子类和父类都包含虚函数,但他们存放在一个虚表中,因此只要一个虚表指针。

D=8-------->4(D虚表指针)+4(D虚表指针)

E=4--------->4(B虚表指针)

(二)、虚基类

1.什么是虚基类:

当在多条继承路径上有一个公共的基类,在这些路径中的某几条汇合处,这个公共的基类就会产生多个实例(或多个副本),若只想保存这个基类的一个实例,可以将这个公共基类说明虚基类。所以可以说,虚基类是为了只实例化一次基类存在的。

2.虚基类的特点

(1):虚基类构造函数的参数必须由最新派生出来的类负责初始化(即使不是直接继承).
(2)虚基类的构造函数先于非虚基类的构造函数执行。

3.二义性的问题

1.问题所在:

代码展示:
#include <iostream>
using namespace std;
class Student
{
public:
  int age;
 int number;
 Student(int a, int nu) :age(a), number(nu) {}
 Student() {}
  void set()
 {
  cout << "请输入学生的年龄和学号" << endl;
  cin >> age >> number;
 }
  void show()
 {
  cout << "学生的年龄为:" << " " << "学号为:" << endl;
 }
};
class Student1 :public Student
{
 void show()
 {
  cout << "Student1的" << endl; }
};
class Student2 :public Student{
 void show()
 {
  cout << "Student2的" << endl;}
};
class Student3 :public Student1,public Student2
{
 void show() {
  cout << "Student3的" << endl; }
};
int main()
{
 Student3 s3;
 s3.set();
 return 0;}
效果展示:

在这里插入图片描述

2.【二义性的解决非虚基类正确示范】

代码展示
#include <iostream>
using namespace std;
class Student
{
public:
 int age;
 int number;
 Student(int a, int nu) :age(a), number(nu) {}
 Student() {}
 void set(){
  cout << "请输入学生的年龄和学号" << endl;
  cin >> age >> number;}
 void show()
 {
  cout << "学生的年龄为:" << " " << "学号为:" << endl;
 }
};
class Student1 :public Student
{
public:
 void show()
 {
 cout << "Student1的" << endl;
 }
};
class Student2 :public Student
{
 void show()
 {
  cout << "Student2的" << endl; }
};
class Student3 :public Student1, public Student2
{
 void show()
 {
  cout << "Student3的" << endl; }
};
int main()
{
 Student3 s3;
 s3.Student1::set();
 return 0;
}
效果展示:

在这里插入图片描述

3.【二义性解决虚基类的方法正确示范】

#include <iostream>
using namespace std;
class Student
{
public:
 int age;
 int number;
 Student(int a, int nu) :age(a), number(nu) {}
 Student() {}
 void set(){
  cout << "请输入学生的年龄和学号" << endl;
  cin >> age >> number;}
 void show()
 {
  cout << "学生的年龄为:" << " " << "学号为:" << endl;
 }
};
class Student1 :virtual public Student
{
public:
 void show(){
  cout << "Student1的" << endl;}};
class Student2 :virtual public Student
{
 void show()
 {
  cout << "Student2的" << endl;}
};
class Student3 :public Student1, public Student2
{
 void show() {
  cout << "Student3的" << endl;}
};
int main()
{
 Student3 s3;
 s3.set();
 return 0;
}
代码展示

在这里插入图片描述
在这里插入图片描述

(三)、纯虚函数

1.纯虚函数的作用

在许多情况下,在基类中不能对虚函数给出有意义的实现,而把它声明为纯虚函数,它的实现留给该基类的派生类去做。这就是纯虚函数的作用。

2.基本格式:

class <类名>
{
    virtual <类型><函数名>(<参数表>)=0;};

(四)、总结

虚基类
1, 一个类可以在一个类族中既被用作虚基类,也被用作非虚基类。
2, 在派生类的对象中,同名的虚基类只产生一个虚基类子对象,而某个非虚基类产生各自的子对象。
3, 虚基类子对象是由最新派生类的构造函数通过调用虚基类的构造函数进行初始化的
4, 最派生类是指在继承结构中建立对象时所指定的类。
5, 派生类的构造函数的成员初始化列表中必须列出对虚基类构造函数的调用;如果未列出,则表示使用该虚基类的缺省构造函数。
6, 从虚基类直接或间接派生的派生类中的构造函数的成员初始化列表中都要列出对虚基类构造函数的调用。但只有用于建立对象的最派生 类的构造函数调用虚基类的构造函数,而该派生类的所有基类中列出的对虚基类的构造函数的调用在执行中被忽略,从而保证对虚基类子对象 只初始化一次。
7, 在一个成员初始化列表中同时出现对虚基类和非虚基类构造函数的调用时,虚基类的构造函数先于非虚基类的构造函数执行

虚函数
1, 虚函数是非静态的、非内联的成员函数,而不能是友元函数,但虚函数可以在另一个类中被声明为友元函数。
2, 虚函数声明只能出现在类定义的函数原型声明中,而不能在成员函数的函数体实现的时候声明。
3, 一个虚函数无论被公有继承多少次,它仍然保持其虚函数的特性。
4, 若类中一个成员函数被说明为虚函数,则该成员函数在派生类中可能有不同的实现。当使用该成员函数操作指针或引用所标识的对象时 ,对该成员函数调用可采用动态联编。
5, 定义了虚函数后,程序中声明的指向基类的指针就可以指向其派生类。在执行过程中,该函数可以不断改变它所指向的对象,调用不同 版本的成员函数,而且这些动作都是在运行时动态实现的。虚函数充分体现了面向对象程序设计的动态多态性。 纯虚函数 版本的成员函数,而且这些动作都是在运行时动态实现的。虚函数充分体现了面向对象程序设计的动态多态性。

纯虚函数
1, 当在基类中不能为虚函数给出一个有意义的实现时,可以将其声明为纯虚函数,其实现留待派生类完成。
2, 纯虚函数的作用是为派生类提供一个一致的接口。
3, 纯虚函数不能实列化

  • 20
    点赞
  • 66
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

吉士先生

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值