我是靠谱客的博主 眯眯眼高跟鞋,这篇文章主要介绍C++ 中虚函数的用法,现在分享给大家,希望可以做个参考。

虚函数

虚函数是实现动态绑定的函数。

虚函数必须是非静态的成员函数,虚函数经过派生后据可以实现运行过程中的多态。

什么函数可以是虚函数呢

1.一般成员函数

2.构造函数不能使虚函数

3.析构函数可以是虚函数

注意事项:如果使用了抽象基类,那么在抽象基类里,虚析构函数就变成了纯虚析构虚函数,而纯虚析构函数必须定义,否则编译器会报错,这时候纯虚析构函数的定义就和普通的析构函数一样定义就行。

 

虚函数的声明

virtual 函数类型 函数名(形参表);

虚函数声明只能出现在类定义中的函数原型声明中,而不能在成员函数实现的时候

派生类中可以对基类中的成员函数进行覆盖

虚函数一般不声明为内联函数,因为对虚函数的调用需要动态绑定,而对内联函数的处理是静态的。

virtual关键字

派生类可以不显式地用virtual声明虚函数,这时候系统就会用以下规则来判断派生类的一个成员函数是不是虚函数

1.该函数是否与基类的虚函数有相同的名称,参数个数以及对应参数类型

2.该函数是否与基类的虚函数有相同的返回值或者满足类型兼容规则的指针,引用行的返回值

