C++纯虚函数、虚函数、实函数、抽象类,重载、重写、重定义

  首先,面向对象程序设计(object-oriented programming)的核心思想是数据抽象、继承、动态绑定。通过数据抽象,可以使类的接口与实现分离,使用继承,可以更容易地定义与其他类相似但不完全相同的新类,使用动态绑定,可以在一定程度上忽略相似类的区别,而以统一的方式使用它们的对象。

  虚函数的作用是实现多态性(Polymorphism),多态性是将接口与实现进行分离,采用共同的方法,但因个体差异而采用不同的策略。纯虚函数则是一种特殊的虚函数。虚函数联系到多态,多态联系到继承。

一、虚函数

1 . 定义

  C++的虚函数主要作用是“运行时多态”,父类中提供虚函数的实现,为子类提供默认的函数实现

  子类可以重写父类的虚函数实现子类的特殊化。

  如下就是一个父类中的虚函数:

class A
{
public:
    virtual void out2(string s)
    {
        cout<<"A(out2):"<<s<<endl;
    }
};

  当我们在派生类中覆盖某个函数时,可以在函数前加virtual关键字。然而这不是必须的,因为一旦某个函数被声明成虚函数,则所有派生类中它都是虚函数。任何构造函数之外的非静态函数都可以是虚函数。派生类经常(但不总是)覆盖它继承的虚函数,如果派生类没有覆盖其基类中某个虚函数,则该虚函数的行为类似于其他的普通成员,派生类会直接继承其在基类中的版本。

2 . 动态绑定

  当我们使用基类的引用(或指针)调用一个虚函数时将发生动态绑定(dynamic binding)。因为我们直到运行时才能知道到底调用了哪个版本的虚函数,可能是基类中的版本也可能是派生类中的版本,判断的依据是引用(或指针)所绑定的对象的真实类型。与非虚函数在编译时绑定不同,虚函数是在运行时选择函数的版本,所以动态绑定也叫运行时绑定(run-time binding)。

3 . 静态类型与动态类型

  静态类型指的是变量声明时的类型或表达式生成的类型,它在编译时总是已知的;动态类型指的是变量或表达式表示的内存中的对象的类型,它直到运行时才可知。当且仅当通过基类的指针或引用调用虚函数时,才会在运行时解析该调用,也只有在这种情况下对象的动态类型才有可能与静态类型不同。如果表达式既不是引用也不是指针,则它的动态类型永远与静态类型一致。

二、纯虚函数

1 . 定义

  C++中包含纯虚函数的类,被称为是“抽象类”。抽象类不能使用new出对象,只有实现了这个纯虚函数的子类才能new出对象。

  C++中的纯虚函数更像是“只提供申明,没有实现”,是对子类的约束,是“接口继承”。

  C++中的纯虚函数也是一种“运行时多态”。

  如下面的类包含纯虚函数,就是“抽象类”:

class A
{
public:
    virtual void out1(string s)=0;
    virtual void out2(string s)
    {
        cout<<"A(out2):"<<s<<endl;
    }
};

  请注意,纯虚函数应该只有声明,没有具体的定义,即使给出了纯虚函数的定义也会被编译器忽略。

2.引入原因:
      1) 为了方便使用多态特性,我们常常需要在基类中定义虚拟函数。
      2) 在很多情况下,基类本身生成对象是不合情理的。例如,动物作为一个基类可以派生出老虎、孔雀等子类,但动物本身生成对象明显不合常理。
     为了解决上述问题,引入了纯虚函数的概念,将函数定义为纯虚函数(方法:virtual ReturnType Function()= 0;),则编译器要求在派生类中必须予以重载以实现多态性。同时含有纯虚拟函数的类称为抽象类,它不能生成对象。这样就很好地解决了上述两个问题。

三、普通函数(no-virtual)

  普通函数是静态编译的,没有运行时多态,只会根据指针或引用的“字面值”类对象,调用自己的普通函数

  普通函数是父类为子类提供的“强制实现”。

  因此,在继承关系中,子类不应该重写父类的普通函数,因为函数的调用至于类对象的字面值有关。

四、重载、重写、重定义

  重载overload:是函数名相同,参数列表不同。重载只是在类的内部存在。但是不能靠返回类型来判断。

  重写override:也叫做覆盖。子类重新定义父类中有相同名称和参数的虚函数。函数特征相同。但是具体实现不同,主要是在继承关系中出现的。

  重写需要注意:

  1 被重写的函数不能是static的。必须是virtual的

  2 重写函数必须有相同的类型,名称和参数列表

  3 重写函数的访问修饰符可以不同。例如:尽管virtual是private的,派生类中重写改写为public,protected也是可以的

  重定义 (redefining)也叫做隐藏:

  子类重新定义父类中有相同名称的非虚函数 ( 参数列表可以不同 ) 。

  如果一个类,存在和父类相同的函数,那么,这个类将会覆盖其父类的方法,除非你在调用的时候,强制转换为父类类型,否则试图对子类和父类做类似重载的调用是不能成功的。

