C++的多态性
今天我们,来将将C++的三大特性之一的—————— 多态性》》》为什么会有这个呢????、
因为,我们编写代码时,有的时候,当对象不同时,就需要调用不同的函数。。。在这个时候就需要使用的多态了。
再说类的多态性之前我先来说说对象这个概念。。。
对象的类型
对象的概念可以分为两种类型
所谓的静态类型,就是一般的类型 。。。
而动态类型,举个例子来说就像是赋值兼容规则里说的
一个基类对象可以用 派生类对象来赋值;在这里的这个对象就是动态类型的。
因为只有在运行时调用对象时 才知道调用的实际上是一个派生类的对象。。。
多态
多态概念:多态这一特性,用通俗的话来说就是 一个事物在不同的时候表现为多种状态。。。。
但是在C++中多态有这更为广泛的含义
静态多态
所谓静态多态,指的是编译器在编译期间完成的,编译器根据函数实参的类型(可能会进行隐式类型转换),可推
断出要调用那个函数,如果有对应的函数就调用该函数,否则出现编译错误。
断出要调用那个函数,如果有对应的函数就调用该函数,否则出现编译错误。
函数重载,编译器在编译时,就已经判断出参数的类型 ,根据类型的不同来调用不同的函数。。。。
模板 ,也如函数重载一般,根据模板参数的不同调用函数。。。。
函数重载例子::::
关于模板的示例可以看看我之前写的C++函数模板
动态多态
动态绑定指的是在程序执行期间(非编译期)判断所引用对象的实际类型,根据其实际类型调用相应的方法。
在这里我们动态多态讲的主要是 说 虚函数
虚函数
使用virtual关键字修饰类的成员函数时,指明该函数为虚函数,派生类需要重新实现,编译器将实
现动态绑定。
现动态绑定。
下面来举个虚函数的示例:
class B
{
public:
virtual void Base()
{
cout << "void B::Base()" << endl;
}
};
class D:public B
{
public:
void Base()
{
cout << "void D::Base()" << endl;
}
};
int main()
{
B b;
D d;
B* b1 = &d;
b.Base();
b1->Base();
d.Base();
return 0;
}
代码执行结果:
从输出的结果上我们看出
b.Base();//调用的是基类的函数
b1->Base();//调用的是派生类的函数
d.Base();//调用的是派生类的函数
函数调用时b1调用的是对象d内存,所以调用的是派生类的函数;;;;
【动态绑定条件】
1、必须是虚函数
2、通过基类类型的引用或者指针调用虚函数
1、必须是虚函数
2、通过基类类型的引用或者指针调用虚函数
下面说说在多态中 我们可能会遇到几种函数名相同的情况:
1、函数重载
限定条件为:
1)在同一作用域内;(一定要记住这一点)
2)函数名相同;
3)参数列表相同(参数的顺序 ,类型 ,长度)
2、同名隐藏
限定条件为:
1)一个在基类中,一个在派生类里;
2)函数名相同;
3、函数重写
限定条件为:
1)一个在基类中,一个在派生类里
2)
都是虚函数;
3)函数原型相同(函数名,参数,返回值相同)
4、协变
限定条件为:
1)一个在基类中,一个在派生类里
2)都是虚函数
3)函数名相同,参数列表相同
4)函数返回值分别为基类的指针(引用), 派生类类的指针(引用)
下面有一段代码我们来看看
class B
{
public:
virtual void Test1(int _test){ cout << "B::Test1()" << endl; }
void Test2(int _test){ cout << "B::Test2()" << endl; }
void Test3(int _test){ cout << "B::Test3()" << endl; }
virtual B* Test4(int _test1, int _test2)
{
cout << "B::Test4()" << endl;
return this;
}
};
class D :public B
{
public:
virtual void Test1(int _test){ cout << "D::Test1()" << endl; }
virtual void Test2(int _test){ cout << "D::Test2()" << endl; }
void Test3(int _test){ cout << "D::Test3()" << endl; }
virtual D* Test4(int _test1, int _test2)
{
cout << "D::Test4()" << endl;
return this;
}
};
上述代码中
Test1()属于函数重写
Test2()属于虚函数
Test3()属于同名隐藏
Test4()属于协变
纯虚函数
所谓纯虚函数
在成员函数的形参后面写上=0,则成员函数为纯虚函数。
包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象。纯虚函数在派生类中重新定义以后,派生类才能实例化出对象。
用代码来直观说明一下吧
class Base
{
public:
virtual void Test() = 0;//纯虚函数
int _b;
};
class Derived :public Base
{};
在这段代码中,Base就是一个抽象类,而Test函数就是一个纯虚函数
所以,不能使用Base类 来定义一个对象 。
那些函数可以定义为虚函数呢?????
总结一句话就是 只有类的非静态成员函数才能定义为虚函数
但是类的构造函数不能定义为虚函数,因为构造函数是在类对象创建时调用的,
而虚函数是通过基类的指针和派生类的指针和引用调用的,
如果将构造函数定义为虚函数,无法正常实现虚函数与构造函数的功能。
一般情况下,要将基类析构函数定义为虚函数,为什么呢???
class Base
{
public:
Base();
virtual ~Base();
};
class Derived :public Base
{
public:
Derived();
~Derived();
};
当声明一个基类的引用 ,用派生类赋值
int mian()
{
Derived d;
Base &rb = d;
return 0;
}
当函数结束后 ,rb类析构时,应该调用的派生类的析构函数,所以应该将基类的析构函数定义为虚函数,
来根据参数的不同调用不同
的析构函数;
至于为什么只有非静态的成员函数才能定义 成虚函数??
我还没搞清楚》》》之后再研究研究
下面是对上面所说的虚函数编写时所要注意的地方(对上面的总结)
1、派生类重写基类的虚函数实现多态,要求函数名、参数列表、返回值完全相同。(协变除外)
2、基类中定义了虚函数,在派生类中该函数始终保持虚函数的特性。(就像是上面说的函数Test2)
3、关于哪些函数可以定义为虚函数,只有类的非静态成员函数才能定义为虚函数,静态成员函数不能定义为虚函数。(构造函数除外 )
4、构造函数不能定义为虚函数,虽然可以将operator=定义为虚函数,但最好不要这么做,使用时容
易混淆
易混淆
5、如果在类外定义虚函数,只能在声明函数时加virtual关键字,定义时不用加(声明和定义只在一个地方加上virtual)
6、不要在构造函数和析构函数中调用虚函数,在构造函数和析构函数中,对象是不完整的,可能会
出现未定义的行为。(因为虚函数是通过虚表来调用的,构造与析构时,对象不完整)。
出现未定义的行为。(因为虚函数是通过虚表来调用的,构造与析构时,对象不完整)。
7、最好将基类的析构函数声明为虚函数。(析构函数比较特殊,因为派生类的析构函数跟基类的析构
函数名称不一样,但是构成覆盖,这里编译器做了特殊处理)
函数名称不一样,但是构成覆盖,这里编译器做了特殊处理)