三种继承、多态-虚函数

来源:互联网 发布:抹茶美妆软件 编辑:程序博客网 时间:2024/05/16 18:31

总结一下最近学到的类继承的知识,包括三种继承方式、多态的实现、动态联编和静态联编。
欢迎各位指正其中的错误。
以后的理解更加深刻了回来更新和修改。


  • 三种继承

从一个类,派生出另一个类时,原始类称为基类(父类),继承类称为派生类(子类)

派生类对象存储了基类的数据成员,且可以使用基类的方法,但不能直接访问基类的私有成员,必须使用基类的公有方法进行访问。可以根据需要添加额外的成员

因为构造函数不能继承,那么 派生类在创建对象时先使用基类的构造函数初始化继承的基类的数据成员,再使用自己的构造函数( 类的构造函数可以为空,但必须存在

例如:

#include<iostream>using namespace std;class A{    int x;    public:    A( int xx )    {        x = xx;    }    void show()    {         cout << " A : " << x << endl;    }};class B:public A{    int y;    public:    B( int yy,int xx) : A( xx ) //成员初始化列表机制    {        y = yy;    }    void show()    {        A::show();        cout << " B : " << y << endl;     }};int main(){    B b( 3,2 );    b.show();    return 0;}

输出结果为

A : 2
B : 3

这里我在父类A里定义的构造函数只有一个有参类型,所以调用时要显示调用。即如果父类有显示声明了构造函数,那么子类最低限度实现父类的一个构造函数,如果子类没有定义构造函数,则调用父类无参数的构造方法

派生类构造函数的要点
1.首先创建基类对象
2.派生类构造函数应通过成员初始化列表将信息传递给基类构造函数
3.派生类构造函数应初始化派生类新增的数据成员

公有继承方式

(1) 基类成员对派生类的可见性:
公有成员和保护成员可见,而私有成员不可见。这里保护成员同于公有成员。

(2) 基类成员对派生类对象的可见性:
公有成员可见,其他成员不可见。

所以,在公有继承时,派生类的对象可以访问基类中的公有成员;派生类的成员函数可以访问基类中的公有成员和保护成员。这里,一定要区分清楚派生类的对象和派生类中的成员函数对基类的访问是不同的。

私有继承方式

(1) 基类成员对派生类的可见性:
公有成员和保护成员是可见的,而私有成员是不可见的。

(2) 基类成员对派生类对象的可见性:
所有成员都是不可见的。

所以,在私有继承时,基类的成员只能由直接派生类访问,而无法再往下继承。

保护继承方式

这种继承方式与私有继承方式的情况相同。两者的区别仅在于对派生类的成员而言,对基类成员有不同的可见性。
上述所说的可见性也就是可访问性。

关于可访问性还有另的一种说法。这种规则中,称派生类的对象对基类访问为水平访问,称派生类的派生类对基类的访问为垂直访问。

综上所述:

公有继承:
对派生类成员共有和保护成员可访问,对对象只有公有成员可访问

私有继承:
对派生类成员公有和保护成员可访问,对对象所有成员都不可访问

保护继承:
保护继承时基类中各成员属性均变为protected,并且基类中private成员被隐藏。派生类成员只能访问基类的公有和保护成员,对派生类对象所有成员均不可访问


  • 多态的实现

多态,接口的多种不同的实现方式即为多态

多态存在的三个条件:
1.必须存在继承关系;
2.继承关系中必须有同名的虚函数,并且它们是覆盖关系(重载不行)。
3.存在基类的指针,通过该指针调用虚函数。

多态通过虚函数实现,引入新关键字virtual
在函数声明前加入virtual,即将其变为虚函数

#include<iostream>using namespace std;class A{    int x;    public:    A( int xx )    {        x = xx;    }    virtual void show()    {         cout << " A : " << x << endl;    }};class B:public A{    int y;    public:    B( int yy,int xx) : A( xx )    {        y = yy;    }    void show()    {        cout << " B : " << y << endl;     }};int main(){    A *b = new B( 3,2 );    b->show();    return 0;}

这里将上述例子里的show函数声明为虚函数,输出结果为B :3

这里如果show不是虚函数,那么b->show()将执行父类的函数,因为b是一个A类型的指针

也就是说,多态也就是通过父类指针来指向子类对象实现不同的操作,父类就像ATM机,插入不同的卡(子类)要取钱时,ATM机不需要为每一张卡写不同的函数,只需要用ATM机(父类)指针指向卡(子类)去完成相对应的操作

方法在父类中被声明为虚的后,在子类中自动成为虚方法

-LZJ:父类指针为什么能指向子类指针?
这两个不是类型不一样么,为什么父类指针还能指向子类呢。(子类指针不能指向父类
其实,不止是指针,C++允许基类引用指任何从该基类派生而来的任何类型
类型一致其实不是说一定要类型完全一样,类型是一种约束,帮助你验证程序的正确性,类的继承就是表示一种 “继承类是基类中更具体的东西”,子类和父类的关系并不是两个独立的类型,但无法使用不存在于基类只存在于派生类的元素(所以我们需要虚函数、纯虚函数)

例如

#include<iostream>using namespace std;class A{    int x;    public:    A( int xx )    {        x = xx;    }    void q()    {    }};class B:public A{    int y;    public:    B( int yy,int xx) : A( xx )    {        y = yy;    }    void w()    {    }};int main(){    A *b = new B( 3,2 );    b->show();    b->q();   //ok    b->w();   //error    return 0;}

  • 动态联编、静态联编

程序调用函数时,将使用哪个可执行代码块呢?编译器负责回答你。将源代码中的函数调用解释为执行特定的函数代码块称为函数名联编。
在编译过程中完成这种联编被称为静态联编,又称为早期联编,那虚函数的出现让编译器无法在编译过程确定使用哪一个函数,所以编译器可以在执行过程中选择正确的虚方法的代码,这被称为动态联编,也被称为晚期联编。

  • 虚函数表
    虚函数的工作原理:编译器处理虚函数的方法是,给每一个对象添加一个隐藏成员。隐藏成员保存了一个指向函数地址数组的指针。这种数组称为虚函数表。当一个对象调用了虚函数,实际的被调用函数通过这个隐藏指针到虚函数表里找到真正的函数指针。

一个类只有一个虚函数表,子类有多少个父类就有多少个虚函数表
虚函数表放在程序的常量区。
在GNU C++中,虚指针位于对象的尾部,在Visual C+中在起始位置
所有的类都不会和其他的类共享一张虚函数表。

构造函数不需要是虚函数,也不允许是虚函数

编译时多态性:重载函数
运行时多态性:虚函数
重载并不是多态,多态针对对象,重载针对方法

原创粉丝点击