20、C++ 继承深入探讨

C++ 继承深入探讨

1. 抽象类与纯虚函数

在设计应用程序的类时,一些看似不相关的类可能有共同的数据。例如,开发一个管理直角三角形和矩形的应用程序。可以定义 Triangle 类来管理三角形,其成员为三角形的两条直角边;定义 Rectangle 类来管理矩形,成员为矩形的两条垂直边。还可以在两个类中添加显示形状尺寸的函数(如 show() )、计算面积的函数(如 area() )和计算周长的函数(如 perim() )。

如果直接使用继承,将其中一个类作为基类,由于这两个形状本身并无关联,这种方法并不合适。更好的编程方法是应用抽象的概念,定义一个 Shape 类,包含两个类的共同成员(如两条垂直边)和函数(如 show() )。 Shape 作为基类, Triangle Rectangle 继承它。将每个类中实现不同的函数声明为虚函数,如 area() ,因为不同形状的面积计算方式不同。由于 Shape 并不代表具体形状,不需要实现 area() 。在 C++ 中,可以将函数声明为纯虚函数,即在函数声明末尾添加 = 0 语法。

以下是示例代码:

#include <iostream> // Example 21.1
#include <vector>
#include <cmath>
using std::cout;
using std::cin;
using std::vector;

class Shape
{
protected:
    float l;
    float h;
public:
    Shape(float len, float hght) {l = len; h = hght;}
    virtual ~Shape() {}; 
    void show() {cout << "L:" << l << " H:" << h << '\n';}
    virtual void area() const = 0; // Pure virtual function.
    virtual void perim() const = 0; // Pure virtual function.
};

class Triangle : public Shape
{
public:
    Triangle(float l, float h) : Shape(l, h) {} 
    virtual void area() const {cout << "A:" << l*h/2 << '\n';}
    virtual void perim() const {cout << "P:" << l+h+sqrt(l*l+h*h) << '\n';} 
};

class Rectangle : public Shape
{
public:
    Rectangle(float l, float h) : Shape(l, h) {}
    virtual void area() const {cout << "A:" << l*h << '\n';}
    virtual void perim() const {cout << "P:" << 2*(l+h) << '\n';}
};

int main()
{
    const int SIZE = 2;
    int i, ch, len, hght;
    vector<Shape*> vec_sh(SIZE);

    for(i = 0; i < SIZE; i++)
    {
        cout << "Enter choice (1 for Triangle): ";
        cin >> ch;
        cout << "Enter length: ";
        cin >> len;
        cout << "Enter height: ";
        cin >> hght;
        if(ch == 1)
            vec_sh[i] = new Triangle(len, hght);
        else
            vec_sh[i] = new Rectangle(len, hght);
    }
    for(i = 0; i < SIZE; i++)
    {
        vec_sh[i]->show();
        vec_sh[i]->area();
        vec_sh[i]->perim();
        delete vec_sh[i];
    }
    return 0;
}

包含至少一个纯虚函数的类(如 Shape )称为抽象类。抽象类不能创建对象,只能作为基类使用,它代表了相关实体的共同特征。抽象类中的纯虚函数在派生类中未定义时,派生类也会成为抽象类。

2. 私有和保护继承

除了公共继承,类还可以通过私有和保护访问进行派生。

2.1 私有继承

使用私有继承时,基类的公共和保护成员成为派生类的私有成员,只能从派生类的函数中访问,不能从派生类外部访问。示例代码如下:

#include <iostream> // Example 21.2

class A
{
protected:
    int r;
    void sub(int i) {v-=i;}
public:
    int v;
    A() : v(1), r(2) {};
    void add(int i) {r+=i;}
};

class B : private A
{
public:
    void show();
};

void B::show()
{
    add(3);
    sub(4);
    std::cout << v+r << '\n';
}

int main()
{
    B b;
    b.show();
    b.add(5); // Illegal action.
    b.sub(6); // Illegal action.
    std::cout << b.v << '\n'; // Illegal action.
    return 0;
}

