1、什么是多态?
通俗来说就是不同的对象接收到相同的消息时,产生不同的行为。在C++程序设计中,多态性是指用一个名字定义不同的函数,这些函数执行不同但又类似的操作,这样就可以用同一个函数名调用不同函数的内容。其实我们之前学习的运算符就使用了多态,例如“+”,它可以实现int型,float型,double型各自的加法操作,但是它在加法的过程中却是不同的,是有不同的函数去实现的。
2、多态分为哪些?
多态分为静多态和动多态。
详解:一个源程序经过编译,连接成为可执行文件的过程是把可执行文件代码联编(也叫绑定)在一起的过程。其中在运行之前就完成的称为静态联编,又叫前期联编;而在运行时才完成的联编称为动态联编,也称后期联编。静态联编是指系统在编译时就决定如何实现某一动作。静态联编要求程序在编译时就知道调用函数的全部信息。因此,这种联编类型的函数调用速度很快。效率高是静态联编的主要优点。动态联编是指系统在运行时动态实现某一动作,采用这种方式联编,一直要到程序运行时才能确定调用哪个函数。动态联编的主要优点是:提供了更好的灵活性、问题抽象性和程序易维护性。
静态联编支持的多态性称为编译时多态性,也称静态多态性。在C++中,编译时多态性是通过函数重载(包括运算符重载)和模板实现的。而动态联编所支持的多态性称为运行时多态性,也称动态多态性。
3、使用多态有什么作用?动多态产生的条件是什么?
使用多态可以消除类型之间的耦合关系,通过分离做什么和怎么做,从另一角度将接口和实现分离开来。(就比如同一个自主购票机,相同的接口,它却可以有成人票、儿童票两种类型);
条件:
- 指针或引用调用虚函数 + 对象必须完整
- 完整对象:构造函数执行完毕,析构函数还没开始
下面通过代码来详细解释多态产生过程:
这个还是没加虚特性的:
class Person
{
public:
Person(int x, int y)
{
a = x;
b = y;
}
void show()
{
cout << "调用基类show()" << endl;
cout << a << b << endl;
}
private:
int a, b;
};
class my_Person :public Person
{
public:
my_Person(int x, int y, int z) :Person(x, y)
{
c = z;
}
void show()
{
cout << "调用子类show()" << endl;
}
private:
int c;
};
int main()
{
Person mb(23, 34),*mp;
my_Person mc(12, 23, 56);
mp = &mb;
mp->show();
mp = &mc;
mp->show();
return 0;
}
该代码的执行结果:
问题1:为什么这里调用的都是基类的show函数?
答:C++中规定,基类的对象指针可以指向它的公有派生的对象,但是当其指向公有派生类是,它只能访问派生类中从基类继承来的成员,而不能访问公有派生类中定义的成员。
问题2:既然都是基类的show函数,为什么打印的结果不一样?
答:当程序执行到mp = &mc;这一步时,基类指针指向子类对象,通过子类提供的参数给基类的对象赋值。
加虚特性之后
class Person
{
public:
Person(int x, int y)
{
a = x;
b = y;
}
virtual void show()
{
cout << "调用基类show()" << endl;
cout << a <<" "<< b << endl;
}
private:
int a, b;
};
class my_Person :public Person
{
public:
my_Person(int x, int y, int z) :Person(x, y)
{
c = z;
}
virtual void show()//这里也可不写virtual,因为同名函数会直接继承为虚函数
{
cout << "调用子类show()" << endl;
cout << c << endl;
}
private:
int c;
};
int main()
{
Person mb(23, 34),*mp;
my_Person mc(12, 23, 56);
mp = &mb;
mp->show();
mp = &mc;
mp->show();
return 0;
}
要想详细了解调用过程,在这里需要引入一个新概念:虚函数表(vftable)
虚函数表是在编译期产生的,用来存储虚函数指针(vfptr)
如图:
图解:
再联合上边的监视图看这幅监视图:
在监视图可以清晰的看到,子类把从父类继承过来的虚函数表进行覆盖
我们再看一下打印时的监视图:
由图可以看出,当调用不同的show()函数时,mp就指向不同的对象,对象里的vfptr再指向虚函数表,在虚函数表中找出对应的虚函数。
覆盖:子类中的成员方法会覆盖父类中相同(同返回值,同函数名,同参数列表)的虚函数
虚函数具有传递性:父类中如果有虚函数,那么子类中对应的相同的函数会被传递为虚函数,相同的指的是同返回值,同函数名,同参数列表
我们再来看一个代码:
class base
{
public:
base()
{
cout << "base()" << endl;
}
~base()
{
cout << "~base()" << endl;
}
void fun1(int a)
{
cout << "base::void fun1()" << endl;
}
protected:
private:
int _a;
};
class Derive :public base
{
public:
Derive()
{
cout << "Derive()" << endl;
}
~Derive()
{
cout << "~Derive()" << endl;
}
void fun1()
{
cout << "Derive::void fun1()"<fun1(10);
delete pb;
}
我们发现同时调用了基类的构造函数和子类的构造函数,但是却只调用了父类析构函数,却没有调用子类的析构函数,那么这个问题如何解决呢?
这个问题要解决很简单,只需要给父类析构函数前边加virtual:
class base
{
public:
base()
{
cout << "base()" << endl;
}
virtual ~base()
{
cout << "~base()" << endl;
}
void fun1(int a)
{
cout << "base::void fun1()" << endl;
}
protected:
private:
int _a;
};
再看执行结果:
这里需要注意一个问题:我们可能会疑惑析构函数的函数名并不相同,为什么还会覆盖?
C++中规定,析构函数就是它的函数名字。
对上述知识点再作一简单拓展:
-
动多态的过程
使用指针或者引用调用虚函数
在对象中找到vfptr
找到vftable 在表中找到对应的函数 -
vftable什么时候产生?在哪里存储?
编译期产生
放在rodata(只读数据段) -
构造函数能不能写成虚函数
不能
构造函数无法通过指针或者引用调用,所以写成虚函数没有意义
vfptr是在构造时候写入对象,而动态调用虚函数需要用到vfptr -
静态函数能不能写成虚函数
不能,静态函数不依赖于对象,无法产生动多态 -
析构函数能不能写成虚函数
能 -
虚函数能不能被处理成内联
不能,虚函数需要将函数指针放到vftable,而内联函数在编译期展开
在release版本没有地址。 -
什么情况下析构函数必须写成虚函数
当存在父类指针指向堆上的子类对象的时候,
就必须把父类的析构函数写成虚函数 -
父类指针能不能指向子类对象?子类指针能不能指向父类对象?
能,不能 -
父子类/组合类 的构造顺序
类的编译顺序
先编译类名
再编译成员名
最后编译成员函数体 -
什么是RTTI,RTTI在什么时候产生?RTTI信息存储在哪里?
运行时期的类型信息,是一个指向类型信息的指针。
编译期产生,RTTI指针放在vftable里面,类型信息放在rodata段 -
父类指针如何转化成子类指针?转化有什么条件?
Derive* pd = dynamic_cast(p); dynamic_cast 父类指针转为子类指针专用的类型强转
要求:1.必须有RTTI 2.父类指针指向的对象中的RTTI确实是子类的
此代码后续再进行解释:
class base
{
public:
base()
{
//this->fun();
}
virtual ~base()
{
cout << "~base()" << endl;
}
void fun()
{
cout << "base::fun()" << endl;
}
void operator delete(void* p)
{
cout << p << endl;
free(p);
}
};
class Derive : public base
{
public:
Derive()
{
//this->fun();
}
virtual ~Derive()
{
cout << "~Derive()" << endl;
}
virtual void fun()
{
cout << "Derive::fun()" << endl;
}
void* operator new(size_t size)
{
void* p = malloc(size);
cout << p << endl;
return p;
}
};
int main()
{
Derive d;
base* p = new base();//new Derive();
//delete (base*)((char*)p - 4);
cout << typeid(p).name() << endl;
cout << typeid(*p).name() << endl;
Derive* pd = dynamic_cast(p);
cout << pd << endl;
pd->fun();
delete p;
return 0;
}



