Реализация полиморфизма в C

#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() or right->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 or value3.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* фактически указывает указатель.