C++虚函数和多态性

一 多态性

多态的实现:

函数重载

运算符重载

虚函数

从面向对象技术来看,多态性可分为4类:

1.重载多态:函数重载 ,运算符重载

2.强制多态:将一个变量类型加以变化,以符合一个函数 或操作的要求。例如:强制类型转换

3.包含多态:同样的操作可用于一个类型及其子类型。包 含多态一般需要进行运行时类型检查,主要通过虚函数 来实现

4.参数多态:采用参数化模板,使得一个结构可以适用于 多种数据类型。函数模板类模板

从系统实现的角度来说

1.静态联编,编译时多态,静态多态性

  函数重载和运算符重载

  的优点是函数调用速度快、效率较高,缺点 是编程不够灵活 

2.动态联编,运行时多态,动态多态性

  继承和虚函数

  动态联编的优点是提供了更好的编程灵活性、问题 抽象性和程序易维护性

  动态联编的缺点是:与静态联编相比,函数调用速 度慢。

静态联编和多态联编的本质就是静态类型检查和动态类 型检查

对象的静态类型是指声明在程序代码中的类型

Shape *ps; // 静态类型 = Shape*

Shape *pc = new Circle; // 静态类型 = Shape*

Shape *pr = new Rectangle; // 静态类型 = Shape*

对象的动态类型是由它当前所指的对象的类型决定的。 即,对象的动态类型表示它将执行何种行为。

举一个例子引出虚函数

如下代码

// 示例静态联编。函数重载在多态性中的应用
#include <iostream>
using namespace std;
class Student
{ public:
void print()
{
cout<<"A student"<<endl;
}
};
class GStudent:public Student
{ public:
void print()
{
cout<<"A graduate student"<<endl;
}
};
int main()
{
Student s1,*ps;
GStudent s2;
s1.print();
s2.print();
s2.Student::print();
ps=&s1;
ps->print();
ps=&s2;
ps->print();
//希望调用对象s2的输出函数,但调用的却是对象s1的输出函数
cin.ignore();
return 0;
}
/*

A student
A graduate student
A student
A student
A student


*/

可以看的出,就是上一章的基类指针指向派生类对象的问题

基类指针可以指向派生类对象,但 仍然调用的是基类成员函数:但是 由于静态联编的原因,编译器不知 道指针指向的对象已经重写了该成 员函数

希望调用对象s2的输出函数,但调用的却是对象s1的输出函数

问题:如何实现通过一个基 类指针调用派生类重写后的 成员函数?

回答:必须通过动态联编技 术,把派生类中要重写的函 数声明为虚函数

那么就来到了虚函数

二 虚函数

  虚函数的作用:简单讲即实现多态。

  基类定义了虚函数,子类可以重写该函数,当子类重新定义了父类的虚函数后,父类指针根据赋给它的不同的子类指针,
  动态地调用属于子类的该函数,且这样的函数调用是无法在编译器期间确认的,而是在运行期确认,也叫做迟绑定。

  虚函数是动态联编的基础。

  在类的声明中,在函数原型之前写virtual。虚函数必须 存在于类的继承环境中

  virtual 只用来说明类声明中的原型,不能用在函数实现 时。

  具有继承性,基类中声明了虚函数,派生类中无论是否 说明,同原型函数都自动为虚函数。

  调用方式:通过基类指针或引用,执行时会根据指针指 向的对象的类,决定调用哪个函数。

  如果采用对象调用虚函数,则采用的是静态联编方式。

使用对象指针对1中的进行修改的话,使用虚函数的效果如下:

// 范例1:示例动态联编(采用对象指针调用虚函数)。
#include <iostream>
using namespace std;
class Student
{
public:
    virtual void print() //定义虚函数
    {
        cout << "A student" << endl;
    }
};
class GStudent :public Student
{
public:
    virtual void print() //关键字virtual可以省略
    {
        cout << "A graduate student" << endl;
    }
};
int main()
{
    Student s1, *ps;
    GStudent s2;
    s1.print();
    s2.print();
    s2.Student::print();
    ps = &s1;
    ps->print();
    ps = &s2;
    ps->print(); //对象指针调用虚函数,采用动态联编方式
    cin.ignore();
    return 0;
}
/*
A student
A graduate student
A student
A student
A graduate student
*/

  可以看到达到了预期的效果

使用对象引用又如何呢

#include <iostream>
using namespace std;
class Student
{
public:
    virtual void print()
    {
        cout << "A student" << endl;
    }
};
class GStudent :public Student
{
public:
    virtual void print()
    {
        cout << "A graduate student" << endl;
    }
};
void fun(Student &s)
{
    s.print(); //采用对象引用调用虚函数
}
int main()
{
    Student s1;
    GStudent s2;
    fun(s1);
    fun(s2);
    cin.ignore();
    return 0;
}
/*
A student
A graduate student
*/

这里不用虚函数的话,结果就是

A student

A student

  虚函数的定义格式

  在派生类中重新定义虚函数时,必须保证函数的返 回值类型和参数与基类中的声明完全一致。

  如果在派生类中没有重新定义虚函数,则派生类的 对象将使用基类的虚函数代码。

  虚函数必须是类的成员函数,且静态函数、内联函 数不能声明为虚函数

  构造函数不能是虚函数

  析构函数可以是虚函数,且往往被定义为虚函数

  当一个基类中声明了虚函数,则其虚函数特性会在 其直接派生类和间接派生类中一直保持下去

  在派生类中重新定义基类的虚函数是函数重载另一种形式 ,但它不同于一般的函数重载。 当普通的函数重载时,其函数的参数或参数类型必须有所 不同,函数的返回类型也可以不同。 对于虚函数,如果仅仅是返回类型不同,其余均相同,系 统会给错误信息; 若仅仅函数名相同,而参数的个数、类型或顺序不同,系 统将它作为普通的函数重载,这时虚函数的特性将丢失。

  多继承和虚函数

  如果两个基类没有同名函数,那好办,分开看即可