五、 虚析构函数

虚析构函数: 在析构函数前面加上关键字virtual进行说明,称该析构函数为虚析构函数。虽然构造函数不能被声明为虚函数,但析构函数可以被声明为虚函数。

一般来说,如果一个类中定义了虚函数, 析构函数也应该定义为虚析构函数。

六、 抽象基类

  含有(或者未经覆盖直接继承)纯虚函数的类叫抽象基类(abstract base class)。抽象基类负责定义接口,而后续的其他类可以覆盖该接口。如果派生类中没有重新定义纯虚函数,而只是继承基类的纯虚函数,则这个派生类仍然还是一个抽象基类。因为抽象基类含有纯虚函数(没有定义),所以我们不能创建一个抽象基类的对象,但可以声明指向抽象基类的指针或引用。

  之所以要存在抽象类,最主要是因为它具有不确定因素。我们把那些类中的确存在,但是在父类中无法确定具体实现的成员函数称为纯虚函数。纯虚函数是一种     特殊的虚函数,它只有声明,没有具体的定义。抽象类中至少存在一个纯虚函数;存在纯虚函数的类一定是抽象类。存在纯虚函数是成为抽象类的充要条件。
  

#include <iostream>
using namespace std;

class A
{
public:
    virtual void out1()=0;  ///由子类实现
    virtual ~A(){};
    virtual void out2() ///默认实现
    {
        cout<<"A(out2)"<<endl;
    }
    void out3() ///强制实现
    {
        cout<<"A(out3)"<<endl;
    }
};

class B:public A
{
public:
    virtual ~B(){};
    void out1()
    {
        cout<<"B(out1)"<<endl;
    }
    void out2()
    {
        cout<<"B(out2)"<<endl;
    }
    void out3()
    {
        cout<<"B(out3)"<<endl;
    }
};

int main()
{
    A *ab=new B;
    ab->out1();
    ab->out2();
    ab->out3();
    cout<<"************************"<<endl;
    B *bb=new B;
    bb->out1();
    bb->out2();
    bb->out3();
   bb->A::out3();
    delete ab;
    delete bb;
    return 0;
}

out3()是一个实函数的重定义.
调用ab->out3();会去调A类中的out3(),它是在我们写好代码的时候就会定好的。因为out3()不是虚函数,不会动态绑定,也就是根据它是由A类定义的,这样就调用这个类的函数。 out2()是虚函数。调用ab->out2();会调用bb中保存的对象中对应的这个函数。这是由于new的B对象。 out1()与out2()一样,只是在基类中不需要写函数实现。同时,还可以通过作用域运算符来实现在子类中调用父类的虚函数。

总结:

①.虚函数必须实现,不实现编译器会报错。

②.父类和子类都有各自的虚函数版本。由多态方式在运行时动态绑定。

③.通过作用域运算符可以强行调用指定的虚函数版本。

④.纯虚函数声明如下:virtual void funtion()=0; 纯虚函数无需定义。包含纯虚函数的类是抽象基类,抽象基类不能创建对象,但可以声明指向抽象基类的指针或引用。

⑤.派生类实现了纯虚函数以后,该纯虚函数在派生类中就变成了虚函数,其子类可以再对该函数进行覆盖。

⑥.析构函数通常应该是虚函数,这样就能确保在析构时调用正确的析构函数版本。

时间: 2024-12-25 02:37:11

C++纯虚函数、虚函数、实函数、抽象类,重载、重写、重定义的相关文章

C++基础6 【继承】 类型兼容 satatic 多继承 虚继承 【多态】 案例 虚析构函数 重载重写重定义

[继承] 继承的访问控制域 图 类型兼容性原则  指针 与 引用 用子类直接初始化父类 类的继承模型示意 图 [继承结论] [非常重要的概念] 继承与组合混搭情况下,构造和析构调用原则 原则:先构造父类,再构造成员变量.最后构造自己 先析构自己,在析构成员变量.最后析构父类 继承中,同名的成员变量的处理办法 继承中,同名的成员函数的处理办法 派生类中的static关键字 如果静态成员变量,你没有使用,也没有初始化的话 编译不会报错 经典错误 : 类中函数默认是private的,无法在外部访问 具

父子类之间,成员函数重写、重载以及重定义的区别

1.重写override:也叫做覆盖.子类重新定义父类中有相同名称和参数列表的虚函数.函数特征相同. 重写需要注意: 1) 被重写的函数不能是static的.必须是virtual的 2) 重写函数必须有相同的类型,名称和参数列表 3) 重写函数的访问修饰符可以不同.尽管virtual是private的,派生类中重写改写为public,protected也是可以的 2.重载overload:是函数名相同,参数列表等特征不同.但是不能靠返回类型来判断. 3.重定义redefining也叫做隐藏: 子

