Как я могу сделать переменную всегда равной результату некоторых вычислений? - программирование
Подтвердить что ты не робот

Как я могу сделать переменную всегда равной результату некоторых вычислений?

В математике, если z = x+y/2, то z всегда будет меняться всякий раз, когда мы заменяем значения x и y. Можем ли мы сделать это в программировании без необходимости конкретного обновления z всякий раз, когда мы меняем значения x и y?

Я имею в виду что-то подобное не будет работать, верно?

int x;
int y;
int z{x + y};
cin >> x;
cin >> y;
cout << z;

Если вы не уверены, зачем мне это нужно, я хочу, чтобы переменная показывалась в реальном времени, и она обновлялась автоматически при изменении rhs-переменной.

Например, когда убиваете крипа и получаете золото, чистая стоимость (наличные + стоимость собственных предметов) меняется. Или измеритель скорости автомобиля, который меняется в зависимости от скорости или скорости движения.

4b9b3361

Ответ 1

Изменить: Хотя я полностью ответил на вопрос в том виде, в котором он был задан, пожалуйста, взгляните и на ответ Артелия. В нем рассматриваются некоторые вопросы, на которые мой ответ не распространяется (инкапсуляция, избежание избыточности, риски висячих ссылок). Возможная оптимизация, если расчет дорог, показан в ответе Джонатана Ми.


Вы имеете в виду что-то вроде этого:

class Z
{
    int& x;
    int& y;
public:
    Z(int& x, int& y) : x(x), y(y) { }
    operator int() { return x + y; }
};

Класс задерживает вычисление результата, пока не будет приведен к типу int. Поскольку оператор приведения не является явным, Z может использоваться всякий раз, когда требуется int. Поскольку существует перегрузка operator<< для int, вы можете использовать его, например, непосредственно с std::cout:

int x, y;
Z z(x, y);
std::cin >> x >> y;
if(std::cin) // otherwise, IO error! (e. g. bad user input)
    std::cout << z << std::endl;

Имейте в виду, что вызов функции (неявный оператор приведения) по-прежнему существует, даже если он не отображается. И на самом деле оператор выполняет некоторые истинные вычисления (вместо того, чтобы просто получить доступ к внутреннему члену), поэтому сомнительно, что хорошая идея скрыть вызов функции...

Ответ 2

Вы можете приблизиться к этому с помощью лямбды в C++. Как правило, когда вы устанавливаете переменную, как

int x;
int y;
int z{x + y};

z будет только результатом x + y в это время. Вы должны будете сделать z = x + y; каждый раз, когда вы меняете x или y чтобы обновлять его.

Однако если вы используете лямбду, вы можете сделать так, чтобы он захватывал, на какие объекты он должен ссылаться, и какие вычисления следует выполнить, и затем каждый раз, когда вы получаете доступ к лямбде, он будет давать вам результат в тот момент времени. Это выглядит как

int x;
int y;
auto z = [&](){ return x + y; };
cin >> x;
cin >> y;
cout << z();

и теперь z() будет иметь правильное значение вместо неинициализированного мусора, который имел оригинальный код.

Если вычисления очень дороги, вы можете даже добавить кеширование в лямбду, чтобы убедиться, что вычисления не выполняются, когда вам это не нужно. Это будет выглядеть

auto z = [&](){ static auto cache_x = x; 
                static auto cache_y = y; 
                static auto cache_result = x + y;
                if (x != cache_x || y != cache_y)
                {
                    cache_x = x; 
                    cache_y = y; 
                    cache_result = x + y;
                }
                return cache_result;
};

Ответ 3

Самое близкое, что вы можете получить, это создать функтор:

#include <iostream>

int main() {
    int x;
    int y;

    auto z = [&x, &y] { return x + y; }; // a lambda capturing x and y

    while(true) {
        std::cin >> x;
        std::cin >> y;
        std::cout << z() << "\n";
    }
}

Ответ 4

Есть два главных метода:

  1. Отсроченный расчет - вместо того, чтобы z была простой переменной, сделайте ее функцией, которая вычисляет значение по требованию (примеры см. В других ответах). Это может быть прозрачно для исходного кода, если z - некоторый прокси-объект с неявным преобразованием в требуемый тип (как в ответе Аконкагуа).

  2. Явное уведомление об изменениях. Это требует, чтобы x и y были наблюдаемыми типами; когда любой из них изменяет значение, z обновляет себя (и уведомляет своих наблюдателей, если это применимо).

Первая версия обычно предпочтительнее, но вторая может быть более подходящей, если вам нужно, чтобы z был наблюдаемым типом.

Ответ 5

Это звучит как проблема XY (каламбур).

Судя по всему, вы на самом деле не пишете код в соответствии с хорошими объектно-ориентированными практиками. Я бы посоветовал вам не использовать "хитрости", предложенные другими людьми, а научиться лучше использовать структуру ОО.

