一、多态定义
1. 构成条件
多态:多种形式
静态的多态:函数重载,原理是编译时实现
动态的多态:
- 必须通过基类的指针或者引用调用虚函数
- 被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写(覆盖)
重写(覆盖)的含义: 当子类和父类(或者只有父类但建议都有、这里可以理解为,子类继承了父类的虚函数属性,再进行重写)中虚函数(有virtual关键字),返回值,函数名,参数一模一样。
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <string>
using namespace std;
class A
{
public:
virtual void AB()
{
cout << 'A' << endl;
}
};
class B : public A
{
public:
virtual void AB()
{
cout << 'B' << endl;
}
};
void test(A* a)
{
a->AB();
}
int main()
{
A a;
B b;
test(&a);
test(&b);
return 0;
}
如果只是传类,不会构成多态,必须是指针和引用
构成多态,跟p的类型没有关系,传的哪个类型的对象,调用的就是这个类型的虚函数 – 跟对象有关
b类型:
a类型:(因为父类不能给子类会报错)
ab型:
2. 虚函数
只能是类的非静态成员函数 + virtual关键字才是虚函数
class A
{
virtual void test()
{}
}
3. 虚函数重写的两个例外
协变
协变(基类与派生类虚函数返回值类型不同) :派生类重写基类虚函数时,与基类虚函数返回值类型不同。
即基类虚函数返回基类对象的指针或者引用,派生类虚函数返回派生类对象的指针或者引用时,称为协变。
class A
{
public:
virtual A* AB()
{
cout << 'A' << endl;
return nullptr;
}
};
class B : public A
{
public:
virtual B* AB()
{
cout << 'B' << endl;
return nullptr;
}
};
析构函数的重写
在继承那,子类的析构函数析构时候,会最后调用父类析构函数
且编译会将两个析构函数都变成destructor()调用
在继承中它们构成隐藏,先析构子类再析构父类
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <string>
using namespace std;
class A
{
public:
virtual ~A()
{
cout << "~A" << endl;
}
};
class B : public A
{
public:
virtual ~B()
{
cout << "~B" << endl;
}
};
int main()
{
A a;
B b;
return 0;
}
如果有这样一个场景的话
复习:
new = operator new + 构造函数
delete = 析构函数 + operator delete
int main()
{
A* a = new A;
A* b = new B;
delete a;
delete b;
return 0;
}
当delete b 的时候,如果不构成多态,只会调用父类析构函数,但new B类型确实存在,虽然A* b发生截断了,但这样不释放空间会导致内存泄漏
加上virtual 之后
总结在创建动态申请子类对象的时候,需要重写
二、什么情况下类不能被继承
- 当父类的构造函数是私有的时候,当子类定义时候,调用不了构造函数
【C++98解决方式:private】
class A
{
A(int a)
:_a(a)
{}
int _a;
};
class B : public A
{
};
int main()
{
B b;
return 0;
}
因为是private被继承不可见
不仅仅子类,父类也是没法对象实例化,同样没法调用构造函数
class A
{
A(int a)
:_a(a)
{}
public:
static A Create(int a)
{
return A(a);
}
int _a;
};
class B : public A
{
};
int main()
{
A a = A::Create(10);
return 0;
}
这里是static是保证可以从类域里面使用(因为还没实例化的时候Create函数不能使用)
【C++11解决方式:final】
class A final
{
public:
A(int a)
:_a(a)
{}
int _a;
};
class B : public A
{
};
int main()
{
B b;
return 0;
}
final还可以解决重写,防止被重写
class A
{
public:
virtual void AB() final
{
cout << "a" << endl;
}
};
class B : public A
{
public :
virtual void AB()
{
cout << "b" << endl;
}
};
int main()
{
A a;
B b;
A* aa = &a;
B* bb = &b;
aa->AB();
bb->AB();
return 0;
}
三、override检查重写
放在子类重写的虚函数后面,检查是否完成,没有重写报错
class A
{
public:
void AB()
{
cout << "a" << endl;
}
};
class B : public A
{
public:
virtual void AB() override
{
cout << "b" << endl;
}
};
总结:
四、抽象类
包含纯虚函数的类叫做抽象类(也叫接口类)
class A
{
public:
virtual void AB() = 0;
};
纯虚函数只声明不实现(但也可以写内容不过没用),因为包含纯虚函数的类不能实例化出对象
class A
{
public:
virtual void AB() = 0
{
cout << "a" << endl;
}
};
int main()
{
A* a = nullptr;
a->AB();
return 0;
}
崩了
派生类继承后也不能实例化出对象,只有重写纯虚函数,派生类才能实例化出对象。纯虚函数规范了派生类必须重写,另外纯虚函数更体现出了接口继承 一个类型,如果一般在现实世界中,没有具体的对应实物就定义成抽象类比较好
纯虚函数的类,本质上强制的子类去完成虚函数重写。 override只是在语法上检查是否完成重写。
五、多态的原理
1. 没有继承的虚函数表
class A
{
virtual void f()
{
}
int a;
char i;
};
int main()
{
A a;
return 0;
}
可见虚函数这里是多了个指针,而且这里虚函数表指针是放在前面
如果有两个虚函数?
这里虚函数表就是函数指针数组
2. 单继承中虚函数表
总结:虚函数表放,重写的虚函数和自己虚函数的地址(这里的地址也不是真实的地址,而是封装过的地址(jump指令的地址,jump之后才能到函数的第一句))
虚表存储在常量区并且没有构成重写的虚函数也在虚表中
这里代码运行成功的原因是,类成员函数存在代码区,p->ab只是把指针传给this不会出错
class A
{
public:
void AB()
{
cout << "a" << endl;
}
};
int main()
{
A* a = nullptr;
a->AB();
return 0;
}
如果把virtual那个函数拿出来,会错误:错误的原因是把指针给了this,this会去找虚函数表的指针,所以空指针会出错(nullptr无法访问数组元素)
3. 多态当中的虚函数表
- 多态例子,多态时候,子类只有重写和继承的函数地址在监控里可以看到在虚表当中,子类其他虚函数不显示,但可以用虚表地址,访问虚表,看到虚表里面其实是有存储其他虚函数的地址
class A
{
public:
virtual void f()
{
cout << "A" << endl;
}
};
class B : public A
{
public:
virtual void f()
{
cout << "B" << endl;
}
};
void test(A& p)
{
p.f();
}
int main()
{
A a;
B b;
test(a);
test(b);
return 0;
}
二者虚函数表不一样
4. 不能直接给类切片的原因
int main()
{
A a;
B b;
A aa = b;
return 0;
}
二者地址虚表地址不同
5. 同类型的对象,虚函数表一样
就像类的函数只存一份一样
都存在公共代码区
int main()
{
A a;
A aa;
return 0;
}
6. private不限制虚函数表
尽管有private修饰子类的多态,但仍然能够调用到函数
所以访问限定符对虚函数表没有影响,所以只要有虚表的指针,就能强制访问
7. 多继承中的虚函数
此时会有两个虚函数表,一个a表一个b表
class A
{
public:
virtual void f()
{
cout << "A" << endl;
}
virtual void fa()
{
cout << "A" << endl;
}
};
class B
{
public:
virtual void f()
{
cout << "B" << endl;
}
virtual void fb()
{
cout << "B" << endl;
}
};
class C : public A, public B
{
};
int main()
{
C c;
A& a = c;
B& b = c;
return 0;
}
|