精华内容
下载资源
问答
  • 多态是指同样的消息被不同的对象接收时导致不同的行为。本文通过实例代码给大家介绍了C++多态性与虚函数相关知识,感兴趣的朋友一起看看吧
  • 多态性虚函数章节中最后的代码! 多态性虚函数章节中最后的代码! 多态性虚函数章节中最后的代码! 多态性虚函数章节中最后的代码!
  • CPP多态性与虚函数

    2020-05-14 01:55:59
    文章目录多态性与虚函数面向对象程序设计中的多态性派生类基类对象之间的赋值虚函数虚析构函数纯虚函数(抽象类)虚函数与虚基类派生类中重定义基类中的重载函数虚函数动态绑定的实现原理 多态性与虚函数 面向对象...

    多态性与虚函数

    面向对象程序设计中的多态性

    多态性(polymorphism)
    调用同一个函数名,根据需要实现不同的功能。

    • 编译时的多态性:静态多态(函数重载、运算符重载)
    • 运行时的多态性:动态多态(虚函数)

    函数重载和运算符重载实现的多态性属于静态多态性,在程序编译时就能决定调用的是哪个函数。静态多态性又称编译时的多态性。静态多态是通过函数重载、运算符重载实现的。

    运行时的多态性是指在程序执行之前,根据函数名和参数无法确定应该调用哪一个函数,必须在程序的执行过程中,根据具体的执行情况来动态地确定。动态多态是通过虚函数(virtual function)实现的。

    派生类与基类对象之间的赋值

    • 1
      可以将派生类对象的值赋给基类对象,但只对从基类继承来的成员赋值。
    class Base{
    };
    
    class Derived:public Base{
    };
    
    Base b;
    Derived d;
    b = d;
    
    • 派生类对象可以初始化基类的引用
    Derived d;
    Base &b = d;
    //但b只能引用从基类继承来的成员
    
    • 派生类对象的地址赋给基类的指针变量

    bp只能引用从基类继承来的成员。

    Base *bp;
    Derived d;
    bp = &d;
    

    公有派生类对象可以被当作基类的对象使用。反之则不可。
    通过基类对象名、指针只能使用从基类继承的成员。

    class A{
    public:
        void show(){cout<<"A"<<endl;}
    };
    class AA:public A
    {
    public:
        void show(){cout<<"AA"<<endl;}
    };
    void f(A *pa){
        pa->show();
    }
    
    int main()
    {
        A a;
        AA aa;
        f(&a);
        f(&aa);
        return 0;
    }
    

    函数f()的形参是类A的指针,所以调用的方法只能是基类A中的方法。

    A
    A
    

    这和java的多态大不一样。

    public class Polymorphism {
        public static void main(String[] args) {
            A a = new A();
            AA aa = new AA();
            f(a);
            f(aa);
        }
        static void f(A a){
            a.show();
        }
    }
    
    class A {
        void show() {
            System.out.println("A");
        }
    }
    class AA extends A{
        void show()
        {
            System.out.println("AA");
        }
    }
    
    A
    AA
    
    • 这种赋值与访问性的关系。发现只有共有派生的子类才能向上转型。
      在这里插入图片描述

    虚函数

    在程序运行时通过基类指针调用相同的函数名,而实现不同功能的函数称为虚函数。

    一旦把基类的成员函数定义为虚函数,由基类所派生出来的所有派生类中,该函数均保持虚函数的特性
    在派生类中重新定义基类中的虚函数时,可以不用关键字virtual来修饰这个成员函数 。

    虚函数的声明
    virtual关键字
    
    virtual 函数类型 函数名(形参表)
    {
    	函数体
    }
    

    若要通过基类指针访问派生类中相同名字的函数,必须将基类中同名函数定义为虚函数,这样,将各种派生类对象的地址赋给基类的指针变量后,就可以动态地根据这种赋值调用不同类中的函数

    若要达成java的效果,只需
    基类A的同名函数show()前加上virtual关键字。

    class A{
    public:
        virtual void show(){cout<<"A"<<endl;}
    };
    

    若要通过基类引用访问派生类中相同名字的函数,也是可以的。

    class A{
    public:
        virtual void show(){cout<<"A"<<endl;}
    };
    class AA:public A
    {
    public:
        void show(){cout<<"AA"<<endl;}
    };
    void f(A &a){
        a.show();
    }
    
    int main()
    {
        A a;
        AA aa;
        f(a);
        f(aa);
        return 0;
    }
    
    A
    AA
    

    如果去掉f(A &a)的&呢?
    输出结果。

    A
    A
    

    所以通过虚函数的实现的多态,必须要通过指针或引用

    只有在程序的执行过程中,依据指针具体指向哪个类对象,或依据引用哪个类对象,才能确定调用哪一个版本,实现动态绑定

    虚析构函数

    如果要通过基类指针调用派生类对象的析构函数(里面包含delete),就需要让基类的析构函数成为虚函数。

    class A{
    public:
        A(){cout<<"A"<<endl;}
        ~A(){cout<<"~A"<<endl;}
    };
    class AA:public A{
    public:
        int *p;
        AA(){cout<<"AA"<<endl;p = new int;}
        ~AA(){cout<<"~AA"<<endl;delete p;}
    };
    
    int main()
    {
        A *pa = new AA;
        delete pa;
        return 0;
    }
    
    A
    AA
    ~A
    

    解决办法就是在基类的析构函数前加上关键字virtual

    class A{
    public:
        A(){cout<<"A"<<endl;}
        virtual ~A(){cout<<"~A"<<endl;}
    };
    class AA:public A{
    public:
        int *p;
        AA(){cout<<"AA"<<endl;p = new int;}
        ~AA(){cout<<"~AA"<<endl;delete p;}
    };
    

    虚函数的几点说明:

    • 当在基类中把成员函数定义为虚函数后,在其派生类中定义的虚函数必须与基类中的虚函数同名,参数的类型、顺序、参数的个数必须一一对应,函数的返回类型也相同。
      若函数名相同,但参数的个数不同或者参数的类型不同时,则属于函数重载而不是虚函数
      若函数名不同,显然是不同的成员函数。

    • 虚函数必须是类的一个成员函数,不能是友元函数也不能是静态的成员函数

    • 可把析构函数定义为虚析构函数。但是,不能将构造函数定义为虚函数

    • 在派生类中如果没有重新定义虚函数时,与一般的成员函数一样,当调用这种派生类对象的虚函数时,则调用基类中的虚函数

    • 实现动态多态性时,必须使用基类类型指针变量引用,指向不同的派生类对象,调用虚函数实现动态多态性。
      通过对象名访问虚函数时,只能静态绑定。即由编译器在编译的时候决定调用哪个函数。

    • 虚函数与一般的成员函数相比较,调用时的执行速度要慢一些。为了实现多态性,在每一个派生类中均要保存相应虚函数的入口地址表,函数的调用机制也是间接实现的。因此,除了要编写一些通用的程序,必须使用虚函数才能完成其功能要求外,通常不必使用虚函数。

    • 一个函数如果被定义成虚函数,则不管经历多少次派生,仍将保持其虚特性(根据实际的类型来调用相应的函数,而不是声明的类型),以实现“一个接口,多个形态”。

    但一定要注意virtual在类派生结构中的位置:

    class Base0{
    public:
        void v(){cout<<"Base0\n";}
    };
    
    class Base1:public Base0{
    public:
        virtual void v(){cout<<"Base1\n";}
    };
    
    class A1:public Base1{
    public:
        void v(){cout<<"A1\n";}
    };
    
    class A2:public A1{
    public:
        void v(){cout<<"A2\n";}
    };
    
    class B1:private Base1{
    public:
        void v(){cout<<"B1\n";}
    };
    
    class B2:public B1{
    public:
        void v(){cout<<"B2\n";}
    };
    
    int main()
    {
        Base0 *pb0;
        A1 a1;
        (pb0=&a1)->v();
        A2 a2;
        (pb0=&a2)->v();
        
        return 0;
    }
    

    原因在于pb0接受了它的派生类的赋值,只能调用自己的函数,virtual只是修饰了它的派生类。

    Base0
    Base0
    

    纯虚函数(抽象类)

    在基类中不对虚函数给出有意义的实现,只在派生类中有具体的意义。这时基类中的虚函数只是一个入口,具体的操作由派生类中的对象实现。这种虚函数称为纯虚函数(pure virtual)。

    至少包含一个纯虚函数的类,称为抽象类(abstract classes)。抽象类只能作为派生类的基类,不能用来声明这种类的对象。

    class <抽象类名>
    {
    	virtual <类型> <函数名>(<参数表>) =  0;
    	……
    }
    
    • 在定义纯虚函数时,不能定义虚函数的实现部分。

    • 把函数名赋于0,本质上是将指向函数的指针值赋为0。与定义空函数不一样,空函数的函数体为空,即调用该函数时,不执行任何动作。在没有重新定义这种纯虚函数之前,是不能调用这种函数的。

    • 因为纯虚函数没有实现部分,所以不能产生对象。但可以定义指向抽象类的指针,即指向这种抽象基类的指针。当用这种基类指针指向其派生类的对象时,必须在派生类中给出纯虚函数的实现,否则会导致程序运行错误。

    • 抽象类的唯一用途是为派生类提供基类
      纯虚函数是作为派生类中的成员函数的基础,实现动态多态性。

    demo

    class A{
    public:
        virtual void f()=0;
    };
    class A1:public A{
    public:
        void f(){cout<<"A1\n";}
    };
    class A2:public A{
    public:
        void f(){cout<<"A2\n";}
    };
    
    void fun(A *pa){
        pa->f();
    }
    
    int main()
    {
        A1 a1;
        A2 a2;
        fun(&a1);
        fun(&a2);
        return 0;
    }
    

    虚函数与虚基类

    • 虚基类的用处:派生类只会有唯一一份基类的拷贝。
      要注意virtual修饰的是那唯一的一份基类,在派生的时候声明。

    demo

    class Base{
    };
    class A1:public Base{
    };
    
    class A2:public Base{
    };
    
    class B:public A1,public A2{
    };
    
    int main()
    {
        B b;
        Base *base = &b; //error , Ambiguous conversion from derived class 'B' to base class 'Base':
        return 0;
    }
    

    要改为:

    class Base{
    };
    class A1:virtual public Base{
    };
    
    class A2:virtual public Base{
    };
    
    class B:public A1,public A2{
    };
    
    int main()
    {
        B b;
        Base *base = &b;
        return 0;
    }
    
    • 虚函数的作用

    用基类的指针或引用去根据实际对象的类型进行函数的调用。

    这就是virtual关键字在c++中主要的两个用处。

    派生类中重定义基类中的重载函数

    C++中继承层次的函数调用遵循以下四个步骤:

    1. 首先确定进行函数调用的对象、引用或指针的静态类型
    2. 在该类中查找函数,如果找不到,就在直接基类中查找,如此循着类的继承链往上找,直到找到该函数或者查找完最后一个类。如果不能在类或其相关基类中找到该名字,则调用是错误的。
    3. 一旦找到了该名字,就进行**常规类型检查,**查看如果给定找到的定义,该函数调用是否合法。
    4. 假定函数调用合法,编译器就生成代码。如果函数是虚函数且通过引用或指针调用,则编译器生成代码以确定根据对象的动态类型运行哪个函数版本,否则,编译器生成代码直接调用函数。

    下面的写法会报错。

    class Base{
    public:
        void print(){
            cout<<"print() in Base"<<endl;
        }
        
        void print(int x){
            cout<<"print(int x) in Base"<<endl;
        }
        void print(string s)
        {
            cout<<"print(string s) in Base"<<endl;
        }
        
    };
    
    class Derived:public Base{
    public:
        void print(){
            cout<<"print() in Derived"<<endl;
        }
    };
    
    int main()
    {
        Derived d;
        
        d.print();
        d.print("");  // error
        d.print(1);   // error
        
        return 0;
    }
    

    当仅重写了其中的一个重载函数时,在做函数名匹配时,在本类中就可以匹配到了,就不会向其父类查找了。在派生类中,仅记录了这个被重写的函数的信息,没有其他重载函数,就导致了上述错误的出现。即:派生类中的函数会将其父类中的同名函数屏蔽掉

    • 如果派生类想通过自身类型使用基类中重载版本,派生类最好重定义所有重载版本

    解决方案1:
    在派生类中通过using为父类函数成员提供声明,这样,派生类不用重定义所继承的每一个基类版本。

    class Derived:public Base{
    public:
        using Base::print;  // 使用这条语句即可继承父类所有print()函数的重载
        void print(){
            cout<<"print() in Derived"<<endl;
        }
    };
    

    解决方案2:
    通过基类指针调用

    int main() {  
      Derived d;  
      Base* bp = &d;   
      d.print();  
      bp->print(10);    // 这里可没有进行虚函数,执行的还是父类中的函数。
      bp->print("");    // 
      return 0;  
    } 
    

    解决方案3:
    在派生类中需要重载的那个版本在基类中声明为virtual实现动态绑定,就能统一的使用基类指针来调用了:

    class Base{
    public:
         virtual void print(){
            cout<<"print() in Base"<<endl;
        }
        
        void print(int x){
            cout<<"print(int x) in Base"<<endl;
        }
        void print(string s)
        {
            cout<<"print(string s) in Base"<<endl;
        }
        
    };
    class Derived:public Base{
    public:
        void print(){
            cout<<"print() in Derived"<<endl;
        }
    };
    int main()
    {
        Derived d;
        Base *pb = &d;
        pb->print(); //动态绑定到Derived的函数
        pb->print(1);
        pb->print("");
        return 0;
    }
    

    个人觉得,第三种方法最为自然,并且最好还是自己显式地将函数全部进行重载(不要只重载一个)。

    虚函数动态绑定的实现原理

    • 多态类型与非多态类型

      • 有虚函数的类类型称为多态类型
      • 其它类型皆为非多态类型
    • 二者的差异
      多态类型支持运行时类型识别;多态类型对象占用额外的空间

    • 虚表(virtual table)
      每个多态类有一个虚表(virtual table),虚表中有当前类的各个虚函数的入口地址

    • 虚指针(virtual point)
      每个对象有一个指向当前类的虚表的指针(虚指针vptr)

    • 动态绑定的实现
      构造函数中为对象的虚指针赋值
      通过多态类型的指针或引用调用成员函数时,通过虚指针找到虚表,进而找到所调用的虚函数的入口地址,通过该入口地址调用虚函数。

    在这里插入图片描述

    展开全文
  • c++多态性与虚函数

    2012-06-16 23:42:43
    主要简述了c++中的多态性,附上代码,详细简述了c++中的多态性,请好好看!
  • 多态性与虚函数

    千次阅读 2016-05-13 23:11:58
    多态性是面向对象程序设计的基本特征之一。通过虚函数实现多态性。 绑定方式与多态性 一、基本概念 多态性:方法和函数具有相同的名字,但有不同de

    多态性是面向对象程序设计的基本特征之一。通过虚函数实现多态性。


    • 绑定方式与多态性

    一、基本概念

    多态性:方法和函数具有相同的名字,但有不同的行为。

    绑定:将函数调用与某个函数体对应起来。

    早期绑定,又静态绑定:在编译阶段完成绑定。

    晚期绑定,又动态绑定:在运行阶段才完成绑定。

    编译时多态性:在编译阶段确定名字的含义,在C++语言中通过函数重载和模板机制实现。

    运行时多态性:在运行阶段才确定名字的含义,在C++语言中使用虚函数结合继承机制、动态绑定机制实现。

    在C++语言中,函数调用的默认绑定方式是静态绑定,只有通过基类类型的引用或指针调用被指定为虚函数的成员函数才进行动态绑定。获得运行时多态性需同时满足以下条件:

    a 有一个继承层次;

    b 在基类中定义虚函数;

    c 在派生类中对基类中定义的虚函数进行重定义;

    d 通过基类指针(或基类引用)调用虚函数。

    运行时多态性的基础是公有派生类对基类的类型兼容性,即指向基类对象的指针可指向该基类的公有派生类对象(类似地,基类对象的引用也可关联到该基类的公有派生类对象)。声明指针(引用)变量时指定的基类型称为指针(或引用)的静态类型,指针变量实际所指向(或引用变量实际所关联)的对象的类型称为指针(或引用)的动态类型。


    二、多态性的作用

    多态性使程序员可以使用相同的名字定义多个操作或函数,对语义相似的操作或函数采用同一标识符进行命名;增强程序的可修改性和可扩充性,在进行程序开发时可有效应对需求的变化。

    面向对象程序设计方法围绕对象组织程序,即根据实体设计程序,以”对象“概念为核心,围绕对象和类(不是函数)组织代码,封装使得对象数据和操作的实现都被屏蔽起来,不受外界影响。




    • 虚函数

    虚函数,指在类定义体中使用保留字virtual声明的成员函数。

    多态类,指包含虚函数的类。

    在公有继承层次中的一个或多个派生类中对基类中定义的虚函数进行重定义,再通过指向基类的指针(或基类引用)调用虚函数实现运行时多态性。

    一、虚函数举例

    除构造函数,任意非static成员函数都可根据需要设计为虚函数。

    其中,构造函数不能设计为虚函数,是因为构造函数在对象完全构造之前运行,在构造函数运行时,对象的类型还是不完整的;static成员函数不能设计为虚函数,是因为static成员函数由类的所有实例共享,不属于某个对象。

    举例1:

    /*
    	虚函数的动态绑定
    */
    #include <iostream>
    using namespace std;
    class Base{
    public:
    	virtual void showName()
    	{
    		cout << "Base class" << endl;
    	}
    };
    class DClass1 :public Base{
    public:
    	void showName()			// 继承成员的重定义
    	{
    		cout << "The first derived class" << endl;
    	}
    };
    class DClass2 :public Base{
    public:
    	void showName()			// 继承成员的重定义
    	{
    		cout << "The second derived class" << endl;
    	}
    };
    int main()
    {
    	Base bObj;
    	DClass1 d1Obj;
    	DClass2 d2Obj;
    
    	Base *ptr;			// 定义指向基类的指针
    	ptr = &bObj;
    	ptr->showName();
    	ptr = &d1Obj;		// 基类指针指向派生类对象
    	ptr->showName();
    	ptr = &d2Obj;		// 基类指针指向派生类对象
    	ptr->showName();
    
    	system("pause");
    	return 0;
    }

    运行结果:

    Base class

    The first derived class

    The second derived class

    分析:

    动态绑定能以统一的方式使用在不同类中定义的成员函数,即以同样方式(函数名)调用在派生类或基类中定义的虚函数;动态绑定在调用虚函数时无需关心对象的具体类型。

    注意:若通过指针调用非虚成员函数,则该调用仅与指针的基类型有关,与该指针当前指向的对象无关。如去掉程序中基类Base中成员函数showName声明中的virtual保留字,则运行结果为:

    Base class

    Base class

    Base class

    因为ptr的基类型是Base,对非虚函数的调用采用静态绑定。

    举例2:

    /*
    	通过基类引用实现动态绑定
    */
    #include <iostream>
    using namespace std;
    class Base{
    public:
    	virtual void showName()
    	{
    		cout << "Base class" << endl;
    	}
    };
    class DClass1 :public Base{
    public:
    	void showName()			// 继承成员的重定义
    	{
    		cout << "The first derived class" << endl;
    	}
    };
    class DClass2 :public Base{
    public:
    	void showName()			// 继承成员的重定义
    	{
    		cout << "The second derived class" << endl;
    	}
    };
    void printIdentity(Base& obj)
    {
    	obj.showName();
    }
    int main()
    {
    	Base bObj;
    	DClass1 d1Obj;
    	DClass2 d2Obj;
    
    	printIdentity(bObj);
    	printIdentity(d1Obj);
    	printIdentity(d2Obj);
    
    	system("pause");
    	return 0;
    }

    注意:

    基类中定义的虚函数,在派生类中仍为虚函数,不管在派生类中是否使用virtual保留字指定。

    虚函数的指定只需在类定义体中的成员函数声明上加保留字virtual,在类定义体外部出现的成员函数定义上不能再用virtual,否则将出现编译错误。


    二、使用虚函数的特定版本

    在一些特定情况下,程序员可能希望覆盖上述默认虚函数调用机制,从而强制函数调用使用虚函数的特定版本。最常见的情况是为了在派生类虚函数调用基类中的相应版本,从而可以重用基类版本完成继承层次中所有类型的公共任务,而每个派生类型只在本类的虚函数版本中添加自己的特殊工作。

    举例:

    假设在一个大学学籍管理程序中需要对一般学生和研究生进行管理,为此可以定义一个学生类Student和一个研究生类GraduateStudent。因为研究生是一种学生,因此可通过继承学生类Student而定义研究生类GraduateStudent。

    学生一般具有姓名、学号、专业、年级等基本信息,研究生除了具有学生的基本信息外,一般还具有导师、类别等信息。

    假设类Student提供虚函数displayInfo显示学生对象的基本信息,类GraduateStudent重定义该displayInfo函数,以显示研究生对象的基本信息。

    定义这两个函数:

    class Student{
    public:
    	virtual void displayInfo()
    	{
    		cout << stuID << endl;
    		cout << name << endl;
    		cout << sex << endl;
    		cout << major << endl;
    	}
    	// 省略其他成员的定义
    };
    class GraduateStudent: public Student{
    public:
    	void displayInfo()
    	{
    		Student::displayInfo();		// 调用基类的displayInfo函数输出基本信息
    		cout << type << endl;		// 输出研究生特有的信息
    		cout << advisor << endl;
    	}
    	// 省略其他成员的定义
    };

    注意:

    在派生类虚函数中调用基类版本时,必须使用作用域分辨操作符。若缺少作用域分辨操作符,则函数调用会在运行时确定并将是一个自身调用,从而导致无穷递归。


    三、虚析构函数

    一般,继承层次的根类中最好要定义虚析构函数。因为在对指向动态分配对象的指针进行delete操作时,也就是对动态创建的对象进行撤销时,需要调用适当的析构函数以清除对象。

    当被撤销的是某个继承层次中的对象时,指针的静态类型可能与实际上被撤销对象的类型不同,对这样的指针进行delete操作时,若基类中的析构函数不是虚函数,将会只调用基类的析构函数,而不调用派生类的析构函数,这可能导致不正确的对象撤销操作:派生类的析构函数没有被调用。

    举例:

    /*
    	非虚析构函数的调用
    */
    #include <iostream>
    using namespace std;
    class Base{
    public:
    	~Base()
    	{
    		cout << "Base destructor" << endl;
    	}
    };
    class DClass :public Base{
    public:
    	~DClass()
    	{
    		cout << "Derived class destructor" << endl;
    	}
    };
    int main()
    {
    	Base *ptr;			// 定义指向基类的指针
    	ptr = new DClass;	// 动态创建派生类对象
    	// 省略对ptr的使用
    	delete ptr;			// 动态撤销派生类对象
    
    	system("pause");
    	return 0;
    }

    执行结果:

    Base destructor

    分析:

    尽管指针ptr实际指向的是派生类DClass的对象,但实际执行的是撤销基类对象的操作,因此被调用的是基类Base的析构函数,而派生类DClass的析构函数并没有被调用。

    若派生类的析构函数中没有什么实质性操作,不会有什么问题;但派生类的析构函数中有操作(如当派生类中含有指针成员时,析构函数中通常会对成员指针进行delete操作,以释放指针成员所指向的内存),则会因为对基类指针进行delete操作没有调用派生类的析构函数导致问题(如派生类对象的指针成员所指向的内存将得不到释放)。

    若在基类中定义需析构函数,将避免出现上述问题,保证执行适当的析构函数。如在程序中将Base类的析构函数指定为虚函数,则运行结果:

    Derived class destructor

    Base destructor

    注意:

    派生类必须对想要重定义的每个继承成员进行声明。在派生类中对基类中定义的虚函数进行声明时,一般要函数的原型(包括函数名和形参列表)完全相同。

    但这一规则有两个例外:一个是派生类的析构函数与基类的析构函数并不同名;另一个是若基类中虚函数返回的是某个类型X的指针(或引用),则派生类中的相应虚函数可以返回类型X的派生类的指针(或引用)。




    • 纯虚函数和抽象类

    一、纯虚函数

    继承机制表现的是事物(实体)类别之间的共性与个性的关系。因此,在一个继承层次中,基类表示了所有派生类所具有的共性,而每个派生类分别表示了本类所特有的个性。基类中的需函数标识的是某个共性操作,派生类中对虚函数的重定义体验了在派生类中该操作所具有的特殊性。若派生类对基类中的虚函数没有进行重定义,则使用基类中定义的版本。

    但在许多情况下,对于基类中的虚函数我们无法给出明确定义,则使用纯虚函数指明在基类中无法给出某个虚函数的定义。

    纯虚函数是一个在基类中声明的虚函数,但在基类中没有定义函数体,要求任何派生类都必须定义自己的版本。

    声明纯虚函数的一般情形如下:

    virtual 返回值类型 函数名(形参列表)=0;

    即在一般虚函数的声明上加一个=0,以指明该函数是一个纯虚函数。


    二、抽象类

    1 定义

    抽象类指包含纯虚函数的类。

    2 特性

    只能用作其他类的基类;

    不能用于直接创建对象实例;

    不能用作函数的形参类型、返回值类型;

    不能用于强制类型转换;

    可声明抽象类的指针和引用;

    举例:

    class Account{
    public:
    	virtual bool deposit(int account) = 0;
    	virtual bool withdraw(int account) = 0;
    	// 类中其他成员的定义省略
    };
    Account *ptr;				// 声明抽象类Account的指针
    Account& fun3(Account& a);	// 抽象类的引用做函数的形参和返回值类型
    
    // Account x;					// 声明抽象类Account的对象(创建抽象类的对象实例)
    // Account fun1(int);			// 抽象类作为函数的返回值类型
    // void fun2(Account a);		// 抽象类作为函数的形参类型

    注意:

    若派生类继承某个抽象类,但派生类并没有对抽象基类中的全部纯虚函数进行重定义,则该派生类也是一个抽象类。


    展开全文
  • C++多态性与虚函数

    2019-09-30 10:52:43
    2、C++多态性是通过虚函数来实现的,虚函数允许子类重新定义成员函数,而子类重新定义父类的做法称为覆盖(override),或者称为重写。 重写的话可以有两种,直接重写成员函数和重写虚函数,只有重写了虚函数的才能...

    1、多态性可以简单地概括为“一个接口,多种方法”,程序在运行时才决定调用的函数(滞后联编)。

    2、C++多态性是通过虚函数来实现的,虚函数允许子类重新定义成员函数,而子类重新定义父类的做法称为覆盖(override),或者称为重写。

         重写的话可以有两种,直接重写成员函数和重写虚函数,只有重写了虚函数的才能算作是体现了C++多态性。

         重载则是允许有多个同名的函数,而这些函数的参数列表不同,允许参数个数不同,参数类型不同,或者两者都不同。

    3、那么多态的作用是什么呢,封装可以使得代码模块化,继承可以扩展已存在的代码,他们的目的都是为了代码重用。而多态的目的则是为了接口重用

    4、抽象类与纯虚函数

        包含纯虚函数的类称为抽象类。由于抽象类包含了没有定义的纯虚函数,所以不能定义抽象类的对象。

    转载于:https://www.cnblogs.com/AllenIverson/p/4513206.html

    展开全文
  • C++多态性与虚函数定义注意事项 1. 多态性 基类指针可以调用基类的成员变量或者成员函数,也可以调用继承类的成员变量或者成员函数,这种多形态多种表现方式称为多态性。 C++提供多态性,可以通过基类指针实现对所有...

    C++多态性与虚函数定义注意事项

    学习笔记,主要来源为《C++从入门到精通》,

    1. 多态性
    基类指针可以调用基类的成员变量或者成员函数,也可以调用继承类的成员变量或者成员函数,这种多形态多种表现方式称为多态性。
    C++提供多态性,可以通过基类指针实现对所有派生类(包括直接派生和间接派生)的成员变量和成员函数进行“全方位”的访问,尤其是成员函数。如没有多态性,只能访问成员变量。

    2.虚函数
    要在C++中实现多态性,成员函数必须是虚函数。虚函数使用非常简单,只需要在函数声明前加是virtual关键字即可。

    结合代码来描述简单一点,使用VS Code完成代码编写和运行,如下

    #include<iostream>
    using namespace std;
    
    //基类Worker
    class Worker 
    {
    	protected:
    		char	*name;
    		int		age;
    
    	public:
    		//基类构造函数
    		Worker(char *wname, int wage ):name(wname), age(wage)		
            /*wname(name), wage(age)参数列表,也可在{}内表示为: 
            name = Wnme;	age = wage;   */
    		{
    			//构造函数的参数列表
    		}
    	
    	//虚函数声明
    	virtual void  myshow()
    	{
    		cout<< "我的名字是" << name << ",年龄是" << age <<endl;
    	}
    };
    
    //派生类People声明
    class People : public Worker  //public为寄生方式
    {
    	public:
        People(char *pname, int page): Worker(name , age)
        {
            //构造函数参数列表
            name = pname;
            age = page;
        }
    	//虚函数声明,命名与基类Worker相同
    	virtual void myshow()
    	{
    		cout<< "我叫"<< name << ",今年" << age <<"岁,请多多关照"<<endl;
    	}
    };
    
    int main()
    {
    	Worker *pw = new Worker ("李亮", 25); //new Worker 必不可少
    	pw->myshow();
    	pw = new People("万明", 22);        //new People必不可少
    	pw->myshow();
    
    }
    

    输出结果如下,由结果可知,…

    由结果可知,虚函数的应用避免了基类与派生类中同名成员函数的的覆盖。
    如果函数myshow前不加virtual声明为虚函数,则实际调的只能是派生类中的myshow。
    3.虚函数定义注意事项
    (1) 函数声明与函数定义分开时,只需要在声明处加上virtual关键字即可。
    (2)派生类中成员函数对基类中同名函数具有覆盖性,可以只将基类中的函数声明为虚函数。
    (3)当基类中定义了虚函数,而派生类中没有定义新的函数来覆盖此函数,那么将直接使用基类的虚函数。
    (4)只有派生类的虚函数覆盖基类的虚函数才能构成多态性,既通过指针访问派生类函数。
    (5)构造函数不可以声明为虚函数。

    此外还有纯虚函数,用于实现基类定义虚函数是不能给其赋予实际意义,以便于在派生类中重新定义该函数的,其语法格式为;

    virtual 返回值类型  函数名 (函数参数) = 0

    其中“=0 ”只是告诉编译系统这是一个纯虚函数。

    接口即为包含纯虚函数的类,也称为抽象类。目的是为其他类提供一个可以继承的适当的基类。注意抽象类不能用用来实例化对象,会产生编译错误。

    展开全文
  • C++__多态性与虚函数

    2017-07-01 17:35:32
    在C++程序设计中,多态性是指具有不同功能的函数可以用同一个函数名,这样就可以用一个函数名调用不同内容的函数。 在面向对象方法中的多态性: 向不同的对象发送同一个消息(函数名),不同的对象在接收时会产生...
  • C++之多态性与虚函数

    2016-08-30 14:07:40
    ++之多态性与虚函数  面向对象程序设计中的多态性是指向不同的对象发送同一个消息,不同对象对应同一消息产生不同行为。在程序中消息就是调用函数,不同的行为就是指不同的实现方法,即执行不同的函数体。也...
  • 6-1 多态性与虚函数

    2017-12-23 11:41:23
    6-1 多态性与虚函数 Time Limit: 1000MS Memory Limit: 65536KB Submit Statistic Problem Description 通过本题目的练习可以掌握多态性的概念和虚函数的定义和使用方法 要求定义一个基类Pet,它有...
  • C++面试题-面向对象-多态性与虚函数 问:在C++程序中调用被C编译器编译后的函数,为什么要加extern "C"? 答:C++语言支持函数重载,C语言不支持函数重载。函数被C++编译后在库中的名字C语言的不同。...
  • 6-2 多态性与虚函数

    2016-09-20 16:55:57
    6-2 多态性与虚函数 Time Limit: 1000MS Memory Limit: 65536KB Submit Statistic Problem Description 通过本题目的练习可以掌握多态性的概念和虚函数的定义和使用方法     要求定义一个基类...
  • C++实验:多态性与虚函数 1. 实验目的 (1) 了解多态性的概念。 (2) 了解虚函数的作用及使用方法。 (3) 了解静态关联和动态关联的概念和用法。 (4) 了解纯虚函数和抽象类的概念和用法。 2.实验内容 事先编写...
  • 多态性 多态性是面向对象程序设计的关键技术之一。若程序设计语言不支持多态性,不能称为面向对象的语言。利用多态性技术,可以调用...以基类为基础的运行时的多态性是面向对象程序设计的标志性特征。体现了类推...
  • c++系列(8):多态性与虚函数

    多人点赞 热门讨论 2021-08-05 10:24:21
    (一) 多态性 1 理解 什么是多态性,顾名思义多态性是指多种状态. 具体含义是类中的同一个方法,在被不同对象执行时有不同的效果.让人想起了函数重载. 静态联编:编译和连接时(这个也建议去了解)确认操作过程.这就得...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 28,306
精华内容 11,322
关键字:

多态性与虚函数有关的代码