實驗6 多態性與虛函數(P285)

實驗目的和要求

    瞭解靜態聯編的動態聯編的概念。掌握動態聯編的條件。
實驗內容
1.分析並調試下列程序。
//sy6_1.cpp
#include<iostream>
using namespace std;
class Base
{
    public:
        virtual void f(float x){cout<<"Base::f(float)"<<x<<endl;}
        void g(float x){cout<<"Base::g(float)"<<x<<endl;}
        void h(float x){cout<<"Base::h(float)"<<x<<endl;}
};
class Derived:public Base
{
    public:
        virtual void f(float x){cout<<"Derived::f(float}"<<x<<endl;}
        void g(int x){cout<<"Derived::g(int)"<<x<<endl;}
        void h(float x){cout<<"Derived::h(float)"<<x<<endl;}
};
int main()
{
    Derived d;
    Base *pb=&d;
    Derived *pd=&d;
    pb->f(3.14f);//語句1
    pd->f(3.14f);//語句2
    pb->g(3.14f);//語句3
    pb->h(3.14f);//語句4
    pd->h(3.14f);//語句5
    return 0;
}

(1)找出以上程序中使用了重載和覆蓋函數。

答:Base類中函數void g(); 和void h();與Derived類中的函數void g(); 和void h();函數名相同,參數類型不同,構成了函數重載。

(2)寫出程序的輸出結果,並解釋輸出結果。
程序的輸出結果如下:
分析:程序首先聲明一個基類Base,類中公有成員部分有虛函數f和兩個成員函數g、h,接着定義派生類Derived並且繼承基類中的公有部分。主函數中先聲明一個對象d,接着定義指針pb。然後執行語句1,調用基類中的虛函數,輸出第一行結果。接着執行語句2、3、4、5,分別調用派生類中虛函數、基類中的g函數、基類中的h函數,派生類中的h函數,輸出第2、3、4、5行結果。
2. 分析並調試下列程序
//sy6_3.cpp
#include<iostream>
using namespace std;
class Base
{
    public:
        void f(int x){cout<<"Base::f(int)"<<x<<endl;}
        void f(float x){cout<<"Base::f(float)"<<x<<endl;}
        virtual void g(void){cout<<"Base::g(void)"<<endl;}
};
class Derived:public Base
{
    public:
        virtual void g(void){cout<<"Derived::g(void}"<<endl;}
};
int main()
{
    Derived d;
    Base *pb=&d;
    pb->f(42);
    pb->f(3.14f);
    pb->g();
    return 0;
}
(1)找出以上程序中使用了重載和覆蓋函數。

答:Base類中函數void f(); 在同一作用域中,函數名相同,參數類型不同,構成了函數重載。

(2)寫出程序的輸出結果,並解釋輸出結果。
程序的輸出結果如下:

輸出結果解釋: pb和pd指向同一地址,它們運行結果應該是相同的,但實際運行出來的結果卻不相同,原因是決定pb和pd調用函數運行結果的不是他們指向的地址,而是他們的指針類型。“只有在通過基類指針或引用間接指向派生類子類型時多態性纔會起作用”。在程序中pb是基類指針,pd是派生類指針,pd的所有函數調用都只是調用自己的函數,和多態性無關,所以pd的所有函數調用的結果都輸出Derived::是完全正常的;pb的函數調用如果有virtual則根據多態性調用派生類的,如果沒有virtual則是正常的靜態函數調用,還是調用基類的,所以有virtual的f函數調用輸出Derived::,其它兩個沒有virtual則還是輸出Base::。


3. 分析並調試下列程序
//sy6_3.cpp
#include<iostream>
using namespace std;
class Point
{
    public:
        Point(double i,double j){x=i;y=j;}
        double Area(){return 0.0;}
    private:
        double x,y;
};
class Rectangle:public Point
{
    public:
        Rectangle(double i,double j,double k,double l):Point(i,j){w=k;h=l;}
        double Area(){return w*h;}
    private:
        double w,h;
};
int main()
{
    Point p(3.5,7);//語句1
    double A=p.Area();//語句2
    cout<<"Area= "<<A<<endl;//語句3
    Rectangle r(1.2,3,5,7.8);//語句4
    A=r.Area();//語句5
    cout<<"Area= "<<A<<endl;//語句6
    return 0;
}

寫出程序的輸出結果,並解釋輸出結果。

程序的輸出結果如下:

