参考:
1,C++ Primer Plus(第六版)中文版
2,http://c.biancheng.net (C语言中文网)
3,https://blog.csdn.net/czg13548930186/article/details/76076642
4,https://blog.csdn.net/chengonghao/article/details/51062291
5,https://blog.twofei.com/496/
C++中多态:
若继承关系为Public时,基类的成员函数,在整个继承链中,可属于每个派生类;只不过成员函数中的this参数,指向不同的基类对象或者派生类对象。
记一局题外话:在需要形成继承关系时,需要分析是否满足is-a关系,如果只是has-a关系的话,利用组合方式:比如将需要的数据以成员变量形成呈现
类其实也是一种数据类型,也可以发生数据类型转换,不过这种转换只有在基类和派生类之间才有意义,并且只能将派生类赋值给基类,包括将派生类对象赋值给基类对象、将派生类指针赋值给基类指针、将派生类引用赋值给基类引用,这在 C++ 中称为向上转型(Upcasting)。相应地,将基类赋值给派生类称为向下转型(Downcasting)。
c++向上转换是安全的,隐式的;向下则需要不安全的,是强式的。
当我们想把派生类转换成基类,我们只需要丢掉一些基类没有数据,即可。
但是,我们想把基类转换成派生类形成一个完整的数据,派生类对象内存中很多信息都无从填充,转换不了一个完成的派生类对象。但是,向下转换在语法上是可行的操作,需要程序员进行干预执行。
1,将派生类对象赋值给基类对象
#include <iostream>
using namespace std;
class A{
public:
void display()
{
cout<<"this is A :"<<m_a<<endl;
}
public:
int m_a;
};
class B: public A
{
public:
void display()//覆盖基类的同名成员函数,则不会用到基类的成员函数
{
cout<<"this is B :"<<m_a<<endl;
}
};
int main(){
A a;
a.m_a = 1;
a.display();
B b;
b.m_a = 2;
b.display();
//向上转换
a = b;//此处,对象b的内存中的数据copy给对象a的内存里
a.display();
//此处调用a对象类型的所属函数,并将a对象地址赋值给this指针
return 0;
}
2,将派生类指针赋值给基类指针
#include <iostream>
using namespace std;
class A{
public:
void display()
{
cout<<"this is A :"<<m_a<<endl;
}
public:
int m_a;
};
class B: public A
{
public:
void display()//覆盖基类的同名成员函数,则不会用到基类的成员函数
{
cout<<"this is B :"<<m_a<<endl;
}
};
int main(){
A* a = new A();//动态分配堆上的内存
a->m_a = 1;
B* b = new B();
b->m_a = 2;
//
a = b;
cout<<a->m_a<<endl;
a->display();
//此处调用指针a类型的所属函数,并将指针a赋值给this指针。
return;
}
题外话:下面演示一下比较有意思的内存偏移现象
#include <iostream>
using namespace std;
class A{
public:
int m_a;
void display()
{
cout<<"this is A :"<<m_a<<endl;
}
};
class C{
public:
int m_c;
void display()
{
cout<<"this is C :"<<m_c<<endl;
}
};
class B: public A, public C
{
public:
void display()
{
cout<<"this is B :"<<m_a<<endl;
}
};
int main(){
C* c = new C();
B* b = new B();
cout<<c<<endl;
cout<<b<<endl;
c = b;
cout<<c<<endl;
cout<<b<<endl;
return 0;
}
当我们把指针变量b的值(即b对象的地址)赋值给了指针变量c后,两个值应该是一样的。但是结果却不是一样。
在派生类指针赋值给基类指针时,c++编译器会自动计算地址偏移(这个程序的偏移量则是因为b先继承了A的m_a的4个字节),并减去!使其符合基类c的内存布局。
(引用的赋值先跳过)
3,主要看看虚成员函数以及虚函数(表)实现机制
3.1 虚函数 演示
#include <iostream>
using namespace std;
class A{
public:
int m_a;
virtual void display()
{
cout<<"this is A"<<endl;
}
};
class B: public A
{
public:
void display()
{
cout<<"this is B"<<endl;
}
};
class C{
public:
int m_c;
void display()
{
cout<<"this is c"<<endl;
}
};
int main(){
A a_test;
B b_test;
a_test = b_test;
a_test.display();//这里还是A类所属的函数
A* a = new A();
a->display();
a = new B();
a->display();
cout<<sizeof(A)<<endl;
cout<<sizeof(B)<<endl;
cout<<sizeof(C)<<endl;
return 0;
}
这里可以看出构成多态的条件:
1,必须存在继承关系;
2,继承关系中必须有同名的虚函数,并且它们是遮蔽(覆盖)关系。
3,存在基类的指针,通过该指针调用虚函数。
3.2 虚函数表 演示
首先,函数不存在对象内存中,然后发现含有虚函数的对象中的内存数据中除了成员变量的4个字节,含有其他的四个字节,这就是虚函数表地址(int型)
#include <iostream>
using namespace std;
class A{
public:
int m_a;
virtual void display()
{
cout<<"this is A"<<endl;
}
};
class B: public A
{
virtual void display()
{
cout<<"this is B"<<endl;
}
};
class C: public A
{
};
int main(){
A a;
a.m_a = 1;
B b;
b.m_a = 2;
C c;
c.m_a = 3;
return 0;
}
虚函数表则是一个数组,元素则是虚函数地址,而在对象中只存放着_vfptr这个变量,其值是第一个数组元素的地址。则可通过这个地址值索引到各个虚函数。可以认为_vfptr是数组名(首元素的地址)
对象b中_vfptr数组中的虚函数地址则是新的地址,而对象c中_vfptr数组中则是基类虚函数的地址
关于更多的虚函数的详情,大家可以直接慢慢的看完这篇博客:https://blog.twofei.com/496/。
这篇博客详细的分析了虚函数表的内存布局,以及虚函数的使用方法及总结要领。如果想了解虚函数表的内存布局可以慢慢研读。这边博客的作者还很热心的帮助博主解疑答惑。
在这里这做下虚函数的总结:(不分析其运行机制,总结要领),想研究其内部机制的同学则可花时间慢慢研究:
1,若调用的函数为虚函数,则在指针指向的对象实例中的虚函数表中查询对应的虚函数进行调用
2,若调用的函数不为虚函数,则调用指针类型的所属函数进行调用。
4,c++中抽象类实现多态
抽象类一般作为基类,在派生过程中去实现成员函数(纯虚函数),抽象类是无法进行对象实例化的。派生类实现了纯虚函数才可以实例化
#include <iostream>
using namespace std;
class Person{
public:
int _age;
Person(int age)
{
_age = age;
}
virtual void displayName() = 0;//纯虚函数
virtual void displayAge() = 0;
};
class Jay:public Person{//继承于Person但未完全实现其方法函数,于是不能实例化
public:
Jay(int age):Person(age)
{
}
void displayAge()
{
cout<<_age<<endl;
}
};
class Tim:public Person{
public:
Tim(int age):Person(age)
{
}
void displayAge()
{
cout<<_age<<endl;
}
void displayName()
{
cout<<"Tim"<<endl;
}
};
class Hebe:public Person{
public:
Hebe(int age):Person(age)
{
}
void displayAge()
{
cout<<_age<<endl;
}
void displayName()
{
cout<<"Hebe"<<endl;
}
};
int main()
{
Person* p = new Tim(18);
p->displayAge();
p->displayName();
p = new Hebe(19);
p->displayAge();
p->displayName();
return 0;
}
**
以上关于c++多态相关demo和演示,下面总结下C#相关多态的实现。
**
1,override&hide
1.1 demo1
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace multi
{
internal class Program
{
private static void Main(string[] args)
{
Base basetest= new Base();
basetest.show();
basetest = new Deriveover();
basetest.show();
basetest = new Derivehide();
basetest.show();
}
}
class Base
{
public virtual void show()
{
Console.WriteLine("this is base");
}
}
class Deriveover : Base
{
public override void show()
{
Console.WriteLine("this is Deriveover");
}
}
class Derivehide : Base
{
public void show()
{
Console.WriteLine("this is Derivehide");
}
}
}
当基类和派生类形成重写状态,则实现多态效果;
当派生类隐藏覆盖原先的函数(注意不是重载!)时,函数的调用则选择基类函数所属方法 basetest = new Derivehide();basetest.show();
1.2 demo2
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace multi
{
internal class Program
{
private static void Main(string[] args)
{
Base basetest= new Base();
basetest.show();
basetest = new Deriveover();
basetest.show();
basetest = new Derivehide();
basetest.show();
}
}
class Base
{
public virtual void show()
{
Console.WriteLine("this is base");
}
}
class Deriveover : Base
{
public override void show()
{
Console.WriteLine("this is Deriveover");
}
}
class Derivehide : Deriveover
{
public void show()
{
Console.WriteLine("this is Derivehide");
}
}
}
demo2这种情况比较有意思,这种情况在c++时不会出现的。c++在基类存在虚函数时,派生类时不会出现隐藏效果的。
2,c#抽象基类
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace multi
{
internal class Program
{
private static void Main(string[] args)
{
Base basetest = new Derive1();
basetest.display();
basetest = new Derive2();
basetest.display();
}
}
abstract class Base
{
public abstract void display();
}
class Derive1 : Base
{
public override void display()
{
Console.WriteLine("this is Derive1");
}
}
class Derive2 : Base
{
public override void display()
{
Console.WriteLine("this is Derive2");
}
}
}
2,c#接口
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ConsoleApplication2
{
class Program
{
static void Main(string[] args)
{
Worker w1 = new jay();
w1.showname();
w1.showange();
w1 = new tim();
w1.showname();
w1.showange();
}
}
interface Worker
{
void showname();
void showange();
}
internal class jay:Worker
{
public void showname()
{
Console.WriteLine("jay");
}
public void showange()
{
Console.WriteLine("22");
}
}
internal class tim : Worker
{
public void showname()
{
Console.WriteLine("tim");
}
public void showange()
{
Console.WriteLine("21");
}
}
}