2.2 保护继承

使用保护继承时,基类的公共和保护成员成为派生类的保护成员,同样只能从派生类的函数中访问,不能从派生类外部访问。但当从派生类再派生新类时,与私有继承不同,新派生类可以直接访问基类的成员。示例代码如下:

#include <iostream> // Example 21.3

class A
{
protected:
    void f() const {std::cout << v << ' ';}
public:
    int v;
    A() : v(10) {};
};

class B : protected A
{
protected:
    int r;
    void g() const {std::cout << r << '\n';}
public:
    B() : r(20) {};
};

class C : protected B
{
public:
    void h();
};

void C::h()
{
    v = r = 30;
    g();
    f();
}

int main()
{
    C c;
    c.h();
    return 0;
}

访问基类成员的规则总结如下表:
| 基类成员 | 公共继承 | 保护继承 | 私有继承 |
| — | — | — | — |
| 公共成员变为 | 派生类的公共成员 | 派生类的保护成员 | 派生类的私有成员 |
| 保护成员变为 | 派生类的保护成员 | 派生类的保护成员 | 派生类的私有成员 |
| 私有成员变为 | 派生类的私有成员 | 派生类的私有成员 | 派生类的私有成员 |

3. 访问重定义

有时我们希望派生类对基类的一个或多个成员的访问与继承类型指定的访问不同。可以使用 using 声明来改变成员的访问权限。示例代码如下:

#include <iostream> // Example 21.4

class A
{
private:
    int p;
protected:
    int v;
public:
    void add(int i) {v+=i;}
    void sub(int i) {v-=i;}
};

class B : public A
{
private:
    using A::add;
public:
    using A::v; 
    void f() {add(1);}
};

int main()
{
    B b;
    b.f();
    b.sub(1); // OK.
    b.v = 10; // OK.
    b.add(2); // Wrong.
    return 0;
}

需要注意的是,不能使用 using 改变基类私有成员的访问权限,因为私有成员不能在派生类中访问。

4. 多重继承

多重继承允许一个类从多个基类派生。多重继承通常比单继承使用频率低,适用于将不同类的数据合并到一个新类的情况,但由于复杂度增加,应合理使用。

4.1 多重继承示例及问题

class A
{
public:
    void show() const {...}
    ...
};

class B
{
public:
    void show() const {...}
    ...
};

class C : public A, public B
{
    ...
};

int main()
{
    C c;
    c.show(); // Ambiguity, which show() will be called;
    ...
}

在上述代码中, C 类从 A B 类公共继承,由于 A B 类都有 show() 函数,调用 c.show() 时会产生歧义。

4.2 解决歧义的方法

4.2.1 使用作用域运算符 ::
c.A::show(); // The show() of A is called.
4.2.2 使用 using 声明
class C : public A, public B
{
public:
    using A::show;
    ...
};

此时,调用 c.show() 会调用 A 类的 show() 函数。

4.3 多重基对象的创建

当类层次结构中基类和派生类之间存在多条路径时,会创建基类的多个子对象,导致其成员有多个副本。示例代码如下:

#include <iostream> // Example 21.5
using std::cout;

class A
{
private:
    int r;
public:
    A(int k) : r(k) {cout << " A()";}
    virtual ~A() {cout << " ~A()";}
    void show_A() const {cout << " A:" << r;}
};

class B : public A
{
private:
    int t;
public:
    B(int m) : A(m), t(m) {cout << " B()";}
    ~B() {cout << " ~B()";}
    void show_B() const;
};

void B::show_B() const
{
    cout << " B:" << t;
    show_A();
}

class C : public A
{
private:
    int v;
public:
    C(int p) : A(p), v(p) {cout << " C()";}
    ~C() {cout << " ~C()";}
    void show_C() const;
};

void C::show_C() const
{
    cout << " C:" << v;
    show_A();
}

