我在面试中被问过这个问题可以有人回答吗?
#include<string>
#include<iostream>
#include <stdio.h>
using namespace std;
class A
{
int k;
public:
void f1()
{
int i;
printf("1");
}
void f2()
{
k = 3;
printf("3");
}
};
class B
{
int i;
public:
virtual void f1()
{
printf("2");
scanf("%d",&i);
}
};
int main()
{
A* a = NULL;
B* b = NULL;
a->f1(); // works why?(non polymorphic)
b->f1(); // fails why?(polymorphic)
a->f2(); //fails why?
}
最后2个案例是多态类。第一种情况是普通类。我明白,如果我在A的f1中访问i,它将再次给出运行时异常。但我不明白为什么会发生这种情况
答案 0 :(得分:2)
a->f1();
b->f1();
a->f2();
在所有这三种情况下,您都会指向一个指向NULL的指针,即它不指向一个对象。这构成了未定义的行为。他们可能只是纯粹的机会,但你不能依赖它。试图弄清楚为什么一个版本可能起作用也没有多大意义。未定义的行为意味着任何事情都会发生。
答案 1 :(得分:2)
我同意其他帖子,这是未定义的行为,这意味着在执行程序时可能会发生任何事情,包括“做正确的事情”。
现在,让我们来看看如何实现调用:
a->f1()
是正常的方法调用(非虚拟)。大多数编译器将以与以下代码类似的方式编译它:
class A { int i; }
void f1(A* a) { int i; printf("1"); }
这意味着this指针实际上像处理函数的参数一样处理(实际上经常会对这个指针的处理方式进行一些优化,但这在这里是无关紧要的)。现在,由于f1不使用this指针,因此它为null的事实不会导致崩溃。
a->f2()
实际上会崩溃,因为它使用了这个指针:它会更新this->k
。
对b->f1()
的调用是虚函数调用,通常使用虚拟表查找b->vtable[0]()
来实现。由于b为null,因此读取虚拟表的取消引用会崩溃。
答案 2 :(得分:1)
从技术上讲,这是所有未定义的行为。因此,如果没有更多背景(编译器,使用过的设置),这将是正确的答案。
我不相信这是他们期望听到的。
鉴于通常会以这种方式在内部翻译成员函数调用(故意简化):
class A {
void foo(int x) {} // compiler creates function void A_foo(A* this, int x) {}
};
A a;
a.foo(5); // compiler calls A_foo(&a, 5);
但虚拟功能的情况则不同。我不会在这里解释virtual dispatch的原理,但为了简化 - 最终调用的函数依赖于对象的动态类型。如果该对象不存在,则程序无法知道要调用的函数。
至于你a->f2()
失败的原因。想象一下函数A_f2(A* this)
。在里面你访问A的成员k
。这将在我的简化编译器中转换为this->k = 3
。但在实际调用中this
是一个空指针。
答案 3 :(得分:0)
在某种程度上,这三种情况都不“有效”。但另一方面,所有这三个案例都“有效”。
它们都有未定义的行为,因为它们都通过空指针执行间接。
据我所知,如果我在A的f1中访问i,它将再次提供运行时异常
也许,也许不是。未定义的行为未定义,因此任何事情都可能发生。
答案 4 :(得分:0)
所有这三个示例都会导致未定义的行为,因此这是非常特定于实现的,并且不能保证在所有编译器上具有相同的行为。
实现虚函数的一种常用方法是在类的开头添加指向函数指针表的指针。无论何时调用虚函数,程序都会跟随此指针,并在表中查找以确定要调用的函数。因为,在空指针的例子中,它正在查看此指针的无效地址,这会导致运行时错误。
当调用非虚函数时,编译器已经确切地知道要调用的函数,因此它可以直接插入对该函数的调用;访问该对象不是确定调用哪个函数所必需的。因此,如果函数本身不访问该对象,则函数调用将永远不会通过空指针进行访问,因此不会导致运行时错误。