多态(2)纯虚函数与重载、重写(覆盖)、重定义(隐藏)

纯虚函数

纯虚函数是一种特殊的虚函数,在许多情况下,在基类中不能对虚函数给出有意义的实现,而把它声明为纯虚函数,它的实现留给该基类的派生类去做。这就是纯虚函数的作用。纯虚函数的存在是为了更方便使用多态特性。它的一般格式如下:

class <类名>
{
virtual <类型><函数名>(<参数表>)=0;
};
 在成员函数的形参列表后面写上=0, 则成员函数为纯虚函数。纯虚函数可以让类先具有一个操作名称,而没有操作内容,让派生类在继承时再去具体地给出定义。凡是含有纯虚函数的类叫做抽象类。这种类不能声明对象,只是作为基类为派生类服务。除非在派生类中完全实现基类中所有的的纯虚函数,否则,派生类也变成了抽象类,不能实例化对象。包含纯虚函数的类叫做抽象类( 也叫接口类) , 抽象类不能实例化出对象。 纯虚函数在派生类中重新定义以后, 派生类才能实例化出对 象。
 1 class A {
 2 public:
 3     A();
 4     virtual ~A();
 5     void f1();
 6     virtual void f2();
 7     virtual void f3()=0;
 8 };
 9 class B:public A{
10 public:
11     B();
12     virtual ~B();
13     void f1();
14     virtual void f2();
15     virtual void f3();
16 };
17 int main(int argc,char * argv[]) {
18     A *m_j = new B();
19     m_j -> f1();
20     m_j -> f2();
21     m_j -> f3();
22     delete m_j;
23     return 0;
24 }
f1()是一个隐藏,调用m_j->f1();会去调用A类中的f1(),它是在我们写好代码的时候就会定好的。也就是根据它是由A类定义的,这样就调用这个类的函数。f2()是重写(覆盖)调用m_j->f2();会调用m_j中到底保存的对象中,对应的这个函数。这是由于new的B对象(调用派生类的f2())f3()与f2()一样,只是在基类中不需要写函数实现。
1 class Person
2 {
3    vi rtual voi d Di splay () = 0; // 纯虚函数
4    protected :
5    stri ng _name ; // 姓名
6 } ;
7 class Student : publi c Person
8 { } ;

总结:

1. 虚函数的定义形式:virtual {method body}   纯虚函数的定义形式:virtual { } = 0;

2. 虚函数和纯虚函数可定义在同一个类(class)中,含有纯虚函数的类是抽象类(abstract class),而含有虚函数的类则不是。

3. 对抽象类进行实例化将会报错,因为抽象基类(ABC)是不能被直接调用的,必须被子类继承重写以后,根据要求调用其子类的方法,因为纯虚函数在基类(base class)只有声明而没有定义。目的是提供一个统一的接口。虚函数可以被直接使用,也可以被子类(sub class)重写后以多态的形式调用。父类和子类都有各自的版本,调用的时候动态绑定。

4. 在虚函数和纯虚函数的定义中不能有static标识符,原因很简单,被static修饰的函数在编译时候要求前期bind,然而虚函数却是动态绑定(run-time bind),而且被两者修饰的函数生命周期(life recycle)也不一样。

5. 虚函数必须实现,如果不实现,编译器将报错,错误提示为:error LNK****: unresolved external symbol "public: virtual void __thiscall
ClassName::virtualFunctionName(void)"

6. 实现了纯虚函数的基类,该纯虚函数可以被子类重写成虚函数,子类的子类即孙子类可以覆盖该虚函数,由多态方式调用的时候动态绑定。

7. 多态性是指相同对象收到不同消息或不同对象收到相同消息时产生不同的实现动作。C++支持两种多态性:编译时多态性,运行时多态性。
a.编译时多态性:通过重载函数实现。
b 运行时多态性:通过虚函数实现。

重载、重写、重定义

1.什么是函数重载