class BC : public B, public C
{
private:
    int z;
public:
    BC(int x) : B(x), C(x+1), z(2*x) {cout << " BC()\n";}
    ~BC() {cout << "~BC()";}
    void show_BC() const {cout << " BC:" << z << '\n';}
};

int main()
{
    BC bc(10);
    bc.show_B();
    bc.show_C();
    bc.show_BC();
    return 0;
}

在这个例子中, BC 对象包含两个 A 类的子对象,调用 bc.show_A() 会产生歧义。可以使用作用域运算符指定要调用的 A 子对象:

bc.B::show_A(); 
bc.C::show_A(); 

4.4 更现实的多重基对象示例

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

class Person
{
private:
    string name;
    int code;
public:
    Person() {name = "None"; code = 0;}
    Person(const string& n, int c) : name(n), code(c) {}
    virtual ~Person() {}
    virtual void show() const;
};

void Person::show() const
{
    cout << "\nName:" << name << ' ' << "Code:" << code << ' ';
}

class Programmer : public Person
{
private:
    string cmp; 
public:
    Programmer() {cmp = "Any";}
    Programmer(const string& n, int c, const string& s) : Person(n, c), cmp(s) {}
    virtual void show() const override;
};

void Programmer::show() const
{
    Person::show();
    cout << "Company:" << cmp;
}

class Author : public Person
{
private:
    int titles; 
public:
    Author() {titles = 0;}
    Author(const string& n, int c, int t) : Person(n, c), titles(t) {}
    virtual void show() const override;
};

void Author::show() const
{
    Person::show();
    cout << "Titles:" << titles;
}

class ProgAuth : public Programmer, public Author
{
};

int main()
{
    int i;
    Person per("First", 1);
    Programmer pro("Second", 2, "Net");
    Author auth("Third", 3, 5);
    Person *p[3] = {&per, &pro, &auth};

    for(i = 0; i < 3; i++)
        p[i]->show();

    ProgAuth pro_auth;
    pro_auth.show(); // Compilation error.

    return 0;
}

在这个例子中, ProgAuth 对象包含两个 Person 子对象,调用 pro_auth.show() 会产生歧义。可以使用作用域运算符或在 ProgAuth 类中定义 show() 函数来解决。

4.5 多重继承对象构造顺序

对象构造时,基类子对象按照基类在类声明中从左到右的顺序构造。析构时,顺序相反。例如:

graph LR
    classDef startend fill:#F5EBFF,stroke:#BE8FED,stroke-width:2px;
    classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px;

    A(A):::process --> C(C):::process
    B(B):::process --> C
    C --> G(G):::process
    D(D):::process --> E(E):::process
    E --> F(F):::process
    F --> G

对于以下继承结构:

class A {...};
class B {...};
class C : public B, public A {...};
class D {...};
class E : public D {...};
class F : public E {...};
class G : public F, public C {...};

int main()
{
    G g;
    ...
}

子对象的构造顺序是 D E F B A C G ,析构顺序相反。

5. 虚基类

当一个对象从多个派生自同一个基类的类中派生时,声明该基类为虚基类可以使对象只包含该基类的一个子对象,避免重复继承带来的问题。

5.1 虚基类示例

#include <iostream> // Example 21.7
using std::cout;

class A
{
    ...
    A(int k) : r(k) {cout << " A()";}
    A() {cout << " Def_A()"; r = 30;}
    virtual ~A() {cout << " ~A()";}
};

class B : virtual public A
{
    ...
    B(int m) : A(m), t(m) {cout << " B()";}
    ~B() {cout << " ~B()";}
};

class C : virtual public A
{
    ...
    C(int p) : A(p), v(p) {cout << " C()";}
    ~C() {cout << " ~C()";}
};

class BC : public B, public C
{
    ...
    BC(int x) : B(x), C(x+1), z(2*x) {cout << " BC()";}
    ~BC() {cout << " ~BC()";}
};

int main()
{
    BC bc(10);
    ...
    return 0;
}

在上述代码中, B C 类虚继承自 A 类, BC 对象只包含一个 A 子对象。调用 bc.show_A() 不会产生歧义。

