厦大计算机系C++程序设计实验(四)

1 实验目的

掌握运算符重载的方法,理解虚函数与多态性,实现运行时多态性的程序设计。

2 实验内容

(1)定义一个复数(Complex)类,并重载运算符+和-,使得两个复数可以进行加法和减法的运算,注意:加法和减法可能是复数加减上另一个复数,也可能是复数加上一个普通的整数。

(2)定义一个交通工具(Vehicles)基类,包含run、stop成员函数,由此派生出自行车(Bicycle)类、汽车(Car)类,从Bicycle和Car派生出摩托车(Motorcycle)类,它们都有run、stop等成员函数,定义一个包含7个元素的Vehicles类型的数组,在数组中存入1个Vehicles对象,1个Car对象,3个Bicycle对象以及2个Motorcycle对象,遍历此数组,观察虚函数的作用。

3 实验步骤

(1)运算符重载

定义复数类Complex,其中数据域包括实部和虚部,成员函数包括构造函数、赋值函数、输出函数以及对加号、减号的重载。代码如下:

#include <iostream>

using namespace std;

class Complex
{
    int real;
    int imaginary;

public:
    /** Constructor function with zero argument */
    Complex(){}
    /** Constructor function to initialize complex number */
    Complex(int real, int imaginary);
    /** Print out complex number */
    void print(void);
    /** Set complex number with given arguments */
    void set(int real, int imaginary);
    /** Overload + to compute sum of two complex numbers */
    Complex operator +(const Complex &a);
    /** Overload + to compute sum of one complex number and one integer */
    Complex operator +(const int &a);
    /** Overload + to compute sum of one integer and one complex number */
    friend Complex operator +(const int &a, const Complex &b);
    /** Overload - to compute difference of two complex numbers */
    Complex operator -(const Complex &a);
    /** Overload - to compute difference of one complex number and one integer */
    Complex operator -(const int &a);
    /** Overload - to compute difference of one integer and one complex number */
    friend Complex operator -(const int &a, const Complex &b);
};

Complex::Complex(int real, int imaginary)
{
    this->real = real;
    this->imaginary = imaginary;
}

void Complex::set(int real, int imaginary)
{
    this->real = real;
    this->imaginary = imaginary;
}

void Complex::print(void)
{
    if(real != 0 || (real == 0 && imaginary == 0))
        cout << real;
    cout.setf(ios::showpos);           //enable to show the sign of imaginary part
    if(real != 0 && imaginary == 1)
        cout << "+i";
    else if(imaginary == 1)
        cout << "i";
    else if(imaginary == -1)
        cout << "-i";
    else if(imaginary != 0)
        cout << imaginary << "i";
    cout << endl;
    cout.unsetf(ios::showpos);         //disable to show the sign of imaginary part
}

Complex Complex::operator +(const Complex &a)
{
    return Complex(this->real + a.real, this->imaginary + a.imaginary);
}

Complex Complex::operator +(const int &a)
{
    return Complex(this->real + a, this->imaginary);
}

Complex operator +(const int &a, const Complex &b)
{
    return Complex(a + b.real, b.imaginary);
}

Complex Complex::operator -(const Complex &a)
{
    return Complex(this->real - a.real, this->imaginary - a.imaginary);
}

Complex Complex::operator -(const int &a)
{
    return Complex(this->real - a, this->imaginary);
}

Complex operator -(const int &a, const Complex &b)
{
    return Complex(a - b.real, -b.imaginary);
}

int main()
{
    Complex a, b, add, difference;
    int real, imaginary;

    cout << "Enter the real part and imaginary part of a: ";
    cin >> real >> imaginary;
    a.set(real, imaginary);

    cout << "Enter the real part and imaginary part of b: ";
    cin >> real >> imaginary;
    b.set(real, imaginary);

    /* Test for one complex number adding another complex number */
    add = a + b;
    cout << "a + b = ";
    add.print();
    /* Test for one complex number adding one integer */
    add = a + 5;
    cout << "a + 5 = ";
    add.print();
    /* Test for one integer adding one complex number */
    add = 5 + a;
    cout << "5 + a = ";
    add.print();

    /* Test for one complex number minus another complex number */
    difference = a - b;
    cout << "a - b = ";
    difference.print();
    /* Test for one complex number minus one integer */
    difference = a - 5;
    cout << "a - 5 = ";
    difference.print();
    /* Test for one integer minus one complex number */
    difference = 5 - a;
    cout << "5 - a = ";
    difference.print();

    return 0;
}