輸出結果解釋:程序首先執行語句1,創建對象p,調用基類中的構造函數x和y得到初始值3.5和7,接着執行語句2,將p.Area( )賦值給A,因爲基類中的Area的返回值爲0,因此輸出第一行的結果Area=0。然後執行語句4,創建對象r,調用派生類中的構造函數,i,j,w,h得到初始值1.2、3、5、7.8,Area的返回值爲w*h=5*7.8=39,輸出第二行Area的值爲39。

4. 分析並調試下列程序
//sy6_4.cpp
#include<iostream>
using namespace std;
const double PI=3.1415;
class Shap
{
    public:
        virtual double Area()=0;
};
class Triangle:public Shap
{
    public:
        Triangle(double h,double w){H=h;W=w;}
        double Area(){return 0.5*H*W;}
    private:
        double H,W;
};
class Rectangle:public Shap
{
    public:
        Rectangle(double h,double w){H=h;W=w;}
        double Area(){return H*W;}
    private:
        double H,W;
};
class Circle:public Shap
{
    public:
        Circle(double r){R=r;}
        double Area(){return PI*R*R;}
    private:
        double R;
};
class Square:public Shap
{
    public:
        Square(double s){S=s;}
        double Area(){return S*S;}
    private:
        double S;
};
double Total(Shap *s[],int n)
{
    double sum=0;
    for(int i=0;i<n;i++)
        sum+=s[i]->Area();
    return sum;
}
int main()
{
   Shap *s[5];
   s[0]=new Square(8.0);
   s[1]=new Rectangle(3.0,8.0);
   s[2]=new Square(12.0);
   s[3]=new Circle(8.0);
   s[4]=new Triangle(5.0,4.0);
   double sum=Total(s,5);
   cout<<"SUM = "<<sum<<endl;
    return 0;
}
程序的輸出結果如下:

(1)指出抽象類。

答:抽象類是包含純虛函數的類,也就是類Shap。

(2)指出純虛函數,並說明它的作用。

答:純虛函數是virtual double Area()=0;作用是爲派生類提供一個一致的接口,最終在派生列中實現了Area(),用於求具體形狀的面積。

(3)每個類的作用是什麼?整個程序的作用是什麼?

答:基類是Shap,類Triangle、類Rectangle、類Circle、類Square是Shap的派生類,並且以公有繼承的方式繼承及Shap,其中類Triangle、類 Rectangle、類Circle、類Square分別是爲了計算三角形的面積、矩形的面積、圓的面積、正方形的面積。然後又定義了一個Total函數用來計算各圖形的總面積。

5. 某學校對教師每個月工資的計算規定如下:固定工資+課時補貼;教授的固定工資爲5000元,每個課時補貼50;副教授的固定工資爲3000,每個課時補貼30元;講師的固定工資爲2000元,每個課時補貼20元。定義教師抽象類,派生不同職稱的教師類,編寫程序求若干個教師的月工資。(sy6_5.cpp)
//sy6_5.cpp
#include <iostream>
using namespace std;
class Teacher
{
public:
    virtual int Salary()=0;
    virtual void Print(int)=0;
};

class Professor:public Teacher
{
private:
    char name[128];
    int lessons;
public:
    Professor()
    {
        cout<<"請輸入姓名:";
        cin>>name;
        cout<<"請輸入課時:";
        cin>>lessons;
    };
    int Salary()
    {
        return (5000+lessons*50);
    };
    void Print(int money)
    {
        cout<<"職稱:教授 姓名:"<<name<<" 薪水:"<<money<<endl<<endl;
    };
};

class AssociateProfessor:public Teacher
{
private:
    char name[128];
    int lessons;
public:
    AssociateProfessor()
    {
        cout<<"請輸入姓名:";
        cin>>name;
        cout<<"請輸入課時:";
        cin>>lessons;
    };
    int Salary()
    {
        return (3000+lessons*30);
    };
    void Print(int money)
    {
        cout<<"職稱:副教授 姓名:"<<name<<" 薪水:"<<money<<endl<<endl;
    };
};

class Lecturer:public Teacher
{
private:
    char name[128];
    int lessons;
public:
    Lecturer()
    {
        cout<<"請輸入姓名:";
        cin>>name;
        cout<<"請輸入課時:";
        cin>>lessons;
    };
    int Salary()
    {
        return (2000+lessons*20);
    };
    void Print(int money)
    {
        cout<<"職稱:講師 姓名:"<<name<<"薪水:"<<money<<endl<<endl;
    };
};

