做项目的过程中,碰到一个问题。
问题可以抽象为下面的问题:
普通人吃饭拿筷子,小孩吃饭拿勺子。
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方法并进行调用。