Прежде чем я углублюсь в это, обратите внимание, что присвоение отличается от отношения равенства. = В C++ является присваиванием, которое не совпадает с = в математике. Существует несколько (но не так много) языков программирования, которые поддерживают отношения равенства, но C++ не является одним из них. Дело в том, что добавление поддержки отношений равенства создает кучу новых проблем, поэтому это не так просто, как "почему этого еще нет в C++".

В любом случае, в этом случае вам, вероятно, следует инкапсулировать связанные переменные в классе. Затем вы можете использовать методы для получения "актуальной" информации. Например:

class Player {
    std::vector<int> inventory;
    int cash;
public:
    int inventory_total();
    int net_worth();
}

//adds up total value of inventory
int Player::inventory_total() {
    int total = 0;
    for(std::vector<int>::iterator it = inventory.begin(); it != inventory.end(); ++it) {
        total += *it;
    }
    return total;
}

//calculates net worth
int Player::net_worth() {
    //we are using inventory_total() as if it were a variable that automatically
    //holds the sum of the inventory values
    return inventory_total() + cash;
}


...


//we are using net_worth() as if it were a variable that automatically
//holds the sum of the cash and total holdings
std::cout << player1.net_worth();

Я допускаю, что добавить это поведение в класс немного сложнее, чем сказать z = x + y, но на самом деле это всего лишь несколько дополнительных строк кода.

Это было бы очень раздражающим и подверженным ошибкам, если бы вы забыли вызвать функцию где-нибудь.

В этом случае у объекта нет переменной-члена net_worth, поэтому вы не можете случайно использовать ее вместо вызова функции.

Ответ 6

  1. Вы создаете функцию для этого.
  2. Вы вызываете функцию с соответствующими аргументами, когда вам нужно значение.

int z(int x, int y)
{
   return (x + y);
}


int x;
int y;

// This does ot work
// int z{x + y};

cin >> x;
cin >> y;
cout << z(x, y);

Ответ 7

Вы можете определить следующую лямбду z которая всегда возвращает текущее значение x+y потому что x и y захвачены ссылкой:

DEMO

int main()
{
    int x;
    int y;

    const auto z = [&x, &y](){ return x+y; };

    std::cin  >> x; // 1
    std::cin  >> y; // 2
    std::cout << z() << std::endl; // 3

    std::cin  >> x; // 3
    std::cin  >> y; // 4
    std::cout << z() << std::endl; // 7
}

Ответ 8

Таким образом, большая проблема, которую я вижу с предоставленными лямбда-решениями, состоит в том, что z вычисляется каждый раз, когда он проверяется, даже если x и y не изменились. Чтобы обойти это, вам действительно нужно связать эти переменные. Я бы предложил сделать это через class:

class foo {
    int x;
    int y;
    int z;
    void calculate() { z = (x + y) / 2; }
    friend istream& operator >>(istream& lhs, foo& rhs);
public:
    void set_x(const int param) {
        x = param;
        calculate();
    }
    int get_x() const { return x; }
    void set_y(const int param) {
        y = param;
        calculate();
    }
    int get_y() const { return y; }
    int get_z() const { return z; }
};

istream& operator >>(istream& lhs, foo& rhs) {
    lhs >> rhs.x >> rhs.y;
    rhs.calculate();
    return lhs;
}

Это будет пересчитывать z каждый раз, когда x или y установлены. Это хорошее решение, если вы часто z к z, а x и y задаются нечасто. Если x и y устанавливаются часто или calculate стоит дорого, вы можете подумать:

class foo {
    int x;
    int y;
    int z;
    bool dirty;
    void calculate() { z = (x + y) / 2; }
    friend istream& operator >>(istream& lhs, foo& rhs);
public:
    void set_x(const int param) {
        x = param;
        dirty = true;
    }
    int get_x() const { return x; }
    void set_y(const int param) {
        y = param;
        dirty = true;
    }
    int get_y() const { return y; }
    int get_z() const { 
        if(dirty) {
            calculate();
        }
        return z;
    }
};

istream& operator >>(istream& lhs, foo& rhs) {
    lhs >> rhs.x >> rhs.y;
    rhs.dirty = true;
    return lhs;
}

Обратите внимание, что я включил оператор извлечения, поэтому любой выбранный вами код может превратиться во что-то простое:

foo xyz;

cin >> xyz;
cout << xyz.get_z();

Ответ 9

Вы можете получить то, что просите, используя макросы:

{
    int x, y;
#define z (x + y)
    /* use x, y, z */
#undef z
}

#undef для небольшого здравомыслия. Для большего здравомыслия, не используйте макросы вообще, иди с одним из других ответов, и разберись с лишним многословием.

Хотя во многих случаях класс с пользовательским operator int будет работать... хм.

Ответ 10

