虚函数的调用:通过基类的指针或者引用
#include<iostream>
using namespace std;
class Base
{
public:
virtual void func1()
{
cout << "base::func1()" << endl;
}
virtual void func2()
{
cout << "base::func2()" << endl;
}
virtual void func3()
{
cout << "base::func3()" << endl;
}
void func4()
{
cout << "base::func4()" << endl;
}
private:
int _a;
};
class Dev : public Base
{
public:
virtual void func1()
{
cout << "func1" << endl;
}
virtual void func2()
{
cout << "func2" << endl;
}
virtual void func3()
{
cout << "func3" << endl;
}
void func4()
{
cout << "func4" << endl;
}
};
void TestVirtual(Base* pb)
{
pb->func1();
pb->func2();
pb->func3();
pb->func4();
}
int main()
{
Base a;
Dev b;
TestVirtual(&a);
TestVirtual(&b);
return 0;
}
打印结果:
base::func1()
base::func2()
base::func3()
base::func4()
func1
func2
func3
base::func4()
请按任意键继续. .
#include<iostream>
using namespace std;
class Base
{
public:
void func4()
{
cout << "base::func4()" << endl;
}
};
class Dev : public Base
{
public:
void func4()
{
cout << "func4" << endl;
}
};
void TestVirtual(Base* pb)
{
pb->func4();
}
int main()
{
Base a;
Dev b;
TestVirtual(&b);
return 0;
}
打印结果:
base::func4()
说明当没有发生多态的时候,使用基类的指针或者引用指向子类的对象的时候这个时候,使用指针,只能调用基类的函数。
假如基类中有三个虚函数,子类中除了对基类的三个虚函数重写之外,还自己添加了一个普通函数,此时使用基类的指针或者引用指向子类的对象的时候,使用基类的指针直接调用的就是子类中重写的三个函数,此时要是使用基类的指针调用子类中的普通函数的时候就没有办法调用,因为基类中没有这个普通函数。
假如基类中有一个和子类重名的函数,此时发生的是重写(覆盖),使用积累的指针指向子类的对象,再调用到的就是基类的同名函数,而不是子类的。
=====================================================================================
多态的函数调用原理:
在子类父类中发生的是同名函数的重写(覆盖)的时候,使用基类的指针或者引用只想之类的对象的时候,调用的是基类的函数,而不会调用子类的
=====================================================================================
多继承虚表构建过程:
多态的分类:
静态多态:静态绑定/早绑定在编译阶段就可以确定具体调用哪个函数(函数重载 + 模板)
动态多态:动态多态/晚绑定在编译阶段无法确定具体的调用,需要在运行时才能确定具体的调用(虚函数+继承实现),原因是在编译阶段无法确定基类的指针或者引用指向的哪一个子类的对象。
内联函数不能作为虚函数:
原因,假设已经是内联函数,在编译阶段,会将该函数展开,如果展开,就不会通过虚表去调用,就是不能实现多态了
静态的成员函数不能作为虚函数:
因为通过类名来调用静态成员函数的时候,根本找不到虚表,也就无法调用。最重要的一点就是,静态成员函数没有this指针。
构造函数也不能作为虚函数构造函数也是不能用const来修饰的
原因:一个类中如果包含虚函数,那么该类的构造函数有两个作用一个是初始化类中的成员变量,另一个就是在初始化列表的位置,由编译器将虚表的指针填充在对象的前4个字节里面。也就是说虚表的构造需要由构造函数参与,先有构造函数,才有虚表。没有构造函数就没有虚表,假如将构造函数给成虚函数,那么就需要通过虚表去调用构造函数,此时虚表在哪里呢?由此可以知道构造函数不能作为虚函数
析构函数可以作为虚函数
构造函数可以重载,析构函数不可以
析构函数是重写的一种特例
=====================================================================================
静态类型:声明变量时的类型
在编译期间起作用
动态类型:实际引用(指向)的类型
在运行时确定调用哪个类的虚函数
什么时候将基类的析构函数设置为虚函数,如果派生类中涉及到动态资源的管理,比如子类从堆上申请了空间,建议:积累的析构函数最好设置为虚函数,否则可能会存在内存泄露。
#include<iostream>
using namespace std;
class A
{
public:
A()
{}
virtual ~A()
{
cout << "A::~A()" << endl;
}
};
class B : public A
{
public:
B(int num)
{
_p = new int(num);
}
~B()
{
delete _p;
cout << "B::~B()" << endl;
}
int* _p;
};
void func()
{
B b(10);
A& a = b;
}
int main()
{
A* a = new B(10);
delete a;
return 0;
}
普通函数的调用和虚函数的调用那个比较快:
普通函数比较快,原因在上面
普通函数(指非虚函数)调用:
1.传参(如果有参数)
2.通过call指令调用该函数(call函数入口地址)
虚函数调用:
1.没有完全实现多态条件,比如:没有通过基类的指针或者引用调用虚函数,与普通的函数调用相同
2.多态的实现条件完全满足:
a>>从对象的前四个字节中取虚表的地址
b>>传参,包括this指针
c>>从虚表中获取虚函数地址
d>>调用虚函数
动态多态的缺血:可能会降低程序的运行速度
静态多态,动态多态:
#include<iostream>
using namespace std;
class A
{
public:
A()
{}
virtual ~A()
{
cout << "A::~A()" << endl;
}
};
class B : public A
{
public:
B(int num)
{
_p = new int(num);
}
~B()
{
delete _p;
cout << "B::~B()" << endl;
}
int* _p;
};
void func(A* a)
{
a = new B(10); //动态多态
}
int main()
{
A* a = new B(10); //静态多态
func(new B(20));//动态多态,函数调用参数在运行时才会传递,才会确定积累的指针或者引用到底指向哪个子类对象
delete a;
return 0;
}
虚表是在那个阶段生成的?
编译阶段
虚表在内存中的那个区域存放:
代码段
插入:
求最大公约数:
int gcd(int m, int n)
{
if (n == 0)
return m;
if(m < n)
swap(m,n);
int temp = m%n;
m = n;
n = temp;
return gcd(m, n);
}