Ошибочный вывод из программы с использованием производных классов. Не от чистого сердца

#c

#c

Вопрос:

Примечание: Прошу прощения, если на эту программу больно смотреть. это моя первая программа, использующая производные классы. Я планирую отправить его на code review после того, как я заставлю его делать то, что я хочу, чтобы я мог получить советы о том, как его можно было бы разработать лучше. Не стесняйтесь приводить эти советы здесь, но моя главная цель в этом посте — заставить его работать правильно.

Эта программа обрабатывает снятие средств и депозиты для банковских счетов, а затем применяет процентную ставку.

проблема: По какой-то причине кажется, что значение checkingBalance застревает и не обновляется до нового значения на протяжении остальной части цикла при обработке учетных записей типа класса «CheckingAccount». Это странно, потому что тот же цикл используется для обработки другого производного класса «SavingsAccount», и он работает нормально, а одна из учетных записей типа «CheckingAccount», похоже, даже была обработана правильно, поэтому я полностью сбит с толку относительно того, что происходит.

Если бы кто-нибудь мог протянуть руку помощи и помочь мне решить эту проблему, я был бы очень признателен. Пожалуйста, не стесняйтесь просить меня что-либо уточнить, я отвечу как можно скорее.

считываемые данные:

введите описание изображения здесь

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

Код, в котором, как я подозреваю, проблема, заключается:

 //This loop is exited early:

 for (int j = 0; j < transactionNum; j  )
            {
                inFile >> transactionTypeTemp >> amountTemp;
                inFile.get(discard);
                ba.applyTransaction(accountType, transactionTypeTemp, amountTemp, j);
            }

//when something in here occurs?
// c = j from loop

double checkingAccount:: applyTransaction(char transactionTypeTemp, int amountTemp, int c, double checkingBalance)
{
  if (transactionTypeTemp == 'D')
        {
            checkingBalance = checkingBalance   amountTemp;
        }
  else if (transactionTypeTemp == 'W')
        {
            if (checkingBalance < amountTemp)
            {
            cout << "error: transaction number " << c   1 << " never occured due to insufficent funds." << endl;
            }
            else
            {
                checkingBalance = checkingBalance - amountTemp;
                if(checkingBalance < minimumBalance) //if last transaction brought the balance below minimum balance
                {
                    checkingBalance = (checkingBalance - serviceCharge); //apply service charge
                }
            }
        }

        return checkingBalance;
}
  

вся программа:

 //header file
#include <iostream>
#include <fstream>


using namespace std;

class bankAccount
{
    public:
    bankAccount();
    void setAccountInfo(int accountNumTemp, double balanceTemp);
    void prePrint(char accountType);
    void applyTransaction(char accountType, char transactionTypeTemp, int amountTemp, int j);
    void applyInterest(char accountType);
    void postPrint();

    private:
    int accountNumber;
    double balance;
};

class checkingAccount: public bankAccount
{
    public:
    void prePrint(int accountNumber, char accountType, double checkingBalance);
    checkingAccount();
    double  applyTransaction(char transactionTypeTemp, int amountTemp, int c, double    checkingBalance);
    double  applyInterest(double checkingBalance);

    private:
    float interestRate;
    int minimumBalance;
    float serviceCharge;

};

class savingsAccount: public bankAccount
{
    public:
    void prePrint(int savingsAccountNumber, char accountType, double savingsBalance);
    savingsAccount();
    double applyTransaction(char transactionTypeTemp, int amountTemp, int c, double savingsBalance);
    double applyInterest(double checkingBalance);

    private:
    float interestRate;
};

//class implementation .cpp file

bankAccount:: bankAccount()
{
    accountNumber = 0;
    balance = 0;
}

void bankAccount:: setAccountInfo(int accountNumTemp, double balanceTemp)
{
    accountNumber = accountNumTemp;
    balance = balanceTemp;
}

void bankAccount:: prePrint(char accountType)
{
    if(accountType == 'C')
    {
        int checkingAccountNumber = accountNumber;
        double checkingBalance = balance;
        checkingAccount ca;
        ca.prePrint(checkingAccountNumber, accountType, checkingBalance);
    }
    else if (accountType == 'S')
    {
        int savingsAccountNumber = accountNumber;
        double savingsBalance = balance;
        savingsAccount sa;
        sa.prePrint(savingsAccountNumber, accountType, savingsBalance);
    }


}