本题中遇到了这样几个问题:

1、在进行运算符重载时,参数列表内参数的修饰最好都加上关键字const,比如遇到a+5这样的情况,对于常数5由于没有实际分配内存,是无法进行引用的。但是加上const后,就可以正常引用了。

2、在重载运算符时有这样两种办法,第一种是直接在成员函数中重载,无需写出左操作数,另一种是通过友元函数进行重载,但是需要写全各个操作数。本题中,若没有写友元函数的运算符重载,则当遇到类似5+a的情况时,编译出错,因为没有在成员函数中定义一个整数加一个复数,而只有一个复数加一个整数。

3、在重载运算符函数内部,可以直接通过使用类构造函数直接构造一个临时类返回计算结果,而不需要另外申请一个类存放数据,如

returnComplex(this->real + a, this->imaginary);

这样会简化程序。

4、为了使输出更加美观,这里使用了cout的格式化输出,调用cout类中的setf和unsetf函数,设置或取消格式。本题中的ios::showpos指令是使得输出虚部时会输出符号。

(2)虚函数与多态性

首先定义基类Vehicles,其中成员函数定义为虚函数,方便之后的覆盖。然后派生出Bicycle和Car,再派生出Motorcycle。具体程序如下:

#include <iostream>

using namespace std;

class Vehicles
{
public:
    virtual void run(void) { cout << "Vehicles' running: unknown" << endl; }
    virtual void stop(void) {cout << "Vehicles' stopping: unknown" << endl;}
};

class Bicycle:virtual public Vehicles
{
public:
    virtual void run(void) { cout << "Bicycle's running: ride with pumping the pedals" << endl; }
    virtual void stop(void) { cout << "Bicycle's stopping: brake by hands" << endl; }
};

class Car:virtual public Vehicles
{
public:
    virtual void run(void) { cout << "Car's running: drive" << endl; }
    virtual void stop(void) { cout << "Car's stopping: brake by feet" << endl; }
};

class Motorcycle:public Bicycle, public Car
{
public:
    virtual void run(void) { cout << "Motorcycle's running: ride without pumping the pedals" << endl; }
    virtual void stop(void) { cout << "Motorcycle's stopping: brake by hands also" << endl; }
};

int main()
{
    Vehicles *vehicles[7];
    Vehicles v1;
    Car c1;
    Bicycle b1, b2, b3;
    Motorcycle m1, m2;

    vehicles[0] = &v1;
    vehicles[1] = &c1;
    vehicles[2] = &b1;
    vehicles[3] = &b2;
    vehicles[4] = &b3;
    vehicles[5] = &m1;
    vehicles[6] = &m2;

    for(int i = 0; i < 7; ++i)
    {
        vehicles[i]->run();
        vehicles[i]->stop();
        cout << endl;
    }
    return 0;
}

本题中遇到了这样几个问题:

1、在派生Bicycle和Car类时,若不使用虚基类,则会出现编译错误。因为编译器认为Vehicle类是Motorcycle类的模糊的基类,在vehicles[5] =&m1中将无法把Motorcycle类的指针强制转换为Vehicle类指针。但是如果使用虚基类,那么Vehicle类中的内容将只会在Motorcycle类中出现一次,就不会产生歧义。

2、在开辟Vehicle类的动态数组时,如果将数组定义为

Vehiclesvehicles[7];

并且其内容分别赋值为

vehicles[0]= v1;

vehicles[1]= c1;

vehicles[2]= b1;

vehicles[3]= b2;

vehicles[4]= b3;

vehicles[5]= m1;

vehicles[6]= m2;

那么,利用基类指针调用数组内各个元素的成员函数时,将只会调用基类的run和stop函数,该类自己定义的函数将不能被访问。因为在进行赋值时,已经隐含了将派生类强制转换为基类,那么在利用基类指针访问时,自然也就变成访问基类的成员函数了。所以,这里将数组定义为基类指针数组,那么在进行访问时,就可以访问到该类自定义的成员函数了。