5.2 虚基类构造顺序变化

使用虚基类时,构造函数的调用方式会发生变化。中间类不再向虚基类传递信息,构造虚基类子对象的责任交给最遥远的派生类。如果虚基类没有默认构造函数,编译器会报错。可以在最遥远的派生类的构造函数中直接调用虚基类的特定构造函数。例如:

BC(int x) : A(x+5), B(x), C(x+1), z(2*x) {cout << " BC()";}

5.3 多个虚基类对象构造顺序

当一个类有多个虚基类时,虚基类子对象按照它们在类声明中从左到右的顺序构造,然后再构造非虚基类子对象。例如:

class A {...};
class B {...};
class C {...};
class D {...};
class E : public D, public C, virtual public B, virtual public A {...}

E e1;
E e2(e1);

e1 对象构造时,子对象的构造顺序是 B A D C E 。析构顺序相反。

5.4 混合虚基类和非虚基类

一个基类在某些派生类中可以是虚基类,在其他派生类中可以是非虚基类。当一个新类从这些派生类中派生时,该类会包含对应虚路径的一个基类子对象和每个非虚路径的单独子对象。示例代码如下:

#include <iostream> // Example 21.8
using std::cout;

class A
{
public:
    A() {cout << " A()";}
};

class B : virtual public A
{
public:
    B() {cout << " B()";}
};

class C : virtual public A
{
public:
    C() {cout << " C()";}
};

class D : public A
{
public:
    D() {cout << " D()";}
};

class E : public A
{
public:
    E() {cout << " E()";}
};

class F : public B, public C, public D, public E
{
public:
    F() {cout << " F()";}
};

int main()
{
    F f;
    return 0;
}

f 对象包含一个对应 B C 类的 A 子对象,以及分别对应 D 类和 E 类的 A 子对象。

6. 继承与静态成员

如果在基类中定义了静态成员,无论从该基类派生多少个类,静态成员只创建一次。静态成员的访问规则与普通成员相同。示例代码如下:

#include <iostream> // Example 21.9

class A
{
public:
    static inline int v = 10;
};

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

class C : public A
{
public:
    void g() {v = 30;}
};

int main()
{
    B b;
    C c;

    b.f();
    c.g();
    std::cout << b.v << ' ' << c.v << '\n'; 
    return 0;
}

由于 v 是静态成员, f() g() 函数会依次改变其值,程序输出 30 30

7. 练习题及解答

7.1 练习题1

找出以下程序中的错误:

#include <iostream>

class A
{
public:
    int p;
    A(int a) {p = a;}
};

class B
{
public:
    int p;
    B() {p = 20;}
    void show(const char str[]) const {std::cout << str << '\n';}
};

class C : public A, public B
{
private:
    int v;
public:
    C() : v(10) {}
    void show() const {std::cout << v << '\n';}
};

int main()
{
    C c;
    c.p = 10;
    c.show("test");
    return 0;
}

答案
- 创建 c 对象时,由于 A 类没有默认构造函数,无法构造 A 子对象。应在 C 类构造函数中调用 A 类的构造函数,如 C() : A(5), v(10) {}
- 访问 c.p 时,由于 A B 类都有成员 p ,编译器不知道访问哪个,应使用作用域运算符指定,如 c.A::p = 10;
- 调用 show() 时, C 类的 show() 函数隐藏了 B 类的 show() 函数。调用 C 类的 show() 应写 c.show() ,调用 B 类的 show() 应写 c.B::show("test");

7.2 练习题2

定义 A B 类,以及从 A B 公共派生的 C 类,从 B 公共派生的 D 类,从 C D 公共派生的 E 类。使 E 对象只包含一个 B 类子对象。编写程序实现该继承结构,并显示对象的构造和析构顺序。

#include <iostream>
using std::cout;

class A
{
public:
    A() {cout << "A()";}
    virtual ~A() {cout << " ~A()";}
};

