精华内容
下载资源
问答
  • 什么是虚函数

    千次阅读 2019-11-20 19:33:27
    什么是虚函数 在某基类中生命为virtual并在一个或多个派生类中被重新定义的成员函数,用法格式为: virtual函数返回类型 函数名(参数列表){函数体} 实现多态性,通过指向派生类的基类指针或引用,访问派生类中的...

    什么是虚函数

    在某基类中生命为virtual并在一个或多个派生类中被重新定义的成员函数,用法格式为:

    virtual函数返回类型 函数名(参数列表){函数体}
    

    实现多态性,通过指向派生类的基类指针或引用,访问派生类中的同名覆盖函数。

    虚函数的创建继承

    虚函数继承是解决多态性的,当用基类指针指向派生类对象的时候,基类指针调用虚函数的时候会自动调用派生类的虚函数,这就是多态性,也叫动态编联。
    演示,先看下面代码:

    #include<iostream>
    using namespace std;
    class A
    {
        public:
            void print()
            {
                cout<<"This is A"<<endl;
            }
    };
     
    class B : public A
    {
        public:
            void print()
            {
                cout<<"This is B"<<endl;
            }
    };
     
    int main()
    {
        //为了在以后便于区分,我这段main()代码叫做main1
        A a;
        B b;
        a.print();
        b.print();
        return 0;
    }
    

    输出结果是:
    This is A
    This is B
    上面通过调用不同类的print实现了不同的策略,不过这不是多态,这是不同类型指针持续的结果而已,我们改一下main函数:

    int main()
    {
        //main2
        A a;
        B b;
        A *p1 = &a;
        A *p2 = &b;
        p1->print();
        p2->print();
        return 0;
    }
    

    输出结果为:
    This is A
    This is A
    这明显不是我们需要的结果,而如果我们采用虚函数,将类改动一下,如下:

    class A
    {
        public:
            virtual void print()
            {
                cout<<"This is A"<<endl;
            }
    };
     
    class B : public A
    {
        public:
            void print()
            {
                cout<<"This is B"<<endl;
            }
    };
    

    这个时候输出就是:
    This is A
    This is B
    这就是多态。

    正确理解函数的重载、覆盖、隐藏

    上面三种都是对应同名函数的三种关系:
    重载:函数名相同,作用域不同,函数参数不同。
    覆盖:基类和派生类中的函数同名,同参数,基类中的函数是虚函数,派生类中的同名函数会覆盖基类中的同名同参的虚函数。
    隐藏:派生类中的函数会隐藏基类的函数。

    虚函数是如何访问的

    我们这里就用上面的类A和B来做解释,首先上面两个类里面都有虚函数,当编译器发现两个类中有虚函数存在的时候,就会为他们分别插入一段数据,并且分别为他们见一个表,那段数据叫做vptr指针,指向的表叫做vtbl,vtbl的作用就是保存自己类中虚函数的地址,可以看成是一个数组,这个数据的每一个元素存放的就是虚函数的地址。
    然后我们创建一个A类的指针,然后去调用print函数,这个时候肯定调用的是A::print函数,这操作首先是取出类里面的vptr的值,这个值就是vtbl的地址,在根据这个值找到vtbl,这个时候vtbl里面只有A::print的地址,所以直接就是调用A::print函数
    如果我们指向B类型的指针,这个时候vtbl里面保存的就是有A和B的print函数,但是B类里面的vptr里面指向的地址是B::print而不是A::print,这是时候就会访问B::print函数

    参考资料:
    https://baike.baidu.com/item/%E8%99%9A%E5%87%BD%E6%95%B0/2912832
    https://blog.csdn.net/n1314n/article/details/90712962

    展开全文
  • 构造一个对象时,必须知道对象实际类型,而虚函数是在运行期间确定实际类型的。而在构造一个对象时,由于对象还未构造成功,编译器就无法知道对象的实际类型,是该类本身,还是派生类,还是其他。 虚函数的执行依赖...

    构造函数不能声明为虚函数,而析构函数可以声明为虚函数,在有的情景下析构函数必须声明为虚函数。
    不建议在构造函数和析构函数里调用虚函数。

    构造函数不能声明为虚函数的原因?

    1. 构造一个对象时,必须知道对象实际类型,而虚函数是在运行期间确定实际类型的。而在构造一个对象时,由于对象还未构造成功,编译器就无法知道对象的实际类型,是该类本身,还是派生类,还是其他。

    2. 虚函数的执行依赖于虚函数表,而虚函数表是在构造函数中进行初始化的,即初始化虚表指针(vptr),使得正确指向虚函数表。而在构造对象期间,虚函数表(vtable)还没有被初始化,将无法进行。

    析构函数设为虚函数的原因?

    析构函数的作用在对象撤销前把类的对象从内存中撤掉,通常系统只会执行基类的析构函数,不执行派生类的析构函数。

    将基类的析构函数声明为虚函数,当撤销基类对象的同时也撤销派生类的对象,这个过程是动态关联完成的。

    析构函数设为虚函数的原因是为了防止内存泄露。在继承体系中,当基类的指针或引用指向派生类,用基类delete时,如果析构函数没有声明为虚函数,只能析构基类对象,派生类对象将无法析构。

    建议将析构函数设为虚函数

    说明:

    1. 使用虚函数,系统会有一定的空间开销。当一个类带有虚函数时,编译系统会为该类构造一个虚函数表,是一个指针数组,存放每个虚函数的入口地址。系统在进行动态关联的时间开销很少,提高了多态性的效率。
    2. 编译器生成的析构函数都是非虚的,除非是一个子类,其父类有个虚析构函数,此时的虚函数特性继承自基类。
    3. 有虚函数的类,一般情况下要定义一个虚析构函数。

    纯虚函数

    在有的情景下,基类的虚函数是为了派生类中的使用而声明定义的,但在基类中没有任何意义,此类函数称为纯虚函数,不需要写成空函数的形式,只需要在声明为以下格式:

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

    纯虚函数是没有函数体的,“=0”并不代表函数的名字不具备函数的功能,不能被调用,在派生类中对该函数定义后,才能具备函数的功能,可以被调用。

    展开全文
  • c++、虚函数、强制类型转换、多态

    千次阅读 2018-08-13 20:52:58
    一、虚函数、覆盖、多态 虚函数:成员函数在定义时添加了 virtual 关键字,这种函数叫虚函数。 覆盖:如果在子类中实现与父类中的虚函数具有相同的函数,那么子类中的成员函数会覆盖父类中的成员函数。 多态:如果...

    一、虚函数、覆盖、多态

    • 虚函数:成员函数在定义时添加了 virtual 关键字,这种函数叫虚函数。
    • 覆盖:如果在子类中实现与父类中的虚函数具有相同的函数,那么子类中的成员函数会覆盖父类中的成员函数。
    • 多态:如果子类中的成员函数对父类中的成员进行了覆盖,当一个指向中子类的父类指针或引用了子类的父类引用,当使用它调用虚函数,然后根据实际的调用对象调用子类中的覆盖函数,而不是父类中了虚函数,这种语法现象叫多态。
    多态的意义在于,同一种类发出同一种调用,而产生不同的反映。

    三、多态的条件

    • 1、多态特性除了父子类之间要构造覆盖,还必须是父类以指针或引用的方式指向子类。

    • 2、当指针或引用已经构造多态时,此时调用成员所传的this指针再调用成员函数时也可以构造多态。

    • 3、在子类的构造函数执行前会先调用父类的构造函数,如果调用被覆盖的虚函数,由于子类还没有构造完成,因此只能是调用父类中的虚函数。
      构造函数在进入函数体执行时,类中看的见的资源已经全部构造完成。

    • 4、在子类的析构函数执行完成后会再调用父类的析构函数,如果调用被覆盖的虚函数,由于子类已经开始析构完成已经不能算是完整的子类了,因此只能调用父类中的虚函数。

    四、纯虚函数和抽象类

    1、纯虚函数
     class A
     {
     public:
      virtual void test(void) = 0;
      virtual void test(void) const = 0;
     };
    • a、纯虚函数不需要被实现,如果非要实现也不能再类中,必须要在类外(虚函数)。
    • b、纯虚函数如果想调用必须在子类中覆盖,然后以多态的方式调用。
    2、抽象类
    成员函数中有纯虚函数的叫抽象类,这种类不能创建对象。
    • 如果子类继承了抽象类,则必须把父类中的纯虚函数覆盖了,否则它也变成了抽象类不能被实例化。
    • 因此抽象类只能以指针或引用的方式指向子类来调用自己的非纯虚函数。
    3、纯抽象类
    所有的成员函数都是纯虚函数,这种类叫纯抽象类。

    面向对象的程序设计四大特性:

    • 抽象、
    定义:对具体问题(对象)进行概括,抽出一类对象的公共性质并加以描述的过程。
    • 封装、
    定义:将抽象得到的数据和行为相结合,形成一个有机的整体,也就是把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏.封装是对象和类概念的主要特性. 简单的说,一个类就是一个封装了数据以及操作这些数据的代码的逻辑实体。在一个对象内部,某些代码或某些数据可以是私有的,不能被外界访问。通过这种方式,对象对内部数据提供了不同级别的保护,以防止程序中无关的部分意外的改变或错误的使用了对象的私有部分.
    • 继承、
    定义:指可以让某个类型的对象获得另一个类型的对象的属性的方法。它支持按级分类的概念。继承是指这样一种能力:它可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。 通过继承创建的新类称为“子类”或“派生类”,被继承的类称为“基类”、“父类”或“超类
    • 多态
    同样的消息被不同类型的对象接收时导致不同的行为。所谓消息是指对类的成员函数的调用,不同的行为是指不同的实现,也就是调用了不同的成员函数。
    纯抽象类可以是封装类的一个过程,同时抽象类也可以当作一个统一的接口类。
    1. 继承是构成多态的基础;

    2. 虚函数是构成多态的关键;

    3. 函数覆盖是构成多态的必备条件;

    4. 多态的应用:函数参数,函数返回值。

    5. 多态的产生必须依靠上面的四点,缺一不可。

    4、纯抽象类的应用场景 代码测试
    • 回调模式:
      函数a由程序员小明实现完成,如果在此时他想调用小光实现的函数b。 1970年
      函数b由程序员小光实现,小光在1980年出生。
    • 命令模式:
      输入一个指使然后执行对应的操作。
    • 生产者与消费者模式
    • 单例模式(饿汉、懒汉)
    • 工厂模式:一个类以专门制造其它类的己任,这种编程模式叫做工厂模式。

    五、C++中的强制类型转换

    1、C++即支持C风格的类型转换,又有自己风格的类型转换。C风格的转换格式很简单,但是有不少缺点的:
    • a.转换太过随意,可以在任意类型之间转换。你可以把一个指向const对象的指针转换成指向非const对象的指针,把一个指向基类对象的指针转换成一个派生类对象的指针,这些转换之间的差距是非常巨大的,但是传统的C语言风格的类型转换没有区分这些。

    • b.C风格的转换没有统一的关键字和标示符。对于大型系统,做代码排查时容易遗漏和忽略。

    C++风格完美的解决了上面两个问题。
    • a.对类型转换做了细分,提供了四种不同类型转换,以支持不同需求的转换;

    • b.类型转换有了统一的标示符,利于代码排查和检视。

    2、C++的强制类型转换使用很麻烦,其实是C++之父不建议使用强制类型转换,一旦代码中需要使用强制类型转换说明代码设计的不合理,强制类型转换是一种亡羊补牢的方法。
    3、在C/C++语言中,强制转型是“一个你必须全神贯注才能正确使用”的特性。所以一定要慎用强制转型。
    4、 类似的问题还会发生在有符号负数转化为无符号数、双精度类型转化为单精度类型、浮点数转化为整型等时候。以上这些情况都属于数值的强制转型,在转换过程中,首先生成临时变量,然后会进行数值截断。
    5 、在标准C中,强制转型还有可能导致内存扩张与截断。这是因为在标准C中,任何非void类型的指针都可以和void类型的指针相互指派,也就可以通过void类型指针这个中介,实现不同类型的指针间接相互转换了。代码如下所示:
    double PI = 3.1415926;  
    double *pd = &PI;  
    void *temp = pd;  
    int *pi = temp; //转换成功 
    
    指针pd指向的空间本是一个双精度数据,8字节。但是经过转换后,pi却指向了一个4字节的int类型。这种发生内存截断的设计缺陷会在转换后进行内存访问时存在安全隐患。不过,这种情况只会发生在标准C中。在C++中,设计者为了杜绝这种错误的出现,规定了不同类型的指针之间不能相互转换,所以在使用纯C++编程时大可放心。而如果C++中嵌入了部分C代码,就要注意因强制转型而带来的内存扩张或截断了。

    static_cast转换 (静态类型转换 )

    1、基本用法:static_cast<目标类型> (原类型 )
    2、使用场景:
    • a、用于类层次结构中基类和派生类之间指针或引用的转换

        上行转换(派生类—->基类)是安全的;

        下行转换(基类—->派生类)由于没有动态类型检查,所以是不安全的。

    • b、用于基本数据类型之间的转换,如把int转换为char,这种带来安全性问题由程序员来保证

    • c、把空指针转换成目标类型的空指针(int* p = static_cast

    3、使用特点:
    • a、主要执行非多态的转换操作,用于代替C中通常的转换操作

    • b、隐式转换都建议使用static_cast进行标明和替换

    代码测试

    class A { // … };  
    void Function()  
    {  
        const A *pConstObj = new A;  
        A *pObj = pConstObj; //ERROR: 不能将const对象指针赋值给非const对象  
      pObj = const_cast<A*>( pConstObj); // OK  
    
    } 
    

    const_cast转换(去常类型转换)

    1、基本用法:const_cast<type-id>expression
    2、使用场景:
    • a、常量指针转换为非常量指针,并且仍然指向原来的对象

    • b、常量引用被转换为非常量引用,并且仍然指向原来的对象

    3、使用特点:
    • a、cosnt_cast是四种类型转换符中唯一可以对常量进行操作的转换符

    • b、去除常量性是一个危险的动作,尽量避免使用。一个特定的场景是:类通过const提供重载时,一般都是非常量函数调用const_cast将参数转换为常量,然后调用常量函数,然后得到结果再调用const_cast 去除常量性。

      代码测试

    reinterpret_cast转换( 重解释类型转换)

    1.基本用法:reinterpret_cast<type-id>expression
    2.使用场景:不到万不得已,不用使用这个转换符,高危操作
    3.使用特点:  
    • a、reinterpret_cast是从底层对数据进行重新解释,依赖具体的平台,可移植性差

    • b、reinterpret_cast可以将整型转换为指针,也可以把指针转换为数组

    • c、reinterpret_cast可以在指针和引用里进行肆无忌惮的转换

     int num = 0x13141516;
     int* p = reinterpret_cast<int*>(num); //0x13141516
     cout << p << endl;
     cout << reinterpret_cast<int>(p) << endl;

    整数与指针之间有类型转换。
    代码测试

    dynamic_cast转换( 动态类型转换)

    1、基本用法:dynamic_cast<type-id> expression
    2、使用场景:
    • 只有在派生类之间转换时才使用dynamic_cast,type-id必须是类指针,类引用或者void*。

    • 不能用于内置的基本数据类型的强制转换

    3、使用特点:
    • a、基类必须要有虚函数,因为dynamic_cast是运行时类型检查,需要运行时类型信息,而这个信息是存储在类的虚函数表中,只有一个类定义了虚函数,才会有虚函数表(如果一个类没有虚函数,那么一般意义上,这个类的设计者也不想它成为一个基类)。

    • b、

    对于下行转换:
            dynamic_cast是安全的(当类型不一致时,转换过来的是空指针),
    
            static_cast是不安全的(当类型不一致时,转换过来的是错误意义的指针,可能造成踩内存,非法访问等各种问题)
    
    对于上行转换时:
        dynamic_cast和static_cast的效果是一样的
    
    • c、dynamic_cast还可以进行交叉转换

    • d、用于父子类的强制转换

    • e、其他三种都是编译时完成的,而它是运行时处理的,运行时要进行类型检查。
      代码测试
      用于构成多态的父子类之间指针类型转换。

    六、虚函数表

    1、什么是虚函数表,当一个类中有虚函数时,编译器会为这个分配一个表专门记录这些虚函数,在类中会一个隐藏的指针成员来指向这张表。
    2、如何证明这张表存在。

    有虚数的类会比没有虚函数的类(相同中的)多4字节,还会添加补齐和对齐。

    3、一个类只有一个张虚函数表,所有的对象共享一张虚函数表。
    4、一般对象的前4个字节是指向虚函数表的指针变量。

    七、动态类型绑定

    1、当使用父类的指针或引用指向子类时,编译器并没有立即生成调用函数的指针,而生成了一段代码,用于检查指针指向的真正的对象是什么类型。
    2、在代码真正运行时才通过对象的指针找到指向虚函数的成员指针。
    3、再通过成员指针访问到虚函数表,再从中找到调用的函数地址。
    4、使用多态会产生额外的一些代码和调用,因此使用多态会降低代码的执行速度。

    八、类的信息

    1、在C++中,使用typeid可以获取类一些信息,以此来确定指针指向的到底是什么类。
    2、typeid可以直接使用来判断是否是同一种类型
    • typeid(标识符) == typeid(标识符)
    3、typeid(标识符).name() 获取类型名,以字符串的形式呈现。
    4、如果typeid(指针)只能获取到指针的类型,typeid(*指针)可以获取到对象实际的类型信息。

    代码测试

    九、虚析构

    1、如果通过父类指针或引用指向子类对象,当使用delete释放对象时,此时只能调用父类的析构函数,如果在子类中使用new/malloc申请的内存资源,那么将导致内存泄漏。
    2、解决方法就是把父类的析构函数设置为虚函数。
    3、在设计类时如果析构函数什么都不需要做,编译器也会生成一个空的析构造函数,但这样会让继承它的子类有安全隐患。
    4、最好把所有的析构函数都设置为虚函数。

    十、C++中的的字符串的使用

    仿照string类设计一个String类。

    实现String类的
    1、构造
    2、析构
    3、拷贝构造
    4、赋值构造
    5、+=、+、<<

    #include <iostream>
    using namespace std;
    class Base
    {
        public:
            Base(int c = 2):_c(c){}
        public:
            int _c;
    };
    class Derived:public Base
    {
        public:
            int _d;
            int _e;
    };
    int main(void)
    {
        int tempA = 2;
        int tempB = 3;
        Base base;
    
        /*1.无编译告警,但是危险操作,譬如说调用drvPtrA->_d会造成不可预知的后果*/
        Derived *drvPtrA = static_cast<Derived *>(&base);
    
        drvPtrA->_d = 4;
        drvPtrA->_e = 5;
        /*2.输出:tempA为4,tempB为5,踩内存了(机器信息:32位ubuntu,编译器c++)*/
        cout<<tempA<<endl;
        cout<<tempB<<endl;
    
        /*3.Base中没有虚函数,无法查看运行时信息,编译不过*/
        Derived *drvPtrB = dynamic_cast<Derived *>(base);
    
        return 0;
    }
    //在基类派生类互相转换时,虽然static_cast是在编译期完成,效率更高,但是不安全,上例中就示范了一个踩内存的例子。相比之下因为dynamic_cast可以查看运行时信息,上例如果Base含有虚函数,那么drvPtrB就是一个空指针(这可比踩内存什么的好多了),不能操作Derived中_d的数据从而保证安全性,所以应该优先使用dynamic_cast。

    返回

    #include <iostream>
    using namespace std;
    class BaseA
    {
        public:
            BaseA(int c = 2):_c(c){}
            int _c;
    };
    class BaseB
    {
        public:
            BaseB(int d = 3):_d(d){}
            int _d;
    };
    int main(void)
    {
        BaseA baseA;
        /*1.编译不过*/
       // BaseB *baseB = static_cast<BaseB *>(&baseA);
        /*2.无任何编译告警,编译通过,正常运行*/
        BaseB *baseC = reinterpret_cast<BaseB *>(&baseA);
        cout<<baseC->_d<<endl; //2
    
        return 0;
    }
    // static_cast虽然也不是一种绝对安全的转换,但是它在转换时,还是会进行必要的检测(诸如指针越界计算,类型检查)。reinterpret_cast完全是肆无忌惮,直接从二进制开始重新映射解释,是极度不安全的,再次提醒,不到万不得已,不要使用

    返回

    #include <iostream>
    
    using namespace std;
    
    int main()
    {
    //加上关键字 防止编译器隐士的优化  之前常量放在寄存器中 提高效率  加上以后要去对应的内存中找数据    
     volatile const int num = 100;
     int* p = (int*)&num;
     *p = 200;
    
     cout << num << endl; //200
     cout << *p << endl; //200
     /*
    
     const int num = 100;
     int* p = const_cast<int*>(&num);
     *p = 200;
     cout << num << endl;//100
     cout << *p << endl;*/ 200
    }
    

    返回

    #include <iostream>
    #include <stdlib.h>
    
    using namespace std;
    
    class A
    {
    };
    
    class B : public A
    {
    };
    
    int main()
    {
     int ch = 48;
     char num = static_cast<char> (ch);
     cout << num << endl;
    
     int* p = static_cast<int*>(malloc(4));
     *p = 20;
     cout << *p << endl;
    
     A* a = new B;
     //B* b = new A; 此行会报错误
     B* b = static_cast<B*>(new A);
    }
    

    返回

    #include <iostream>
    #include <typeinfo>
    using namespace std;
    class A
    {
    public:
     virtual void test(void){}
    };
    
    class Base : public A
    {
     int num;
    public:
    };
    
    int main()
    {
     A* a = new Base;
     A* a1;
     //如果typeid(指针)只能获取到指针的类型,typeid(*指针)可以获取到对象实际的类型信息。
     if(typeid(a) == typeid(a1)) //true if(tyepid (a) == typeid(Base)) false
     {
      cout << "true" << endl;
     }
     else
     {
      cout << "false" << endl;
     }
     cout << typeid(*a).name() << endl;
    }
    

    代码测试

    #include <iostream>
    
    using namespace std;
    
    class Base
    {
    public:
     virtual void show(void) = 0;
    };
    
    class A : public Base
    {
    public:
     void show(void)
     {
      cout << "-----我是类A-----" << endl;
     }
    };
    
    class B : public Base
    {
    public:
     void show(void)
     {
      cout << "-----我是类B-----" << endl;
     }
    };
    
    class C : public Base
    {
    public:
     void show(void)
     {
      cout << "-----我是类C-----" << endl;
     }
    };
    
    class D : public Base
    {
    public:
     void show(void)
     {
      cout << "-----我是类D-----" << endl;
     }
    };
    
    enum CLASS_TYPE {TYPE_A=1,TYPE_B,TYPE_C,TYPE_D};
    
    Base* create_class(CLASS_TYPE type)
    {
     switch(type)
     {
      case TYPE_A: return new A;
      case TYPE_B: return new B;
      case TYPE_C: return new C;
      case TYPE_D: return new D;
     }
    }
    
    int main()
    {
     Base* b1 = create_class(TYPE_A);
     b1->show();
     Base* b2 = create_class(TYPE_B);
     b2->show();
     Base* b3 = create_class(TYPE_C);
     b3->show();
     Base* b4 = create_class(TYPE_D);
     b4->show();
    }
    

    返回

    展开全文
  • 将可能会被继承的父类的析构函数设置为虚函数,可以保证当我们new一个子类,然后使用基类指针指向该子类对象,释放基类指针时可以释放掉子类的空间,防止内存泄漏。 C++默认的析构函数不是虚函数是因为虚函数需要...

    将可能会被继承的父类的析构函数设置为虚函数,可以保证当我们new一个子类,然后使用基类指针指向该子类对象,释放基类指针时可以释放掉子类的空间,防止内存泄漏。

    C++默认的析构函数不是虚函数是因为虚函数需要额外的虚函数表和虚表指针,占用额外的内存。而对于不会被继承的类来说,其析构函数如果是虚函数,就会浪费内存。因此C++默认的析构函数不是虚函数,而是只有当需要当作父类时,设置为虚函数。

    展开全文
  • 什么析构函数必须是虚函数?为什么C++默认的析构函数不是虚函数 将可能会被继承的父类的析构函数设置为虚函数,可以保证当我们new一个子类,然后使用基类指针指向该子类对象,释放基类指针时可以释放掉子类的...
  • 将可能会被继承的父类的析构函数设置为虚函数,可以保证当我们new一个子类,然后使用基类指针指向该子类对象,释放基类指针时可以释放掉子类的空间,防止内存泄漏。 C++默认的***析构函数不是虚函数是因为虚函数需要...
  • 1,从存储空间角度 虚函数对应一个虚表vtbl,可是这个vtbl其实是存储在对象的内存空间的。问题出来了,如果构造函数是虚的,就需要通过 vtbl来调用,可是对象还没有实例化,也就是内存空间还没有,无法找到vtbl。...
  • 虚函数表指针与类型

    2013-03-03 17:05:32
    虚函数指针是在首次声明虚函数类型中北编译器添加进去的,因为在所有这个类型的派生类中和虚函数有相同签名的函数自动作为虚函数处理,所以所有那些派生类型中都需要有一个虚函数表指针以作为支虚拟特性的基础设施...
  • C++虚函数表剖析

    万次阅读 多人点赞 2016-02-18 20:28:23
    关键词:虚函数,虚表,虚表指针,动态绑定,多态一、概述为了实现C++的多态,C++使用了一种动态绑定的技术。这个技术的核心是虚函数表(下文简称虚表)。本文介绍虚函数表是如何实现动态绑定的。二、类的虚表每个...
  • 虚函数

    千次阅读 多人点赞 2015-12-17 22:37:05
    1、什么是虚函数? 指向基类的指针在操作它的多态类对象时,会根据不同的类对象调用其相应的函数,这个函数就是虚函数虚函数用virtual修饰函数名。虚函数的作用是在程序的运行阶段动态地选择合适的成员函数。在...
  • 虚函数 虚函数

    千次阅读 2020-03-09 20:52:33
    虚函数是面向对象编程函数的一种特定形态,是C++用于实现多态的一种有效机制。C++的多态可以分为静态多态和动态多态。函数重载和运算符重载实现的多态属于静态多态,而通过虚函数可以实现动态多态。实现函数的动态联...
  • 今天我们来谈一谈面试 C++ 工程师时经常被谈到的一个问题:为什么析构函数必须是虚函数?为什么默认的析构函数不是虚函数? 首先,我们看一下百度百科对虚函数是怎么定义的: 在某基类中声明为 virtual并在一个或多个...
  • 构造函数为什么不能是虚函数呢?  首先需要了解 vptr指针和虚函数表的概念,以及这两者的关联。  vptr指针指向虚函数表,执行虚函数的时候,会调用vptr指针指向的虚函数的地址。  当定义一个对象的时候,首先...
  • 虚函数 2 之虚函数的定义

    千次阅读 2019-01-24 23:20:34
    1、虚函数的定义 虚函数就是在基类中被关键字 virtual 说明,并在派生类中重新定义的函数。 虚函数的作用是允许在派生类中重新定义与基类同名的函数,并且可以通过基类...virtual 函数类型 函数名(形参表){ ...
  • C++虚函数详解

    万次阅读 多人点赞 2019-04-08 19:31:35
    C++虚函数详解 前言 C++的特性使得我们可以使用函数继承的方法快速实现开发,而为了满足多态与泛型编程这一性质,C++允许用户使用虚函数**(virtual function)来完成运行时决议这一操作,这与一般的编译时决定**...
  • C++构造函数为什么不能是虚函数

    千次阅读 2014-06-19 12:42:56
     那虚函数表指针是什么时候初始化的呢?当然是构造函数。当我们通过new来创建一个对象的时候,第一步是申请需要的内存,第二步就是调用构造函数。试想,如果构造函数是虚函数,那必然需要通过vtbl来找到虚构造函数...
  • 1.首先要了解什么是虚函数:  简单地说,那些被virtual关键字修饰的成员函数,就是虚函数(实现多态)。  作用:指向基类的指针在操作它的多态类对象时,会根据不同的类对象,调用其相应的函数,这个函数就是...
  • C++虚函数指针虚函数

    千次阅读 多人点赞 2017-07-10 22:25:57
    函数重载和运算符重载实现的多态属于静态多态,而通过虚函数可以实现动态多态。实现函数的动态联编其本质核心则是虚表指针与虚函数表。   1. 虚函数与纯虚函数区别 1)虚函数在子类里面也可以不重载的;但纯虚必须...
  • 虚函数的作用是实现动态联编,也就是在程序的运行阶段动态地选择合适的成员函数,在定义了虚函数后,可以在基类的派生类中对虚函数重新定义,在派生类中重新定义的函数应与虚函数具有相同的形参个数和形参类型(也...
  • C++ 构造函数和析构函数可以是虚函数嘛?

    千次阅读 多人点赞 2019-03-22 21:26:37
    简单总结就是:构造函数不可以是虚函数,而析构函数可以且常常是虚函数。 构造函数不能是虚函数 1. 从vptr角度解释 虚函数的调用是通过虚函数表来查找的,而虚函数表由类的实例化对象的vptr指针(vptr可以参考C++...
  • 虚函数详解

    千次阅读 多人点赞 2019-03-09 20:05:26
    文章目录一、虚函数实例二、虚函数的实现(内存布局)1、无继承情况2、单继承情况(无虚函数覆盖)3、单继承情况(有虚函数覆盖)4、多重继承...1、构造函数为什么不能定义为虚函数2、析构函数为什么要定义为虚函数...
  • 虚函数 inline函数

    2014-05-20 15:40:45
    一、首先回顾下什么是虚函数及其作用,以便更好理解什么函数不能声明或定义为虚函数: 1. 定义: 虚函数必须是基类的非静态成员函数,其访问权限可以是protected或public,在基类的类定义中定义虚函数的一般...
  •  一个成员函数被声明为虚函数后,在同一类族中的类不能再定义一个非virtual的但与该虚函数具有相同的参数(包括个数和类型)和函数返回值类型的同名函数。  根据什么考虑是否把一个成员函数声明为虚函数?  ①...
  • C++虚函数虚函数表原理

    万次阅读 多人点赞 2018-07-26 19:49:54
    虚函数的地址存放于虚函数表之中。运行期多态就是通过虚函数虚函数表实现的。 类的对象内部会有指向类内部的虚表地址的指针。通过这个指针调用虚函数虚函数的调用会被编译器转换为对虚函数表的访问: ptr-...
  • 1 构造一个对象的时候,必须知道对象的实际类型,而虚函数行为是在运行期间确定实际类型的。而在构造一个对象时,由于对象还未构造成功。编译器无法知道对象 的实际类型,是该类本身,还是该类的一个派生类,或是更...
  • 一、构造函数为什么不能为虚函数 1. 从存储空间角度,虚函数对应一个指向vtable虚函数表的指针,这大家都知道,可是这个指向vtable的指针其实是存储在对象的内存空间的。问题出来了,如果构造函数是虚的,就需要...
  • 1)因为创建一个对象时需要确定对象的类型,而虚函数是在运行时确定其类型的。而在构造一个对象时,由于对象还未创建成功,编译器无法知道对象的实际类型,是类本身还是类的派生类等等 2)虚函数的调用需要虚函数表...
  • C++中为什么析构函数是虚函数

    万次阅读 多人点赞 2018-09-11 22:42:48
    如果基类的析构函数不是虚函数,在特定情况下会导致派生来无法被析构。 情况1:用派生类类型指针绑定...为什么会出现这种现象呢,个人认为析构的时候如果没有虚函数的动态绑定功能,就只根据指针的类型来进行的,...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 150,771
精华内容 60,308
关键字:

虚函数是什么类型的函数