19、C++ 继承与多态全面解析

C++ 继承与多态全面解析

1. 继承基础

1.1 继承概念与公共继承

继承是 C++ 和面向对象编程的重要特性,它允许从现有类创建新类,新类称为派生类,现有类称为基类。派生类继承基类的特性,还可添加新特性或修改现有特性。

公共继承的一般形式为:

class name_of_derived_class : access_type name_of_base_class

当访问类型为公共时,派生类的函数和友元函数可访问基类的公共成员,但不能直接访问私有成员,只能通过基类的公共和受保护函数访问。派生类的函数和友元函数可直接访问基类的受保护成员,但其对象不能。

1.2 数据隐藏与类层次结构

数据隐藏方面,只有私有成员不能从类外部直接访问,提供了数据隐藏。派生类可作为基类创建新的派生类,形成类的层次结构。

基类包含实体的一般属性,派生类是基类的特化。例如:

#include <iostream>
#include <string>
using std::cout;
using std::cin;
using std::string;

class Product
{
public:
    string code;
    float prc;

    Product() {cout << "Base Constructor\n";}
    ~Product() {cout << "Base Destructor\n";}
    void show() const {cout << "\nC:" << code << '\n';}
};

class Book : public Product
{
public:
    string title;
    string auth;

    Book() {cout << "Derived Constructor\n";}
    ~Book() {cout << "Derived Destructor\n";}
    void display() const {cout << "T:" << title << " A:" << auth << " P:" << prc << '\n';}
};

int main()
{
    Book b;

    cout << "Product Details: ";
    cin >> b.code >> b.prc;

    cout << "Book Details: ";
    cin >> b.title >> b.auth;

    b.show();
    b.display();
    return 0;
}

此例中, Book 类继承了 Product 类的成员和函数,创建 Book 对象时,先调用基类构造函数,再调用派生类构造函数;销毁对象时,顺序相反。

1.3 继承的优势

继承的主要优势是代码复用,避免重写代码。同时,简化了程序的维护和升级,若基类添加新特性或修复错误,派生类会自动继承。

1.4 Is - a 和 Has - a 关系

公共继承通常模拟 “is - a” 关系,即派生类对象也是基类对象,如 Book Product 。而 “has - a” 关系适用于一个类包含另一个类的对象,如书店包含书籍。

1.5 受保护成员与继承

若类可能作为基类,可将部分成员声明为受保护,派生类可直接访问。但这会降低内部安全性,可能导致基类操作难以控制。例如:

class A
{
protected:
    int p;
};

class B : public A
{
public:
    void f();
};

void B::f()
{
    p = 10; // 允许,访问基类子对象的受保护成员
    A a;
    a.p = 10; // 错误,普通基类对象的受保护成员不可访问
}

1.6 作用域与继承

每个类定义自己的作用域,派生类的作用域包含在基类作用域内。使用名称时,编译器先在类作用域查找,若未找到,继续在基类作用域查找。

若不同类使用相同名称,内部作用域的名称会隐藏外部作用域的名称。可使用作用域解析运算符 :: 引用其他类的成员。

1.7 派生类与构造函数

创建派生类对象时,需先创建基类子对象,派生类构造函数要调用基类构造函数初始化基类子对象。例如:

#include <iostream>
#include <string>
using std::cout;
using std::string;

class Product
{
private:
    string code;
    float prc;
public:
    Product(const string& c = "No", float p = 0);
    void show() const;
};

class Book : public Product
{
private:
    string title;
    string auth;
public:
    Book(const string& c, float p, const string& t, const string& a);
    void display() const;
};

Product::Product(const string& c, float p)
{
    code = c;
    prc = p;
}

void Product::show() const
{
    cout << "C:" << code << " P:" << prc << '\n';
}

Book::Book(const string& c, float p, const string& t, const string& a) : Product(c, p)
{
    title = t;
    auth = a;
}

void Book::display() const
{
    show();
    cout << "T:" << title << " A:" << auth << '\n';
}

int main()
{
    Book b("AB25", 8.5, "Nice", "Many");

    b.display();
    return 0;
}

成员初始化按在类中声明的顺序进行,而非初始化列表的顺序。