void bankAccount:: applyTransaction(char accountType, char transactionTypeTemp, int amountTemp, int j)
{
    int c;
    c = j;
    double checkingBalance, savingsBalance;
    checkingAccount ca;
    savingsAccount sa;

        if (accountType == 'C')
        {
            checkingBalance = balance;
            balance = ca.applyTransaction(transactionTypeTemp, amountTemp, c, checkingBalance);
        }
        else if (accountType == 'S')
        {
            savingsBalance = balance;
            balance = sa.applyTransaction(transactionTypeTemp, amountTemp, c, savingsBalance);
        }

}

void bankAccount:: applyInterest(char accountType)
{
    double checkingBalance, savingsBalance;
    checkingAccount ca;
    savingsAccount sa;

    if (accountType == 'C')
    {
    checkingBalance = balance;
    balance = ca.applyInterest(checkingBalance);
    }
    else if (accountType == 'S')
    {
    savingsBalance = balance;
    balance = sa.applyInterest(savingsBalance);
    }


}

void bankAccount:: postPrint()
{
   cout << "Balance after processing: " << balance << endl;
}

checkingAccount:: checkingAccount()
{
    interestRate = .02;
    minimumBalance = 500;
    serviceCharge = 20;
}

void checkingAccount:: prePrint(int checkingAccountNumber, char accountType, double checkingBalance)
{
    cout << "Account Number:" << checkingAccountNumber << " account type:" << accountType << " Starting Balance:" << checkingBalance << endl;
}

double checkingAccount:: applyTransaction(char transactionTypeTemp, int amountTemp, int c, double checkingBalance)
{
  if (transactionTypeTemp == 'D')
        {
            checkingBalance = checkingBalance   amountTemp;
        }
  else if (transactionTypeTemp == 'W')
        {
            if (checkingBalance < amountTemp)
            {
            cout << "error: transaction number " << c   1 << " never occured due to insufficent funds." << endl;
            }
            else
            {
                checkingBalance = checkingBalance - amountTemp;
                if(checkingBalance < minimumBalance) //if last transaction brought the balance below minimum balance
                {
                    checkingBalance = (checkingBalance - serviceCharge); //apply service charge
                }
            }
        }

        return checkingBalance;
}

double checkingAccount:: applyInterest(double checkingBalance)
{
    checkingBalance = (checkingBalance   (checkingBalance * interestRate));
    return checkingBalance;
}
savingsAccount:: savingsAccount()
{
    interestRate = .04;
}


void savingsAccount:: prePrint(int savingsAccountNumber, char accountType, double savingsBalance)
{
    cout << "Account Number:" << savingsAccountNumber << " account type:" << accountType << " Starting Balance:" << savingsBalance << endl;
}

double savingsAccount:: applyTransaction(char transactionTypeTemp, int amountTemp, int c, double savingsBalance)
{
  if (transactionTypeTemp == 'D')
        {
            savingsBalance = savingsBalance   amountTemp;
        }
  else if (transactionTypeTemp == 'W')
        {
            if (savingsBalance < amountTemp)
            {
            cout << "error: transaction number" << c   1 << " never occured due to insufficent funds." << endl;
            }
            else
            {
                savingsBalance = savingsBalance - amountTemp;     //apply transaction
            }
        }

        return savingsBalance;
}

double savingsAccount:: applyInterest(double savingsBalance)
{
    savingsBalance = (savingsBalance   (savingsBalance * interestRate));
    return savingsBalance;
}

//main .cpp file
int main()
{
    ifstream inFile;
    int numberOfAccounts, accountNumTemp, transactionNum, amountTemp;
    double balanceTemp;
    char discard, accountType, transactionTypeTemp;
    bankAccount ba;

    cout << "Processing account data..." << endl;

    inFile.open("Bank.txt");

    if (!inFile)
    {
        for  (int a = 0; a < 20; a  )
            cout  << endl;
        cout << "Cannot open the input file."
             << endl;
            return 1;
    }

    inFile >> numberOfAccounts;
    inFile.get(discard);

    for (int i = 0; i < numberOfAccounts; i  )
    {
            inFile >> accountNumTemp >> accountType >> balanceTemp >> transactionNum;
            inFile.get(discard);
            ba.setAccountInfo(accountNumTemp, balanceTemp);
            ba.prePrint(accountType);

            for (int j = 0; j < transactionNum; j  )
            {
                inFile >> transactionTypeTemp >> amountTemp;
                inFile.get(discard);
                ba.applyTransaction(accountType, transactionTypeTemp, amountTemp, j);
            }
            ba.applyInterest(accountType);
            ba.postPrint();
    }


    inFile.close();

    return 0;
}
  

