c++规范之多态

Polymorphism

Before getting into this section, it is recommended that you have a proper understanding of pointers and class inheritance. If any of the following statements seem strange to you, you should review the indicated sections:

Statement:Explained in:
int a::b(int c) { }Classes
a->bData Structures
class a: public b { };Friendship and inheritance

Pointers to base class

One of the key features of derived classes is that a pointer to a derived class is type-compatible with a pointer to its base class. Polymorphism is the art of taking advantage of this simple but powerful and versatile feature, that brings Object Oriented Methodologies to its full potential.

We are going to start by rewriting our program about the rectangle and the triangle of the previous section taking into consideration this pointer compatibility property:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// pointers to base class
#include <iostream>
using namespace std;

class CPolygon {
  protected:
    int width, height;
  public:
    void set_values (int a, int b)
      { width=a; height=b; }
  };

class CRectangle: public CPolygon {
  public:
    int area ()
      { return (width * height); }
  };

class CTriangle: public CPolygon {
  public:
    int area ()
      { return (width * height / 2); }
  };

int main () {
  CRectangle rect;
  CTriangle trgl;
  CPolygon * ppoly1 = &rect;
  CPolygon * ppoly2 = &trgl;
  ppoly1->set_values (4,5);
  ppoly2->set_values (4,5);
  cout << rect.area() << endl;
  cout << trgl.area() << endl;
  return 0;
}
20
10


In function main, we create two pointers that point to objects of class CPolygon (ppoly1 and ppoly2). Then we assign references torect and trgl to these pointers, and because both are objects of classes derived fromCPolygon, both are valid assignment operations.

The only limitation in using *ppoly1 and *ppoly2 instead of rect and trgl is that both *ppoly1 and *ppoly2 are of typeCPolygon* and therefore we can only use these pointers to refer to the members thatCRectangle and CTriangle inherit from CPolygon. For that reason when we call thearea() members at the end of the program we have had to use directly the objectsrect and trgl instead of the pointers *ppoly1 and *ppoly2.

In order to use area() with the pointers to class CPolygon, this member should also have been declared in the classCPolygon, and not only in its derived classes, but the problem is that CRectangle and CTriangle implement different versions of area, therefore we cannot implement it in the base class. This is when virtual members become handy:

Virtual members

A member of a class that can be redefined in its derived classes is known as a virtual member. In order to declare a member of a class as virtual, we must precede its declaration with the keywordvirtual:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// virtual members
#include <iostream>
using namespace std;

class CPolygon {
  protected:
    int width, height;
  public:
    void set_values (int a, int b)
      { width=a; height=b; }
    virtual int area ()
      { return (0); }
  };

class CRectangle: public CPolygon {
  public:
    int area ()
      { return (width * height); }
  };

class CTriangle: public CPolygon {
  public:
    int area ()
      { return (width * height / 2); }
  };

int main () {
  CRectangle rect;
  CTriangle trgl;
  CPolygon poly;
  CPolygon * ppoly1 = &rect;
  CPolygon * ppoly2 = &trgl;
  CPolygon * ppoly3 = &poly;
  ppoly1->set_values (4,5);
  ppoly2->set_values (4,5);
  ppoly3->set_values (4,5);
  cout << ppoly1->area() << endl;
  cout << ppoly2->area() << endl;
  cout << ppoly3->area() << endl;
  return 0;
}
20
10
0


Now the three classes (CPolygon, CRectangle and CTriangle) have all the same members:width, height, set_values() and area().

The member function area() has been declared as virtual in the base class because it is later redefined in each derived class. You can verify if you want that if you remove thisvirtual keyword from the declaration of area() within CPolygon, and then you run the program the result will be0 for the three polygons instead of 20, 10 and 0. That is because instead of calling the correspondingarea() function for each object (CRectangle::area(), CTriangle::area() andCPolygon::area(), respectively), CPolygon::area() will be called in all cases since the calls are via a pointer whose type isCPolygon*.