(从函数的名称,参数,返回值来确定,是否都有const

一般习惯于在派生类的函数也是用virtual关键字,以增加程序的可读性

用法一:

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include<iostream> using namespace std; class A { public: virtual void display(){cout<<"A"<<endl;} }; class B:public A { public: void display(){cout<<"B"<<endl;} }; void Display(A *p) { p->display(); } int main() { B b; Display(&b); }

这段代码打印结果是 B,但是我们把A类里面的virtual去掉后就输出A。当没有virtual的时候,在调用display的时候,编译器会自然会调用A类的方法,如果有virtual后,display就变成了虚方法,编译器在编译的时候会去找这个dispaly具体是谁的方法。就实现了多态的效果。virtual相当于告诉编译器,先不要急着确定函数的对象,等到实际运行时候在确定。

如何实现多态呢?

每一个带有virtual的类的对象,都比它本身不带virtual的类的对象占的空间要大,因为带virtual的类的最头上会加上一个隐藏的指针,它指向一张表——虚函数表,这张表上是这个类里所有的有函数的地址.比如在执行p->display()函数的时候,访问指针p指向的对象里面的那个虚函数表,然后调用那个display函数。

 

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include<iostream> using namespace std; class A { int i; public : A():i(10){} virtual void f(){cout<<"A::f()"<<i<<endl;} }; class B:public A { int j; public: B():j(20){} virtual void f(){cout<<"B::f()"<<j<<endl;} }; int main() { A a; B b; A *p=&b; p->f(); a=b; p=&a; p->f(); return 0; }

输出结果


我们用A类型的指针指向对象b,然后执行f函数,会执行b的,因为虚函数表没有改变,所以当我们把b的值赋给a的时候,由于虚函数表没有改变,我们在执行
p->f()的时候,执行的是A类的f函数

复制代码
1
2
3
4
5
6
7
8
9
10
int main() { A a; B b; A *p=&a; int* r=(int*)&a; int* t=(int*)&b; *r = *t; p->f(); return 0;

虚函数表的地址位于类的起始地址紧接着的地址
如果我们把主函数改成这样


输出结果会变成这样

也就是说赋值的过程,不会改变虚函数表的地址
但是如果是 A *a,  B* b,然后 b=a,这样的话,执行 b->f()就会执行A::f了

复制代码
1
2
3
4
void Display(A *p) { p->display(); }

这个函数原理跟上面那个指针是一样的

用法二:

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#include<iostream> using namespace std; class Person { public: Person() { cout<<"Person构造"<<endl; } ~Person() { cout<<"Person析构"<<endl; } }; class Teacher : virtual public Person { public: Teacher() { cout<<"Teacher构造"<<endl; } ~Teacher() { out<<"Teacher析构"<<endl; } }; class Student : virtual public Person { public: Student() { cout<<"Student构造"<<endl; } ~Student() { cout<<"Student析构"<<endl; } }; class TS : public Teacher, public Student { public: TS() { cout<<"TS构造"<<endl; } ~TS() { cout<<"TS析构"<<endl; } }; int main() {     TS ts;     return 0; }

输出结果

复制代码
1
2
3
4
5
6
7
8
Person构造 Teacher构造 Student构造 TS构造 TS析构 Student析构 Teacher析构 Person析构

当Teacher类和Student类没有虚继承Person类的时候,也就是把virtual去掉时候终端输出的结果为: 

复制代码
1
2
3
4
5
6
7
8
9
10
Person构造 Teacher构造 Person构造 Student构造 TS构造 TS析构 Student析构 Person析构 Teacher析构 Person析构

大家可以很清楚的看到这个结果明显不是我们所期望的。我们在构造TS的时候需要先构造他的基类,也就是Teacher类和Student类。而Teacher类和Student类由都继承于Person类。这样就导致了构造TS的时候实例化了两个Person类。同样的道理,析构的时候也是析构了两次Person类,这是非常危险的,也就引发出了virtual的第三种用法,虚析构。

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
#include<iostream> using namespace std; class Person { public: Person() { name = new char[16]; cout<<"Person构造"<<endl; } virtual ~Person() { delete []name; cout<<"Person析构"<<endl; } private: char *name; }; class Teacher :virtual public Person { public: Teacher() { cout<<"Teacher构造"<<endl; } ~Teacher() { cout<<"Teacher析构"<<endl; } }; class Student :virtual public Person { public: Student() { cout<<"Student构造"<<endl; } ~Student() { cout<<"Student析构"<<endl; } }; class TS : public Teacher,public Student { public: TS() { cout<<"TS构造"<<endl; } ~TS() { cout<<"TS析构"<<endl; } }; int main() { Person *p = new TS(); delete p; return 0; }

这段代码的运行结果为:

复制代码
1
2
3
4
5
6
7
8
Person构造 Teacher构造 Student构造 TS构造 TS析构 Student析构 Teacher析构 Person析构

但是当我们把Person类中析构前面的virtual去掉之后的运行结果为:

复制代码
1
2
3
4
5
6
Person构造 Teacher构造 Student构造 TS构造 Person析构 程序崩溃

很明显这个结果不是我们想要的程序,崩溃造成的后果是不可预计的,所以我们一定要注意在基类的析构函数前面加上virtual,使其变成虚析构在C++程序中使用虚函数,虚继承和虚析构是很好的习惯 可以避免许多的问题。

 

纯虚函数

纯虚函数式一个在基类中声明的虚函数,它在该类中没有定义具体的操作内容,要求各派生类根据实际需要定义自己的版本

例如我们定义了一个图形的类,把他作为基类,它的派生类可以是三角形,正方形等等,图形可以求面积,求面积首先要直到图形式什么图形,所以在基类中无法确定求面积的成员函数的具体算法,但是在它的派生类里就可以实现了。

语法

virtual 函数名(参数)= 0;

含有纯虚函数的类叫做抽象类,抽象类没有对象,它虽然不能实例化,但是它可以规定对外接口的统一性

简单来讲就是,我们在实现多态的时候,多态函数在找方法的时候,不会去抽象类里找方法。

最后

以上就是眯眯眼高跟鞋最近收集整理的关于C++ 中虚函数的用法的全部内容,更多相关C++内容请搜索靠谱客的其他文章。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(103)

评论列表共有 0 条评论

立即
投稿
返回
顶部