重载,简单说,就是函数或者方法有相同的名称,但是参数列表不同的情形,这样的同名不同参数的函数或者方法之间,互相称之为重载函数或者方法。一般是用于在一个类内实现若干重载的方法,这些方法的名称相同而参数形式不同。
重载的规则:
   1、在使用重载时只能通过相同的方法名、不同的参数形式实现。不同的参数可以是参数类型,参数个数,参数顺序(参数类型必须不一样);
   2、不能通过访问权限、返回类型、抛出的异常进行重载;
   3、方法的异常类型和数目不会对重载造成影响;

成员函数被重载的特征:

(1)相同的范围(在同一个类中); 
(2)函数名字相同; 
(3)参数不同; 
(4)virtual 关键字可有可无 。

其中与其他另外两个概念最大的区别是:函数重载在同一个作用域内。

因为首先函数重载的第一个条件就没有满足,即:在相同的范围中(在同一个类中),派生类和基类是两个不同的类域,即不是同一个作用域,所以在继承中,基类和派生类之间永远不可能进行函数重载。

 1 class Base
 2 {
 3 public:
 4     Base(int data = 0)
 5         :b(data)
 6     {
 7         cout << "Base()" << endl;
 8     }
 9     ~Base()
10     {
11         cout << "~Base()" << endl;
12     }
13     void B()
14     {
15         cout << "Base::B()" << endl;
16     }
17     void B(int b)
18     {
19         cout << "Base::B(int)" << endl;
20     }
21     //B()与B(int b)构成了函数重载
22     //因为上面两个函数是在同一作用域中
23     int b;
24 };
25 class Derive :public Base
26 {
27 public:
28     Derive()
29     {
30         cout << "Derive()" << endl;
31     }
32     ~Derive()
33     {
34         cout << "~Derive()" << endl;
35     }
36     void B(int a, int b)
37     {
38         cout << "Derive::B(int,int)" << endl;
39     }
40     //不会与Base类中的两个B名的函数构成重载
41     //因为作用域不同
42 };

2.什么是重写

子类可继承父类中的方法,而不需要重新编写相同的方法。但有时子类并不想原封不动地继承父类的方法,而是想作一定的修改,这就需要采用方法的重写。方法重写又称方法覆盖。重写即覆盖,是指派生类函数覆盖基类函数。例如,假设动物类存在"跑"的方法,从中派生出马和狗,马和狗的跑得形态是各不相同的,因此同样方法需要两种不同的实现,这就需要"重新编写"基类中的方法。"重写"基类方法就是修改它的实现或者说在派生类中重新编写。

重写(覆盖)的规则:
   1、重写方法的参数列表必须完全与被重写的方法的相同,否则不能称其为重写。
   2、重写方法的访问修饰符一定要大于被重写方法的访问修饰符(public>protected>default>private)。
   3、重写的方法的返回值与被重写的方法的返回值可能不同,(协变)可能是基类返回基类的指针或引用,子类返回子类的指针或引用;
   4、重写的方法所抛出的异常必须和被重写方法的所抛出的异常一致,或者是其子类;
   5、被重写的方法不能为private,否则在其子类中只是新定义了一个方法,并没有对其进行重写。
   6、静态方法不能被重写为非静态的方法(会编译出错)。
重写(覆盖)的主要特征:

(1)不同的范围(分别位于派生类与基类); 
(2)函数名字相同; 
(3)参数相同; 
(4)基类函数必须有virtual 关键字。

 对象调用函数的情况

class Base
{
public:
    Base(int data = 1)
        :b(data)
    {
        cout << "Base()" << endl;
    }
    ~Base()
    {
        cout << "~Base()" << endl;
    }
    virtual void Test()
    {
        cout << "Base::Test()" << endl;
    }
    int b;
};
class Derive :public Base
{
public:
    Derive(int data = 2)
        :d(data)
    {
        cout << "Derive()" << endl;
    }
    ~Derive()
    {
        cout << "~Derive()" << endl;
    }
    void Test()
    {
        cout << "Derive::Test()" << endl;
    }
    int d;
};

int main()
{
    Derive d;
    d.Test();
    return 0;
}