class B
{
public:
    B() {cout << " B()";}
    virtual ~B() {cout << " ~B()";}
};

class C : virtual public A, virtual public B
{
public:
    C() {cout << " C()";}
    ~C() {cout << " ~C()";}
};

class D : public C
{
public:
    D() {cout << " D()";}
    ~D() {cout << " ~D()";}
};

class E : public D
{
public:
    E() {cout << " E()";}
    ~E() {cout << " ~E()";}
};

class F : public D, public E
{
public:
    F() {cout << " F()";}
    ~F() {cout << "\n~F()";}
};

int main()
{
    F f;
    return 0;
}

程序输出:

A()    B()    C()    D()     C()    E()    F()
~F()   ~E()   ~C()   ~D()    ~C()   ~B()   ~A()

7.3 练习题3

定义抽象类 Person ,包含私有成员 name code 。定义从 Person 公共派生的 Student 类,包含私有成员 grd[5] ;定义从 Person 公共派生的 Employee 类,包含私有成员 fee[3] 。添加适当的函数使以下程序工作:

int main()
{
    Person *p;
    Student s; 
    Employee e; 
    p = &s;
    (*p)[3] = 5; 
    p = &e;
    (*p)[1] = 100; 
    return 0;
}

答案

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

class Person
{
private:
    string name;
    int code;
public:
    Person(const string& n = "None", int c = -1) : name(n), code(c) {}
    virtual ~Person() {}
    virtual float& operator[](int i) = 0;
};

class Student : public Person
{
private:
    float grd[5];
public:
    virtual float& operator[](int i);
};

class Employee : public Person
{
private:
    float fee[3];
public:
    virtual float& operator[](int i);
};

float& Student::operator[](int i)
{
    return grd[i];
}

float& Employee::operator[](int i)
{
    return fee[i];
}

7.4 练习题4

定义 School 类,包含私有成员 name 。定义从 School 公共派生的 Programming Network 类,分别包含私有成员 courses 。定义从 Programming Network 公共派生的 Student 类,包含私有成员 name code 。创建 Student 对象时只创建一个 School 类子对象。添加适当的函数使以下程序工作:

int main()
{
    Student s("Univ", "P.Tec", 100, 10, 12); 
    School& p = s;
    p.show(); 
    return 0;
}

答案

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

class School
{
private:
    string name;
public:
    School(const string& n) : name(n) {}
    School() {}
    virtual ~School() {}
    virtual void show() const;
};

void School::show() const
{
    cout << "School: " << name << '\n';
}

class Programming : virtual public School
{
private:
    int courses;
public:
    Programming(int num) : courses(num) {}
    virtual void show() const;
};

void Programming::show() const
{
    cout << "Prog_Courses: " << courses << '\n';
}

class Network : virtual public School
{
private:
    int courses;
public:
    Network(int num) : courses(num) {}
    virtual void show() const;
};

void Network::show() const
{
    cout << "Net_Courses: " << courses << '\n';
}

class Student : public Programming, public Network
{
private:
    string name;
    int code;
public:
    Student(const string& sch_n, const string& stud_n, int num, int prg_c, 
    int prg_n) : School(sch_n), Programming(prg_c), Network(prg_n), name(stud_n), 
    code(num) {}
    virtual void show() const;
};

void Student::show() const
{
    cout << "Stud:" << name << ' ' << "Code:" << code << '\n';
    Programming::show();
    Network::show();
    School::show();
}

7.5 练习题5

定义 Shape 类,包含私有成员 x y 和纯虚函数 area() 。定义从 Shape 公共派生的 Shape2D 类,包含私有成员 name ;定义从 Shape2D 公共派生的 Rect 类,包含私有成员 length height 。定义从 Shape 公共派生的 Shape3D 类,包含私有成员 colour 和纯虚函数 vol() ;定义从 Shape3D 公共派生的 Sphere 类,包含私有成员 radius Rect Sphere 类不是抽象类。添加适当的函数使以下程序工作:

