我正在为考试而学习,我收到了以下代码:
#include <iostream>
#include <string>
#include <cmath>
using namespace std;
class Expression {
public:
Expression() = default;
Expression(const Expression&) = delete;
Expression& operator=(const Expression&) = delete;
virtual ~Expression() {}
virtual double eval()const = 0;
virtual void print(ostream& out)const = 0;
friend ostream& operator<<(ostream& out, const Expression& e) {
// cout << "@";
e.print(out);
return out;
}
};
class BinaryExpression : public Expression {
Expression* _e1, * _e2;
char _sign;
virtual double eval(double d1, double d2)const = 0;
public:
BinaryExpression(Expression* e1, Expression* e2, char sign) : _e1(e1), _e2(e2), _sign(sign) {}
~BinaryExpression() override { delete _e1; delete _e2; }
virtual double eval()const override {
cout << "BE eval" << endl;
return eval(_e1->eval(), _e2->eval());
}
virtual void print(ostream& out)const override {
out << '(' << *_e1 << _sign << *_e2 << ')';
}
};
class Sum : public BinaryExpression {
virtual double eval(double d1, double d2)const override {
cout << "Sum private eval" << endl;
return d1 + d2;
}
public:
Sum(Expression* e1, Expression* e2) : BinaryExpression(e1, e2, '+') {}
};
class Exponent : public BinaryExpression {
virtual double eval(double d1, double d2)const override {
cout << "E private eval" << endl;
return std::pow(d1, d2);
}
public:
Exponent(Expression* e1, Expression* e2) : BinaryExpression(e1, e2, '^') {}
};
class Number : public Expression {
double _d;
public:
Number(double d) : _d(d) {}
virtual double eval()const override {
cout << "Num eval" << endl;
return _d;
}
virtual void print(ostream& out)const override {
out << _d;
}
};
int main() {
Expression* e = new Sum(
new Exponent(
new Number(2),
new Number(3)),
new Number(-2));
cout << *e << " = " << e->eval() << endl;
delete e;
}
我使用调试器来查看执行了哪些行,但我仍然想知道编译器如何知道每次在我们调用e->eval()
的main()
中调用哪个函数
输出:
BE eval
Num eval
BE eval
Num eval
Num eval
E private eval
Sum private eval
((2^3)+-2) = 6
假设每个类都有一个eval函数,其中一些类甚至有2,使用Expression指针让我有点失望。编译器在查找每次运行哪个eval()
时,究竟要搜索什么?
您的问题有两个主题:
如果某些类有多个同名函数,编译器如何知道要使用哪个函数?
嗯,它们可能有相同的名称,但它们没有相同的签名。eval()
和eval(x,y)
调用之间没有歧义,因为只有一个eval
不接受任何参数,只有一个eval
接受两个参数。
给定
Expression* e
,编译器如何知道在e->eval()
表达式中调用哪个函数?
答案是编译器不知道。这种情况发生在运行时,而不是编译期间。除非应用一种名为"去机会化"的高级优化技术(这是我不打算在这里谈论的一个大话题(。
通常1当在类上定义虚拟函数时,编译器会在该类型的每个对象中存储额外的数据,即所谓的vtable,它只是一个函数指针数组。然后,当您在虚拟方法上执行e->eval()
时,编译器将用两个步骤替换此调用:(1(从与eval
虚拟方法对应的e
对象中存储的vtable中获取函数指针,(2(用e
对象(以及可能的其他参数(调用该函数指针。
1:这是一个实现细节,是可能的策略之一,不一定会发生什么
虚拟功能基于VTable工作。
简而言之,在声明类时,它实际上要求类具有指向实际函数的指针数组。它不是对函数进行常规调用,而是首先从数组中获取此地址,然后调用它
在实践中,虚拟类在引用该数组(VTable(的类中有一个额外的指针,当构建类时,该指针将填充该类的VTable地址(在编译时计算(。
因此,简单的心理模型:它是指向函数的指针,而不是指向数据的指针,背后有一些编译器的魔力
如果你真的愿意,你可以在C中使用函数指针和大量手动代码来模拟这一点。这肯定是一个很好的练习来了解他们。