在上面的代码中,分别在基类和派生类中定义了同名同参数的函数Test(),看一下运行结果,看会调用基类的函数还是派生类的函数:

Base()

Derive()

Derived::Test()

运行结果可以表明: 这里的Test()函数发生了函数覆盖。

指针或引用调用函数的情况

 1 class Base
 2 {
 3 public:
 4     Base(int data = 1)
 5         :b(data)
 6     {
 7         cout << "Base()" << endl;
 8     }
 9     ~Base()
10     {
11         cout << "~Base()" << endl;
12     }
13     virtual void Test()
14     {
15         cout << "Base::Test()" << endl;
16     }
17     int b;
18 };
19 class Derive :public Base
20 {
21 public:
22     Derive(int data = 2)
23         :d(data)
24     {
25         cout << "Derive()" << endl;
26     }
27     ~Derive()
28     {
29         cout << "~Derive()" << endl;
30     }
31     void Test()
32     {
33         cout << "Derive::Test()" << endl;
34     }
35     int d;
36 };
37 
38 int main()
39 {
40     Base *pb;
41     Derive d;
42     pb = &d;
43     pb->Test();
44     return 0;
45 }

运行结果同上

多态的本质:不是重载声明而是覆盖。 虚函数调用方式:通过基类指针或引用,执行时会根据指针指向的对象的类,决定调用哪个函数。

重写总结:

1、必须是在继承体系下;
2、方法名、参数个数和参数类型 都必须相同;
3、返回值类型可以与父类相同,也可以与父类不同,但是要求返回值类型是父类的子类。如:父类的返回值类型是Object类,子类的返回值可以是Object类的子类
4、派生类重写的方法的访问权限不能低于基类的权限;
5、派生类抛出的异常应该和基类相等或比基类更小。

3.什么是重定义

 重定义 (redefining)也叫做隐藏:
子类重新定义父类中有相同名称的非虚函数 ( 参数列表可以不同 ) 。
如果一个类,存在和父类相同的函数,那么,这个类将会覆盖其父类的方法,除非你在调用的时候,强制转换为父类类型,否则试图对子类和父类做类似重载的调用是不能成功的。 
 “隐藏”是指派生类的函数屏蔽了与其同名的基类函数,规则如下 
(1)如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有virtual 关键字。此时,基类的函数被隐藏(注意别与覆盖混淆) 
(2)如果派生类的函数与基类的函数同名,但是参数不相同。此时,不论有无virtual关键字,基类的函数将被隐藏(注意别与重载混淆)。
 1 class Base
 2 {
 3 public:
 4     Base(int data = 1)
 5         :b(data)
 6     {
 7         cout << "Base()" << endl;
 8     }
 9     ~Base()
10     {
11         cout << "~Base()" << endl;
12     }
13     void Test()
14     {
15         cout << "Base::Test()" << endl;
16     }
17     int b;
18 };
19 class Derive :public Base
20 {
21 public:
22     Derive(int data = 2)
23         :d(data)
24     {
25         cout << "Derive()" << endl;
26     }
27     ~Derive()
28     {
29         cout << "~Derive()" << endl;
30     }
31     void Test()
32     {
33         cout << "Derive::Test()" << endl;
34     }
35     int d;
36 };
37 
38 int main()
39 {
40     Derive d;
41     d.Test();
42     return 0;
43 }

在我的另一篇文章C++中的继承(3)作用域与重定义,赋值兼容规则中对重定义也有解释。

总结:

1.函数重载必须是在同一作用域的,在继承与多态这里,在基类与派生类之间是不能进行函数重载。 

2.函数覆盖是多态的本质在基类中的虚函数,在派生类定义一个同名同参数的函数,就可以用派生类新定义的函数对基类函数进行覆盖。

3.函数隐藏是发生在基类和派生类之间的,当函数同名但是不同参数的时候,不论是不是虚函数,都会发生函数隐藏。

posted @ 2017-04-19 17:29  滴巴戈  阅读(5896)  评论(0编辑  收藏  举报