走进C++程序世界-----继承和派生(2)

覆盖基类的函数

    覆盖基类函数顾名思义就是在派生类中对基类的函数进行的重新定义。这里将会讲到下面的2个知识点:

1、隐藏基类的方法

2、调用基类的方法(隐式和显示调用基类的方法)

 
/*
 *derive2.cpp
 *Date : 2013-9-24
 *Author: sjin
 *Mail:[email protected]
 */
#include <iostream>
#define N 0


using namespace std;

/*知识点:
 * 1、隐藏基类的方法(就是在派生类中重新实现函数的定义)
 * 2、当基类的方法被覆盖时,仍可通过限定方法名来调用它,如下格式:
 *    baseclass::Method();
 */

/*基类*/
class Manmal{
public:
    //Manmal():itsAge(2),itsWeight(5){ cout << " create Manmal object..."<<endl;}
    //~Manmal(){cout << " destroy Manmal object..."<<endl;}
    
    void Move() const { cout << "Manmal move one step..." << endl;}
    void Move(int distance) const
    {
        cout << "Manmal Move '" << distance << "' steps..." << endl;
    }

private:
    int itsAge;
    int itsWeight;
};

/*派生类*/
class Dog : public Manmal {
public:
    //Dog() {cout << " create Dog object..." << endl;}
    //~Dog() {cout << " destroy Dog object..."<< endl;}

    /*隐藏了基类的Move()方法*/
    void Move() const 
    { 
        cout << "Dog move 5 steps..." << endl;
        cout << "隐式调用基类方法(start):" << endl;
        Manmal::Move();
        Manmal::Move(1000);
        cout << "隐式调用基类方法(end):" << endl;
    }

};

int main()
{
    Manmal bigAnimal;
    Dog Fido;
    cout << "**********Manmal bigAnimal*************" << endl;
    bigAnimal.Move();
    bigAnimal.Move(2);
    cout << "**********Dog Fido*************" << endl;
    Fido.Move();
    //Fido.Move(2);
    /* 上面将会出现下面的错误
     * derive2.cpp:56: error: no matching function for call to ‘Dog::Move(int)’
     * derive2.cpp:43: note: candidates are: void Dog::Move() const
     * 也就是说: 覆盖任一个重载的方法后,该方法的其他所有版本都会被隐藏。
     *            如果补希望他们被隐藏,必须对其进行覆盖。
     */
    
    /*显式调用被隐藏基类的方法*/
    cout << "显式调用基类方法(start):" << endl;
    Fido.Manmal::Move();
    Fido.Manmal::Move(200);
    cout << "显式调用基类方法(end):" << endl;
     

}
输出如下:

**********Manmal bigAnimal*************
Manmal move one step...
Manmal Move '2' steps...
**********Dog Fido*************
Dog move 5 steps...
隐式调用基类方法(start):
Manmal move one step...
Manmal Move '1000' steps...
隐式调用基类方法(end):
显式调用基类方法(start):
Manmal move one step...
Manmal Move '200' steps...
显式调用基类方法(end):


    虚方法 

DOG对象是一个Manmal对象,因此这就意味着Dog对象继承了基类的属性和功能。C++扩展了其多态性,允许将派生类对象赋给指向基类的指针。因此,可以这样编写代码:

Manmal * pManmal = new Dog;

上面的代码创建了一个新的Dog对象,并返回了一个指向该对象的指针,然后将该指针赋给一个Manmal指针。之所以可以这样做,是因为狗也是一种动物。如果希望在调用被Dog覆盖的方法是,将调用正确的函数,虚函数让你能够做到这一点,要创建虚函数,在函数声明前加上关键字Virtual。看下面的代码:

/*
 *virtual.cpp
 *Date : 2013-9-20
 *Author: sjin
 *Mail:[email protected]
 */
#include <iostream>
using namespace std;