1.8 类层次结构

在类层次结构中,派生对象包含直接基类和间接基类的子对象。构造时,从最基类开始调用构造函数;析构时,顺序相反。

若不想让类被继承,可使用 C++11 的 final 关键字:

class A final {...}; // 类 A 不能作为基类

2. 基类与派生类的特殊关系

2.1 从派生类到基类的转换

基类的指针或引用可指向或引用派生类对象,无需类型转换,这称为派生到基类的转换。例如:

Book b("AB25", 8.5, "Nice", "Many");
Product *p = &b;
Product &r = b;
p->show();
r.show();

但基类的指针或引用不能访问派生类的成员。

派生类对象可用于需要基类对象的表达式,函数接受基类对象的指针或引用时,也可接受派生类对象的指针或引用。

2.2 从基类到派生类的转换

基类到派生类的转换不能自动进行,若要转换需类型转换,但这不安全,因为基类对象不包含派生类的子对象。

若要将基类对象赋值给派生类对象,可重载赋值运算符或定义转换构造函数。

2.3 派生类的复制操作

2.3.1 定义派生类的复制构造函数

简单继承示例中,默认复制构造函数会复制基类子对象的成员。复杂示例如下:

#include <iostream>

class A
{
public:
    int a;
    A(int i) : a(i) {}
    A(const A& r) : a(r.a + 1) {}
};

class B : public A
{
private:
    int t;
public:
    B(int i) : A(i), t(i+20) {}
    B(const B& b) : A(b), t(b.t + 1) {}
    void show() const {std::cout << a << ' ' << t << '\n';}
};

int main()
{
    B b1(10);

    B b2 = b1;
    b2.show();
    return 0;
}

派生类的复制构造函数需调用基类的复制构造函数处理基类子对象。

2.3.2 定义派生类的复制赋值函数

复制赋值函数也需复制基类子对象的成员。例如:

#include <iostream>

class A
{
public:
    int v;
    A(int i) : v(i) {}
};

class B : public A
{
public:
    int r;
    B(int i) : A(i+5), r(i) {}
    B& operator=(const B& b);
};

B& B::operator=(const B & b)
{
    A::operator=(b);
    r = b.r;
    return *this;
}

int main()
{
    B b1(10), b2(20);

    b2 = b1;
    std::cout << b2.v << ' ' << b2.r << '\n';
    return 0;
}

3. 函数重定义

3.1 相同签名的函数重定义

当派生类重定义基类的函数且签名相同时,派生类函数会隐藏基类函数。例如:

void Book::show() const
{
    cout << "T:" << title << " A:" << auth << '\n';
}

若要调用基类的函数,可使用作用域解析运算符:

void Book::show()
{
    cout << "T:" << title << " A:" << auth << '\n';
    Product::show();
}

使用指针或引用调用函数时,若指针或引用的类型为基类,调用的是基类的函数。

3.2 不同签名的函数重定义

当基类函数在派生类中声明不同时,重定义的函数会隐藏基类中同名的所有函数。例如:

#include <iostream>

class A
{
public:
    void show() const {std::cout << "A\n";}
};

class B : public A
{
public:
    void show(int k) const {std::cout << k << '\n';}
};

class C : public B
{
public:
    void show(const char *s) const {std::cout << s << '\n';}
};

int main()
{
    B b;
    b.show(10);
    b.show(); // 错误

    C c;
    c.show("Test");
    c.show(10); // 错误
    c.show(); // 错误
    return 0;
}

若要在派生类中拥有基类函数的重载版本,可使用 using 声明:

class C : public B
{
public:
    using B::A::show;
    using B::show;
    void show(const char *s) const {std::cout << s << '\n';}
};

3.3 函数重定义的流程图

graph TD;
    A[调用函数] --> B{函数签名是否相同};
    B -- 是 --> C{是否为虚函数};
    C -- 是 --> D[动态绑定,根据对象类型调用];
    C -- 否 --> E[静态绑定,根据指针或引用类型调用];
    B -- 否 --> F[新函数,隐藏基类同名函数];

3.4 函数重定义的表格总结