Therefore, what the virtual keyword does is to allow a member of a derived class with the same name as one in the base class to be appropriately called from a pointer, and more precisely when the type of the pointer is a pointer to the base class but is pointing to an object of the derived class, as in the above example.

A class that declares or inherits a virtual function is called a polymorphic class.

Note that despite of its virtuality, we have also been able to declare an object of typeCPolygon and to call its own area() function, which always returns 0.

Abstract base classes

Abstract base classes are something very similar to our CPolygon class of our previous example. The only difference is that in our previous example we have defined a validarea() function with a minimal functionality for objects that were of classCPolygon (like the object poly), whereas in an abstract base classes we could leave thatarea() member function without implementation at all. This is done by appending=0 (equal to zero) to the function declaration.

An abstract base CPolygon class could look like this:

1
2
3
4
5
6
7
8
9
// abstract class CPolygon
class CPolygon {
  protected:
    int width, height;
  public:
    void set_values (int a, int b)
      { width=a; height=b; }
    virtual int area () =0;
};


Notice how we appended =0 to virtual int area () instead of specifying an implementation for the function. This type of function is called apure virtual function, and all classes that contain at least one pure virtual function areabstract base classes.

The main difference between an abstract base class and a regular polymorphic class is that because in abstract base classes at least one of its members lacks implementation we cannot create instances (objects) of it.

But a class that cannot instantiate objects is not totally useless. We can create pointers to it and take advantage of all its polymorphic abilities. Therefore a declaration like:

CPolygon poly;


would not be valid for the abstract base class we have just declared, because tries to instantiate an object. Nevertheless, the following pointers:

1
2
CPolygon * ppoly1;
CPolygon * ppoly2;


would be perfectly valid.

This is so for as long as CPolygon includes a pure virtual function and therefore it's an abstract base class. However, pointers to this abstract base class can be used to point to objects of derived classes.

Here you have the complete example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// abstract base class
#include <iostream>
using namespace std;

class CPolygon {
  protected:
    int width, height;
  public:
    void set_values (int a, int b)
      { width=a; height=b; }
    virtual int area (void) =0;
  };

class CRectangle: public CPolygon {
  public:
    int area (void)
      { return (width * height); }
  };

class CTriangle: public CPolygon {
  public:
    int area (void)
      { return (width * height / 2); }
  };

int main () {
  CRectangle rect;
  CTriangle trgl;
  CPolygon * ppoly1 = &rect;
  CPolygon * ppoly2 = &trgl;
  ppoly1->set_values (4,5);
  ppoly2->set_values (4,5);
  cout << ppoly1->area() << endl;
  cout << ppoly2->area() << endl;
  return 0;
}
20
10


If you review the program you will notice that we refer to objects of different but related classes using a unique type of pointer (CPolygon*). This can be tremendously useful. For example, now we can create a function member of the abstract base class CPolygon that is able to print on screen the result of thearea() function even though CPolygon itself has no implementation for this function:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// pure virtual members can be called
// from the abstract base class
#include <iostream>
using namespace std;

class CPolygon {
  protected:
    int width, height;
  public:
    void set_values (int a, int b)
      { width=a; height=b; }
    virtual int area (void) =0;
    void printarea (void)
      { cout << this->area() << endl; }
  };

class CRectangle: public CPolygon {
  public:
    int area (void)
      { return (width * height); }
  };

class CTriangle: public CPolygon {
  public:
    int area (void)
      { return (width * height / 2); }
  };

int main () {
  CRectangle rect;
  CTriangle trgl;
  CPolygon * ppoly1 = &rect;
  CPolygon * ppoly2 = &trgl;
  ppoly1->set_values (4,5);
  ppoly2->set_values (4,5);
  ppoly1->printarea();
  ppoly2->printarea();
  return 0;
}
20
10


Virtual members and abstract classes grant C++ the polymorphic characteristics that make object-oriented programming such a useful instrument in big projects. Of course, we have seen very simple uses of these features, but these features can be applied to arrays of objects or dynamically allocated objects.

