面向对象程序设计——Visual C++
第11章 继承

思考题

1、派生有几种方式,每种方式中派生类对基类成员的继承如何?

C++中有三种派生方式:公有派生、私有派生和保护派生。

  1. 公有派生:派生类继承基类的公有成员和保护成员,但不继承基类的私有成员。
  2. 私有派生:派生类继承基类的公有成员、保护成员和私有成员,但它们都成为了派生类的私有成员。
  3. 保护派生:派生类继承基类的公有成员和保护成员,但不继承基类的私有成员。继承的成员在派生类中都成为了保护成员。

需要注意的是,无论哪种派生方式,派生类都可以访问基类的公有成员和保护成员,但不能访问基类的私有成员。

2、在创建派生类对象时,构造函数的执行次序是怎么样的?析构函数的执行次序又是怎样的?

在创建派生类对象时,构造函数的执行次序是先执行基类的构造函数,再执行派生类的构造函数。析构函数的执行次序则是先执行派生类的析构函数,再执行基类的析构函数。
这是因为对象的构造和析构是按照继承关系的层次结构进行的,先构造基类对象,再构造派生类对象,先析构派生类对象,再析构基类对象。

3、派生类对象如何对基类中的成员进行初始化?

派生类对象可以通过调用基类的构造函数来对基类中的成员进行初始化。在派生类的构造函数中,可以使用初始化列表来调用基类的构造函数并传递参数,以初始化基类中的成员。例如:

class Base {
public:
    Base(int x) : m_x(x) {}
private:
    int m_x;
};
class Derived : public Base {
public:
    Derived(int x, int y) : Base(x), m_y(y) {}
private:
    int m_y;
};
int main() {
    Derived d(1, 2);
    return 0;
}

在上面的例子中,派生类Derived的构造函数中使用了初始化列表来调用基类Base的构造函数,并传递参数x,以初始化基类中的成员m_x。同时,派生类Derived中的成员m_y也被初始化为y。

4、在多重继承中,在什么情况下会产生冲突?如何消除冲突?

在多重继承中,当派生类同时继承了两个或多个基类,并且这些基类中有同名的成员函数或成员变量时,就会产生冲突。这种情况被称为“二义性”。
为了消除冲突,可以使用作用域解析运算符“::”来指定要调用的成员函数或成员变量所属的基类。例如:

class Base1 {
public:
    void func() {
        cout << "Base1::func()" << endl;
    }
};
class Base2 {
public:
    void func() {
        cout << "Base2::func()" << endl;
    }
};
class Derived : public Base1, public Base2 {
public:
    void callFunc() {
        Base1::func(); // 调用Base1中的func函数
        Base2::func(); // 调用Base2中的func函数
    }
};

在上面的例子中,派生类Derived继承了Base1和Base2两个基类,它们都有一个名为“func”的成员函数。在派生类中,可以使用作用域解析运算符来指定要调用的成员函数所属的基类,以消除冲突。

5、列举出我们已学习的作用域运算符“::”的所有用途。

作用域运算符“::”可以用于以下几个方面:

  1. 命名空间:用于指定命名空间中的成员。
  2. 类:用于指定类中的静态成员。
  3. 枚举:用于指定枚举类型中的枚举值。
  4. 基类:用于指定派生类中继承的基类中的成员。
  5. 全局作用域:用于指定全局作用域中的变量或函数。
  6. 类型限定符:用于指定成员函数的类型限定符,如const、volatile等。
  7. 模板:用于指定模板中的成员。

总之,作用域运算符“::”可以用于指定不同作用域中的成员,以避免命名冲突和二义性。

6、属于不同类的对象在什么情况下可以相互赋值?

属于不同类的对象在仅当它们之间存在类型转换关系时才可以相互赋值。
例如,如果类A有一个转换构造函数,可以将类B的对象转换为类A的对象,那么就可以将类B的对象赋值给类A的对象。
但是,这种类型转换可能会导致数据丢失或精度损失,因此需要谨慎使用。
另外,如果两个类之间存在继承关系,派生类的对象可以赋值给基类的对象,但反过来是不行的。

7、什么叫虚基类?为什么要引进虚基类?