情况 处理方式
相同签名,非虚函数 派生类函数隐藏基类函数,根据指针或引用类型调用
相同签名,虚函数 动态绑定,根据对象类型调用
不同签名 新函数,隐藏基类同名函数

4. 虚函数

4.1 虚函数的基本概念

当使用对象调用函数时,调用的是对象所属类的函数。但使用指针或引用调用重定义的基类函数时,若未声明为虚函数,调用的是指针或引用类型对应的函数,这称为静态绑定。例如:

Product p("A", 1);
Book b("B", 2, "Nice", "Many");

Product *ptr1 = &p;
ptr1->show();
Product *ptr2 = &b;
ptr2->show();
Product& ref = b;
ref.show();

上述代码中, ptr2 ref 虽指向或引用 Book 对象,但调用的是 Product 类的 show() 函数。

若在基类中将函数声明为虚函数,情况会不同。例如:

class Product
{
    ...
    virtual void show() const;
};

class Book : public Product
{
    ...
    virtual void show() const;
};

此时,当通过指针或引用调用虚函数时,会根据对象的实际类型调用相应函数,这称为动态绑定或运行时多态。

4.2 函数重写

当派生类的函数重定义基类的虚函数且签名相同时,称为函数重写。重写时,派生类函数的返回类型通常应与基类相同,或为更特化的类型。若签名不匹配,派生类函数不会重写基类函数,而是隐藏基类函数。

4.3 静态和动态类型

指针或引用有静态类型和动态类型。静态类型是声明时的类型,动态类型是指向或引用对象的实际类型。例如:

Product *ptr2 = &b;

ptr2 的静态类型是 Product* ,动态类型是 Book* 。使用虚函数时,编译器根据动态类型选择要调用的函数。

4.4 动态多态

使用虚函数时,函数的选择在程序运行时决定,实现了动态多态。例如:

int a;
Product *ptr;
cin >> a;
if(a == 1)
    ptr = new Product("A", 1);
else
    ptr = new Book("B", 2, "Nice", "Many");
ptr->show();

编译时,编译器不知道 ptr 指向的对象类型,运行时根据实际对象类型绑定 show() 函数。

编译器通常为每个类创建一个虚函数表(vtbl),存储虚函数的内存地址。调用虚函数时,编译器根据对象类型在 vtbl 中查找函数地址并执行。

4.5 虚函数的示例

#include <iostream>
using std::cout;

class A
{
public:
    void f() const {cout << "A ";}
    virtual void g() const {cout << "A ";}
    void v() const {cout << "A ";}
};

class B : public A
{
public:
    void f() const {cout << "B ";}
    virtual void g() const {cout << "B ";}
    virtual void v() const {cout << "B ";}
};

int main()
{
    B b;
    A &r = b;

    r.f();
    r.g();
    r.v();
    return 0;
}

上述代码中, f() 不是虚函数,调用 A.f() g() 是虚函数,调用 B.g() v() 在基类未声明为虚函数,调用 A.v()

4.6 虚函数的表格总结

函数类型 绑定方式 调用依据
非虚函数 静态绑定 指针或引用的类型
虚函数 动态绑定 对象的实际类型

4.7 虚函数的流程图

graph TD;
    A[调用函数] --> B{是否为虚函数};
    B -- 是 --> C[根据对象实际类型调用];
    B -- 否 --> D[根据指针或引用类型调用];

5. 使用 override final 说明符

5.1 override 说明符

C++11 引入 override 说明符,用于确保派生类正确重写基类的虚函数。例如:

class Book : public Product
{
    ...
    void show() override;
};

使用 override 可避免因签名不匹配导致的错误。例如:

#include <iostream>
using std::cout;

class A
{
public:
    void f() const {cout << "A ";}
    virtual void g() const {cout << "A ";}
};

class B : public A
{
public:
    virtual void f() const {cout << "B ";}
    virtual void g(int i) const {cout << i;}
};

int main()
{
    B b;
    A &r = b;

    r.f();
    r.g();
    return 0;
}

上述代码中,由于 f() A 类未声明为虚函数, r.f() 调用 A.f() g() 签名不匹配,未重写基类函数。使用 override 可让编译器检测此类错误。

5.2 final 说明符

final 说明符可用于防止类被继承或虚函数被重写。例如:

