多态,顾名思义,就是一件事物具备不同的形态
,是继承之后,面向对象的第三大特性
,可以这样说:有了继承才有了类的多态,而类的多态是为了更好的实现继承。
多态的列车即将起航,不知你准备好了吗?
继承与多态相辅相成
。
举个例子:
我们都是人(具备人都有的信息——性别,年龄等),在社会上我们又会具备不同的身份——老师,学生,工人等,那这时放假回家,要去买火车票,那这时如果你是学生火车票五折
。如果是老师火车票七五折
。
老师和学生都是人,人又具有不同的身份,而不同身份面临的同一件事情表现的具体形态不同。
因此:老师和学生继承人的信息,从而使人对同一件事表现出不同的形态,这样就是多态。
那语法层面上是如何实现多态呢?
多态,我们其实已经接触过一种,叫函数重载
,根据传参不同而调用同一函数的不同形态。这叫做静态的多态,也叫静态绑定
,而我们今天讲的主要是在继承之后延伸出的类的多态,叫动态的多态,也叫动态绑定
。
这里根据上面的例子,列出一段代码:
#include
using namespace std;
class Person
{
public:
virtual void BuyTickets()
{
cout << "全价" << endl;
}
};
class Student : public Person
{
public:
virtual void BuyTickets()
{
cout << "学生->半价" << endl;
}
};
class Teacher : public Person
{
public:
virtual void BuyTickets()
{
cout << "老师->七五折" << endl;
}
};
void BuyTicket(Person & per)
{
per.BuyTickets();
}
int main()
{
Student stu;
Teacher tea;
BuyTicket(stu);
BuyTicket(tea);
return 0;
}
初学者看懂个大概即可,语法和原理下面会细讲。
主要实现方法有两个。
virtual
。那重点就在于重写,也叫覆盖。
如何才能构成重写呢?或者什么叫做重写呢?
细节:
协变
到这重写的条件就讲清了,至于什么叫重写,其实很简单就是:在达成重写的条件下,子类的虚函数替换掉父类的虚函数,从而达成用指向子类的父类指针,在调用此虚函数时,会调用子类的虚函数,而不是父类的虚函数。
列一段代码,看结果便可明了。
#include
using namespace std;
class Person
{
public:
//其它类的引用和指针也行,但必须是父是父的,子是子的!
virtual Person* BuyTickets(int val1 = 1)
{
cout << "全价" << endl;
cout << val1 << endl;
return nullptr;
}
};
class Student : public Person
{
public:
virtual Student* BuyTickets(int val2 = 0)
{
cout << "学生->半价" << endl;
cout << val2 << endl;
return nullptr;
}
};
class Teacher : public Person
{
public:
virtual Teacher* BuyTickets(int val3 = - 1)
{
cout << "老师->七五折" << endl;
cout << val3 << endl;
return nullptr;
}
};
void BuyTicket(Person & per)
{
per.BuyTickets();
}
int main()
{
Student stu;
Teacher tea;
BuyTicket(stu);
BuyTicket(tea);
return 0;
}
底层用的是地址和寄存器,根本不关心参数名
,这也是细节1的原因。class Student : public Person
{
public:
Student* BuyTickets (int val2 = 0) override
{
cout << "学生->半价" << endl;
cout << val2 << endl;
return nullptr;
}
//void fun1() override
//{
//}
//
//此注释代码不符合重写条件,故报错。
};
class Person
{
public:
virtual void BuyTickets()final
{
cout << "全价" << endl;
}
};
class Student : public Person
{
public:
//因为此函数构成重写,派生类会重写虚函数,因此会报错。
void BuyTickets ()
{
cout << "学生->半价" << endl;
}
};
class A final
{};
//因为B继承A,所以会报错。
class B : public A
{};
C++98采用构造函数/析构函数私有来进行实现不可被继承(应用层面)。
class A
{
public:
static A* CreatObj()
{
return new A;
}
private:
A()
{}
};
class B : public A
{
//原理为父类的私有成员在派生类中不可见。
};
int main()
{
A* a = A::CreatObj();
//B b;报错
return 0;
}
class A
{
public:
void Destory()
{
A::~A();
}
private:
~A()
{}
};
class B : public A
{
public:
//原理为父类的私有成员在派生类中不可见。
};
int main()
{
A* ptra = new A;
ptra->Destory();
operator delete (ptra);
//B b;
//报错
return 0;
}
为啥必须是父类的指针和引用呢?
从概念上理解,是人具有多种形态,而不是老师具有多种形态,因为人是比较抽象的,赋予了某种身份才具象化。
再换一个例子,是植物具有多种形态,还是玫瑰花具有多种形态?其原因还是一样的,植物没有赋予固定的形态,是比较抽象的,而给植物赋予玫瑰花的身份是具象的。
为了改变父类的指向从而调用父类的虚函数
。补充:
子类也可以有多种形态,这是把子类当做父类来看的,就比如动物里面有猫,而猫分为很多种,比如波斯猫,布偶猫等。
细节: 指定作用域,可破坏多态的条件:
void BuyTicket(Person & per)
{
per.Person::BuyTickets();
}
为啥不能是子类的指针或者引用?
为啥不是是父类对象?(涉及原理之后再讲)
#include
using namespace std;
class A
{
public:
virtual void fun1()
{
cout << "A::fun1()" << endl;
}
int _a = 0;
};
class B
{
public:
virtual void fun1()
{
cout << "B::fun1()" << endl;
}
};
class C : public A, public B
{
public:
virtual void fun1()
{
cout << "C::fun1()" << endl;
}
};
int main()
{
C c;
//普通调用
c.fun1();
//多态调用
B* b = &c;
b->fun1();
//看汇编代码之后,想一下为什么,不构成多态也去虚函数里面找,再进行调用。
C* ptrc = &c;
return 0;
}
class A
{
public:
virtual void fun1() = 0;
};
纯虚函数所在类是不能实例化的。
补充:空函数——实现啥也没有
如:void func() {}
class B : public A
{}
这里B继承了A,纯虚函数也被继承了,因此B也无法进行实例化。
像这样,有纯虚函数的类,就是抽象类。
那如何使用呢?很简单子类将纯虚函数进行重写,不就能使用了么。
class B : public A
{
public:
virtual void fun1()
{
cout << "B::fun1()" << endl;
}
};
B进行重写后,就不含纯虚函数,也就不是抽象类了。
如果你执意要调用,也是可以的,不过会报错:
int main()
{
B b;
A* a = &b;
a->fun1();
return 0;
}
引入:
#include
using namespace std;
class A
{
public:
virtual void fun1()
{}
private:
char _a = 1;
};
int main()
{
A a;
cout << sizeof(A) << endl;
return 0;
}
查看监视窗口:
之后证明
。那这张存放虚函数的表,我们称之为虚函数表,简称虚表。
那虚表是用来干啥呢?当然是肯定是用来实现多态的了,再说细点就是为了实现重写。
既然是这样,那我们对以下代码进行调试。
#include
using namespace std;
class A
{
public:
virtual void fun1()
{}
virtual void fun2()
{}
private:
char _a = 1;
};
class B : public A
{
public:
virtual void fun1()
{}
};
int main()
{
A a;
B b;
return 0;
}
至于如何验证第三个位置是fun3,给出如下代码。
#include
using namespace std;
class A
{
public:
virtual void fun1()
{
cout << " A :: fun1()" << endl;
}
virtual void fun2()
{
cout << " A :: fun2()" << endl;
}
private:
int _a = 1;
};
class B : public A
{
public:
virtual void fun1()
{
cout << " B :: fun1()" << endl;
}
virtual void fun3()
{
cout << " B :: fun3()" << endl;
}
int _b = 1;
};
typedef void (*FUN_PTR)();
void Print(FUN_PTR * arr)
{
for (size_t i = 0; arr[i] != nullptr; i++)
{
printf("%p->", arr[i]);
FUN_PTR ptr = arr[i];
ptr();
}
}
int main()
{
B b;
int ptr = *(int*)(&b);
Print((FUN_PTR*)ptr);
return 0;
}
总结一下:
下面我们继续讨论遗留下来的问题:
class A
{
public:
virtual void fun1()
{}
};
class B : public A
{
public:
virtual void fun1()
{}
};
int main()
{
A a;
B b;
a = b;
//拷贝不拷贝虚表?
return 0;
}
我们只看赋值之后的监视窗口:
那虚表存在哪呢?给出如下代码进行验证。
#include
class A
{
public:
virtual void fun1()
{}
};
int main()
{
A a;
//虚表的地址
void** ptr = (void**)(*(int*)&a);
//栈区的地址
int _a = 0;
//静态区的地址
static int b = 0;
//常量区地址
const char* str = "abc";
//堆的地址
int* ptr1 = new int;
printf("虚表地址->%p\n", ptr);
printf("栈区地址->%p\n", &_a);
printf("堆区地址->%p\n", ptr1);
printf("静态区地址->%p\n", &b);
printf("常量区地址->%p\n", str);
return 0;
}
那为什么呢?
利用之前得到的结论,虚表指针是在构造函数调用时才被初始化的! 如果构造函数是虚函数,那虚表指针都没有初始化,如何调用虚函数呢?典型的先有虚函数指针 还是 先调用构造函数的问题。因此语法上禁掉了。
这是为啥呢?
举一段错误代码,一看便知:
#include
using namespace std;
class A
{
public:
~A()
{
cout << "A::~A()" << endl;
}
int _a = 0;
};
class B : public A
{
public:
~B()
{
cout << "B::~B()" << endl;
}
int _b = 1;
};
int main()
{
B* b = new B;
A* a = b;
delete a;
return 0;
}
如何解决?
举出如下代码进行实验:
#include
using namespace std;
class A
{
public:
virtual void fun1()
{
cout << "A::fun1()" << endl;
}
virtual void fun2()
{
cout << "A::fun2()" << endl;
}
int _a = 0;
};
class B
{
public:
virtual void fun1()
{
cout << "B::fun1()" << endl;
}
virtual void fun2()
{
cout << "B::fun2()" << endl;
}
int _b = 0;
};
class C : public A ,public B
{
public:
virtual void fun1()
{
cout << "C::fun1()" << endl;
}
virtual void fun3()
{
cout << "C::fun3()" << endl;
}
int _c = 0;
};
typedef void (*FUN_PTR)();
void Print(FUN_PTR * arr)
{
for (size_t i = 0; arr[i] != nullptr; i++)
{
printf("%p->", arr[i]);
FUN_PTR ptr = arr[i];
ptr();
}
cout << endl;
}
int main()
{
C c;
void** vftptr1 = (void **)(*(int*)(&c));
B* ptr = &c;
void** vftptr2 = (void **)(*(int*)(ptr));
Print((FUN_PTR*)vftptr1);
Print((FUN_PTR*)vftptr2);
return 0;
}
而是将虚函数放在第一个父类的虚表中
; 多继承父类不共享虚表,而是各用个的。
除此之外,这里还会衍生出一个问题:运行结果C::fun1()的地址竟然不同,这是为什么呢?
将上述代码进行简化:
#include
using namespace std;
class A
{
public:
virtual void fun1()
{
cout << "A::fun1()" << endl;
}
int _a = 0;
};
class B
{
public:
virtual void fun1()
{
cout << "B::fun1()" << endl;
}
};
class C : public A, public B
{
public:
virtual void fun1()
{
cout << "C::fun1()" << endl;
}
};
int main()
{
C c;
B* b = &c;
A* a = &c;
a->fun1();
b->fun1();
return 0;
}
为啥要这样这样做呢?
同样给出一段代码实验:
#include
using namespace std;
class A
{
public:
virtual void fun1()
{
cout << "A::fun1()" << endl;
}
int _a = 0;
};
class B : public A
{
public:
virtual void fun1()
{
cout << "B::fun1()" << endl;
}
virtual void fun2()
{
cout << "B::fun2()" << endl;
}
int _b = 0;
};
class C : public A
{
public:
virtual void fun1()
{
cout << "C::fun1()" << endl;
}
virtual void fun2()
{
cout << "C::fun2()" << endl;
}
int _c = 0;
};
class D : public B , public C
{
public:
virtual void fun2()
{
cout << "D::fun2()" << endl;
}
virtual void fun3()
{
cout << "D::fun3()" << endl;
}
};
typedef void (*FUN_PTR)();
void Print(FUN_PTR * arr)
{
for (size_t i = 0; arr[i] != nullptr; i++)
{
printf("%p->", arr[i]);
FUN_PTR ptr = arr[i];
ptr();
}
cout << endl;
}
int main()
{
D d;
void** vftptr1 = (void**)(*((int*)(&d)));
C* b = &d;
void** vftptr2 = (void**)(*((int*)(b)));
Print((FUN_PTR*)vftptr1);
Print((FUN_PTR*)vftptr2);
return 0;
}
初始化对象后的D类的监视窗口:
运行结果:
据此画出D类对象的对象模型:
贴出一段代码进行实验:
#include
using namespace std;
class A
{
public:
virtual void fun1()
{
cout << "A::fun1()" << endl;
}
int _a = 0;
};
class B : virtual public A
{
public:
virtual void fun2()
{
cout << "B::fun2()" << endl;
}
int _b = 0;
};
class C : virtual public A
{
public:
virtual void fun2()
{
cout << "C::fun2()" << endl;
}
int _c = 0;
};
class D : public B , public C
{
public:
virtual void fun3()
{
cout << "D::fun3()" << endl;
}
virtual void fun4()
{
cout << "D::fun4()" << endl;
}
int _d = 0;
};
typedef void (*FUN_PTR)();
void Print(FUN_PTR * arr)
{
for (size_t i = 0; arr[i] != nullptr; i++)
{
printf("%p->", arr[i]);
FUN_PTR ptr = arr[i];
ptr();
}
cout << endl;
}
int main()
{
D d;
void** vftptr1 = (void**)(*((int*)(&d)));
C* b = &d;
void** vftptr2 = (void**)(*((int*)(b)));
A* a = &d;
void** vftptr3 = (void**)(*((int*)(a)));
Print((FUN_PTR*)vftptr1);
Print((FUN_PTR*)vftptr2);
Print((FUN_PTR*)vftptr3);
return 0;
}
ff ff ff fc
,转换成int
也就是 - 4
,这是虚表指针的地址相对类的this指针偏移量(this指针 - 虚表指针的地址)。
第二个位置存的是虚表指针的地址相对于A的偏移量
。第三个位置存的是0,个人理解:表示终止位置
。此外,我们还需注意避免不同子类重写基类的问题:
#include
using namespace std;
class A
{
public:
virtual void fun1()
{}
};
class B : virtual public A
{
public:
virtual void fun1()
{}
};
class C : virtual public A
{
public:
virtual void fun1()
{}
};
class D : public B, public C
{
public:
virtual void fun2()
{}
};
代码如下:
class A
{
public:
virtual void fun1()
{}
int _a = 0;
};
class B : virtual public A
{
public:
virtual void fun1()
{}
int _b = 0;
};
class C : virtual public A
{
public:
virtual void fun1()
{}
int _c = 0;
};
class D : public B, public C
{
public:
virtual void fun1()
{}
virtual void fun2()
{}
int _d;
};
补充知识:
1.内联函数,可以说不是函数,是一段代码。
2.类里面的成员函数,默认inline修饰。
3. inline修饰函数,不一定是内联函数,取决于函数的实现是否复杂,最终还是要编译器决定的。但内联函数一定是被inline修饰的!
虚函数是为了实现多态的
,而多态的条件是父类的指针或者引用,其本质上都是传了子类的this指针,但static修饰的函数是没有this指针的
,无法实现多态,因此不能用static修饰虚函数。 今天的分享就到这里了,如果觉得文章不错,点个赞鼓励一下吧!我们下篇文章再见
!