虚基类是在多重继承中使用的一种特殊的基类。它是通过在基类名前加上关键字“virtual”来声明的。
虚基类的作用是解决多重继承中的“菱形继承”问题,即当一个派生类同时继承了两个或多个基类,并且这些基类中有共同的基类时,就会出现多个相同的基类实例,导致数据冗余和访问二义性的问题。引进虚基类的目的就是为了避免这种问题的发生,使得派生类中只包含一个共同的基类实例。
具体来说,虚基类的成员在派生类中只有一份拷贝,而不是多份拷贝。这样就可以避免数据冗余和访问二义性的问题。同时,虚基类也可以通过虚函数实现多态性,使得派生类可以根据需要重写虚基类的成员函数。
需要注意的是,虚基类的构造函数必须由最派生的类负责调用,以保证虚基类的成员只被初始化一次。同时,虚基类的析构函数也必须由最派生的类负责调用,以保证虚基类的成员只被销毁一次。

习题

1、定义一个图形类,其中有保护类型的成员数据:高度和宽度,一个公有的构造函数。由该图形类建立两个派生类:矩形类和等腰三角形类。在每个派生类中都包含一个函数Area(),分别用来计算矩形和等腰三角形的面积。

#include <iostream>
using namespace std;

class Shape {
protected:
    int height;
    int width;
public:
    Shape(int h, int w) {
        height = h;
        width = w;
    }
};

class Rectangle : public Shape {
public:
    Rectangle(int h, int w) : Shape(h, w) {}
    int Area() {
        return height * width;
    }
};

class IsoscelesTriangle : public Shape {
public:
    IsoscelesTriangle(int h, int w) : Shape(h, w) {}
    int Area() {
        return height * width / 2;
    }
};

int main() {
    Rectangle r(5, 6);
    IsoscelesTriangle t(5, 6);

    cout << "The area of the rectangle is: " << r.Area() << endl;
    cout << "The area of the isosceles triangle is: " << t.Area() << endl;

    return 0;
}

这个程序定义了一个基类Shape和两个派生类Rectangle和IsoscelesTriangle,每个派生类都有一个计算面积的函数Area()。在主函数中创建了一个Rectangle对象和一个IsoscelesTriangle对象,并分别计算它们的面积。输出结果为:

The area of the rectangle is: 30
The area of the isosceles triangle is: 15

2、定义一个人员类CPeople,其属性有(保护类型):姓名、性别、年龄;从中派生出学生类CStudent,添加属性:学号、入学时间和入学成绩;从CPeople类再派生出教师类CTeacher,添加属性:职务、部门、工作时间;由CStudent类派生出研究生类CGraduate,添加属性:研究方向和导师,由CGraduate和CTeacher共同派生出在职研究生类CGradOnWork,分别定义其中的构造函数和输出函数。主函数中定义各种类的对象,并完成测试。

#include <iostream>
#include <string>
using namespace std;

class CPeople {
protected:
    string name;
    char gender;
    int age;
public:
    CPeople(string n, char g, int a) : name(n), gender(g), age(a) {}
};

class CStudent : public CPeople {
private:
    int id;
    string admission_date;
    double admission_score;
public:
    CStudent(string n, char g, int a, int i, string ad, double s)
        : CPeople(n, g, a), id(i), admission_date(ad), admission_score(s) {}
};

class CTeacher : public CPeople {
private:
    string position;
    string department;
    int work_years;
public:
    CTeacher(string n, char g, int a, string p, string d, int y)
        : CPeople(n, g, a), position(p), department(d), work_years(y) {}
};

class CGraduate : public CStudent {
private:
    string research_area;
    string mentor;
public:
    CGraduate(string n, char g, int a, int i, string ad, double s, string ra, string m)
        : CStudent(n, g, a, i, ad, s), research_area(ra), mentor(m) {}
};

class CGradOnWork : public CGraduate, public CTeacher {
public:
    CGradOnWork(string n, char g, int a, int i, string ad, double s, string ra, string m, string p, string d, int y)
        : CGraduate(n, g, a, i, ad, s, ra, m), CTeacher(n, g, a, p, d, y) {}
    