class Book : public Product
{
    ...
    virtual void show() override final;
};

class Technical_Book : public Book
{
    ...
    virtual void show(); // 错误
};

override final 不是保留关键字,但为避免混淆,不建议用作变量名。

6. 虚函数的使用

6.1 接受基类引用的函数

虚函数常用于接受基类对象引用的函数中。例如:

#include <iostream>

class Shape
{
public:
    virtual void draw() const {std::cout << "Shape ";}
};

class Circle : public Shape
{
public:
    virtual void draw() const override {std::cout << "Circle ";}
};

void f(const Shape& s)
{
    s.draw();
}

int main()
{
    Circle c;
    f(c);
    return 0;
}

上述代码中, f() 接受 Shape 类的引用,可传入 Circle 对象,根据对象实际类型调用 Circle::draw() 函数。

6.2 管理不同类型对象

可使用基类指针数组或智能指针向量管理不同类型的对象。例如:

int main()
{
    const int SIZE = 10;

    int i, ch;
    float prc;
    string code, title, auth;
    Product *p[SIZE];

    for(i = 0; i < SIZE; i++)
    {
        cout << "Enter choice (1 for Product): ";
        cin >> ch;

        cout << "Enter code: ";
        cin >> code;
        cout << "Enter price: ";
        cin >> prc;
        if(ch == 1)
            p[i] = new Product(code, prc);
        else
        {
            cout << "Enter title: ";
            cin >> title;
            cout << "Enter auth: ";
            cin >> auth;
            p[i] = new Book(code, prc, title, auth);
        }
    }

    for(i = 0; i < SIZE; i++)
    {
        p[i]->show();
        delete p[i];
    }
    return 0;
}

通过虚函数,可根据指针指向的实际对象类型调用相应的 show() 函数。

6.3 调用特定虚函数

若要强制调用特定虚函数,可使用作用域解析运算符。例如:

#include <iostream>

class A
{
public:
    virtual void f() const {std::cout << "A";}
};

class B : public A
{
public:
    virtual void f() const override;
};

void B::f() const
{
    A::f();
}

int main()
{
    B b;
    A &r = b;
    A *p = &b;

    b.f();
    r.A::f();
    p->A::f();
    return 0;
}

上述代码中,通过作用域解析运算符可静态解析调用 A 类的 f() 函数。

6.4 虚函数与访问说明符

虚函数在派生类中的访问说明符可与基类不同,但使用基类指针或引用调用时,以基类的访问说明符为准。例如:

#include <iostream>

class A
{
public:
    virtual void f() const {std::cout << "A" << '\n';}
};

class B : public A
{
private:
    virtual void f() const override {std::cout << "B" << '\n';}
};

int main()
{
    B b;

    A *p = &b;
    b.f(); // 错误
    p->f(); // 输出 B
    return 0;
}

6.5 虚函数与预定义参数

虚函数可使用预定义参数,但通过基类指针或引用调用时,使用基类的预定义参数。例如:

#include <iostream>

class A
{
public:
    virtual void f(int i = 10) const {std::cout << "A:" << i << '\n';}
};

class B : public A
{
public:
    virtual void f(int i = 20) const override {std::cout << "B:" << i << '\n';}
};

int main()
{
    B b;
    A &r = b;

    b.f(); // 输出 B:20
    r.f(); // 输出 B:10
    return 0;
}

为避免问题,不建议在派生类中更改虚函数的默认参数。

6.6 虚析构函数

析构函数可声明为虚函数。若基类可能指向派生类对象,应将基类析构函数声明为虚函数,以确保正确释放内存。例如:

class Book : public Product
{
    ...
    char *n;
    ~Book();
    ...
};

Book::Book(const string &c, float p, const string &t, const string &a) : Product(c, p)
{
    n = new char[100];
    ...
}

Book::~Book()
{
    delete[] n;
}

int main()
{
    Product *p = new Book("B", 2, "Nice", "Many");
    delete p; // 应调用 Book 析构函数
    return 0;
}

Product 析构函数未声明为虚函数, delete p 只会调用 Product 析构函数,导致内存泄漏。

6.7 构造函数和析构函数中调用虚函数

