c++ 类内部函数调用虚函数

做项目的过程中,碰到一个问题。

问题可以抽象为下面的问题:

普通人吃饭拿筷子,小孩吃饭拿勺子。

class People {
public:
    void eat() {
        get_util_to_eat();
    }

    virtual void get_util_to_eat() {
        std::cout << "People get chopsticks" << std::endl;
    }
};

class Children : public People {
public:
    void get_util_to_eat() {
        std::cout << "Children get scoop" << std::endl;
    }
};

int main() {
    People* people = new Children();
    people->eat();
    return 0;
}

输出结果:

Children get scoop

当然这也符合我们的预期。

因为people不是虚函数,所以上述程序调用的是people中的eat方法,这就涉及到一个之前我一直模糊的概念,在一个类方法中调用虚方法,是如何调用的。

这又涉及到之前不得不说的一个问题:

class A {
public:
    void print() {
        std::cout << "i am A" << std::endl;
    }
};

int main() {
    A* a = NULL;
    a->print();
    return 0;
}

上述代码会输出什么,按照直观的感觉NULL怎么可能调用方法呢,要出core吧。

但是事实上,输出的是:

i am A

调用类函数的时候,c++编译器并不会管该类是否为空,而是将该类的地址当做this指针传到函数中去。

a->print() 时,在编译器中就相当于print(&a)

有因为print中没有用到成员变量的情况,所以自然能很正确的运行。

然后来看下汇编代码就能更理解了。以下是People类内的汇编代码。

