#c #polymorphism
#c #полиморфизм
Вопрос:
Я смотрел, как Google рассказывает о полиморфизме и чистом коде.
В этом выступающий объяснял, как создать чистый код для такой операции, как 1 2*3:
class Node{
virtual double evaluate() = 0;
};
class ValueNode :Node{
double value;
double evaluate(){
return value;
}
};
class OpNode :Node{
Node left;
Node right;
virtual double evaluate() = 0;
};
class AdditionNode : OpNode{
double evaluate(){
return left.evaluate() right.evaluate();
}
};
class MultiplyNode : OpNode{
double evaluate(){
return left.evaluate() * right.evaluate();
}
};
Я только что скопировал java-код докладчика на c . Но в c
Node left;
Node right;
будет означать, что left и right являются объектами узла, который является абстрактным классом и, следовательно, не разрешен.
Как мне это исправить.
Редактировать: я изменил код на основе предложений, предоставленных @Remy Lebeau. Я ожидал, что результат будет равен 7, но вместо этого я получаю 687194771.
class Node {
public:
virtual int evaluate() = 0;
};
class ValueNode : public Node {
public:
int value;
int evaluate() {
return value;
}
};
class OpNode : public Node {
public:
Node *left;
Node *right;
};
class AdditionNode : public OpNode {
public:
int evaluate() {
return left->evaluate() right->evaluate();
}
};
class MultiplyNode : public OpNode {
public:
int evaluate() {
return left->evaluate() * right->evaluate();
}
};
int _tmain(int argc, _TCHAR* argv[])
{
ValueNode value1;
value1.value = 1;
ValueNode value2;
value1.value = 2;
ValueNode value3;
value1.value = 3;
MultiplyNode multiply;
multiply.left = amp;value2;
multiply.right = amp;value3;
AdditionNode add;
add.left = amp;value1;
add.right = amp;multiply;
int result = add.evaluate();
cout << resu<
return 0;
}
Где моя ошибка?
Комментарии:
1. класс ValueNode и класс AdditionNode используют префикс virtual перед объявлениями evaluate() .
2. может быть, использовать
Node*
(или интеллектуальные указатели)
Ответ №1:
Полиморфизм работает только при использовании указателей / ссылок на объекты. В Java объекты класса всегда выделяются динамически и на них ссылается указатель. Итак, в C объявления классов должны выглядеть следующим образом:
class Node {
public:
virtual double evaluate() = 0;
};
class ValueNode : public Node {
public:
double value;
double evaluate() {
return value;
}
};
class OpNode : public Node {
public:
Node *left;
Node *right;
};
class AdditionNode : public OpNode {
public:
double evaluate() {
return left->evaluate() right->evaluate();
}
};
class MultiplyNode : public OpNode {
public:
double evaluate() {
return left->evaluate() * right->evaluate();
}
};
И затем вы можете настроить пример выражения 1 (2*3)
следующим образом:
ValueNode value1;
value1.value = 1;
ValueNode value2;
value2.value = 2;
ValueNode value3;
value3.value = 3;
MultiplyNode multiply;
multiply.left = amp;value2;
multiply.right = amp;value3;
AdditionNode add;
add.left = amp;value1;
add.right = amp;multiply;
double result = add.evaluate();
При add.evaluate()
вызове он возвращает сумму значений, возвращенных вызовом left->evaluate()
и right->evaluate()
, где left
указывает на value1
и right
указывает на multiply
.
При value1->evaluate()
вызове он возвращает свое value
поле, которое является 1
.
При multiply->evaluate()
вызове он возвращает произведение значений, возвращенных вызовом left->evaluate()
и right->evaluate()
, где left
указывает на value2
и right
указывает на value3
.
При value2->evaluate()
вызове он возвращает свое value
поле, которое является 2
.
При value3->evaluate()
вызове он возвращает свое value
поле, которое является 3
.
Таким образом, add.evaluate()
возвращает сумму 1
plus 2*3
, которая равна 7
.
Комментарии:
1. Узлы слева и справа хранят здесь адреса памяти. Как они вычисляют значения в операции «на лету»?
2. @roang: это то, что
->
делает; когда вы используетеx.y()
для вызова метода,x
должно быть необработанное значение или действовать как таковое (например, бесшовный ссылочный тип C , объявленный сtypenameamp; x
помощью), когда вы используетеx->y()
,x
это указатель (или указательная вещь, например, итератор), который разыменовывается для поискаи вызовитеy
.3. @roang: При вызове
left->evaluate()
orright->evaluate()
компилятор разыменовывает указатель, чтобы добраться до фактическогоNode
объекта, а затем вызываетevaluate()
его. Посколькуevaluate()
является виртуальным, вызов отправляется наиболее производной реализации, будь тоValueNode::evaluate()
,MultiplyNode::evaluate()
, илиAdditionNode::evaluate()
, в зависимости от того, на какой тип объекта указывает указатель. Если вы действительно хотите узнать конкретные подробности о том, как отправляются полиморфные методы, вам следует взять хорошую книгу по C , в которой объясняется, как работают таблицы виртуальных методов.4. @roang:
->
оператор такой же, как*
и.
операторы and, работающие вместе, например, вызовleft->evaluate()
— это то же самое, что и вызов(*left).evaluate()
.5. @roang: вы инициализируете
value1.value
3 раза и не инициализируетеvalue2.value
orvalue3.value
вообще, поэтому они имеют случайные значения. Это была опечатка в моем первоначальном ответе, которую я исправил в более позднем редактировании, но вы, очевидно, не заметили этого. И к вашему сведению, когда вы редактируете вопрос, не удаляйте детали исходного вопроса, просто добавляйте новые детали по мере необходимости. Я исправил вашу правку.
Ответ №2:
Как мне это исправить.
Используйте Node*
вместо Node
.
class OpNode : public Node{
Node* left;
Node* right;
// There is no need to redeclare a pure virtual function.
// virtual double evaluate() = 0;
};
Ответ №3:
Вам нужно сделать левый и правый указатели:
Node* left;
Node* right;
Ответ №4:
Вы не можете просто скопировать код Java в эквивалентный синтаксис C и ожидать, что он будет работать.
Объекты C работают совершенно иначе, чем объекты Java. Вы не можете сравнивать объекты C с объектами Java. Они принципиально отличаются.
C эквивалентом объектов Java будут объекты с подсчетом ссылок, например std::shared_ptr
, поэтому эквивалентный псевдокод C (имитирующий ваш стиль) будет:
class OpNode :Node{
std::shared_ptr<Node> left;
std::shared_ptr<Node> right;
virtual double evaluate() = 0;
};
С типичным подклассом, являющимся:
class AdditionNode : OpNode{
double evaluate(){
return left->evaluate() right->evaluate();
}
};
Я оставляю те же детали, которые вы упустили, такие как частные / общедоступные классы доступа и т.д…
Комментарии:
1. Правильно ли использовать left-> evaluate() right-> evaluate(); Поскольку left и right имеют тип Node, который является абстрактным классом?
2. Это так же правильно, как и вызов любого другого виртуального метода в абстрактном классе.
3. @roang:
evaluate()
это виртуальный метод. Когда вы вызываете виртуальный метод, используя указатель / ссылку на базовый класс (независимо от того, является ли базовый класс абстрактным или нет), вызов отправляется на наиболее производную реализацию этого метода объекта. Итак, когда вы вызываетеevaluate()
Node*
указатель, вы фактически вызываетеValueNode::evaluate()
,MultiplyNode::evaluate()
, илиAdditionNode::evaluate()
, в зависимости от того, на какой тип производного объектаNode*
фактически указывает указатель.