    void print_info() {
        cout << "Name: " << name << endl;
        cout << "Gender: " << gender << endl;
        cout << "Age: " << age << endl;
        cout << "Student ID: " << id << endl;
        cout << "Admission Date: " << admission_date << endl;
        cout << "Admission Score: " << admission_score << endl;
        cout << "Research Area: " << research_area << endl;
        cout << "Mentor: " << mentor << endl;
        cout << "Position: " << position << endl;
        cout << "Department: " << department << endl;
        cout << "Work Years: " << work_years << endl;
    }
};

int main() {
    CGradOnWork grad("John Smith", 'M', 26, 12345678, "2021-09-01", 90.5, "Machine Learning", "Dr. Jane Doe", "Assistant Professor", "Computer Science", 3);
    grad.print_info();
    return 0;
}

3、定义一个基类CAnimal,有私有整型数据成员age,并由其派生一个新类CDog,在该类的成员函数SetAge(int n)中直接给age赋值,看看会有什么问题,把age改为公有成员后,还会有问题吗?

在CDog的成员函数SetAge(int n)中直接给CAnimal的私有成员age赋值,会导致编译错误,因为age是CAnimal的私有成员,不能被派生类直接访问。

当将age改为公有成员时,CDog的成员函数SetAge(int n)就可以直接给age赋值,不会再有编译错误。但这种做法不推荐,因为公有成员可以被任何地方访问和修改,没有封装性,不利于程序的可维护性和安全性。通常情况下,私有成员应该被保护起来,只能通过公有成员函数来访问和修改,这样可以控制访问权限,保证数据的封装性和安全性。

4、定义一个形状基类CShape,在此基础上派生出矩形类CRectangle和圆类CCircle,两者都有GetArea()函数计算对象的面积。CRectangle类再派生一个正方形类Square。试编写一个完整的程序。

#include <iostream>
#include <cmath>

using namespace std;

class CShape {
public:
    virtual double GetArea() = 0;
};

class CRectangle : public CShape {
protected:
    double width, height;
public:
    CRectangle(double w, double h) : width(w), height(h) {}
    double GetArea() override { return width * height; }
};

class CCircle : public CShape {
protected:
    double radius;
public:
    CCircle(double r) : radius(r) {}
    double GetArea() override { return M_PI * radius * radius; }
};

class CSquare : public CRectangle {
public:
    CSquare(double s) : CRectangle(s, s) {}
};

int main() {
    CRectangle rect(5, 4);
    CCircle circle(3);
    CSquare square(2);

    cout << "Rectangle area: " << rect.GetArea() << endl;
    cout << "Circle area: " << circle.GetArea() << endl;
    cout << "Square area: " << square.GetArea() << endl;

    return 0;
}

5、设计一个描述儿童、成人和老人的类系统,儿童分为学龄前和学龄期儿童,成人有工作,老人已经退休。提取其中的共性作为基类,并派生出满足要求的各个类及每一个类上的必要的操作。试编写一个完整的程序。

#include <iostream>
#include <string>

using namespace std;

class CPerson {
protected:
    string name;
    int age;
public:
    CPerson(string n, int a) : name(n), age(a) {}
    virtual void ShowInfo() {
        cout << "Name: " << name << endl;
        cout << "Age: " << age << endl;
    }
};

class CChild : public CPerson {
protected:
    bool isPreschooler;
public:
    CChild(string n, int a, bool p) : CPerson(n, a), isPreschooler(p) {}
    void ShowInfo() override {
        CPerson::ShowInfo();
        if (isPreschooler) {
            cout << "Status: Preschooler" << endl;
        } else {
            cout << "Status: Elementary student" << endl;
        }
    }
};

class CAdult : public CPerson {
protected:
    string occupation;
public:
    CAdult(string n, int a, string o) : CPerson(n, a), occupation(o) {}
    void ShowInfo() override {
        CPerson::ShowInfo();
        cout << "Occupation: " << occupation << endl;
    }
};

class COld : public CPerson {
public:
    COld(string n, int a) : CPerson(n, a) {}
    void ShowInfo() override {
        CPerson::ShowInfo();
        cout << "Status: Retired" << endl;
    }
};

