C++-为什么会调用父类的函数

C++-为什么会调用父类的函数

甜柠檬 发布于 2017-03-05 字数 508 浏览 1231 回复 6

看下面一段代码:

#include <iostream>
using namespace std;
class Base
{
public:
void print()
{
cout <<" invoked from Base" << endl;
}
};

class Derived: public Base
{
public:
void print()
{
cout <<" invoked from Derived" << endl;
}
};

int main()
{
Base *base = new Derived();
base->print();
delete base;
}

如果你对这篇文章有疑问,欢迎到本站 社区 发帖提问或使用手Q扫描下方二维码加群参与讨论,获取更多帮助。

扫码加入群聊

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

泛泛之交 2017-10-19 6 楼

我今天在看《Effective C++》的时候,发现更加准确的解释应该是下面的原因。

先看下面一段代码,派生类没有重新实现non-virtual函数print函数:

 #include <iostream>
using namespace std;
class Base
{
public:
void print()
{
cout <<" invoked from Base" << endl;
}
};

class Derived: public Base
{
public:
//void print()//隐藏了Base::printf函数
//{
// cout <<" invoked from Derived" << endl;
//}
};

int main()
{
Derived d;
Base *base = &d;
Derived *derived = &d;
base->print();
derived->print();
}

运行结果:

这个结果很明显,在此不做解释。
但是如果派生类又定义了自己的函数print版本:

 #include <iostream>
using namespace std;
class Base
{
public:
void print()
{
cout <<" invoked from Base" << endl;
}
};

class Derived: public Base
{
public:
void print()//隐藏了Base::printf函数
{
cout <<" invoked from Derived" << endl;
}
};

int main()
{
Derived d;
Base *base = &d;
Derived *derived = &d;
base->print();
derived->print();
}

输出结果:

虽然两个指针都是通过对象d来调用成员函数print,但是结果却不一样。子类Derived有自己的实现版本,隐藏了父类的print函数。
为什么这种情况下会结果不一样呢?
这是因为non-virtual函数都是静态绑定。由于base被声明为一个pointer-to-Base,通过base调用的non-virtual函数永远是Base所定义的版本。即使base指向一个类型为Base派生之Derived的对象。

归属感 2017-10-02 5 楼

没有声明为虚函数,不会动态查找table,所以不可能调用父类的函数。编译器直接编译为base->print(base);

灵芸 2017-06-26 4 楼

不是续表,没有vtbl指针,定义的是基类的指针,在调用成员函数时候,就调用在父类作用域下的成员函数了。

如果print声明为virtual,则调用的就是派生类的成员函数。

想挽留 2017-06-02 3 楼

C++中的class这些OOP特性其实是和C中间的面向过程是一一对应的。编译器在处理类的时候会把类实现成C中间的函数和结构体:

 class Base
{
public:
void print()
{
//Base
}
};

class Derived: public Base
{
public:
void print()
{
//Derived
}
};

编译出来大致相当于:

 struct Base
{
}

void Base_Constructor(struct Base *)
{
}

void Base_print(struct Base *)
{
//Base
}

struct Derived
{
struct Base base;
}

void Derived_Constructor(struct Derived *p)
{
Base_Constructor(&(p->base));
}

void Derived_print(struct Derived *)
{
}

...
struct Derived *temp = (struct Derived*)malloc(sizeof(struct Derived));
Derived_Constructor(temp);
struct Base *ptr = (struct Base *)temp;
Base_print(ptr);

可见根本就不存在“为什么调用的是基类函数的问题”。
严格来说C++中类的成员函数遵循__thiscall的调用约定而不是__cdecl,不过差别不大。
有虚函数存在的情况:

 class Base
{
public:
virtual void print()
{
//Base
}
};

class Derived: public Base
{
public:
void print()
{
//Derived
}
};

编译成:

 struct Base;

struct Base_vTable
{
void (*print)(struct Base*);
};