То, что вы описываете, является поздним связыванием, которое скомпилированный язык, такой как C++, может делать только с трудом. В интерпретируемом языке все, что вам нужно, - это возможность установить z в неоцененное выражение и задержать привязку значения z до тех пор, пока не понадобятся вычисления, как правило, сигнализируемые вызовом функции, которая вызывает оценку, такую как eval в Lisp. На языке правил моей Экспертной системы у меня есть не только eval, но и noeval, который защищает свои аргументы от одного уровня оценки. Это обеспечивает детальный контроль над связыванием, причем некоторые подвыражения оцениваются (связываются), а другие - нет, если это необходимо. Это не применимо к вашему сценарию, но устанавливает сцену с точки зрения языкового ландшафта.

Ответ 11

Используйте функцию. Всякий раз, когда вы захотите изменить x или y, вызовите функцию, передавая их новые значения, и z будет соответственно обновлено.

void func(int x, int y)
{
    z = // something involving x or y
}

Ответ 12

Вы можете написать класс, который инкапсулирует его состояние, чтобы обновлять его либо при мутации, либо возвращать правильный результат по запросу:

#include <iostream>

template<typename T, typename U, typename V>
class DynamicCalc
{
public:
    DynamicCalc(const T& func, const U& memberOne, const V& memberTwo) :
        _func(func)
      , _memberOne(memberOne)
      , _memberTwo(memberTwo)
    {

    }

    void SetMemberOne(const U& memberOne) { _memberOne = memberOne; }
    void SetMemberTwo(const U& memberTwo) { _memberTwo = memberTwo; }
    auto Retrieve() { return _func(_memberOne, _memberTwo); }

    U GetMemberOne() { return _memberOne; }
    V GetMemberTwo() { return _memberTwo; }

private: 
    T _func;

    U _memberOne;
    V _memberTwo;
};

int main() {

    auto func = [](int x, int y) {
        return x + y;
    };
    DynamicCalc<decltype(func), int, int> c(func, 3, 5);

    c.SetMemberOne(5);
    std::cout << c.Retrieve();
}

По правде говоря, если вы счастливы, что вычисление произойдет при повторном запросе значения, тогда методы получения/установки не нужны.

Ответ 13

Хорошо, позвольте мне наконец написать правильный и единственно верный ответ на поставленный вопрос:

Ты не можешь

Вы не можете написать z = x + y а затем заставить весь код, используя z магически перезапускаться всякий раз, когда x или y изменяется.

Так что можно сделать?

Как упомянуто в других ответах, есть несколько шаблонов, чтобы выразить, что вы хотите, чтобы изменения x и y вызывали некоторые обновления, но в любом случае вам нужно, чтобы эти обновления происходили более или менее явно.

В зависимости от варианта использования вы можете:

  • Имейте значение, все равно пересчитанное в любом случае, это имеет значение. Например, если вы пишете игру и перерисовываете экран каждый кадр, то, вероятно, достаточно просто убедиться, что вы случайно не удерживаете значение z между кадрами. Знайте, когда ваша ценность может измениться, а когда нет. Используете ли вы функцию, лямбду, метод класса или просто повторяете выражение - это в основном эстетическое решение. Если возможно, это лучший подход, потому что он полностью прозрачен.

    Например, в гоночной игре вы, скорее всего, обновите текущую скорость в начале расчета нового тика, а затем будете использовать обновленное значение при вычислении движения вашего автомобиля, при перерисовке индикатора скорости, при создании размытия движения и т.д., Вам не нужно никакого волшебства и даже не функции, вы можете просто использовать переменную, потому что вы знаете, что ваша скорость не изменится в течение одного кадра.

  • Назовите обновление явно. Используйте его, например, когда у вас есть один виджет, который нужно обновить. Недостатком является то, что вы должны помнить, чтобы вызвать обновление, что несколько хрупко, но с другой стороны - это очень просто. Середина - интегрировать вызов обновления с сеттером, что делает его некачественной реализацией Observer.

  • Используйте шаблон Observer (см. Также сигналы и слоты, это один из способов реализации Observer). Используйте его, например, когда у вас есть много виджетов для обновления, или вы создаете их динамически. Старайтесь не использовать его, когда один из вышеперечисленных работает, они намного проще.

  • Используйте выделенную библиотеку реактивного программирования. Поскольку такие вещи существуют, я чувствую себя обязанным упомянуть об этом. Однако я, честно говоря, не вижу ни одного приложения, где бы я его использовал. В основном это кажется сложным способом отстрелить ноги. Неявные обновления будут иметь неприятные последствия, и вам придется все переписать. Только не в C++. Что важно: хотя этот подход наиболее близок к "магическому обновлению всего", он будет накладывать ограничения на то, как вы пишете свой код, и в итоге вы получите одно из вышеперечисленных решений, только более сложное.