/*知识点:
 * 1、如何创建虚函数?
 *    在函数声明前加上virtual 关键字
 * 2、使用虚方法的意义
 *    基类中如果声明了虚方法,说明它的派生类中有可能会覆盖这个方法。
 * 3、如果在基类中将一个成员方法标记为虚方法,还需要在派生类中将它标记为虚方法吗?
 *    不需要,方法被声明为虚方法后,如果在派生类覆盖它,它仍是虚方法。在派生类继续
 *    将其标记为虚方法是个不错的主意,但是没有必要这么作,这样使代码更容易理解。
 * */

class Manmal {
public:
    Manmal():itsAge() { cout << "Manmal constrctor...\n";}
    virtual ~Manmal() { cout << "Manmal destructor...\n";}
    void Move() const { cout << "Manmal move one step\n";}
    virtual void Speak() const { cout << "Manmal speak!\n";}

protected:
    int itsAge;
};

class Dog :public Manmal{
public:
    Dog() { cout << " Dog constrctor...\n";}
    virtual ~Dog() { cout << " Dog destructor..\n";}
    void WagTail() { cout << " Wagging Tail...\n";}
    void Speak() const { cout << "Dog woof!\n";}
    void Move() const { cout << "Dog moves 5 steps ...\n";}
};

int main()
{
    /*创建一个信的dog对象,并返回该对象的指针,然后将该指针付给一个
     * Manmal指针。
     * 这是多态的本质,例如:可创建很多类型的窗口,包括对话框,可滚动
     * 窗库和列表框,然后给每种窗口定义一个虚方法draw().通过创建一个窗口指针,
     * 并将对话框和其他的派生对象赋給指针,就可以通过调用draw(),而不用考虑运行
     * 时指针只想的实际对象类型,程序将调用正确的draw()方法。
     * */

    Manmal *pDog = new Dog;
    pDog->Move();
    pDog->Speak();/*虚方法,被覆盖*/
    /*释放对象*/
    delete pDog;
    return 0;
}

输出:
Manmal constrctor...
 Dog constrctor...
Manmal move one step
Dog woof!
 Dog destructor..
Manmal destructor...

从上面的输出可以看到,Manmal 指针pDog ,调用了Dog类的SPEAK()方法,这里很神奇!但是Move()函数调用的仍然是manmal类的方法。看下面的代码依次调用多个虚方法:

/*
 *virtual2.cpp
 *Date : 2013-9-20
 *Author: sjin
 *Mail:[email protected]
 */
#include <iostream>
using namespace std;

/*依次调用多个虚方法。
 * 通过基类指针访问派生类的方法*/

class Manmal {
public:
//    Manmal():itsAge() { cout << "Manmal constrctor...\n";}
  //  virtual ~Manmal() { cout << "Manmal destructor...\n";}
    virtual void Speak() const { cout << "Manmal speak!\n";}

protected:
    int itsAge;
};
class Dog :public Manmal{
public:
    void Speak() const { cout << "Dog woof!\n";}
};

class cat :public Manmal{
public:
    void Speak() const { cout << "cat Meow!\n";}
};

class Horse :public Manmal{
public:
    void Speak() const { cout << "Horse Winnie!\n";}
};

class Pig :public Manmal{
public:
    void Speak() const { cout << "Dog Qink!\n";}
};

int main()
{
    /*创建一个信的dog对象,并返回该对象的指针,然后将该指针付给一个
     * Manmal指针。
     * 这是多态的本质,例如:可创建很多类型的窗口,包括对话框,可滚动
     * 窗库和列表框,然后给每种窗口定义一个虚方法draw().通过创建一个窗口指针,
     * 并将对话框和其他的派生对象赋給指针,就可以通过调用draw(),而不用考虑运行
     * 时指针只想的实际对象类型,程序将调用正确的draw()方法。
     * */

    Manmal * theArray[5] = {NULL};
    Manmal * ptr;
    int choice ,i;

    for(i = 0; i<5; i++){
        cout << "1) Dog 2) cat 3) Horse 4) Pig 请输入:";
        cin >> choice;
        switch(choice){
            case 1: ptr = new Dog;   break;
            case 2: ptr = new cat;   break;
            case 3: ptr = new Horse; break;
            case 4: ptr = new Pig;   break;
            default:ptr = new Manmal;break;
        }
        theArray[i] = ptr;
    }
    for(i=0;i<5;i++){
        theArray[i]->Speak();
        delete theArray[i];
    }
    return 0;
}

