目录
1.类/对象的多态性***
是考虑不同层次或同一层次函数的不同状态(意思就是同一个接口,不同的实现)
运行时多态(动态多态)
类成员函数在编译时,无法确定调用的位置,只有在运行时,依据函数名和参数表来确定你调用的位置
怎样达到运行时多态?
-
条件:继承+重写基类的虚函数+应用场景
虚函数:virtual void hi(){...}
vfptr 虚函数指针
-
应用场景:父类的引用/指针指向子类的对象
//继承+重写基类的虚函数+应用场景
class Person {
public:
virtual void hi()
{
cout << "Person hi()" << endl;
}
};
//1.继承
class Student :public Person {
//2.重写基类的虚函数
//override 关键字作用 确认此成员函数是否正确重写
//重写虚函数的三同:返回类型、函数名、参数表
virtual void hi() override
{
cout << "Student hi()" << endl;
}
};
int main()
{
/*Person p1;
p1.hi();*/
//3.运行时多态应用场景
Person* p1 = new Student();
p1->hi();
return 0;
}
Student hi()
D:\C++\x64\Debug\C++.exe (进程 6440)已退出,代码为 0。 按任意键关闭此窗口. . .
class Person {
public:
virtual void hi()
{
cout << "Person hi()" << endl;
}
};
class Student :public Person {
virtual void hi() override
{
cout << "Student hi()" << endl;
}
};
class Teacher : public Person {
virtual void hi() override
{
cout << "Teacher hi()" << endl;
}
};
class Worker : public Person {
virtual void hi() override
{
cout << "Worker hi()" << endl;
}
};
void hello(Person& person)
{
// 因为此处是Person类的引用
// 同时, hi成员函数是虚函数,因此编译时无法确认调用的位置:编译器不知道你这个指针 / 引用,最终指向的是哪个子类对象
// 只有在程序运行之后,调用此函数传入具体的对象时,才能确认调用位置
person.hi();
}
int main()
{
Person p1;
Worker w1;
Teacher t1;
Student s1;
//运行时多态体现
hello(p1);
hello(w1);
hello(t1);
hello(s1);
return 0;
}
Person hi() Worker hi() Teacher hi() Student hi()
D:\C++\x64\Debug\C++.exe (进程 14088)已退出,代码为 0。 按任意键关闭此窗口. . .
程序是如何在运行时识别并调用子类实现的?
只要类里面有虚函数,对象就会自动带一个虚指针,父类对象里面有父类虚指针,子类对象里面有子类虚指针。只要子类重写了父类的虚函数,那么子类的虚表,会把父类虚函数的地址 “覆盖掉”。存放的就是子类函数的地址,调用会调用子类的
为什么要实现多态?
主要是为了统一接口,方便扩展,提高代码的可维护性
例子:
比如说如果没有多态,要为每个子类准备一个单独的函数。catspeak,dogspeak,pigspeak。有了多态之后,一个函数可以解决所有的子类,只写一个speak函数里面传子类的对象,函数规范简洁。
易于扩展主要就是比如说要写一个birdspeak,不用新写一个函数,只用创建一个类,继承animal,重写虚函数。
提高代码的可维护性,要修改的话改一处就好,重复代码太少了
2.全局函数/同类中成员函数的多态性
编译时多态(静态多态)
类成员函数在编译时,可以确定调用的位置
实现方式:函数重载、运算符重载、模板
可以根据参数/类型在编译时确定调用哪个版本
虚函数使用规则
1.派生类/子类重写虚函数时,必须三同(返回类型、函数名、参数表)
2.虚函数只能属于类(只修饰类的成员函数,外部全局函数是无法使用virtual关键字)
3.静态成员函数,内联函数,构造函数和拷贝构造函数,不能为虚函数
不能加virtual
-
静态成员函数在子类中只能覆盖,重定义,不能加virtual,因为它没有this指针
-
构造函数用于初始化成员,创建类时必须唯一确定
-
拷贝构造函数也必须唯一确定
4.析构函数可以为虚函数(运行时多态必须用虚函数回收,因为实际回收的空间是子类的空间)
5.inline函数不能为虚函数
override
编译器会强制检查是否正确重写
class Person {
private:
int pid;
char* name;
public:
Person(int pid, const char* name) :pid(pid) {
this->name = new char[32] {0};
strcpy(this->name, name);
}
virtual void hi() {
cout << "Pid: " << pid << ", Name: " << name << endl;
}
virtual ~Person() {
cout << "~Person()" << endl;
delete[] name;
}
};
class Student :public Person {
private:
char* clsname;
public:
Student(int pid, const char* name,const char* clsname) :Person(pid,name) {
this->clsname = new char[32] {0};
strcpy(this->clsname, clsname);
}
void hi()
{
Person::hi();
cout << "clsName: " << clsname << endl;
}
~Student() {
cout << "~Student()" << endl;
delete[] clsname;
}
};
int main()
{
Person* p = new Student(1001, "Lucy", "c++2502");
p->hi();
delete p;
return 0;
}
Pid: 1001, Name: Lucy clsName: c++2502 ~Student() ~Person()
D:\C++\x64\Debug\C++.exe (进程 32516)已退出,代码为 0。 按任意键关闭此窗口. . .
5.基类的指针或引用,指向派生类的对象:
6.运行时多态,执行速度会稍慢些。
7.虚函数声明和定义分开时,只需要在声明上加virtual关键字。禁止在外部定义时加此virtual关键字。
多态的实现原理
运行时多态的原理
虚函数个数和虚函数表里面的个数不一样


虚函数指针指向虚函数表,确定了实际虚函数的位置,虚函数表实际上是函数指针数组,里面存储虚函数地址,
抽象类和接口类
抽象类
定义
包含纯虚函数的类
纯虚函数 是没有函数体的,只声明接口,不实现:
virtual 返回类型 函数名(参数表)=0;
特点
-
不能实例化,不能创建对象(实例)
-
派生类未实现纯虚函数时,仍是抽象类(子类必须重写全部的纯虚函数,有一个没实现,他就是抽象类)
-
可以有构造函数、普通成员函数、成员变量
接口类
定义
类中所有的函数(除了构造函数)都是纯虚函数,且没有任何成员变量。
特点
-
没有任何成员变量
-
所有函数(除构造)都是纯虚函数
-
完全用来定义 “规范 / 协议 / 接口”
-
子类必须实现所有接口
-
专门用于约束行为、统一规范

426

被折叠的 条评论
为什么被折叠?