在基类构造函数或析构函数中调用虚函数时,调用的是当前类的函数,因为此时对象的构造或析构未完成。例如:

#include <iostream>

class A
{
public:
    A() {f();}
    ~A() {f();}
    virtual void f() const {std::cout << "A" << '\n';}
    void test() const {f();}
};

class B : public A
{
public:
    virtual void f() const override {std::cout << "B" << '\n';}
};

int main()
{
    B b;

    b.test();
    return 0;
}

上述代码中,程序输出 A B A

6.8 虚函数使用的表格总结

情况 处理方式
调用特定虚函数 使用作用域解析运算符
虚函数与访问说明符 使用基类的访问说明符
虚函数与预定义参数 使用基类的预定义参数
虚析构函数 基类析构函数声明为虚函数
构造和析构中调用虚函数 调用当前类的函数

6.9 虚函数使用的流程图

graph TD;
    A[使用虚函数] --> B{是否为构造或析构函数中调用};
    B -- 是 --> C[调用当前类的函数];
    B -- 否 --> D{是否使用基类指针或引用};
    D -- 是 --> E{是否为虚析构函数};
    E -- 是 --> F[根据对象实际类型调用析构函数];
    E -- 否 --> G{是否指定特定类};
    G -- 是 --> H[调用指定类的函数];
    G -- 否 --> I[根据对象实际类型调用函数];
    D -- 否 --> J[根据对象类型调用函数];

7. 注意事项

7.1 转换错误示例

在使用数组传递对象时,要注意类大小不同可能导致的错误。例如:

#include <iostream>

class A
{
public:
    virtual void show() const {std::cout << "A\n";}
};

class B : public A
{
public:
    int a;
    int b;
    virtual void show() const override {std::cout << "B\n";}
};

void f(A* p, int s)
{
    for(int i = 0; i < s; i++)
        p[i].show();
}

int main()
{
    A a[5];
    f(a, 5);

    B b[3];
    f(b, 3);
    return 0;
}

上述代码中,由于 B 类大小大于 A 类, f(b, 3) 调用可能导致程序崩溃。可使用 vector 替代数组解决此问题:

template <typename T> void f(const vector<T>& v)
{
    for(int i = 0; i < v.size(); i++)
        v[i].show();
}

int main()
{
    vector<A> a(5);
    f(a);
    vector<B> b(3);
    f(b);
    return 0;
}

7.2 实践建议

学习 C++ 继承和多态的概念时,要多实践,通过实验书中的示例和练习,编写自己的程序,从错误中学习,逐渐熟悉这些概念。

8. 练习题解答

8.1 练习题 1

实现 TCP/IP 模型的层次类结构,代码如下:

#include <iostream>
#include <string>
using std::cout;
using std::cin;
using std::string;

class Physical
{
protected:
    int speed;
};

class Data_Link : public Physical
{
protected:
    string MAC_addr;
};

class Network : public Data_Link
{
protected:
    string IP_addr;
};

class Transport : public Network
{
protected:
    string prtcl;
};

class Application : public Transport
{
protected:
    int port;
public:
    void get();
    void show() const;
};

void Application::get()
{
    cout << "Enter speed: ";
    cin >> speed;
    cout << "Enter MAC_addr: ";
    cin >> MAC_addr;
    cout << "Enter IP_addr: ";
    cin >> IP_addr;
    cout << "Enter transport protocol: ";
    cin >> prtcl;
    cout << "Enter application port: ";
    cin >> port;
}

void Application::show() const
{
    cout << "\nS:" << speed << " MAC:" << MAC_addr << " IP:" << IP_addr << " T:" << prtcl << " P:" << port << '\n';
}

int main()
{
    Application app;
    app.get();
    app.show();
    return 0;
}

8.2 练习题 2

定义类层次结构,代码如下:

#include <iostream>
#include <string>
using std::cout;
using std::string;

class A
{
private:
    string name;
public:
    A(const string& s) : name(s) {}
    virtual void show() const {cout << name << '\n';}
};

class B : public A
{
private:
    string name;
public:
    B(const string& s1, const string& s2) : A(s2), name(s1) {}
    virtual void show() const {cout << name << '\n';}
};