Let's end with the same example again, but this time with objects that are dynamically allocated:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// dynamic allocation and polymorphism
#include <iostream>
using namespace std;

class CPolygon {
  protected:
    int width, height;
  public:
    void set_values (int a, int b)
      { width=a; height=b; }
    virtual int area (void) =0;
    void printarea (void)
      { cout << this->area() << endl; }
  };

class CRectangle: public CPolygon {
  public:
    int area (void)
      { return (width * height); }
  };

class CTriangle: public CPolygon {
  public:
    int area (void)
      { return (width * height / 2); }
  };

int main () {
  CPolygon * ppoly1 = new CRectangle;
  CPolygon * ppoly2 = new CTriangle;
  ppoly1->set_values (4,5);
  ppoly2->set_values (4,5);
  ppoly1->printarea();
  ppoly2->printarea();
  delete ppoly1;
  delete ppoly2;
  return 0;
}
20
10


Notice that the ppoly pointers:

1
2
CPolygon * ppoly1 = new CRectangle;
CPolygon * ppoly2 = new CTriangle;


are declared being of type pointer to CPolygon but the objects dynamically allocated have been declared having the derived class type directly.
<think>好的,我现在需要详细介绍一下C++中的继承与多态。首先,我得回顾一下自己之前学过的知识,确保没有记错的地方。然后,按照用户的要求,结构要清晰,分步骤解释,同时要注意使用正确的中文术语和正确的LaTeX格式。 首先,继承是面向对象编程的三大特性之一,另外两个是封装和多态。继承允许我们定义一个类(称为派生类或子类)基于另一个类(基类或父类)的属性和方法。这样可以实现代码重用,提高效率。我需要解释继承的基本概念,比如基类和派生类的关系,以及不同类型的继承:public、protected和private继承。其中public继承是最常用的,它保持基类的访问权限不变。protected和private继承会改变基类成员的访问权限,不过实际使用中较少见,可能需要简单提一下区别。 然后,多态性指的是同一操作作用于不同类的实例时,产生不同的执行结果。在C++中,多态主要通过虚函数和动态绑定来实现。这里需要详细说明虚函数的作用,如何声明虚函数,以及如何通过基类指针或引用来调用派生类的函数。还需要提到纯虚函数和抽象类,抽象类不能被实例化,只能作为接口使用。 接下来,可能需要结合代码示例来说明这些概念。例如,定义一个基类Animal,其中有一个虚函数makeSound(),然后派生类Dog和Cat重写这个函数。这样,当通过Animal指针指向Dog或Cat对象时,调用makeSound()会根据实际对象类型执行相应的函数,这就是运行时多态。 另外,需要注意构造函数和析构函数在继承中的处理。基类的析构函数应该声明为虚函数,以确保在删除基类指针指向的派生类对象时,能够正确调用派生类的析构函数,避免内存泄漏。 还有,覆盖(override)和隐藏(hide)的区别。当派生类中的函数与基类中的虚函数同名且参数列表相同时,会覆盖基类的函数;如果参数列表不同,或者基类函数不是虚函数,则派生类函数会隐藏基类函数。C++11引入了override关键字来显式指明覆盖,这有助于避免错误。 最后,可能还需要提到多态的实现机制,即虚函数表(vtable)和虚函数指针(vptr)。每个有虚函数的类都有一个vtable,存放虚函数的地址,对象则包含一个vptr指向该表,从而实现动态绑定。 在整理这些内容时,要确保结构清晰,分点说明,每个部分都有明确的解释和示例。同时,检查是否有遗漏的重要概念,比如多重继承、虚继承的问题,或者菱形继承的问题,但可能用户只需要基础介绍,所以这部分可以简要带过或者不提,视情况而定。 还要注意用户提到的系统级指令,所有行内数学表达式用$...$,独立公式用$$...$$,但在这个问题中可能数学公式较少,更多是代码示例。不过如果有涉及到数学表达式的地方,比如算法复杂度,可能需要应用这些规则。但当前主题是继承与多态,可能数学内容较少,但代码示例需要用正确的格式,比如用反引号或代码块,但用户没有特别要求代码格式,只要LaTeX正确即可,可能不需要处理。不过根据用户给的例子,似乎只要数学公式用LaTeX,而代码部分可能用普通代码块,但用户可能希望代码示例用正确的格式,但原问题中没有明确说明,所以可能保持代码示例用中文描述,或者用LaTeX的代码环境?或者可能用户希望用自然语言解释,配合必要的术语即可。 总之,我需要组织内容,分步骤详细介绍继承的概念、类型,多态的实现方式,虚函数的作用,抽象类,覆盖与隐藏,以及相关的注意事项,如虚析构函数等。确保内容准确,结构清晰,符合用户的要求。</think>### C++继承与多态详解 #### 1. **继承的基本概念** **继承**是面向对象编程的核心特性之一,允许派生类(子类)复用和扩展基类(父类)的属性和方法。 - **基类与派生类关系** 基类定义通用特性,派生类通过继承获得这些特性并添加新功能。 ```cpp class Base { public: void baseMethod() { /* 基类方法 */ } }; class Derived : public Base { // public继承 public: void derivedMethod() { /* 派生类新增方法 */ } }; ``` - **继承类型** - **public继承**(最常用):基类的`public`成员在派生类中保持`public`,`protected`保持`protected`。 - **protected继承**:基类的`public`和`protected`成员在派生类中变为`protected`。 - **private继承**:基类的所有成员在派生类中变为`private`。 #### 2. **多态的实现方式** **多态**允许通过基类接口调用派生类的具体实现,分为**编译时多态**(函数重载、运算符重载)和**运行时多态**(虚函数)。 - **虚函数与动态绑定** 使用`virtual`关键字声明虚函数,通过基类指针或引用调用时,实际执行派生类重写的函数。 ```cpp class Animal { public: virtual void makeSound() { // 虚函数 std::cout << "Animal sound" << std::endl; } virtual ~Animal() {} // 虚析构函数 }; class Dog : public Animal { public: void makeSound() override { // 重写虚函数 std::cout << "Woof!" << std::endl; } }; // 使用示例 Animal* animal = new Dog(); animal->makeSound(); // 输出 "Woof!" delete animal; ``` - **纯虚函数与抽象类** 纯虚函数(`= 0`)强制派生类实现接口,包含纯虚函数的类称为**抽象类**,不能实例化。 ```cpp class Shape { public: virtual double area() = 0; // 纯虚函数 }; class Circle : public Shape { private: double radius; public: Circle(double r) : radius(r) {} double area() override { return 3.14 * radius * radius; } }; ``` #### 3. **关键注意事项** - **虚析构函数** 若基类指针指向派生类对象,基类析构函数必须为虚函数,否则可能导致资源泄漏。 ```cpp class Base { public: virtual ~Base() {} // 虚析构函数 }; ``` - **覆盖(override)与隐藏(hide)** - **覆盖**:派生类重写基类虚函数(参数列表相同),需使用`override`关键字(C++11)。 - **隐藏**:若基类函数非虚,或派生类函数参数列表不同,派生类函数会隐藏基类同名函数。 #### 4. **多态底层机制** - **虚函数表(vtable)** 每个包含虚函数的类有一个虚函数表,存储虚函数地址。 - **虚函数指针(vptr)** 每个对象内含一个指向vtable的指针,动态绑定时通过vptr找到实际调用的函数。 #### 5. **总结** - **继承**:代码复用和层次化设计。 - **多态**:通过虚函数实现接口统一、扩展灵活。 - **关键实践**: 1. 基类析构函数声明为虚函数。 2. 使用`override`明确函数重写。 3. 合理设计抽象类定义接口规范。 通过继承与多态C++能够高效支持面向对象设计模式(如工厂模式、策略模式),提升代码可维护性和扩展性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值