21        void eat() {
   0x0000000000400bd2 <+0>:    push   %rbp
   0x0000000000400bd3 <+1>:    mov    %rsp,%rbp
   0x0000000000400bd6 <+4>:    sub    $0x10,%rsp
   0x0000000000400bda <+8>:    mov    %rdi,-0x8(%rbp) //rsp表示第一个参数,也就是类的指针
get_util_to_eat();
   0x0000000000400be9 <+23>:    mov    -0x8(%rbp),%rax //将类指针放入rax寄存器中
   0x0000000000400bed <+27>:    mov    (%rax),%rax //取首地址值,也就是虚表地址
   0x0000000000400bf0 <+30>:    mov    -0x8(%rbp),%rdi //放入rdi中,下次函数调用的时候取参用
   0x0000000000400bf4 <+34>:    mov    (%rax),%rax //取出虚表中函数的地址
   0x0000000000400bf7 <+37>:    callq  *%rax //调用改函数

总结就是,进入类的非静态成员函数时,会默认携带类的指针(this),然后改函数内用到成员变量、成员方法都等同于在前面加了一个this->

So 回到最初的那个问题,在People::eat中传入的是Chilren的指针,所以调用 get_util_to_eat 时从虚表中取出了Children::get_util_to_eat方法并进行调用。

时间: 2024-10-05 04:26:01

c++ 类内部函数调用虚函数的相关文章

C++学习笔记(十二):类继承、虚函数、纯虚函数、抽象类和嵌套类

类继承 在C++类继承中,一个派生类可以从一个基类派生,也可以从多个基类派生. 从一个基类派生的继承称为单继承:从多个基类派生的继承称为多继承. 1 //单继承的定义 2 class B:public A 3 { 4 < 派生类新定义成员> 5 }; 6 //多继承的定义 7 class C:public A,private B 8 { 9 < 派生类新定义成员> 10 }; 我们这篇主要说单继承. 派生类共有三种C++类继承方式: 公有继承(public) 基类的公有成员和保护成

C++基础知识 基类指针、虚函数、多态性、纯虚函数、虚析构

一.基类指针.派生类指针 父类指针可以new一个子类对象 二.虚函数 有没有一个解决方法,使我们只定义一个对象指针,就可以调用父类,以及各个子类的同名函数? 有解决方案,这个对象指针必须是一个父类类型,我们如果想通过一个父类指针调用父类.子类中的同名函数的话,这个函数是有要求的: 在父类中,eat函数声明之前必须要加virtual声明eat()函数为虚函数. 一旦某个函数被声明为虚函数,那么所有派生类(子类)中eat()函数都是虚函数. 为了避免你在子类中写错虚函数,在C++11中,你可以在函数

C++:抽象基类和纯虚函数的理解

转载地址:http://blog.csdn.net/acs713/article/details/7352440 抽象类是一种特殊的类,它是为了抽象和设计的目的为建立的,它处于继承层次结构的较上层. ⑴抽象类的定义: 称带有纯虚函数的类为抽象类. ⑵抽象类的作用: 抽象类的主要作用是将有关的操作作为结果接口组织在一个继承层次结构中,由它来为派生类提供一个公共的根,派生类将具体实现在其基类中作为接口的操作.所以派生类实际上刻画了一组子类的操作接口的通用语义,这些语义也传给子类,子类可以具体实现这些

自绘CListCtrl类,重载虚函数DrawItem

[cpp] view plain copy //自绘CListCtrl类,重载虚函数DrawItem void CNewListCtrl::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct) { // TODO: Add your code to draw the specified item ASSERT(lpDrawItemStruct->CtlType == ODT_LISTVIEW); CDC dc; dc.Attach(lpDrawItemStruc

形状类的纯虚函数

/* * Copyright (c) 2013, 烟台大学计算机学院 * All rights reserved. * 作 者:马广明 * 完成日期:2014 年 5 月 27 日 * 版 本 号:v1.0 * 问题描述:形状类中的纯虚函数 */ #include <iostream> using namespace std; const double PI=3.14; class Shape { public: virtual double area() const=0; }; class

3.1.9 类内部定义的函数:绑定方法与非绑定方法

'''在类内部定义的函数,分为两大类: 一:绑定方法:绑定给谁,就应该由谁来调用,谁来调用就会把调用者当作第一个参数自动传入 1. 绑定到对象的方法:在类内定义的没有被任何装饰器修饰的 2. 绑定到类的方法:在类内定义的被装饰器@classmethod修饰的方法 二:非绑定方法:没有自动传值这么一说了,就类中定义的一个普通工具,对象和类都可以使用 非绑定方法:不与类或者对象绑定,在类内定义的被装饰器@staticmethod修饰的方法 说明:未做任何修饰时:在类内定义的方法是绑定给对象的,对象调

空类,含有虚函数的类的大小

1.为何空类的大小不是0呢? 为了确保两个不同对象的地址不同,必须如此. 类的实例化是在内存中分配一块地址,每个实例都有独一无二的内存地址.空类也会实例化,为保证空类实例化后的独一无二性,编译器会给空类隐含的添加一个字节.所以,空类的sizeof为1,而不是0. 2.继承关系中的类大小: case 1: 父类有虚函数,子类继承. class A{ virtual void f(){} }; class B:public A{} 此时,类A和类B都不是空类,其sizeof都是4,因为它们都具有虚函

基类析构函数为虚函数

代码:析构函数为非虚函数 #include <iostream> using namespace std; class A { public: A() { cout << "A" << endl; } ~A() { cout << "~A" << endl; } }; class B : public A{ public: B() { cout << "B" <<

避免构造/析构函数调用虚函数(转)

不要在类的构造或者析构函数中调用虚函数,因为这种调用不会如你所愿,即使成功一点,最后还会使你沮丧不已.如果你以前是一个Java或者C#程序员,请密切注意本节的内容-这正是C++与其它语言的大区别之一. 假设你有一个为股票交易建模的类层次结构,例如买单,卖单,等等.为该类交易建立审计系统是非常重要的,这样的话,每当创建一个交易对象,在审计登录项上就生成一个适当的入口项.这看上去不失为一种解决该问题的合理方法: class Transaction {// 所有交易的基类 public: Transa