class C : public B
{
private:
    string name;
public:
    C(const string& s1, const string& s2, const string& s3) : B(s2, s3), name(s1) {}
    virtual void show() const {cout << name << '\n';}
};

int main()
{
    C c("Last", "Sec", "First");
    A *p = &c;
    p->show();
    return 0;
}

8.3 练习题 3

找出程序中的错误,错误分析如下:
- show() 函数无法直接访问 p ,因为 p A 类的私有成员。
- B 类的构造函数未调用 A 类的构造函数,无法创建 A 对象。
- A 类没有 show() 函数, r.show(20) 调用错误。

8.4 练习题 4

定义 Product Book 类,代码如下:

#include <iostream>
#include <string>
using std::cout;
using std::string;

class Product
{
protected:
    int code;
public:
    Product(int c);
    virtual ~Product() {};
    virtual void show() const;
};

class Book : public Product
{
private:
    string title;
public:
    Book(string t, int c);
    virtual void show() const override;
};

void f(const Product *p1, const Product *p2);

Product::Product(int c)
{
    code = c;
}

void Product::show() const
{
    cout << "C:" << code << '\n';
}

Book::Book(string t, int c) : Product(c)
{
    title = t;
}

void Book::show() const
{
    cout << "C:" << code << " T:" << title << '\n';
}

int main()
{
    Product prod(100);
    Book b("C++", 200);

    Product *p1 = &prod;
    Product *p2 = &b;
    f(p1, p2);
    return 0;
}

void f(const Product *p1, const Product *p2)
{
    p1->show();
    p2->show();
}

8.5 练习题 5

程序输出为 1 A.v() 1 B.v() 。分析如下:
- t1(b) 调用时,仅传递 b 对象的 A 部分, f() 显示 1 v() 显示 A.v()
- t2(b) 调用时, r 引用 b 对象, f() 非虚函数,调用 A.f() 显示 1 v() 是虚函数,调用 B.v()

8.6 练习题 6

定义 Fruit Apple 类,代码如下:

#include <iostream>
#include <string>
using std::cout;
using std::string;

class Fruit
{
private:
    string name;
    int cal;
public:
    Fruit(const string& s, int c) : name(s), cal(c) {}
    virtual ~Fruit() {};
    virtual int operator+(int num);
};

class Apple : public Fruit
{
private:
    int antiox;
public:
    Apple(int ant, const string& name, int cal) : Fruit(name, cal), antiox(ant) {}
    virtual int operator+(int num) override;
};

int Fruit::operator+(int num)
{
    cal += num;
    return cal;
}

void f(Fruit& p, int num);

int Apple::operator+(int num)
{
    antiox += num;
    return antiox;
}

int main()
{
    Fruit fr("F1", 10);
    Apple ap(20, "F2", 30);

    f(fr, 5);
    f(ap, 5);
    return 0;
}

void f(Fruit& p, int num)
{
    cout << p+num << '\n';
}

8.7 练习题 7

程序输出为 A C C.f() A.v() ~B ~A 。分析如下:
- 创建 C 对象时,调用 A C 的构造函数,输出 A C
- p->f() 调用 C.f() ,因为 f() 是虚函数。
- p->v() 调用 A.v() ,因为 v() 不是虚函数。
- 销毁对象时,由于 B 的析构函数不是虚函数,不调用 C 的析构函数,输出 ~B ~A

8.8 练习题 8

为类添加适当函数,代码如下:

#include <iostream>
#include <cstring>

class A
{
private:
    char *s;
public:
    explicit A(const char str[]);
    A();
    virtual ~A();
    A(const A& a);
    A& operator=(const A& a);
    void show() const;
};

A::A(const char str[])
{
    s = new char[strlen(str)+1];
    strcpy(s, str);
}

A::A()
{
    s = nullptr;
}

A::~A()
{
    delete[] s;
}

A& A::operator=(const A& a)
{
    if(this == &a)
        return *this;

    delete[] s;
    s = new char[strlen(a.s)+1];
    strcpy(s, a.s);
    return *this;
}

A::A(const A& a)
{
    s = new char[strlen(a.s)+1];
    strcpy(s, a.s);
}

void A::show() const
{
    std::cout << s << '\n';
}

