c++

C++继承与多态

For study!

Posted by Winray on February 21, 2016
  • 继承(Inheritance)
    • 允许在保持原有类特性的基础上进行扩展,增加功能。这样产生的新类称为“派生类”。继承呈现了面向对象程序设计的层次结构,体现了人类对现实世界由简单到复杂的认识过程。
  • 多态性(Polymorphism)
    • 包括静态的多态性和动态的多态性。前者亦称编译时的多态性,包括函数重载和运算符重载。多态体现了类推和比喻的思想方法。

继承

  • 被继承的类型称为基类(base class)或超类(superclass),新产生的类为派生类(derived class)或子类(subclass)。基类和派生类的集合称作类继承层次结构(hierarchy)。如果基类和派生类共享相同的公有接口,则派生类被称作类的子类型(subtype)。

  • 继承机制体现了现实世界的层次结构,如下图所示。

  • 单一继承(single-inheritance) :一个派生类只有一个直接基类。
  • 多重继承(multiple-inheritance) :一个派生类可以同时有多个基类。
编制派生类的步骤
  1. 吸收基类的成员:不论是数据成员,还是函数成员,除构造函数与析构函数外全盘接收(继承方式)。
  2. 改造基类成员:声明一个和某基类成员同名的新成员,该新成员将屏蔽基类同名成员。称为同名覆盖(override)
    • 新成员若是成员函数,参数表也必须一样,否则是重载。
  3. 发展新成员:派生类新成员必须与基类成员不同名,它的加入保证派生类在功能上有所发展。
  4. 重写构造函数与析构函数。
公有派生与私有派生
  • 派生类定义时的访问限定符,是指访问控制,亦称为继承方式,用于在派生类中对基类成员进一步的限制。

  • 访问控制也是三种:公有(public)方式,保护(protected)方式和私有(private)方式,相应的继承亦称公有继承、保护继承和私有继承。访问限定有两方面含义:

    • 派生类新增成员函数对基类(继承来的)成员的访问;
    • 从派生类对象之外对派生类对象中的基类成员的访问。
虚基类
  • 关键字:virtual
  • 类A派生出类B和类C,类D继承自类B和类C,这个时候类A中的成员变量和成员函数继承到类D中变成了两份,一份来自 A–>B–>D 这一路,另一份来自 A–>C–>D 这一条路。

  • 为了解决这个问题,C++提供了虚基类,使得在派生类中只保留间接基类的一份成员。
#include <iostream>
using namespace std;
class A{
protected:
	int a;
public:
    A(int a):a(a){}
};
class B: virtual public A{  //声明虚基类
protected:
    int b;
public:
    B(int a, int b):A(a),b(b){}
};
class C: virtual public A{  //声明虚基类
protected:
    int c;
public:
    C(int a, int c):A(a),c(c){}
};
class D: virtual public B, virtual public C{  //声明虚基类
private:
    int d;
public:
    D(int a, int b, int c, int d):A(a),B(a,b),C(a,c),d(d) {}
    void display();
};

void D::display(){
    cout << "a=" << a << endl;
    cout << "b=" << b << endl;
    cout << "c=" << c << endl;
    cout << "d=" << d << endl;
}
int main(){
    (new D(1, 2, 3, 4)) -> display();
    return 0;
}

结果如下:

a=1
b=2
c=3
d=4

多态

  • C++中,实现多态有以下方法:虚函数,抽象类,覆盖,模板(重载和多态无关)。

  • 在C++中有两种多态性:
    • 编译时的多态性:通过函数的重载和运算符的重载来实现的。
    • 运行时的多态性:在程序执行前,无法根据函数名和参数来确定该调用哪一个函数,必须在程序执行过程中,根据具体情况来动态地确定。它是通过类继承关系和虚函数来实现的,目的也是建立一种通用的程序。
  • 虚函数是一个类的成员函数,定义格式:virtual 返回类型 函数名(参数表);
    • 当在派生类中重新定义虚函数(overriding a virtual function,亦译作超载或覆盖)时,不必加关键字virtual。但重新定义时不仅要同名,而且它的参数表和返回类型全部与基类中的虚函数一样,否则联编时出错。
    • 只有类的成员函数才能说明为虚函数。这是因为虚函数仅适用于有继承关系的类对象。
    • 静态成员函数,是所有同类对象共有,不受限于某个对象,不能作为虚函数。
    • 内联函数每个对象一个拷贝,无映射关系,不能作为虚函数。
    • 析构函数可定义为虚函数,构造函数不能定义虚函数,因为在构造函数时对象还没有完成实例化。在基类中及其派生类中都动态分配的内存空间时,必须把析构函数定义为虚函数,实现撤消对象时的多态性。
    • 虚函数执行速度要稍慢一些。因为,为了实现多态性,每一个派生类中均要保存相应虚函数的入口地址表,函数的调用机制也是间接实现。所以多态性总是要付出一定代价,但通用性是一个更高的目标。
    • 为了方便,你可以只将基类中的函数声明为虚函数,所有派生类中具有覆盖关系的同名函数都将自动成为虚函数。
