2

在下面的代码中有两个类。创建一个类型二的对象,然后将其分配给类一的指针。

在调用 out 函数时,调用第一类的 out 函数。

#include<iostream>
using namespace std;

class one
{
    public :
        void  out()
        {
            cout<<"one ";
        }
};

class two
{
    public : 
        void out()
        {
            cout<<"two ";
        }
};

int main()
{ 
    two dp[3];
    one *bp = (one *)dp;
    for (int i=0; i<3;i++)
    (bp++)->out();
}    

输出

one one one

根据我的输出应该是两个而不是一个。当我们创建类型二的对象时,该对象的内存位置包含类外函数的地址,那么为什么在赋值时调用类外函数呢?

编辑-此外,即使我们更改了第二类中的函数名称,输出也不会更改。

4

3 回答 3

4

新手假设所有C++ 成员函数“属于”一个对象并不罕见。
正如你所注意到的,他们没有。

从概念上讲——精确的过程是编译器实现的细节——你的out成员函数被转换成看起来像这样的“自由”函数:

void one_out(one* this) { cout << "one"; }
void two_out(two* this) { cout << "two"; }

对于非成员函数,这就是所有需要的。

当编译器看到

  (bp++)->out();

知道bp 是一个指向one(它不知道你撒谎)的指针,所以它调用

one_out(bp++);

因为这就是编译器所做的。

于 2012-06-19T17:49:50.023 回答
1

您的机器上的输出可能是“一一”,但它可能很容易被炸毁、消失并给您买冰淇淋或发射导弹。您的代码会引发未定义的行为。

class one
/*...*/

class two
/*...*/

请注意,onetwo是完全不相关的类。您不是从 派生twoone,反之亦然。它们是完全不同的类型。

因为这...

two dp[3];
one *bp = (one *)dp;
for (int i=0; i<3;i++)
    (bp++)->out();

此代码唤起了未定义的行为*。 bp不指向类型的对象one,它指向类型的对象two。鉴于上述代码,您不能以这种方式转换指针。

one(* 注意:未定义行为是指当对象实际上是.时尝试调用方法two。强制转换本身不会引发未定义行为。)

您正在使用的这种转换语法(one *)dp是 C 风格的转换,在这种情况下归结为reinterpret_cast<one*>(bp);. 最好在reinterpret_cast您真正打算这样做时使用,如果除了编写自文档代码之外没有其他原因。

如果你真的想one*从 a 中得到 a two*,你有两个选择。

  1. 创建一个继承层次结构,这样您就可以在不调用 UB 的情况下进行投射
  2. 创建一个转换运算符,以便您可以one从 a构造 a,two反之亦然。

在您的情况下,由于您正在循环对象数组one并尝试two通过这些指针执行方法,因此您最好的选择可能是上面的#1。

class one
{
    public :
        virtual void  out()
        {
            cout<<"one ";
        }
};

class two : public one
{
    public : 
        void out()
        {
            cout<<"two ";
        }
};

现在您的循环将起作用,代码将发出“二二二”,而不是您实际看到的任何随机行为。

于 2012-06-19T17:50:05.293 回答
-3

由于您的out()方法未声明virtual,它是在对象的静态类型而不是对象的运行时类型上调度的。

此外,这两个类之间没有子类型关系,因此以这种方式分配指针是不正确的。

于 2012-06-19T16:38:33.207 回答