360 likes | 471 Views
Extending the Class Capabilities. Let’s extend the definition of the BankAccount class to illustrate some additional capabilities provided by C++ classes. ////////////////////////////////////////////////////////////////////////
E N D
Extending the Class Capabilities Let’s extend the definition of the BankAccount class to illustrate some additional capabilities provided by C++ classes. //////////////////////////////////////////////////////////////////////// // BankAcct.h // // The class definition for the "NEW AND IMPROVED" BankAccount class. // //////////////////////////////////////////////////////////////////////// #ifndef BANK_ACCOUNT_H using namespace std; const int ACCOUNT_NBR_LENGTH = 13; const int PIN_LENGTH = 7; class BankAccount { public: // Constructors BankAccount(); BankAccount(string newAccountNumber, string newPIN, float newBalance, float newInterestRate); BankAccount(const BankAccount &account); // Member Functions void setBalance(float newBalance); void setInterestRate(float newInterestRate); bool checkAccountNumber(string testAccountNumber); bool checkPIN(string testPIN); void deposit(float amount); void withdraw(float amount); const modifier CS 150
BankAcct.h (Continued) const modifiers // Accessor Functions bool getActive() const; float getBalance() const; float getInterestRate() const; // Binary Operators bool operator == (const BankAccount &account) const; bool operator < (const BankAccount &account) const; bool operator <= (const BankAccount &account) const; bool operator > (const BankAccount &account) const; bool operator >= (const BankAccount &account) const; bool operator != (const BankAccount &account) const; // Friend Functions friend void merge(BankAccount &accountA, BankAccount &accountB); friend istream& operator >> (istream &sourceFileStream, BankAccount &account); friend ostream& operator << (ostream &destinationFileStream, const BankAccount &account); overloaded operators friend functions CS 150
BankAcct.h (Continued) private: // Member Functions void setActive(bool newActive); void setAccountNumber(string newAccountNumber); void setPIN(string newPIN); // Accessor Functions string getAccountNumber() const; string getPIN() const; // Data Members string accountNumber; bool active; string pin; float balance; float interestRate; }; #define BANK_ACCOUNT_H #endif const modifiers We’ll examine the const modifiers, overloaded operators, and friend functions as we come to them in the BankAccount’s implementation file and in its driver program. CS 150
BankAcct.cpp ////////////////////////////////////////////////////////////////////////// // BankAcct.cpp // // // // The class implementation for the NEW AND IMPROVED BankAccount class. // ////////////////////////////////////////////////////////////////////////// #include <iomanip> #include <fstream> #include <cstring> #include "BankAcct.h" using namespace std; // Default constructor. Initializes no data members. // BankAccount::BankAccount() { } // Initializing constructor. Initializes appropriate data members // // to parameterized values; initializes active data member to true. // BankAccount::BankAccount(string newAccountNumber, string newPIN, float newBalance, float newInterestRate) { setAccountNumber(newAccountNumber); setPIN(newPIN); setBalance(newBalance); setInterestRate(newInterestRate); setActive(true); } CS 150
BankAcct.cpp (Continued) // Copy constructor. Duplicates the parameterized BankAccount. // BankAccount::BankAccount(const BankAccount &account) { string acctNbr; string pinNbr; account.getAccountNumber(acctNbr); setAccountNumber(acctNbr); setActive(account.getActive()); if (active) { account.getPIN(pinNbr); setPIN(pinNbr); setBalance(account.getBalance()); setInterestRate(account.getInterestRate()); } return; } const modifier Note that the parameter is sent by constant-reference, meaning that no duplicate is made, but that the member function must treat the parameter as a constant (i.e., it cannot alter any aspect of it). Since the member functions getAccountNumber, getActive, getPIN, getBalance, and getInterestRate are all applied to the account parameter, the compiler must be made aware that these member functions cannot alter the BankAccount to which they are applied. We’ll see how this is done when we get to the implementations of those member functions. CS 150
BankAcct.cpp (Continued) // Member function to set the balance data member // // to the parameterized value (if appropriate). // void BankAccount::setBalance(float newBalance) { if (newBalance >= 0.0F) balance = newBalance; return; } // Member function to set the interestRate data member // // to the parameterized value (if appropriate). // void BankAccount::setInterestRate(float newInterestRate) { if (newInterestRate >= 0.0F) interestRate = newInterestRate; return; } // Member function to verify whether the accountNumber data // // member has the same value as the parameterized value. // bool BankAccount::checkAccountNumber(string testAccountNumber) { return (accountNumber == testAccountNumber) } CS 150
BankAcct.cpp (Continued) // Member function to verify whether the pin data member // // has the same value as the parameterized value. // bool BankAccount::checkPIN(string testPIN) { return (pin == testPIN) } // Member function to increment the balance data // // member by the parameterized value (if appropriate). // void BankAccount::deposit(float amount) { if (amount > 0.0F) balance += amount; } // Member function to decrement the balance data // // member by the parameterized value (if appropriate). // void BankAccount::withdraw(float amount) { if (amount <= balance) balance -= amount; return; } CS 150
BankAcct.cpp (Continued) // Accessor member function for the active data member. // bool BankAccount::getActive() const { return active; } // Accessor member function for the balance data member. // float BankAccount::getBalance() const { return balance; } // Accessor member function for the interestRate data member. // float BankAccount::getInterestRate() const { return interestRate; } const modifiers By making these member functions constant, the compiler has been assured that they do not alter the associated BankAccount. If their code did try to alter the BankAccount, either directly or by invoking a non-constant member function, then the compilation would be halted and the code would have to be altered. CS 150
BankAcct.cpp (Continued) // Equality operator. All inactive accounts are considered equal; // // active accounts with the same balance and interest rate are also // // considered equal; otherwise, accounts are not considered equal. // bool BankAccount::operator == (const BankAccount &account) const { if ((!active) && (!account.active)) return true; else if ((!active) || (!account.active)) return false; else return ((interestRate == account.interestRate) && (balance == account.balance)); } overloaded operator Several C++ operators (==, <, <=, >, >=, !=, =, +, -, *, /, %, ++, --, +=, -=, [], etc.) can be overloaded to apply to new classes. In this case, the equality operator has been overloaded for the BankAccount class, making it possible to test the equality of two BankAccount variables x and y with a simple statement like: if (x == y) ... Note that BankAccount equality has been defined very specifically. Also, notice that the parameter has been sent by constant-reference and that the operator has a const modifier, so neitherBankAccount can be altered by the equality operator. CS 150
BankAcct.cpp (Continued) // Less-than operator. An inactive account is considered less than any // // active account; an active account is considered less than another // // active account if it has a smaller interest rate or, if they have // // the same interest rate and it has a smaller balance; otherwise, an // // account is not considered less than another account. // bool BankAccount::operator < (const BankAccount &account) const { if (!account.active) return false; else if (!active) return true; else if (interestRate < account.interestRate) return true; else if (interestRate > account.interestRate) return false; else return (balance < account.balance); } // Less-than-or-equal operator. An account is considered // // less than or equal to another account if it is either // // less than (<) or equal to (==) the other account. // bool BankAccount::operator <= (const BankAccount &account) const { return ((*this < account) || (*this == account)); } overloaded operators Notice that the <= operator has been defined to take advantage of the previously defined < and == operators. Also, observe the use of *this when referring to the “calling object”, in this case, the first BankAccount in the comparison. CS 150
BankAcct.cpp (Continued) // Greater-than operator. An account is considered greater than another // // account if the second account is less than (<) the first account. // bool BankAccount::operator > (const BankAccount &account) const { return (account < *this); } // Greater-than-or-equal operator. An account is considered // // greater than or equal to another account if the second // // account is less than or equal to (<=) the first account. // bool BankAccount::operator >= (const BankAccount &account) const { return ((*this > account) || (*this == account)); } // Inequality operator. An account is considered unequal to another // // account if the account is not equal to (==) the other account. // bool BankAccount::operator != (const BankAccount &account) const { return !(*this == account); } overloaded operators Again, each of these operators has been defined to take advantage of the previously defined operators. Also, observe that it’s okay to reverse the roles of *this and the parameter account in the > operator’s definition, since both BankAccount values have been subjected to a const modifier. CS 150
BankAcct.cpp (Continued) // Friend function to implement the input operator for BankAccounts. It is // // assumed that all BankAccount input will be formatted in the specified // // order: the accountNumber, followed by a character that indicates whether // // the BankAccount is active ('Y' means it is). If active, this character // // is followed by the PIN, the balance, and the interest rate. // istream& operator >> (istream &sourceFileStream, BankAccount &account) { string accountNbr; char activeIndicator; string pinNbr; float bal; float intRate; sourceFileStream >> accountNbr; account.setAccountNumber(accountNbr); sourceFileStream >> activeIndicator; if (activeIndicator != 'Y') account.setActive(false); else { account.setActive(true); sourceFileStream >> pinNbr; account.setPIN(pinNbr); sourceFileStream >> bal; account.setBalance(bal); sourceFileStream >> intRate; account.setInterestRate(intRate); } return sourceFileStream; } friend function A friend function is not a member function of the class, but its “friend” designation does give it access to the private members of the class. While this tends to violate our information hiding principles, it does make it possible to implement fancy functions like this input operator, which can read a properly formatted BankAccount from an input stream. Note that friend functions may be implemented in either the class implementation or in the driver, but they must be declared as friends in the class definition itself. This affords the class at least a modicum of protection. CS 150
BankAcct.cpp (Continued) // Friend function to implement the output operator for BankAccounts. Only // // the accountNumber and an inactive message is output for inactive accounts; // // an active account has its PIN, balance, and interestRate output as well. // ostream& operator << (ostream &destinationFileStream, const BankAccount &account) { destinationFileStream.setf(ios::fixed); destinationFileStream << setprecision(2); string accountNbr; account.getAccountNumber(accountNbr); destinationFileStream << "Account Number: " << accountNbr << endl; if (account.getActive() == false) destinationFileStream << "**** ACCOUNT INACTIVE ****"; else { string pinNbr; account.getPIN(pinNbr); destinationFileStream << "PIN: " << pinNbr << endl; destinationFileStream << "Interest Rate: " << 100*account.getInterestRate() << '%' << endl; destinationFileStream << "Current Balance: $" << account.getBalance(); } return destinationFileStream; } friend function This friend function implements the output operator for the BankAccount class. Notice how dangerous this is: while the accessor functions for the accountNumber and pin data members were private, this operator is public, permitting any driver to output that information! It would probably be smarter to implement this friend function in a driver where this kind of output operator would be appropriate. CS 150
BankAcct.cpp (Continued) // Member function to set the active data member to the parameterized value. // void BankAccount::setActive(bool newActive) { active = newActive; return; } // Member function to set the accountNumber data member to the parameterized value. // void BankAccount::setAccountNumber(string newAccountNumber) { accountNumber = newAccountNumber; return; } // Member function to set the pin data member to the parameterized value. // void BankAccount::setPIN(string newPIN) { pin = newPIN; return; } CS 150
BankAcct.cpp (Continued) // Accessor member function for the accountNumber data member. // string BankAccount::getAccountNumber() const { return accountNumber; } // Accessor member function for the pin data member. // void BankAccount::getPIN() const { return pin; } const modifiers Finally, the last two accessor functions for the BankAccount class have const modifiers to assure the compiler that they don’t alter the “calling object”, the BankAccount*this. To complete our introduction to these new features, we’ll next examine a driver program to test the BankAccount class. CS 150
BankManagerDriver.cpp ////////////////////////////////////////////////////// // BankManagerDriver.cpp // // // // This program permits a bank manager to retrieve, // // peruse, and alter a list of BankAccount data. // ////////////////////////////////////////////////////// #include <iostream> #include <fstream> #include <cstdlib> #include <string> #include "BankAcct.h" using namespace std; const int MAX_ACCOUNT_CAPACITY = 16; void loadAccounts(BankAccount list[], int &length); char queryUser(); void mergeTwoAccounts(BankAccount list[], int length); void openNewAccount(BankAccount list[], int &length); void updateAccount(BankAccount list[], int length); void viewAccount(BankAccount list[], int length); int indexOfAccount(string acctNbr, BankAccount list[], int length); Note that the prototype for the one remaining friend function of the BankAccount class, merge, is not included in the driver’s list of prototypes, in spite of the fact that merge will actually be implemented in this driver. That is because that prototype has already been defined in BankAcct.h, the BankAccount class definition file. CS 150
BankManagerDriver.cpp (Continued) ////////////////////////////////////////////////////////////////////// // The main function drives the bank manager's interactive session. // ////////////////////////////////////////////////////////////////////// void main() { BankAccount acctList[MAX_ACCOUNT_CAPACITY]; int listLength; char request; loadAccounts(acctList, listLength); request = queryUser(); while (request != 'Q') { switch (request) { case 'M': {mergeTwoAccounts(acctList, listLength); break;} case 'N': {openNewAccount(acctList, listLength); break;} case 'U': {updateAccount(acctList, listLength); break;} case 'V': {viewAccount(acctList, listLength); break;} } request = queryUser(); } return; } CS 150
BankManagerDriver.cpp (Continued) ///////////////////////////////////////////////////////////////////////////////// // The merge function is a friend function to the BankAccount class. It // // combines the balances of two active BankAccounts into the BankAccount with // // the higher interest rate (or, if the interest rates are the same, the Bank- // // Account with the higher initial balance), and closes the other BankAccount. // ///////////////////////////////////////////////////////////////////////////////// void merge(BankAccount &accountA, BankAccount &accountB) { if (accountA.accountNumber == accountB.accountNumber) return; if ((accountA.active) && (accountB.active)) { if (accountA >= accountB) { accountA.deposit(accountB.balance); accountB.setActive(false); } else { accountB.deposit(accountA.balance); accountA.setActive(false); } } return; } friend function Here is the other friend function that was defined back in BankAcct.h. Note that, unlike regular functions defined in the driver, this function has access to every private member of the BankAccount class! CS 150
BankManagerDriver.cpp (Continued) ////////////////////////////////////////////////////////////////////////////////// // The loadAccounts function queries the user for the name of a file containing // // BankAccount information, opens that file, and extracts the BankAccount data, // // placing it into the parameterized array of BankAccounts. // ////////////////////////////////////////////////////////////////////////////////// void loadAccounts(BankAccount list[], int &length) { ifstream bankFile; string bankFileName; cout << "Enter the name of the file containing the bank account data: "; cin >> bankFileName; bankFile.open(bankFileName.c_str()); bankFile >> length; for (int i = 0; i < length; i++) bankFile >> list[i]; bankFile.close(); } CS 150
BankManagerDriver.cpp (Continued) //////////////////////////////////////////////////////////////////////////// // The queryUser function asks the user to input a letter indicating what // // BankAccount operation should be performed next. After the user enters // // a valid response, that letter is returned to the calling function. // //////////////////////////////////////////////////////////////////////////// char queryUser() { char ltr; cout << "Select the letter corresponding to the function you wish to perform:” << endl << endl; cout << '\t' << "U - Update an existing account" << endl << '\t' << "V - View an existing account" << endl << '\t' << "N - Open a new account" << endl << '\t' << "M - Merge two existing accounts" << endl << '\t' << "Q - Quit processing bank accounts" << endl << endl << "SELECT ONE LETTER NOW: "; cin >> ltr; ltr = toupper(ltr); while ((ltr != 'U') && (ltr != 'V') && (ltr != 'N') && (ltr != 'M') && (ltr != 'Q')) { cout << "You must choose one of the letters listed above. Try again: "; cin >> ltr; ltr = toupper(ltr); } return ltr; } CS 150
BankManagerDriver.cpp (Continued) /////////////////////////////////////////////////////////////////////////////////// // The mergeTwoAccounts function queries the user for the account numbers of the // // two BankAccounts that are to be combined. If BankAccounts with these numbers // // reside in the parameterized list, then the accounts are appropriately merged. // /////////////////////////////////////////////////////////////////////////////////// void mergeTwoAccounts(BankAccount list[], int length) { string acctNbr; BankAccount dummyAcct; int index[2]; for (int i = 0; i <= 1; i++) { if (i == 0) cout << "Enter the account number of the first account: "; else cout << "Enter the account number of the second account: "; cin >> acctNbr; index[i] = indexOfAccount(acctNbr, list, length); if (index[i] < 0) { cout << "NO ACCOUNT WITH THAT NUMBER. REQUEST DENIED." << endl << endl; return; } } merge(list[index[0]], list[index[1]]); cout << "ACCOUNT MERGER PROCESSED." << endl << endl; return; } CS 150
BankManagerDriver.cpp (Continued) ///////////////////////////////////////////////////////////////////////////////////// // The openNewAccount function queries the user for the account number to be // // given to a brand new BankAccount. If that number doesn't already belong to // // one of the BankAccounts residing in the parameterized list, then a new // // BankAccount is created with the user-supplied account number, PIN, balance, // // and interest rate, and that BankAccount is added to the list (if there's room). // ///////////////////////////////////////////////////////////////////////////////////// void openNewAccount(BankAccount list[], int &length) { string newAcctNbr; string newPIN; float newBal; float newIntRate; int index; if (length == MAX_ACCOUNT_CAPACITY) { cout << "NO ROOM FOR MORE ACCOUNTS. REQUEST DENIED." << endl << endl; return; } cout << "Enter the account number for the new account: "; cin >> newAcctNbr; index = indexOfAccount(newAcctNbr, list, length); if (index >= 0) { cout << "DUPLICATE ACCOUNT NUMBER. REQUEST DENIED." << endl << endl; return; } CS 150
BankManagerDriver.cpp (Continued) cout << "Enter the PIN for the new account: "; cin >> newPIN; cout << "Enter the initial balance for the new account: $"; cin >> newBal; cout << "Enter the interest rate for the new account: "; cin >> newIntRate; BankAccount newAcct(newAcctNbr, newPIN, newBal, newIntRate); list[length] = newAcct; length++; cout << "NEW ACCOUNT PROCESSED." << endl << endl; return; } CS 150
BankManagerDriver.cpp (Continued) ////////////////////////////////////////////////////////////////////////// // The updateAccount function queries the user for the account number // // of a BankAccount whose balance is to be altered. If that number // // corresponds to one of the BankAccounts residing in the parameterized // // list, then the user is asked to supply the amount to be deposited or // // withdrawn from the account, and the transaction is properly handled. // ////////////////////////////////////////////////////////////////////////// void updateAccount(BankAccount list[], int length) { string acctNbr; int index; char request; float amt; cout << "Enter the account number of the account being updated: "; cin >> acctNbr; index = indexOfAccount(acctNbr, list, length); if (index < 0) { cout << "NO ACCOUNT WITH THAT NUMBER. REQUEST DENIED." << endl << endl; return; } CS 150
BankManagerDriver.cpp (Continued) cout << "Enter \"D\" for deposit or \"W\" for withdrawal: "; cin >> request; request = toupper(request); while ((request != 'D') && (request != 'W')) { cout << "You must enter \"D\" or \"W\". Please try again: "; cin >> request; request = toupper(request); } cout << "Enter the amount: $"; cin >> amt; if (request == 'D') list[index].deposit(amt); else list[index].withdraw(amt); cout << "ACCOUNT UPDATE PROCESSED." << endl << endl; return; } CS 150
BankManagerDriver.cpp (Continued) //////////////////////////////////////////////////////////////////////////////////////// // The viewAccount function queries the user for the account number of a BankAccount // // whose data is to be output. If that number corresponds to one of the BankAccounts // // residing in the parameterized list, then BankAccount is appropriately output. // //////////////////////////////////////////////////////////////////////////////////////// void viewAccount(BankAccount list[], int length) { string acctNbr; int index; cout << "Enter the account number of the account to be displayed: "; cin >> acctNbr; index = indexOfAccount(acctNbr, list, length); if (index < 0) { cout << "NO ACCOUNT WITH THAT NUMBER. REQUEST DENIED." << endl << endl; return; } cout << list[index] << endl << endl; return; } CS 150
BankManagerDriver.cpp (Continued) ///////////////////////////////////////////////////////////////////////////////// // The indexOfAccount function searches the parameterized list of BankAccounts // // for one with the parameterized account number. If one is found, its list // // index is returned; otherwise, a value of -1 is returned. // ///////////////////////////////////////////////////////////////////////////////// int indexOfAccount(string acctNbr, BankAccount list[], int length) { for (int i = 0; i < length; i++) { if (list[i].checkAccountNumber(acctNbr)) return i; } return -1; } Now let’s test the BankAccount class and this new driver. CS 150
Testing the View Function: Bad & Good Account Numbers CS 150
Testing the Merge Function CS 150