目录
多态的条件?
析构函数的重写?
C++11 override 和 final
1.final:修饰虚函数,表示该虚函数不能再被重写
2.override: 检查派生类虚函数是否重写了基类某个虚函数,如果没有重写编译报错
重载、覆盖(重写)、隐藏(重定义)的对比
抽象类
多态的原理
虚函数表
多态的原理
单继承和多继承关系的虚函数表?
单继承中的虚函数表
多态的条件?
class Person
{
public:
virtual void BuyTicket()
{
cout << "正常排队—全价买票" << endl;
}
protected:
int _age;
string _name;
};
class Student:public Person
{
public:
virtual void BuyTicket()
{
cout << "正常排队—半价买票" << endl;
}
protected:
int _age;
string _name;
};
class Solider:public Person
{
public:
virtual void BuyTicket()
{
cout << "优先排队—全价买票" << endl;
}
protected:
int _age;
string _name;
};
//多态两个条件
//1,子类重写父类的虚函数
//2,必须是父类的指针或者引用去调用虚函数
void Func(Person* ptr)
{
//多态—ptr指向父类对象调用父类的虚函数,指向子类对象调子类虚函数
ptr->BuyTicket();
}
int main()
{
Person ps;
Student st;
Solider sd;
Func(&ps);
Func(&st);
Func(&sd);
}
多态的两个条件
1.子类重写父类的虚函数
2.必须是父类的指针或者引用去调用虚函数?
析构函数的重写?
class Person
{
public:
virtual ~Person()
{
cout << "~Person()" << endl;
}
};
class Student :public Person
{
public:
virtual ~Student()
{
cout << "~Student()" << endl;
}
};
//如果不是虚函数,他们之间是隐藏关系
//是虚函数,他们是重写关系
int main()
{
普通场景下面,虚函数是否重写都是OK的
//Person p;
//Student s;
//new对象特殊场景
Person* p1 = new Person;
Person* p2 = new Student;
delete p1;//p1->destructor()
delete p2;//p2->destructor()
return 0;
}
注意!析构函数要定义成虚函数
有些书籍会把多态进行更多的划分:
1,静态的多态——函数的重载,调用同一个函数,传不同的参数,就有不同的行为/形态
2,动态的多态——调用一个虚函数,不同对象去调用,就有不同的行为/形态
多态的条件:
a,子类中重写父类的虚函数
b,必须是父类的指针或者引用去调用重写的虚函数
虚函数重写条件:
a,子类和父类都必须是虚函数
b,函数名,参数,返回值都必须相同
例外:
a,协变
b,析构函数被特殊处理
c,子类中的重写虚函数可以不加virtual
C++11 override 和 final
1.final:修饰虚函数,表示该虚函数不能再被重写
2.override: 检查派生类虚函数是否重写了基类某个虚函数,如果没有重写编译报错
class Car{
public:
virtual void Drive(){}
};
class Benz :public Car {
public:
virtual void Drive() override { cout << "Benz-舒适" << endl; }
};
重载、覆盖(重写)、隐藏(重定义)的对比
抽象类
在虚函数的后面写上 =0 ,则这个函数为纯虚函数。包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象。派生类继承后也不能实例化出对象,只有重写纯虚函数,派生类才能实例化出对象。纯虚函数规范了派生类必须重写,另外纯虚函数更体现出了接口继承
class Car
{
public:
virtual void Drive() = 0;
};
class Benz :public Car
{
public:
virtual void Drive()
{
cout << "Benz-舒适" << endl;
}
};
class BMW :public Car
{
public:
virtual void Drive()
{
cout << "BMW-操控" << endl;
}
};
void Test()
{
Car* pBenz = new Benz;
pBenz->Drive();
Car* pBMW = new BMW;
pBMW->Drive();
}
多态的原理
虚函数表
虚函数表:
如下代码的输出结果是?
class Base
{
public:
virtual void Func1()
{
cout << "Func1()" << endl;
}
private:
int _b = 1;
};
int main()
{
Base b;
cout << sizeof(Base) << endl;
return 0;
}
?在看一段代码:
class Base
{
public:
virtual void Func1()
{
cout << "Base::Func1()" << endl;
}
virtual void Func2()
{
cout << "Base::Func2()" << endl;
}
void Func3()
{
cout << "Base::Func3()" << endl;
}
private:
int _b = 1;
};
class Derive : public Base
{
public:
virtual void Func1()
{
cout << "Derive::Func1()" << endl;
}
private:
int _d = 2;
};
int main()
{
Base b;
Derive d;
return 0;
}
父子类无论是否完成虚函数的重载,都有各自独立的虚表
一个类的所有对象,共享一张虚表
多态的原理
class Person
{
public:
virtual void BuyTicket() { cout << "买票-全价" << endl; }
};
class Student : public Person
{
public:
virtual void BuyTicket() { cout << "买票-半价" << endl; }
};
void Func(Person& p)
{
p.BuyTicket();
}
int main()
{
Person Mike;
Func(Mike);
Student Johnson;
Func(Johnson);
return 0;
}
?分析===》
这里存在问题:多态的这种调用也是付出代价的,多态的调用要到虚表里面去找,会有一定程度的性能损失
复习:普通函数是如何确认地址的?编译或链接时确定它的地址
编译:编译的时候往上去找有无这个函数的定义,编译完,找到地址,编程call
链接:只有他的声明,他的定义在其他的目标文件里面,这个时候先过,call(?),链接的时候拿这个函数名去找
多态的调用?
先检查是不是满足多态,多态的调用是运行时去确定地址,如何确定?==》去指向对象的虚函数表中知道虚函数的地址
单继承和多继承关系的虚函数表?
单继承中的虚函数表
class Base
{
public:
virtual void Func1()
{
cout << "Base::Func1()" << endl;
}
virtual void Func2()
{
cout << "Base::Func2()" << endl;
}
void Func3()
{
cout << "Base::Func3()" << endl;
}
private:
int _b = 1;
};
class Derive : public Base
{
public:
virtual void Func1()
{
cout << "Derive::Func1()" << endl;
}
virtual void Func4()
{
cout << "Derive::Func4()" << endl;
}
private:
int _d = 2;
};
typedef void(*VFPTR)();
//打印虚表
void PrintVFT(void* vft[])
{
printf("%p\n",vft);
for (size_t i = 0; vft[i] != nullptr; i++)
{
printf("vft[%d]:%p->",i,vft[i]);
VFPTR f = (VFPTR)vft[i];
f();
}
printf("\n");
}
int main()
{
Base b;
Derive d;
PrintVFT((void**)*(int*)&b);
PrintVFT((void**)*(int*)&d);
return 0;
}
虚函数存在哪的?虚表存在哪的?
答:虚函数存在虚表,虚表存在对象中。注意上面的回答的错的。但是很多童鞋都是这样深以为然的。注意虚表存的是虚函数指针,不是虚函数,虚函数和普通函数一样的,都是存在代码段的,只是他的指针又存到了虚表中。另外对象中存的不是虚表,存的是虚表指针。
那么虚表存在哪的呢?===>常量区(代码段)
虚函数编译出来函数指令跟普通函数一样,存在代码段,虚函数地址又被放到虚函数表中
int main()
{
Base bb;
int a = 0;
int* p1 = new int;
const char* p2 = "hello world";
auto pf = PrintVFT;
static int b = 1;
printf("栈祯变量:%p\n",&a);
printf("堆变量:%p\n", p1);
printf("常量区变量:%p\n", p2);
printf("函数地址变量:%p\n", pf);
printf("静态区变量:%p\n", &b);
printf("虚函数表地址:%p\n", *(int*)&bb);
return 0;
}
|