C++ Primer 学习笔记33_面向对象编程(4)--虚函数与多态(一):多态、派生类重定义、虚函数的访问、 . 和-&gt;的区别、虚析构函数、object slicing与虚函数

C++ Primer学习笔记33_面向对象编程(4)--虚函数与多态(一):多态.派生类重定义.虚函数的访问. . 和->的区别.虚析构函数.object slicing与虚函数 一.多态 多态可以简单地概括为"一个接口,多种方法",前面讲过的重载就是一种简单的多态,一个函数名(调用接口)对应着几个不同的函数原型(方法). 更通俗的说,多态行是指同一个操作作用于不同的对象就会产生不同的响应.或者说,多态性是指发出同样的消息被不同类型的对象接收时有可能导致完全不同的行为. 多态行分

C++中的 虚函数 纯虚函数 虚基类(virtual)

前言:需要了解三者的区别,必须要掌握多态的三个必要条件: 继承 重载 父类指针指向子类对象. 虚函数 纯虚函数 虚基类三者区别 1.虚函数是用于多态中virtual修饰父类函数,确保父类指针调用子类对象时,运行子类函数的. 2.纯虚函数是用来定义接口的,也就是基类中定义一个纯虚函数,基类不用实现,让子类来实现. 3.虚基类是用来在多继承中,比如菱形继承中,如果两个父类继承自同一个类,就只实例化一个父类 ①虚函数第一个是没有使用多态(只用继承)的一般实现方式: class A { public:

【转】深入理解C++的动态绑定和静态绑定 &amp; 不要重定义虚函数中的默认参数

为了支持c++的多态性,才用了动态绑定和静态绑定.理解他们的区别有助于更好的理解多态性,以及在编程的过程中避免犯错误.需要理解四个名词:1.对象的静态类型:对象在声明时采用的类型.是在编译期确定的.2.对象的动态类型:目前所指对象的类型.是在运行期决定的.对象的动态类型可以更改,但是静态类型无法更改.关于对象的静态类型和动态类型,看一个示例: class B { } class C : public B { } class D : public B { } D* pD = new D();//p

C++ - 类的虚函数\虚继承所占的空间

类的虚函数\虚继承所占的空间 本文地址: http://blog.csdn.net/caroline_wendy/article/details/24236469 char占用一个字节, 但不满足4的倍数时, 会进行数据对齐, 使用4的倍数的字节(byte). GCC: GCC中, 不管是虚函数还是虚继承, 都须要将指针存储在虚函数表(virtual function table), 占用4个字节. 继承会继承基类的数据, 和虚函数表, 即继承基类的空间. 代码: /* * test.cpp *

C++学习 - 虚表,虚函数,虚函数表指针学习笔记

虚函数 虚函数就是用virtual来修饰的函数.虚函数是实现C++多态的基础. 虚表 每个类都会为自己类的虚函数创建一个表,来存放类内部的虚函数成员. 虚函数表指针 每个类在构造函数里面进行虚表和虚表指针的初始化. 下面看一段代码: // // main.cpp // VirtualTable // // Created by Alps on 15/4/14. // Copyright (c) 2015年 chen. All rights reserved. // #include <iostr

C++中不能声明为虚函数的有哪些函数

常见的不不能声明为虚函数的有:普通函数(非成员函数):静态成员函数:内联成员函数:构造函数:友元函数. 1.为什么C++不支持普通函数为虚函数? 普通函数(非成员函数)只能被overload,不能被override,声明为虚函数也没有什么意思,因此编译器会在编译时邦定函数. 多态的运行期行为体现在虚函数上,虚函数通过继承方式来体现出多态作用,顶层 函数不属于成员函数,是不能被继承的2.为什么C++不支持构造函数为虚函数? 这个原因很简单,主要是从语义上考虑,所以不支持.因为构造函数本来就是为了明

开源纯C日志函数库iLOG3快速入门(八、如果你喜欢简单日志函数甚于日志函数库)

很多网友来信坚持表达了在项目中应使用简单日志函数,而不喜欢日志函数库,我与之反复争论无果,不过话说回来,我也喜欢短小轻快的日志函数库,而不是融入太多功能的臃肿库,比如日志转档我还是坚持用外部shell来实现会更好,这样的日志函数库既可以小很多,也可以更轻快稳定,我只是迫于另外一批功能控网友的"威胁"而加入了太多的功能,他们喜欢极端的一揽子解决方案,并且展示其它诸如log4c也实现了这些功能为由证明他们是正确的,如果我不实现就免谈. 最近在研发一个小型的应用服务器平台tcpdaemon,