C++语言基础六

1、面向对象的三大特性,并举例说明

C++面向对象的三大特征是:封装、继承、多态。

  1. 封装
            封装就是把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让信任的类或者对象存储操作,对不可信的进行信息隐藏。一个类就是一个封装了数据以及操作这些数据的代码的逻辑实体。在一个对象内部,某些代码或某些数据可以是私有的,不能被外部访问。通过这种方式,对象对内部数据提供了不同级别的保护,以防止程序中无关的部分意外的改变或错误的使用了对象的私有部分。

  2. 继承
    继承是指可以让某个类型的对象获得另一个类型的对象的属性的方法。它支持按级分类的概念。继承是指这样一种能力:它可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。通过继承创建的新类称为“子类”或者“派生类”,被继承的类称为“基类”、“父类”或“超类”。继承的过程,就是从一般到特殊的过程。要实现继承,可以通过“继承”和“组合”来实现。

    继承概念的实现方式有两类:

            实现继承:实现继承是指直接使用基类的属性和方法而无需额外编码的能力。

            接口继承:接口继承是指仅使用属性和方法的名称,但是子类必须提供实现的能力。

  3. 多态
    多态就是向不同的对象发送同一个消息,不同对象在接受时会产生不同的行为(即方法)。即一个接口,可以实现多种方法。

    多态与非多态的实质区别就是函数地址是早绑定还是晚绑定的。如果函数的调用,在编译器编译期间就可以确定函数的调用地址,并产生地址,则是静态的,即地址早绑定。而如果函数调用的地址不能在编译期间确定,需要在运行时才确定,这就属于晚绑定。

2、多态的实现

        多态其实一般就是指继承加虚函数实现的多态,对于重载来说,实际上基于的原理是,编译器为函数生成符号表时的不同规则,重载只是一种语言特性,与多态无关,与面向对象也无关,但这又是C++中增加的新规则,所以也算属于C++,所以如果非要说重载算是多态的一种,那就可以是:多态可以分为静态多态和动态多态。

        静态多态其实就是重载,因为静态多态是指在编译期间就决定了调用哪个函数,根据参数列表来决定;

        动态多态是指通过子类重写父类的虚函数来实现,因为是在运行期间决定调用的函数,所以称为动态多态;

一般情况下我们不区分这两个时所说的多态就是指动态多态。

动态多态的实现与虚函数表,虚函数指针相关。

拓展:子类是否要重写父类虚函数?子类继承父类时,父类的纯虚函数必须重写,否则子类也是一个虚类不可实例化。定义纯虚函数是为了实现一个接口,起到一个规范的作用,规范继承这个类的程序必须实现这个函数。

3、虚函数相关(虚函数表,虚函数指针),虚函数的实现原理

        首先我们来说一下,C++中多态的表象,在基类的函数前加上virtual关键字,在派生类中重写该函数,运行时会根据对象的实际类型来调用相应的函数。如果对象类型是派生类,就调用派生类的函数,如果是基类,就调用基类的函数。

实际上,当一个类中包含虚函数时,编译器会为该类生成一个虚函数表,保存该类中虚函数的地址,同样,派生类继承基类,派生类中自然一定有虚函数,所以编译器也会为派生类生成自己的虚函数表。当我们定义一个派生类对象时,编译器检测该类型有虚函数,所以为这个派生类对象生成一个虚函数指针,指向该类型的虚函数表,这个虚函数指针的初始化是在构造函数中完成的。

后续如果有一个基类类型的指针,指向派生类,那么当调用虚函数时,就会根据所指真正对象的虚函数表指针去寻找虚函数的地址,也就可以调用派生类的虚函数表中的虚函数以此实现多态。

补充:如果基类中没有定义成virtual,那么进行Base B; Derived D;Base *p = D; p->function();这种情况下调用的则是Base中的function()。因为基类和派生类中都没有虚函数的定义,那么编译器就会认为不用留给动态多态的机会,就事先进行函数地址的绑定(早绑定),详述过程就是,定义了一个派生类对象,首先要构造基类的空间,然后构造派生类的自身内容,形成一个派生类对象,那么在进行类型转换时,直接截取基类的部分内存,编译器认为类型就是基类,那么(函数符号表[不同于虚函数表的另一个表]中)绑定的函数地址也就是基类中函数的地址,所以执行的是基类的函数。

4、编译器处理虚函数表应该如何处理

对于派生类来说,编译器建立虚函数表的过程其实一共是三个步骤:

  • 拷贝基类的虚函数表,如果是多继承,就拷贝每个有虚函数基类的虚函数表
  • 当然还有一个基类的虚函数表和派生类自身的虚函数表共用了一个虚函数表,也称为某个基类为派生类的主基类
  • 查看派生类中是否有重写基类的虚函数,如果有,就替换成已经重写的虚函数地址;查看派生类是否有自身的虚函数,如果有,就追加自身的虚函数到自身的虚函数表中。

Derived *pd = new D();B *pb = pd;C *pc = pd;其中pb,pd,pc的指针位置是不同的,要注意的是派生类的自身的内容要追加在主基类的内存块后。

     

    

                

     

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值