struct Base
{
const struct Base_vTable *vTable;
}

void Base_print_virtual(struct Base *)
{
//Base
}

struct Base_vTable Base_vTable_Table = {&Base_print_virtual};

void Base_Constructor(struct Base *p)
{
p->vTable = &Base_vTable_Table;
}

void Base_print(struct Base *p)
{
//Base
(*p->vTable->print)(p);
//这个函数的确是存在的,这样C++中间就可以用指向类成员的指针来指向虚函数,并且正确地访问到派生类成员
}

struct Derived;

struct Derived_vTable
{
struct Base_vTable base;
//如果Derived里面定义了基类中没有的虚函数,就会放到这个后面;不过这个和编译器有关,其他实现也可能另外加一个虚函数表指针
}

struct Derived
{
struct Base base;
}

void Derived_print_virtual(struct Base *p) //虚函数的接口必须是一致的,否则从基类调用时会有问题
{
struct Derived *p2 = (struct Derived *)p; //首先进行转换。多继承的时候指针地址可能还要有偏移。
//Derived
}

struct Derived_vTable Derived_vTable_Table = {{&Derived_print_virtual}};

void Derived_Constructor(struct Derived *p)
{
Base_Constructor(&(p->base));
p->base.vTable = (struct Base_vTable*)(&Derived_vTable_Table);
//由于开头一段是相同的,可以用指针强制转换来指向自己的vTable;基类可以正常调用。
}

void Derived_print(struct Derived *p) //这个接口是用的派生类指针
{
(*p->base.vTable->print)((struct Base *)p); //调用虚函数指针,并且预先进行指针类型转换
}

...

struct Derived *temp = (struct Derived*)malloc(sizeof(struct Derived));
Derived_Constructor(temp);
struct Base *ptr = (struct Base *)temp;
Base_print(ptr);
//这里实际上变成了:
//(*ptr->vTable->print)();
//然后ptr->vTable即temp->base.vTable,指向了Derived_vTable_vTable(或者说Derived_vTable_vTable.base)
//于是ptr->vTable->print指向了Derived_print_virtual,于是正确调用了派生类

了解原理的话就能明白,C++中间的类和继承并不是什么魔术,而是一种自动生成数据结构和函数的套路。

归属感 2017-05-19 2 楼

父类指针虽然指向子类对象,但是每个类都是有访问权限的,父类指针的访问权限就是子类对象中父类部分的函数,所以调用的是父类的函数。
如果你想要让父类指针调用子类的函数,可以用多态机制,就是把父类中的函数声明为虚函数。代码如下:

#include <iostream>
using namespace std;
class Base
{
public:
virtual void print()
{
cout <<" invoked from Base" << endl;
}
};

class Derived: public Base
{
public:
virtual void print()
{
cout <<" invoked from Derived" << endl;
}
};

int main()
{
Base *base = new Derived();
base->print();
delete base;
}

这样之后,父类指针指向子类对象就会调用子类的函数了,因为子类对象中的虚函数表已经用子类的虚函数地址替换了父类的虚函数地址。

偏爱自由 2017-03-30 1 楼

当编译器看到这个句子:

 Base *base = new Derived();

它实际上的行为可以归纳为如下代码:

  1. void* memory=operator new(sizeof(Derived)); //取得原始内存,大小是一个Derived对象的大小
2. call Base::Base();
call Derived::Derived() on *memory; //将内存中对象初始化
这里涉及到继承机制调用构造函数顺序,问题代码默认构造函数,不详说了,反正第2布就是将内存中对象初始化
3. Base *base=static_cast<Base*>(memory); //让base指向新完成的对象,类型是Base

所以,其实这个指针类型最终还是Base类的,里面的print方法指向的也是Base类的print方法,第2步(new Derived())其实只是内存对象初始化--按继承机制的顺序调用构造函数,并没有指定类型,最后base->print()打印结果:

invoked from Base