// 例题6.3,多继承中虚函数的定义和应用范例
#include <iostream>
using namespace std;
class Base1 {
public:
    virtual void TestA()
    {
        cout << "Base1 TestA()" << endl;
    }
};
class Base2 {
public:
    virtual void TestB()
    {
        cout << "Base2 TestB()" << endl;
    }
};
class Derived :public Base1, public Base2
{
public:
    void TestA()
    {
        cout << "Derived TestA()" << endl;
    }
    void TestB()
    {
        cout << " Derived TestB()" << endl;
    }
};
int main()
{
    Derived D;
    Base1 *pB1 = &D;
    Base2 *pB2 = &D;
    pB1->TestA();
    pB2->TestB();
    cin.ignore();
    return 0;
}
/*
Derived TestA()
 Derived TestB()
*/

 这里看似简单,其实就是按照这个来就是了,下面的运行顺序按照这个理解。 

  多继承中基类有同名虚函数的情况

  就是两个基类有同名虚函数需要实现,那么怎么写呢?

// 例题6.4,多继承中基类有同名虚函数的情况
#include <iostream>
using namespace std;
class Base1 {
public:
    virtual void Test()
    {
        cout << "Base1 Test()" << endl;
    }
};
class Base2 {
public:
    virtual void Test()
    {
        cout << "Base2 Test()" << endl;
    }
};
class Derived :public Base1, public Base2
{
public:
    virtual void Test()
    {
        cout << "Derived Test ()" << endl;
    }
};
int main()
{
    Derived D;
    Base1 *pB1 = &D;
    Base2 *pB2 = &D;
    pB1->Test();
    pB2->Test();
    cin.ignore();
    return 0;
}
/*
Derived Test ()
Derived Test ()
*/

显然这两条语句都是调用了派 生类重载的Test函数。 为什么?派生类重载的Test函 数覆盖了两个基类的虚函数

我的理解是动态编译的话,实际类型由右边的决定。这里都是右边的Derive D

但是其实我不怎么理解这个问题,

还是按照这个老哥的想法来看:

有两个类A, B, 它们可能是别人实现的(或是别人提供的库中的类), 很复杂且已经在用, 你不能修改他们, 你想写一个类C同时具有这两个类的特性,

因为自己实现它代价实在是太大, 所以你想到用C继承A, B以达到效果, 但是有一个问题, A, B具有一个同名的虚函数, 你在C中怎么重新实现这个

虚函数呢? 先看下面的代码:

#include <string>
#include <iostream>
using namespace std;
 
class ChineseName
{
public:
    virtual string getname()
    {
        return string();
    }
};
 
class EnglishName
{
public:
    virtual string getname()
    {
        return string();
    }
};
 
class Name : public ChineseName, public EnglishName
{
public:
    virtual string getname()
    {
        return string("chinese or english name? I donot know");
    }
};
 
int main()
{
    Name n;
    ChineseName & c = n;
    EnglishName & e = n;
 
    cout << n.getname() << endl;
    cout << c.getname() << endl;
    cout << e.getname() << endl;
 
    return 0;
}

如我自己上面的例子一样,三个输出都一样,都是Name中的函数。

那么如何使得

c.getname()输出中文,
e.getname()输出英文呢,

最根本的问题是我只能在Name中实现一个getname(), 但是它却被要求有两个功能, 分别输出中文名和英文名, 这着实没办法

也就是说,总的来说,这个动态联编,都是调用派生类的东西的

但是一开始如果两个基类都没有同名的虚函数的话,如上上个例子,不同的基类指针可以有不同的结果,但是这两个

ChineseName & c = n;EnglishName & e = n; 

就因为派生类只能写一个,不能得到不同的结果

这个时候就引入一个中间类既可以解决这个问题了。

// 例题6.5,多继承中基类有同名虚函数的解决办法
#include <iostream>
using namespace std;
class Base1 {
public:
    virtual void Test()
    {
        cout << "Base1 Test()" << endl;
    }
};
class Base2 {
public:
    virtual void Test()
    {
        cout << "Base2 Test()" << endl;
    }
};
//定义针对两个基类的中间类
class MiddleBase1 :public Base1 {
protected:
    virtual void Base1_Test() { }
    virtual void Test()
    {
        Base1_Test();
    }
};
class MiddleBase2 :public Base2 {
protected:
    virtual void Base2_Test() { }
    virtual void Test()
    {
        Base2_Test();
    }
};
//定义最后的派生类
class Derived :public MiddleBase1, public MiddleBase2 {
public:
    void Base1_Test()
    {
        cout << "Derived TestA()" << endl;
    }
    void Base2_Test()
    {
        cout << "Derived TestB()" << endl;
    }
};
int main()
{
    Derived D;
    Base1 *pB1 = &D;
    Base2 *pB2 = &D;
    pB1->Test();
    pB2->Test();
    cin.ignore();
    return 0;
}

其实很简单,强行把两个一样的函数通过中间类变得不一样就是了。

调用顺序: pB1->Test MiddleBase1::Test() Derived::Base1_Test()

调用顺序: pB2->Test MiddleBase2::Test() Derived::Base2_Test()

猜你喜欢

转载自www.cnblogs.com/theda/p/11973680.html