3、在开辟对象数组时,若代码写成

Vehicles*vehicles[7] = {&(Vehicles()), &(Car()), &(Bicycle()),&(Bicycle()), &(Bicycle()), &(Motorcycle()), &(Motorcycle()) };

那么编译器将报错:

error:taking address of temporary [-fpermissive]|

即说明不能对临时类取地址。故本程序中是先声明各个类,然后再取地址。

改进的版本:

为了更好的观察多态数组至于内存的操作,我将程序做了修改如下:

#include <iostream>

using namespace std;

class Vehicles
{
public:
    Vehicles() { cout << "Vehicles's constructor" << endl; }
    virtual ~Vehicles() { cout << "Vehicles's destructor" << endl; }
    virtual void run(void) { cout << "Vehicles' running: unknown" << endl; }
    virtual void stop(void) {cout << "Vehicles' stopping: unknown" << endl;}
};

class Bicycle:virtual public Vehicles
{
public:
    Bicycle() { cout << "Bicycle's constructor" << endl; }
    ~Bicycle() { cout << "Bicycle's destructor" << endl; }
    virtual void run(void) { cout << "Bicycle's running: ride with pumping the pedals" << endl; }
    virtual void stop(void) { cout << "Bicycle's stopping: brake by hands" << endl; }
};

class Car:virtual public Vehicles
{
public:
    Car() { cout << "Car's constructor" << endl; }
    ~Car() { cout << "Car's destructor" << endl; }
    virtual void run(void) { cout << "Car's running: drive" << endl; }
    virtual void stop(void) { cout << "Car's stopping: brake by feet" << endl; }
};

class Motorcycle:public Bicycle, public Car
{
public:
    Motorcycle() { cout << "Motorcycle's constructor" << endl; }
    ~Motorcycle() { cout << "Motorcycle's destructor" << endl; }
    virtual void run(void) { cout << "Motorcycle's running: ride without pumping the pedals" << endl; }
    virtual void stop(void) { cout << "Motorcycle's stopping: brake by hands also" << endl; }
};

int main()
{
    Vehicles **vehicles = new Vehicles* [7];

    vehicles[0] = new Vehicles;
    cout << endl;
    vehicles[1] = new Car;
    cout << endl;
    vehicles[2] = new Bicycle;
    cout << endl;
    vehicles[3] = new Bicycle;
    cout << endl;
    vehicles[4] = new Bicycle;
    cout << endl;
    vehicles[5] = new Motorcycle;
    cout << endl;
    vehicles[6] = new Motorcycle;
    cout << endl;

    for(int i = 0; i < 7; ++i)
    {
        vehicles[i]->run();
        vehicles[i]->stop();
        cout << endl;
    }

    for(int i = 0; i < 7; ++i)
    {
        delete vehicles[i];
        cout << endl;
    }
    delete[] vehicles;

    return 0;
}

运行结果为:

Vehicles's constructor

Vehicles's constructor
Car's constructor

Vehicles's constructor
Bicycle's constructor

Vehicles's constructor
Bicycle's constructor

Vehicles's constructor
Bicycle's constructor

Vehicles's constructor
Bicycle's constructor
Car's constructor
Motorcycle's constructor

Vehicles's constructor
Bicycle's constructor
Car's constructor
Motorcycle's constructor

Vehicles' running: unknown
Vehicles' stopping: unknown

Car's running: drive
Car's stopping: brake by feet

Bicycle's running: ride with pumping the pedals
Bicycle's stopping: brake by hands

Bicycle's running: ride with pumping the pedals
Bicycle's stopping: brake by hands

Bicycle's running: ride with pumping the pedals
Bicycle's stopping: brake by hands

Motorcycle's running: ride without pumping the pedals
Motorcycle's stopping: brake by hands also

Motorcycle's running: ride without pumping the pedals
Motorcycle's stopping: brake by hands also

Vehicles's destructor

Car's destructor
Vehicles's destructor

Bicycle's destructor
Vehicles's destructor

Bicycle's destructor
Vehicles's destructor

Bicycle's destructor
Vehicles's destructor

Motorcycle's destructor
Car's destructor
Bicycle's destructor
Vehicles's destructor