Вот входные данные в форме, доступной для копирования и вставки, чтобы другим не приходилось вводить их в:

 6
35231 C 500 3
W 250
W 200
W 100
46728 S 2700 2
D 250
W 100
87324 C 500 3
D 300
W 100
W 150
79873 S 800 0
89932 C 3000 2
W 1000
W 1500
98322 C 750 6
D 50
W 75
W 100
W 25
W 30
W 75
  

Комментарии:

1. Вы пробовали пошагово изменять код с помощью отладчика? Постарайтесь свести код к наиболее релевантным разделам.

2. И старые добрые инструкции print. Вам следует попробовать распечатать значения, которые он считывает.

3. хм, инструкции print показывают, что цикл не завершается рано, как я подозревал, значение просто случайным образом перестает изменяться.

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

Ответ №1:

Хм … выполнив быструю компиляцию и запуск с вашими данными, я получаю следующий вывод:

 Processing account data...
Account Number:35231 account type:C Starting Balance:500
error: transaction number 3 never occured due to insufficent funds.
Balance after processing: 10.2
Account Number:46728 account type:S Starting Balance:2700
Balance after processing: 2964
Account Number:87234 account type:C Starting Balance:500
Balance after processing: 561
Account Number:79873 account type:S Starting Balance:800
Balance after processing: 832
Account Number:89832 account type:C Starting Balance:3000
Balance after processing: 510
Account Number:98322 account type:C Starting Balance:750
Balance after processing: 484.5
  

Большая часть этого выглядит для меня довольно разумно. Делая быстрое дополнение в моей голове, «недостаточно средств» кажется разумным для первой учетной записи, поскольку вы предоставляете ей начальный баланс в 500, а снятие средств в сумме составляет 550.

Выражаясь так красиво, как я умею, я не думаю, что написал бы код так же, как вы, но вывод, который я получаю, похоже, не имеет почти тех же проблем, которые вы показали. Это могло быть следствием неопределенного поведения (например, использования неинициализированной переменной), но я не потратил достаточно времени на просмотр кода, чтобы быть уверенным. Лично я думаю, что предпочел бы потратить свое время на другой дизайн, чем на поиск возможных ошибок в коде в его нынешнем виде прямо сейчас.

Редактировать: Что касается перепроектирования кода, когда у вас есть код, который должен быть реализован по-разному в двух производных классах, вы хотите использовать виртуальную функцию и соответствующим образом реализовать ее в каждом производном классе. Вместо того, чтобы создавать банковские счета в качестве локальных переменных в каждой функции-члене и создавать новый объект банковского счета в каждой, я бы создал один объект учетной записи, когда я читаю данные, определяющие учетную запись, а затем использовал этот объект учетной записи для всех транзакций по этому объекту.

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

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

Для сберегательного счета оба уровня и плата устанавливаются равными $ 0.0. Для текущего счета уровень «взимания комиссии» составляет 0,0 доллара, а уровень «отказа в транзакции» (скажем) — 50 долларов. За оба варианта плата составляет, скажем, 50 долларов.

Комментарии:

1. Что ж, ваш вывод верен. Можете ли вы дать несколько советов о том, с чего мне следует начать с улучшения дизайна?

Ответ №2:

Крик.

Код на самом деле не кажется неправильным, поэтому вам может потребоваться просмотреть данные — например, вы переместили этот файл с помощью ftp и перепутали окончания строк.

Рефакторинг этого… Для начала просто ужасно писать свою собственную версию virtual с использованием if / else. Второе обновление переменных класса путем передачи их функциям-членам и обновления в результате приводит к путанице процесса.

