多态
虚函数
定义
在成员函数前面加上virtual关键字的函数,称为虚函数。
重定义(重写)的要求
- 基类与派生类中函数名要相同
- 函数的参数列表也要相同(参数的个数、类型、顺序)
- 函数的返回类型一致
小结:基类与派生类中的同名虚函数,除了函数体可以不一样之外,其他的全部都要保持一致。(函数名,函数返回类型,函数的参数列表)
虚函数的实现原理(重要)
当基类定义了虚函数的时候,就会在基类对象的存储布局的前面多一个虚函数指针,该虚函数指针会指向虚函数表(虚表),虚表中存放的是虚函数的入口地址。当派生类继承基类的时候,会吸收基类的虚函数,同样会在派生类对象的存储布局的前面多一个虚函数指针,该虚函数指针指向派生类自己的虚表,该虚表里面存放的是从基类吸收的虚函数的入口地址,当派生类对该虚函数进行重写时,会用派生类自己的虚函数的入口地址进行覆盖,存放的就是派生类自己的虚函数的入口地址。
虚函数被激活的五个条件(重要)
- 基类要定义虚函数
- 派生类要重写(重定义、覆盖)该虚函数
- 创建派生类的对象
- 要用基类的指针(引用绑定)指向派生类的对象
- 要用基类的指针(引用)调用虚函数
那些函数不能设置为虚函数?
- 普通函数(全局函数,非成员函数,自由函数):因为虚函数定义中必须是成员函数
- 静态成员函数:静态成员函数发生的时机在编译的时候,而虚函数要体现多态,必须在运行的时候;静态成员函数没有this指针,被该类的所有对象共享,对该类而言是唯一的
- 内联函数:同样发生的时机在编译的时候;内联函数如果写成虚函数,那么就失去了内联的含义(内联函数目的就是在编译时发生,而虚函数是在运行时发生的)
- 友元函数:如果友元函数本身是一个普通函数,那么友元函数不能被设置为虚函数;但是如果友元函数本身是成员函数,那么该友元函数是可以被设置为虚函数的
- 构造函数:同样发生的时机在编译的时候;构造函数是不可以被继承的,而虚函数是可以被继承的;如果将构造函数设置为虚函数,那么就需要通过虚表找到虚函数的入口地址,则需要虚函数指针指向虚表,而虚函数指针存储在对象存储布局的最前面,而如果构造函数不调用,那么对象就不一定是完整的,那么对象的存储布局的前面就不一定有虚函数指针,但是如果没有虚函数指针就不能指向虚表来找到函数入口,前后矛盾
虚函数的访问
- 使用指针进行访问:可以体现动态多态
- 使用引用进行访问:可以体现动态多态
- 使用对象进行访问:虚函数体现的就是普通成员函数的特性,在编译的时候就已经确定了函数调用
- 使用其他普通成员函数进行访问
- 使用特殊成员函数(构造函数、析构函数):在调用Father的构造函数的时候,调用func1()的时候,派生类Son的对象还没有构建完成,所以就看不到虚表这套逻辑,所以只能看到Father自己的func1()。而当派生类Son销毁时,虚表已经被销毁了,所以在调用Father的func2()的时候,已经看不到Son这套逻辑,所以就只会调用到Father自己的func2函数。都没有体现动态多态
#include <iostream>
using std::cout;
using std::endl;
class Grandpa
{
public:
Grandpa()
{
cout << "Grandpa()" << endl;
}
~Grandpa()
{
cout << "~Grandpa()" << endl;
}
virtual
void func1()
{
cout << "void Grandpa::func1()" << endl;
}
virtual
void func2()
{
cout << "void Grandpa::func2()" << endl;
}
};
class Father
: public Grandpa
{
public:
Father()
: Grandpa()
{
cout << "Father()" << endl;
func1();
}
~Father()
{
cout << "~Father()" << endl;
func2();
}
virtual
void func1()
{
cout << "void Father::func1()" << endl;
}
virtual
void func2()
{
cout << "void Father::func2()" << endl;
}
};
class Son
: public Father
{
public:
Son()
: Father()
{
cout << "Son()" << endl;
}
~Son()
{
cout << "~Son()" << endl;
}
virtual
void func1()
{
cout << "void Son::func1()" << endl;
}
virtual
void func2()
{
cout << "void Son::func2()" << endl;
}
};
int main()
{
Son son;//栈对象
return 0;
}
运行结果:
两个加深理解的代码
1
#include <iostream>
using std::cout;
using std::cin;
using std::endl;
class A
{
public:
A()
{
cout << "A's cons." << endl;
}
virtual
~A()
{
cout << "A's des." << endl;
}
virtual
void f()
{
cout<<"A's f()."<<endl;
}
void g()
{
f();
}
};
class B
: public A
{
public:
B()
{
f();
cout << "B's cons." << endl;
}
~B()
{
cout << "B's des." << endl;
}
};
class C
: public B
{
public:
C()
{
cout<<"C's cons."<<endl;
}
~C()
{
cout<<"C's des."<<endl;
}
void f()
{
cout<<"C's f()."<<endl;
}
};
int main(void)
{
A *pa=new C();
pa->g();
delete pa;
return 0;
}
运行结果:
1:在调用B类的构造函数时会调用f函数,此时C对象还没有构建完整,且B类里面并没有重写f函数,并没有满足激活虚函数的五个条件,只能看到A类里面的f函数,最后也只能调用A类里的f函数。
2:对于pa->g(),这里调用g函数,再通过this指针调用g里面的f函数,且这个指针是指向派生类(C)的基类(A)类型的指针,满足了激活虚函数的五个条件,所以会调用C类里面的f函数。
2
#include <iostream>
using std::cout;
using std::cin;
using std::endl;
class A
{
public:
virtual
void func(int val = 1)
{
cout << "A->" << val << endl;
}
virtual void test()
{
func();
}
private:
long _a;
};
class B
: public A
{
public:
virtual
void func(int val = 10)
{
cout << "B->" << val << endl;
}
private:
long _b;
};
class C
: public B
{
virtual
void func(int val = 10)
{
cout << "C->" << val << endl;
}
};
int main(void)
{
B b;
A *p1 = (A*)&b;
B *p2 = &b;
p1->func();
p2->func();
return 0;
}
运行结果:
注意:为什么用 p1调用func函数时会打印 B->1?
显然,这里是满足虚函数激活的五个条件的,但是虚函数是在运行时起作用的,故在编译阶段已经确定了func里面的参数val的值,而p1是A类型的指针,编译阶段会走A类里面的func函数,故在编译阶段将val的值确定为1了,然后在运行阶段调用B里面的func函数。
标签:调用,虚表,函数,派生类,多态,基类,构造函数 From: https://www.cnblogs.com/MyXjil/p/17288838.html