class B : public A
{
private:
    char *s;
public:
    explicit B(const char str[]);
    B();
    virtual ~B();
    B(const B& b);
    B& operator=(const B& b);
    void show() const;
};

B::B(const char str[]) : A(str)
{
    s = new char[strlen(str)+1];
    strcpy(s, str);
}

B::B()
{
    s = nullptr;
}

B::~B()
{
    delete[] s;
}

B& B::operator=(const B& b)
{
    if(this == &b)
        return *this;

    A::operator=(b);
    delete[] s;
    s = new char[strlen(b.s)+1];
    strcpy(s, b.s);
    return *this;
}

B::B(const B& b) : A(b)
{
    s = new char[strlen(b.s)+1];
    strcpy(s, b.s);
}

void B::show() const
{
    std::cout << s << '\n';
    A::show();
}

int main()
{
    B b1("abc");
    B b2(b1);
    B b3;
    b3 = b2;
    b3.show();
    return 0;
}

8.9 练习题 9

定义 Company 相关类,代码如下:

#include <iostream>
#include <string>
#include <vector>
using std::cout;
using std::string;
using std::vector;

class Company
{
private:
    string cmp_name;
    string tax_num;
    int empl;
public:
    Company(const string& cmp_name, const string& tax_num, int empl);
    Company() {cmp_name = "None"; tax_num = empl = -1;}
    virtual ~Company() {};
    virtual void show() const;
};

class Software_Cmp : public Company
{
private:
    string prod;
    int prgr;
public:
    Software_Cmp(const string& prod, int prgr);
    virtual void show() const override;
};

class Finance_Cmp : public Company
{
private:
    string srvc;
    int acnt;
public:
    Finance_Cmp(const string& srvc, int acnt);
    virtual void show() const override;
};

Company::Company(const string& cmp_name, const string& tax_num, int empl)
{
    this->cmp_name = cmp_name;
    this->tax_num = tax_num;
    this->empl = empl;
}

void Company::show() const
{
    cout << "N:" << cmp_name << " E:" << empl << '\n';
}

Software_Cmp::Software_Cmp(const string& prod, int prgr)
{
    this->prod = prod;
    this->prgr = prgr;
}

void Software_Cmp::show() const
{
    cout << "N:" << prod << " E:" << prgr << '\n';
}

Finance_Cmp::Finance_Cmp(const string& srvc, int acnt)
{
    this->srvc = srvc;
    this->acnt = acnt;
}

void Finance_Cmp::show() const
{
    cout << "N:" << srvc << " E:" << acnt << '\n';
}

int main()
{
    vector<Company*> v;

    Company cmp("Alpha", "12345", 100);
    Software_Cmp sw_cmp("Software_Product", 20);
    Finance_Cmp fin_cmp("Finance_Service", 10);

    v.push_back(&cmp);
    v.push_back(&sw_cmp);
    v.push_back(&fin_cmp);

    for(int i = 0; i < v.size(); i++)
        v[i]->show();
    return 0;
}

8. 未解决的练习题

8.1 练习题 1

使用公共继承定义类层次结构 A B C ,每个类包含一个整数私有成员和公共函数 get() show() 。要求程序读取三个整数并存储在相应类成员中,然后依次显示这些值。

8.2 练习题 2

定义 Circle Sphere 类,使程序能正确初始化对象并显示 Sphere 对象的颜色和体积。

8.3 练习题 3

在之前的类层次结构中,将每个数据成员的访问类型改为私有, Application 类不包含 get() show() 函数,添加构造函数使程序能正确存储和显示数据。

8.4 练习题 4

分析程序的输出结果,涉及类的构造、虚函数调用等知识。

8.5 练习题 5

定义 Shape Rect 类,使程序能读取数据并进行相应计算和显示。

8.6 练习题 6

定义 Person Student Employee 类,实现 f() 函数,使程序能正确显示对象的私有成员值。

8.7 练习题 7

定义 Person Student Phd_Student 类,添加适当函数使程序能正确显示对象的私有成员。

通过对这些练习题的分析和解答,能更好地掌握 C++ 继承和多态的知识。同时,要注意实践和总结,不断提高编程能力。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值