Начиная с BankAccount ba; вы повторно используете это для каждой учетной записи, это затрудняет запись и чтение остальной части программы. Вместо этого используйте метод Factory / Lookup.

 for (int i = 0; i < numberOfAccounts; i  )
{
        inFile >> accountNumTemp >> accountType >> balanceTemp >> transactionNum;
        inFile.get(discard);
        bankAccount amp;ba = bankAccount::getAccount(accountType, accountNumTemp, balanceTemp);
  

Без создания полноценной фабрики это может выглядеть примерно так

 class bankAccount
{
    protected:
    bankAccount(int accountNumTemp, double balanceTemp);

    public:
    static bankAccountamp; getAccount( char accountType, int accountNumTemp, double balanceTemp){
        switch(accountType)
          case 'C':
            return checkingAccount(accountNumTemp, balanceTemp);
          case 'S':
            return savingsAccount(accountNumTemp, balanceTemp);
          default:
            throw exception();
        }

    }
    void prePrint();
    void postPrint();

    virtual string type() = 0;
    virtual void applyTransaction( char transactionTypeTemp, int amountTemp, int j) = 0;
    virtual void applyInterest() =0;

    protected:
    int accountNumber;
    double balance;
};
  

Тогда в качестве примера дочерние классы изменились бы следующим образом

 class checkingAccount: public bankAccount
{
    public:
    checkingAccount(int accountNumTemp, double balanceTemp)
        : bankAccount(accountNumTemp, balanceTemp)
    { // these are currently unchanged, could probably move to static
        interestRate = .02;
        minimumBalance = 500;
        serviceCharge = 20;
    }

    void  applyTransaction(char transactionTypeTemp, int amountTemp, int c );
    void  applyInterest();

    private:
    float interestRate;
    int minimumBalance;
    float serviceCharge;

};

void bankAccount::prePrint()
{
    cout << "Account Number:" << accountNumber 
      << " account type:" << type() 
      << " Starting Balance:" << balance << endl;
}

string checkingAccount::type(){ return "C" }

void checkingAccount:: applyTransaction(char transactionTypeTemp, int amountTemp, int c )
{
  if (transactionTypeTemp == 'D')
        {
            balance = balance   amountTemp;
        }
  else if (transactionTypeTemp == 'W')
        {
            if (balance < amountTemp)
            {
            cout << "error: transaction number " << c   1 << " never occured due to insufficent funds." << endl;
            }
            else
            {
                balance = balance - amountTemp;
                if(balance < minimumBalance) //if last transaction brought the balance below minimum balance
                {
                    balance = (balance - serviceCharge); //apply service charge
                }
            }
        }
}

void checkingAccount:: applyInterest()
{
    balance = (balance   (balance * interestRate));
}
  

Ответ №3:

Что ж, ваш вывод верен. Можете ли вы дать несколько советов о том, с чего мне следует начать с улучшения дизайна?

Я бы начал с

  • модульные тесты
  • отладчик

и продолжайте с

  • valgrind

Обновить

Я только что выполнил шаг 3 (valgrind), и он просто работает, предоставляя тот же вывод, что и Jerry:

 Processing account data...
Account Number:35231 account type:C Starting Balance:500
error: transaction number 3 never occured due to insufficent funds.
Balance after processing: 10.2
Account Number:46728 account type:S Starting Balance:2700
Balance after processing: 2964
Account Number:87324 account type:C Starting Balance:500
Balance after processing: 561
Account Number:79873 account type:S Starting Balance:800
Balance after processing: 832
Account Number:89932 account type:C Starting Balance:3000
Balance after processing: 510
Account Number:98322 account type:C Starting Balance:750
Balance after processing: 484.5
  

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

 6
35231 C 500 3
W 250
W 200
W 100
46728 S 2700 2
D 250
W 100
87324 C 500 3
D 300
W 100
W 150
79873 S 800 0
89932 C 3000 2
W 1000
W 1500
98322 C 750 6
D 50
W 75
W 100
W 25
W 30
W 75
  

Комментарии:

1. Добавлены результаты valgrind и ссылочный вывод И ВВОД 🙂

2. Да, я не понимаю, почему вывод отличается для меня? Это то же самое, даже когда я запускаю этот код на других компьютерах в моем доме, но отличается для всех вас. Почему это должно быть? Мои данные те же

3. Как сказал другой человек, что-то могло сломаться при передаче. Является ли отказывающая машина другого типа (другая ОС, компилятор?) Вы могли бы использовать сумму входных данных MD5 в обоих местах, чтобы убедиться, что нет двоичной разницы (Notepad имеет тенденцию вставлять маркеры спецификации, которые ваш stdlib может не обрабатывать) … подобные волосатые вещи

4. На одной машине установлена 62-разрядная версия win 7, на другой — 32-разрядная версия win xp. я пробовал компилировать в codeblocks и dev c на компьютере с win 7 и dev c на компьютере с win xp

5. Удачи в поиске разницы. Приветствия!