#include <iostream>
using namespace std;

class A {
    public: A() {}
    virtual void foo() {
        cout << "This is A." << endl;
    }
};

class B: public A {
    public: B() {}
    void foo() {
        cout << "This is B." << endl;
    }
};

int main(int argc, char * argv[]) {
    A * a = new B();
    a -> foo();
    if (a != NULL)
        delete a;
    return 0;
}
//output:This is B.
函数多态

也就是我们常说的函数重载(function overloading)。基于不同的参数列表,同一个函数名字可以指向不同的函数定义:

#include <iostream>
#include <string>

int my_add(int a, int b) { //定义两个重载函数
    return a + b;
}

int my_add(int a, std::string b) {
    return a + atoi(b.c_str());
}

int main() {
    int i = my_add(1, 2); // 两个整数相加
    int s = my_add(1, "2"); // 一个整数和一个字符串相加
    std::cout << "i = " << i << "\n";
    std::cout << "s = " << s << "\n";
}
宏多态
  • 带变量的宏可以实现一种初级形式的静态多态:
#include <iostream>
#include <string>

#define ADD(A, B) (A) + (B); // 定义泛化记号:宏ADD

int main()
{
	int i1(1), i2(2);
	std::string s1("Hello, "), s2("world!");
	int i = ADD(i1, i2);                  // 两个整数相加
	std::string s = ADD(s1, s2);          // 两个字符串“相加”
	std::cout << "i = " << i << "\n";
	std::cout << "s = " << s << "\n";
}
动态多态 && 静态多态
  • 动态多态,众所周知的多态,使用虚函数构建
  • 静态多态,使用模版构建
抽象类的纯虚函数

纯虚函数是在基类中声明的虚函数,它在基类中没有定义,但要求任何派生类都要定义自己的实现方法。在基类中实现纯虚函数的方法是在函数原型后加”=0” ,同 java中抽象方法类似virtual void funtion1()=0C++接口使用抽象类来实现

#include <iostream>
using namespace std;
 
// Base class
class Shape 
{
public:
   virtual int getArea() = 0;  //pure virtual function providing interface framework.
   void setWidth(int w)
   {
      width = w;
   }
   void setHeight(int h)
   {
      height = h;
   }
protected:
   int width;
   int height;
};
 
// Derived classes
class Rectangle: public Shape
{
public:
   int getArea()
   { 
      return (width * height); 
   }
};
class Triangle: public Shape
{
public:
   int getArea()
   { 
      return (width * height)/2; 
   }
};
 
int main(void)
{
   Rectangle Rect;
   Triangle  Tri;
 
   Rect.setWidth(5);
   Rect.setHeight(7);
   // Print the area of the object.
   cout << "Total Rectangle area: " << Rect.getArea() << endl;

   Tri.setWidth(5);
   Tri.setHeight(7);
   // Print the area of the object.
   cout << "Total Triangle area: " << Tri.getArea() << endl; 

   return 0;
}

这将产生以下结果:

Total Rectangle area: 35
Total Triangle area: 17

Tips

  • 用C++开发的时候,用来做基类的类的析构函数一般都是虚函数
    • 这样做是为了当用一个基类的指针删除一个派生类的对象时,派生类的析构函数会被调用。

    • 然而,不是所有类的析构函数都要写成虚函数。因为当类里面有虚函数的时候,编译器会给类添加一个虚函数表,里面来存放虚函数指针,这样会增加类的存储空间。故,只有当一个类被用来作为基类的时候,才把析构函数写成虚函数。

  • 关于多态:
    • 编译时:模板、重载
    • 运行时:通过虚表
    • 运行时多态相关的概念:静态类型、动态类型。