int main()
{
    Sphere sph(1, 2, "Blue", 3); 
    Rect r(4, 5, "Rect", 6, 7); 
    Shape *p[2] = {&sph, &r};
    p[0]->show(); 
    p[1]->show(); 
    return 0;
}

答案

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

class Shape
{
private:
    float cntr_x;
    float cntr_y;
public:
    Shape(float x, float y) : cntr_x(x), cntr_y(y) {}
    virtual ~Shape() {}
    virtual void show() const {cout << "Cx:" << cntr_x << ' ' << "Cy:" << 
    cntr_y << '\n';}
    virtual void area() const = 0;
};

class Shape2D : public Shape
{
private:
    string name;
public:
    Shape2D(float x, float y, const string& n) : Shape(x, y), name(n) {}
};

class Rect : public Shape2D
{
private:
    float len;
    float hght;
public:
    Rect(float x, float y, const string& n, float l, float h) : 
    Shape2D(x, y, n), len(l), hght(h) {}
    virtual void area() const {cout << "\nArea(R):" << len*hght << '\n';}
    virtual void show() const;
};

void Rect::show() const
{
    area();
    Shape::show();
}

class Shape3D : public Shape
{
private:
    string col;
public:
    Shape3D(float x, float y, const string& n) : Shape(x, y), col(n) {}
    virtual void vol() const = 0;
};

class Sphere : public Shape3D
{
private:
    float rad;
public:
    Sphere(float x, float y, const string& n, float r) : Shape3D(x, y, n), 
    rad(r) {}
    virtual void area() const {cout << "Area(S):" << 4*3.14*rad*rad << '\n';}
    virtual void vol() const {cout << "Vol(S):" << (4/3.0)*3.14*rad*rad*rad 
    << '\n';}
    virtual void show() const;
};

void Sphere::show() const
{
    area();
    vol();
    Shape::show();
}

8. 未解决的练习题

8.1 练习题1

定义 A B 类,以及从 A B 公共派生的 C 类,从 B 公共派生的 D 类,从 C D 公共派生的 E 类。使 E 对象只包含一个 B 类子对象。编写程序实现该继承结构,并显示对象的构造和析构顺序,程序应输出:

B()    A()    C()    D()     E()
~E()   ~D()   ~C()   ~A()    ~B()

8.2 练习题2

定义抽象类 Product ,包含受保护成员 code price 。定义从 Product 公共派生的 Book 类,包含私有成员 publishing firm ;定义从 Product 公共派生的 Car 类,包含私有成员 horsepower 。添加适当的函数使以下程序工作:

int main()
{
    Book b(1, 2, "pub"); 
    Car c(4, 5, 6); 
    Product& p1 = b;
    Product& p2 = c;
    p1.show(); 
    p2.show(); 
    return 0;
}

8.3 练习题3

编写一个简化的车辆保险应用程序。定义 Vehicle 类,包含私有成员 owner name 。定义从 Vehicle 公共派生的 Car Moto 类,分别包含私有成员 plate number insurance cost 。定义从 Car Moto 公共派生的 Insurance 类,包含私有成员 insurance company name 。创建 Insurance 对象时只创建一个 Vehicle 类子对象。定义 f() 函数,接受一个 Insurance 元素的向量作为参数,显示支付最高保险费用的车主姓名、车辆详细信息和保险公司名称。假设只有一个车主支付最高费用。添加适当的函数使以下程序工作:

int main()
{
    vector<Insurance> v;
    Insurance ins1("Alpha", "1.2", 10, "3.4", 20, "Ins_1"), 
    ins2("Beta", "5.6", 30, "7.8", 40, "Ins_2"); 
    return 0;
}

通过对 C++ 继承的深入探讨,我们了解了抽象类、纯虚函数、私有和保护继承、访问重定义、多重继承、虚基类以及继承与静态成员等重要概念。这些知识对于编写复杂的 C++ 程序非常有帮助,但同时也需要注意它们带来的复杂度和潜在问题。在实际编程中,应根据具体需求合理使用这些特性。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值