int main() {
    CChild child1("Alice", 4, true);
    CChild child2("Bob", 8, false);
    CAdult adult("Charlie", 35, "Engineer");
    COld old("David", 65);

    cout << "Information of child1: " << endl;
    child1.ShowInfo();
    cout << endl;

    cout << "Information of child2: " << endl;
    child2.ShowInfo();
    cout << endl;

    cout << "Information of adult: " << endl;
    adult.ShowInfo();
    cout << endl;

    cout << "Information of old: " << endl;
    old.ShowInfo();
    cout << endl;

    return 0;
}

6、定义一个存折类,并派生出信用卡类,存折类可以实现开户、存款、取款和查询余额的操作,取款金额必须小于余额,信用卡类对取款操作进行修改,允许透支一定金额。模拟有10个人到银行进行存折操作,其中有2人还进行信用卡交易的过程。试编写一个完整的程序。

#include <iostream>
#include <string>
using namespace std;

class CAccount {
protected:
    string m_name;
    int m_balance;
public:
    CAccount(string name) {
        m_name = name;
        m_balance = 0;
    }
    void Deposit(int amount) {
        m_balance += amount;
        cout << "Deposited " << amount << " yuan.\n";
    }
    virtual bool Withdraw(int amount) {
        if (amount > m_balance) {
            cout << "Withdraw failed, insufficient balance.\n";
            return false;
        }
        else {
            m_balance -= amount;
            cout << "Withdrawn " << amount << " yuan.\n";
            return true;
        }
    }
    int GetBalance() const {
        cout << "Balance: " << m_balance << " yuan.\n";
        return m_balance;
    }
};

class CCreditCard : public CAccount {
private:
    int m_credit_limit;
public:
    CCreditCard(string name, int credit_limit) : CAccount(name) {
        m_credit_limit = credit_limit;
    }
    bool Withdraw(int amount) override {
        if (amount > m_balance + m_credit_limit) {
            cout << "Withdraw failed, insufficient balance and credit limit.\n";
            return false;
        }
        else if (amount > m_balance) {
            int credit_used = amount - m_balance;
            m_balance = 0;
            m_credit_limit -= credit_used;
            cout << "Withdrawn " << amount << " yuan (with credit limit).\n";
            return true;
        }
        else {
            m_balance -= amount;
            cout << "Withdrawn " << amount << " yuan.\n";
            return true;
        }
    }
};

int main() {
    CAccount* accounts[10];
    for (int i = 0; i < 10; i++) {
        if (i % 5 == 0) {
            accounts[i] = new CCreditCard("User" + to_string(i), 1000);
        }
        else {
            accounts[i] = new CAccount("User" + to_string(i));
        }
        accounts[i]->Deposit(100);
    }

    accounts[2]->Withdraw(200);
    accounts[7]->Withdraw(300);

    for (int i = 0; i < 10; i++) {
        accounts[i]->GetBalance();
        delete accounts[i];
    }
    return 0;
}

7、定义一个车类CVehicle作为基类,具有max_speed、weight等数据成员,Run、Stop等成员函数,由此派生出自行车类CBicyle、汽车类CMotocar。自行车类CBicycle有高度height等属性,汽车类CMotocar有座位数seat_num等属性,从CBicycle和CMotocar派生出摩托车类CMotocycle,在派生过程中,注意把CVehicle设置为虚基类。

#include <iostream>
using namespace std;

class CVehicle {
protected:
    int max_speed;
    int weight;
public:
    void Run() {
        cout << "Vehicle is running." << endl;
    }
    void Stop() {
        cout << "Vehicle has stopped." << endl;
    }
};

class CBicycle : virtual public CVehicle {
protected:
    int height;
public:
    CBicycle(int h) : height(h) {}
};

class CMotocar : virtual public CVehicle {
protected:
    int seat_num;
public:
    CMotocar(int s) : seat_num(s) {}
};

class CMotorcycle : public CBicycle, public CMotocar {
public:
    CMotorcycle(int h, int s) : CBicycle(h), CMotocar(s) {}
};

int main() {
    CMotorcycle moto(50, 2);
    moto.Run();
    moto.Stop();
    cout << "Height: " << moto.height << endl;
    cout << "Seat number: " << moto.seat_num << endl;
    return 0;
}