int main()
{
    Teacher *t = NULL;

    int money=0;

     t = new Professor();
    money = t->Salary();
    t->Print(money);
    delete t;


    t = new AssociateProfessor();
    money = t->Salary();
    t->Print(money);
    delete t;


    t = new Lecturer();
    money = t->Salary();
    t->Print(money);
    delete t;
    t = NULL;
    return 0;
}
程序輸出結果如下:

6. 把實驗5中的第4題的Shape類定義爲抽象類,提供共同操作界面的純虛函數。TwoDimShape類和ThreeDimShape類仍然抽象類,第3層具體類才能提供全部函數的實現。在測試函數中,使用基類指針實現不同派生類對象的操作。、
程序編寫如下:
//sy6_6.cpp
#include<iostream>
using namespace std;
class Shape
{
    public:
        virtual double area()=0;
        virtual double bulk()=0;
};
class TwoDimShape:public Shape{};
class Circle:public TwoDimShape
{
    public:
    Circle(double r){R=r;}
    double area(){return 3.14*R*R;}
    double bulk(){}
    private:
    double R;
};
class ThreeDimShape:public Shape{};
class sphere:public ThreeDimShape
{
    public:
    sphere(double w){R=w;}
    double area(){}
    double bulk(){return 4/3*3.14*R*R*R;}
    private:
    double R;
};
int main()
{
   Shape *s[2];
   s[0]=new Circle(3.0);
   s[1]=new sphere(4.0);
   cout<<"Area of circle is "<<s[0]->area()<<endl;
   cout<<"Bulk of sphere is "<<s[1]->bulk()<<endl;
    return 0;
}
程序輸出結果如下:

分析與討論
1.結合實驗內容中第1題和第2題,說明重載與覆蓋的區別。

  答:重載與覆蓋的區別:1、方法的覆蓋是子類和父類之間的關係,是垂直關係;方法的重載是同一個類中方法之間的關係,是水平關係2、覆蓋只能由一個方法,或只能由一對方法產生關係;方法的重載是多個方法之間的關係。3、覆蓋要求參數列表相同;重載要求參數列表不同。4、覆蓋關係中,調用那個方法體,是根據對象的類型(對象對應存儲空間類型)來決定;重載關係,是根據調用時的實參表與形參表來選擇方法體的。

2.總結靜態聯編和動態聯編的區別和動態聯編的條件。

  答:靜態聯編是指聯編工作在編譯階段完成的,這種聯編過程是在程序運行之前完成的,又稱爲早期聯編。要實現靜態聯編,在編譯階段就必須確定程序中的操作調用(如函數調用)與執行該操作代碼間的關係,確定這種關係稱爲束定,在編譯時的束定稱爲靜態束定。靜態聯編對函數的選擇是基於指向對象的指針或者引用的類型。其優點是效率高,但靈活性差。

   動態聯編是指聯編在程序運行時動態地進行,根據當時的情況來確定調用哪個同名函數,實際上是在運行時虛函數的實現。這種聯編又稱爲晚期聯編,或動態束定。動態聯編對成員函數的選擇是基於對象的類型,針對不同的對象類型將做出不同的編譯結果。C++中一般情況下的聯編是靜態聯編,但是當涉及到多態性和虛函數時應該使用動態聯編。動態聯編的優點是靈活性強,但效率低。

動態聯編的條件:
①必須把動態聯編的行爲定義爲類的虛函數。
②類之間應滿足子類型關係,通常表現爲一個類從另一個類公有派生而來。
③必須先使用基類指針指向子類型的對象,然後直接或者間接使用基類指針調用虛函數。

實驗總結

  通過本次實驗我瞭解靜態聯編的動態聯編的概念以及靜態聯編和動態聯編的區別,瞭解了什麼是重載和覆蓋函數掌握動態聯編的條件。在同一個作用域內,函數名相同,參數列表不同(參數個數不同,或者參數類型不同,或者參數個數和參數類型都不同),返回值類型可相同也可不同,這種情況叫做C++的重載。而覆蓋又被叫做重寫,是當在子類中定義一個與父類完全相同的虛函數時,則稱子類的這個函數重寫了父類的這個虛函數。在之前一直都不明白這兩者的概念含義,不知道怎麼去運用,通過老師的講解和實驗的操作,大概瞭解了是怎麼一回事,但是還需要在課下花時間去練習。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章