多态
多态的概念
概念
多态即多种形态,当不同对象去执行同一个行为时,会产生不同的状态
多态的定义及实现
多态的构成条件
- 必须通过基类的指针/引用去调用虚函数(
virtual
所修饰) - 被调用的函数必须是虚函数,并且派生类必须对基类的虚函数进行重写
class Person
{
public:
virtual void Buyticket()
{
cout << "Person:买票-全价" << endl;
}
};
class Student:public Person
{
public:
virtual void Buyticket()
{
cout << "Student:买票-半价" << endl;
}
};
//多态调用
void Display(Person& p)
{
p.Buyticket();
}
int main()
{
Person pn;
Display(pn);
Student st;
Display(st);
return 0;
}
普通调用:和调用对象的类型有关
多态调用:和指针/引用指向的对象有关
class Person
{
public:
virtual void Buyticket()
{
cout << "Person:买票-全价" << endl;
}
};
class Student:public Person
{
public:
virtual void Buyticket()
{
cout << "Student:买票-半价" << endl;
}
};
//普通调用
void Display(Person p)
{
p.Buyticket();
}
int main()
{
Person pn;
Display(pn);
Student st;
Display(st);
return 0;
}
虚函数
虚函数:被 virtual
修饰的类成员函数称作虚函数
class Person
{
public:
virtual void Buyticket()
{
cout << "Person:买票-全价" << endl;
}
};
虚函数的重写
虚函数的重写:派生类中有一个和基类完全相同的虚函数,称派生类的虚函数重写了基类的虚函数
虚函数重写的两个例外
- 协变(基类与派生类虚函数返回值类型不同)
派生类重写基类虚函数时,与基类虚函数返回值类型不同。也就是基类虚函数返回基类对象的指针/引用,派生类虚函数返回派生类对象的指针/引用
class Person
{
public:
~Person()
{
cout << "~Person()" << endl;
delete[] _p;
}
protected:
int* _p = new int[10];
};
class Student :public Person
{
public:
~Student()
{
cout << "~Student()" << endl;
delete[] _s;
}
protected:
int* _s = new int[10];
};
int main()
{
Person pn;
Student st;
return 0;
}
- 析构函数的重写(基类与派生类析构函数名不同)
先观察如果析构函数不设置为虚函数
class Person
{
public:
~Person()
{
cout << "~Person()" <<endl;
delete[] _p;
}
protected:
int* _p = new int[10];
};
class Student :public Person
{
public:
~Student()
{
cout << "~Student()" << endl;
delete[] _s;
}
protected:
int* _s = new int[20];
};
int main()
{
Person pn;
Student st;
return 0;
}
运行起来似乎没有什么问题,做如下修改,结果又是如何
int main()
{
Person* ptr1 = new Person;
Person* ptr2 = new Student;
delete ptr1;
delete ptr2;
return 0;
}
由运行结果可知,发生了内存泄漏,Student
的资源并没有释放;delete
的行为是:使用指针调用析构函数,由于是普通调用,函数与调用对象的类型有关,所以造成了内存泄漏;如果将函数设置为虚函数结构会怎么样呢?
class Person
{
public:
virtual ~Person()
{
cout << "~Person()" << endl;
delete[] _p;
}
protected:
int* _p = new int[10];
};
class Student :public Person
{
public:
virtual ~Student()
{
cout << "~Student()" << endl;
delete[] _s;
}
protected:
int* _s = new int[20];
};
内存泄漏的问题完美地解决
虽然函数名不同,这里其实是构成了虚函数的重写,编译器对析构函数的名称做了处理,编译后的析构函数的名称统一处理成 destructor
- 子类虚函数可以不加
virtual
修饰(不推荐)
C++11override和final
- final:修饰虚函数,表示该虚函数不能被重写
- override:检查派生类虚函数是否重写了某类的某个虚函数,如果没有重写编译报错
重载,覆盖,隐藏的对比
抽象类
概念
在虚函数的后面加上=0
,称这个函数为纯虚函数,包含纯虚函数的类称作抽象类,抽象类不能实例化对象;派生类继承后也不能实例化对象,只有重写纯虚函数,派生类才能实例化对象;纯虚函数规定了派生类必须重写
class Car
{
public:
virtual void Drive()=0;
};
int main()
{
Car c;
return 0;
}
纯虚函数不可以实例化
class Car
{
public:
virtual void Drive() = 0;
};
class NIO : public Car
{
public:
virtual void Drive()
{
cout << "安全驾驶" << endl;
}
};
int main()
{
NIO et7;
et7.Drive();
return 0;
}
接口继承和实现继承
普通函数的继承是是实现继承,派生类继承了基类函数,可以使用函数,继承的是函数的实现;虚函数的继承是一种接口继承,派生类继承的是基类虚函数的接口,目的是为了重写,构造多态,继承的是接口
多态的原理
虚函数表
class Base
{
public:
virtual void Test()
{
cout << "Test()" << endl;
}
private:
int _a = 0;
};
int main()
{
Base b;
cout << sizeof(b) << endl;
return 0;
}
按照以往的方式计算 Base
类的大小:类中成员变量只有 int
类型大小四个字节,成员函数 Test
不占空间,所以类的大小应该是四个字节
运行结果如下
运行结果和预算的结果不一样,这是怎么回事呢?难道是成员函数也要计算大小吗?接下来,通过监视一探究竟
原来对象类中除了成员变量之外,还有_vfptr
,也就是接下来要学习的虚函数表指针,想要学习指针,就先来了解虚函数表
虚函数表:用来存放虚函数,就如上面[0]
中存放的就是Test
的地址;本质就是函数指针数组
class Car
{
public:
virtual void Test1()
{
cout << "Car:Test1()" << endl;
}
virtual void Test2()
{
cout << "Car:Test2()" << endl;
}
private:
int _a = 0;
};
class NIO : public Car
{
public:
virtual void Test1()
{
cout << "NIO:Test1()" << endl;
}
private:
int _b = 0;
};
int main()
{
Car c;
NIO et7;
return 0;
}
完成重写的虚函数,表函数表对应位置覆盖成重写的虚函数
多态的原理
int main()
{
Car c;
NIO et7;
//多态调用
Car* ptr = &c;
ptr->Test1();
ptr = &et7;
ptr->Test1();
//普通调用
ptr = &c;
ptr->Test2();
ptr = &et7;
ptr->Test2();
return 0;
}
运行结果如下
普通调用由调用对象类型决定:Test2()
函数不是虚函数,调用类型是Car
;多态调用由指针/引用指向的对象决定:Test1()
函数是虚函数,两次调用指向的对象都不同
再通过汇编进行观察
普通调用在编译时就确定好的(静态);多态调用在编译时是不确定的(动态)运行之后在,根据调用对象指向的类型,在表函数表中找到对应的函数进行调用
动态绑定与静态绑定
- 静态绑定,在程序编译期间就已经确定程序的行为,也称静态多态
- 动态绑定,在程序运行期间,根据具体的类型确定程序具体的行为,调用具体的函数,也称动态多态
单继承和多继承关系中的虚函数表
单继承中的虚函数表
观察下列代码
class Car
{
public:
virtual void test1()
{
cout << "Car test1()" << endl;
}
virtual void test2()
{
cout << "Car test2()" << endl;
}
private:
int _a;
};
class NIO:public Car
{
public:
virtual void test1()
{
cout << "NIO test1()" << endl;
}
virtual void test3()
{
cout << "NIO test3()" << endl;
}
private:
int _b;
};
int main()
{
Car c;
NIO et7;
return 0;
}
通过监视窗口能够发现:在派生类对象et7
中重写了test1
,继承了test2
,但是本身的test3
却没有,在内存窗口看到一个未知的地址,可以猜测是test3
函数,现在就是想办法将其打印出来进行验证
上面了解到虚函数表本质是函数指针数组,接下来通过函数指针数组将虚函数表打印出来
typedef void(*_vfptr)();
void Print_vfptr(_vfptr vft[])
{
for (int i = 0; vft[i] != nullptr; i++)
{
printf("[%d]:%p->", i, vft[i]);
vft[i]();
}
cout << endl;
}
通过将对象进行取址再强转 int*
获取前四个字节,在类型转换 _vfptr*
传递给函数进行打印
Print_vfptr((_vfptr*)*(int*)&c);
Print_vfptr((_vfptr*)*(int*)&et7);
完整代码如下
class Car
{
public:
virtual void test1()
{
cout << "Car test1()" << endl;
}
virtual void test2()
{
cout << "Car test2()" << endl;
}
private:
int _a;
};
class NIO:public Car
{
public:
virtual void test1()
{
cout << "NIO test1()" << endl;
}
virtual void test3()
{
cout << "NIO test3()" << endl;
}
private:
int _b;
};
typedef void(*_vfptr)();
void Print_vfptr(_vfptr vft[])
{
for (int i = 0; vft[i] != nullptr; i++)
{
printf("[%d]:%p->", i, vft[i]);
vft[i]();
}
cout << endl;
}
int main()
{
Car c;
Print_vfptr((_vfptr*)*(int*)&c);
NIO et7;
Print_vfptr((_vfptr*)*(int*)&et7);
return 0;
}
打印结果和预期一致
多继承中的虚函数表
class NIO
{
public:
virtual void test1()
{
cout << "NIO test1()" << endl;
}
virtual void test2()
{
cout << "NIO test2()" << endl;
}
private:
int _a;
};
class XPENG
{
public:
virtual void test1()
{
cout << "XPENG test1()" << endl;
}
virtual void test2()
{
cout << "XPENG test2()" << endl;
}
private:
int _b;
};
class NEA :public NIO, public XPENG
{
public:
virtual void test1()
{
cout << "NEA test1()" << endl;
}
virtual void test3()
{
cout << "NEA test3()" << endl;
}
private:
int _c;
};
int main()
{
NIO et7;
XPENG p7;
NEA car;
return 0;
}
通过监视会发现与上面类似的疑问,派生类 NEA
对象本身的虚函数存放在第一个基类 NIO
中还是第二个基类 XPENG
中呢?
接下来再次通过函数指针数组进行验证
typedef void(*_vfptr)();
void Print_vfptr(_vfptr vft[])
{
for (int i = 0; vft[i] != nullptr; i++)
{
printf("[%d]:%p->", i, vft[i]);
vft[i]();
}
cout << endl;
}
class NIO
{
public:
virtual void test1()
{
cout << "NIO test1()" << endl;
}
virtual void test2()
{
cout << "NIO test2()" << endl;
}
private:
int _a;
};
class XPENG
{
public:
virtual void test1()
{
cout << "XPENG test1()" << endl;
}
virtual void test2()
{
cout << "XPENG test2()" << endl;
}
private:
int _b;
};
class NEA :public NIO, public XPENG
{
public:
virtual void test1()
{
cout << "NEA test1()" << endl;
}
virtual void test3()
{
cout << "NEA test3()" << endl;
}
private:
int _c;
};
int main()
{
NIO et7;
Print_vfptr((_vfptr*)*(int*)&et7);
XPENG p7;
Print_vfptr((_vfptr*)*(int*)&p7);
NEA car;
//NIO虚函数表
Print_vfptr((_vfptr*)*(int*)&car);
XPENG* ptr = &p7;
//XPENG虚函数表
Print_vfptr((_vfptr*)*(int*)ptr);
return 0;
}
打印结果显示:派生类本身的虚函数是存放在第一个基类的虚函数表中的