[jsh@localhost class]$ ./a.out 
1) Dog 2) cat 3) Horse 4) Pig 请输入:1
1) Dog 2) cat 3) Horse 4) Pig 请输入:2
1) Dog 2) cat 3) Horse 4) Pig 请输入:3
1) Dog 2) cat 3) Horse 4) Pig 请输入:4
1) Dog 2) cat 3) Horse 4) Pig 请输入:5
Dog woof!
cat Meow!
Horse Winnie!
Dog Qink!
Manmal speak!


在程序编译时,无法知道将创建什么类型的对象,因此也无法知道调用哪个Speak方法。ptr指向的对象是在运行阶段确定的,这杯成为动态绑定或运行阶段绑定。与此相对应的静态绑定和编译阶段绑定。

下图是关于虚函数的工作原理很清楚说明了:

走进C++程序世界-----继承和派生(2)_第1张图片

创建虚析构函数

      如果析构函数是虚函数(应该如此),将执行正确的操作:先执行派生类的析构函数,由于派生类的析构函数会自动调用基类的析构函数,因此整个整个对象会被销毁。经验规则是,如果类中任何一个函数是虚函数,析构函数也应该是虚函数。

  虚复制构造函数

 构造函数不能够使虚函数,因此从技术上说,不存在虚复制构造函数。然而,有时候程序非常需要通过传递一个指向激烈对象的指针,穿件一个派生类的对象的副本。对于这种问题,解决的方法是,在基类创建一个Clone()方法,并将其设置为虚方法。Clone()方法创建当前对象的一个副本,并返回该副本。


虚函数和纯虚函数:

所谓虚函数就是在编译的时候不确定要调用哪个函数,而是动态决定将要调用哪个函数。它的作用就是为了能让这个函数在它的子类里面可以被重载,这样的话,编译器就可以使用后期绑定来达到多态了,也就是:用基类的指针来调用子类的这个函数。
要实现虚函数必须保证派生类的函数名与基类相同,参数名参数类型等也要与基类相同。但派生类中的virtual关键字可以省略,也表示这是一个虚函数。


virtual ReturnType Function_1();            //虚函数声明
virtual ReturnType Function_2() = 0;      //纯虚函数声明

虚函数
    1, 虚函数是非静态的、非内联的成员函数。
    2, 若类中一个成员函数被说明为虚函数,则该成员函数在派生类中可能有不同的实现。当使用该成员函数操作指针或引用所标识的对象时,对该成员函数调用可采用动态联编。
    5, 定义了虚函数后,程序中声明的指向基类的指针就可以指向其派生类。在执行过程中,该函数可以不断改变它所指向的对象,调用不同版本的成员函数,而且这些动作都是在运行时动态实现的。虚函数充分体现了面向对象程序设计的动态多态性。虚函数充分体现了面向对象程序设计的动态多态性。

纯虚函数
    1, 当在基类中不能为虚函数给出一个有意义的实现时,可以将其声明为纯虚函数,其实现留待派生类完成。
    2, 纯虚函数的作用是为派生类提供一个一致的接口,它只是个函数的声明而已,它告诉编译器,在这个类中的这个纯虚函数是没有函数定义的,该类不能创建对象(即不能实例化),但可以声明指针,该类的派生类负责给出这个虚函数的重载定义。

附参考资料:
http://blog.sina.com.cn/s/blog_624ca8080100f7th.html

你可能感兴趣的:(虚函数,纯虚函数,覆盖基类的方法)