Motorcycle's destructor
Car's destructor
Bicycle's destructor
Vehicles's destructor

假若基类的析构函数没有被写为虚函数,则输出的最后部分将是:

Vehicles's destructor

Vehicles's destructor

Vehicles's destructor

Vehicles's destructor

Vehicles's destructor

Vehicles's destructor

Vehicles's destructor

可见,派生类的析构函数并没有被调用,造成了内存泄漏!所以需要将基类的构造函数设置为虚函数。

关于这个问题更加深入的思考:

如果我将数组定义为对象数组,而非指针数组,并且在Car类中增加了一个数据域speed,程序改写为

#include <iostream>

using namespace std;

class Vehicles
{
public:
    Vehicles() {}
    virtual ~Vehicles() { cout << "Vehicles's destructor" << endl; }
    virtual void run(void) { cout << "Vehicles' running: unknown" << endl; }
    virtual void stop(void) {cout << "Vehicles' stopping: unknown" << endl;}
};

class Car:virtual public Vehicles
{
    int speed;
public:
    Car() {}
    ~Car() { cout << "Car's destructor" << endl; }
    virtual void run(void) { cout << "Car's running: drive" << endl; }
    virtual void stop(void) { cout << "Car's stopping: brake by feet" << endl; }
};

void Output(Vehicles vehicles[], int num)
{
    for(int i = 0; i < num; ++i)
    {
        vehicles[i].run();
        vehicles[i].stop();
        cout << endl;
    }
}

int main()
{
    Vehicles vehicles[3];
    Car car[5];

    Output(vehicles, 3);
    Output(car, 5);

    return 0;
}

那么,程序将会崩溃:


原因就是Output函数的问题。

如果将其改成基类指针数组,并且加上释放函数,程序为:

……(上同)
void Init_vehicles(Vehicles **vehicles, int num)
{
    for(int i = 0; i < num; ++i)
    {
        vehicles[i] = new Vehicles;
    }
}

void Init_car(Car **car, int num)
{
    for(int i = 0; i < num; ++i)
    {
        car[i] = new Car;
    }
}

void Output(Vehicles **vehicles, int num)
{
    for(int i = 0; i < num; ++i)
    {
        vehicles[i]->run();
        vehicles[i]->stop();
        cout << endl;
    }
}

void Destroy(Vehicles **vehicles, int num)
{
    for(int i = 0; i < num; ++i)
    {
        delete vehicles[i];
        cout << endl;
    }
    delete[] vehicles;
}

int main()
{
    Vehicles **vehicles = new Vehicles* [3];
    Car **car = new Car* [5];

    Init_vehicles(vehicles, 3);
    Init_car(car, 5);

    Output(vehicles, 3);
    Output(reinterpret_cast<Vehicles**>(car), 5);

    Destroy(vehicles, 3);
    Destroy(reinterpret_cast<Vehicles**>(car), 5);

    return 0;
}

那么,程序的运行结果是:

Vehicles' running: unknown
Vehicles' stopping: unknown

Vehicles' running: unknown
Vehicles' stopping: unknown

Vehicles' running: unknown
Vehicles' stopping: unknown

Car's running: drive
Car's stopping: brake by feet

Car's running: drive
Car's stopping: brake by feet

Car's running: drive
Car's stopping: brake by feet

Car's running: drive
Car's stopping: brake by feet

Car's running: drive
Car's stopping: brake by feet

Vehicles's destructor

Vehicles's destructor

Vehicles's destructor

Car's destructor
Vehicles's destructor

Car's destructor
Vehicles's destructor

Car's destructor
Vehicles's destructor

Car's destructor
Vehicles's destructor

Car's destructor
Vehicles's destructor

由此可见,通过基类指针,将不会发生对象数组越界的情况。

4 实验结果

(1)运算符重载

若设置a=3+4i,b=1+2i,那么输出结果为:


(2)虚函数与多态性

输出结果为:

5 心得小结

通过本次实验,我学会了如何使用对运算符利用类成员函数和友元函数两种方式进行重载,如何对cout进行格式化输出设置,以及如何使用虚函数并进行覆盖,如何通过虚基类派生子类,如何利用基类指针访问虚函数等等技能。本次实验对于深入了解C++非常有帮助,让我受益匪浅。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值