Question
This assignment reviews object-oriented programming concepts such as classes, methods, constructors, accessor methods, and access modifiers. It makes use of an array of objects as
This assignment reviews object-oriented programming concepts such as classes, methods, constructors, accessor methods, and access modifiers. It makes use of an array of objects as a class data member, and introduces the concept of object serialization or "binary I/O".
Set Up
(Yes, these commands are rather tedious to type repeatedly. Part 2 of this assignment introduces a new technique for compiling and linking your program files called a makefile. Makefiles require a bit more work up front, but save a lot of typing at the command line once the makefile has been created.)
As in Assignment 1, you should create a subdirectory to hold your files for Assignment 2.
In that directory, make a symbolic link to the data file for this part of the assignment:
ln -s /home/turing/t90kjm1/CS241/Data/Fall2018/Assign2/accounts
In this assignment, you will be creating several source code and header files, as described below. You can create each of these files separately using the nano editor, just as you did on Assignment 1.
To compile and link the program you've created, type:
g++ -Wall -std=c++11 -o assign2 assign2.cpp BankAccount.cpp
Once you've added the AccountDB class, you should type:
g++ -Wall -std=c++11 -o assign2 assign2.cpp BankAccount.cpp AccountDB.cpp
To run the executable file created by the previous command, type:
./assign2
Program
For this assignment, you will need to write three source code files as well as two header files. Each of these files is relatively short, but many inexperienced programmers are overwhelmed by the idea of writing a program as multiple files. "Where do I start?!!" is a common refrain. This assignment sheet attempts to walk you through the steps of writing a multi-file program.
The steps outlined below should not be thought of as a purely linear process, but rather an iterative one - For example, work a little on Step 1, then a little on Step 2, then test what you've written (Step 3).
Step 1: Write the BankAccount class definition
The BankAccount class represents information about a bank account. The code for the BankAccount class will be placed in two separate files, which is the norm for non-template C++ classes.
The header file for a class contains the class definition, including declarations of any data members and prototypes for the methods of the class. The name of the header file should be of the form ClassName.h (for example, BankAccount.h for the header file of the BankAccount class).
A skeleton for the BankAccount.h file is given below. As shown, a header file should begin and end with header guards to prevent it from accidentally being #included more than once in the same source code file (which would produce duplicate symbol definition errors). The symbol name used in the header guards can be any valid C++ name that is not already in use in your program or the C/C++ libraries. Using a name of the format CLASSNAME_H (like BANK_ACCOUNT_H in the code below) is recommended to avoid naming conflicts.
#ifndef BANK_ACCOUNT_H #define BANK_ACCOUNT_H //***************************************************************** // FILE: BankAccount.h // AUTHOR: your name // LOGON ID: your z-ID // DUE DATE: due date of assignment // // PURPOSE: Contains the declaration for the BankAccount class. //***************************************************************** class BankAccount { // Data members and method prototypes for the BankAccount class go here . . . }; #endif
Data Members
The BankAccount class should have the following private data members:
An account number (a char array with room for 10 characters PLUS the null character, i.e. 11 elements total)
A customer name (a char array with room for 20 characters PLUS the null character)
A current account balance (a double variable)
Note: Make that sure you code your data members in THE EXACT ORDER LISTED ABOVE and with THE EXACT SAME DATA TYPES. If you use float instead of double or only make the name array 20 characters long instead of 21, your final program will not work correctly!
C++11 Initialization Option for Data Members
C++11 adds the ability to initialize the non-static data members of a class at the time you declare them using a "brace-or-equal" syntax. This is very convenient, and can eliminate most or all of the code from your default constructor. Here are a few examples of the kind of initializations you can do in a class declaration:
class Foo { // Data members private: int x = 0; // Initialize x to 0 double y = 9.9; // Initialize y to 9.9 char text[21]{}; // Initialize text to an // empty string char name[11]{'J', 'o', 'h', 'n', '\0'}; // Initialize name to "John" string s{"Hello"}; // Initialize s to "Hello" etc. };
Feel free to use this option if you want to.
Method Prototypes
The BankAccount class declaration should (eventually) contain public prototypes for all of the methods in the BankAccount.cpp source code file described in Step 2 below.
Step 2: Write the BankAccount class implementation
The source code file for a class contains the method definitions for the class. The name of the source code file should be of the form ClassName.cpp or ClassName.cc (for example, BankAccount.cpp for the source code file of the BankAccount class).
The BankAccount class implementation should (eventually) contain definitions for all of the methods described below. Make sure to #include "BankAccount.h" at the top of this file.
BankAccount::BankAccount()
Parameters: None.
Returns: Like all C++ constructors, this constructor does not have a return data type.
This "default" constructor should set the account number and customer name data members to "null strings". This can be done by copying a null string literal ("") into the character array using strcpy() or by setting the first element of the array to a null character ('\0'). The account balance data member should be set to 0.
(If you're working in C++11 and you initialized the data members at declaration as described above under C++11 Initialization Option for Data Members, this method's body can be empty. You still need to code the method though, even though it won't actually do anything.)
BankAccount::BankAccount(const char* newAccountNumber, const char* newName, double newBalance)
Parameters: 1) A C string that will not be changed by this method and that contains a new account number; 2) a C string that will not be changed by this method and that contains a new customer name; 3) a new account balance.
Returns: Like all C++ constructors, this constructor does not have a return data type.
This alternate constructor can be used to initialize a new BankAccount object with data. Use strcpy() to copy the new account number parameter into the account number data member and the new customer name parameter into the customer name data member. The new account balance parameter can simply be assigned to the account balance data member.
const char* BankAccount::getAccountNumber() const
Parameters: None.
Returns: The account number data member. In C++, the usual return data type specified when you are returning the name of a character array is const char* or "pointer to a character constant" (since returning an array's name will convert that name into a pointer to the first element of the array, which in this case is data type char. Including the const keyword prevents the code in the calling routine from using the returned pointer to change the account number (which would violate the principle of encapsulation).
Note that this method is marked as const because it does not alter any data members of the BankAccount object for which it is called.
const char* BankAccount::getName() const
Parameters: None.
Returns: The name data member.
double BankAccount::getBalance() const
Parameters: None.
Returns: The balance data member.
void BankAccount::processDeposit(double amount)
Parameters: A deposit amount.
Returns: Nothing.
This method should simply add the deposit amount to the balance for the bank account.
bool BankAccount::processWithdrawal(double amount)
Parameters: A withdrawal amount.
Returns: A boolean value indicating whether or not the withdrawal was successfully processed.
If the bank account's balance is less than the withdrawal amount, this method should just return false. Otherwise, subtract the withdrawal amount from the balance of the bank account and return true.
void BankAccount::print() const
Parameters: None.
Returns: Nothing.
This method should print the values of the data members for the account in a format similar to the following:
Account Number: 0003097439 Name: John Smith Balance: $5234.38
Step 3: Test and debug the BankAccount class
As you write your declaration and implementation of the BankAccount class, you should begin testing the code you've written. Create a basic main program called assign2.cpp that tests your class. This is not the final version of assign2.cpp that you will eventually submit. In fact, you'll end up deleting most (or all) of it by the time you're done with the assignment. An example test program is given below.
You do not have to have written all of the methods for the BankAccount class before you begin testing it. Simply comment out the parts of your test program that call methods you haven't written yet. Write one method definition, add its prototype to the class declaration, uncomment the corresponding test code in your test program, and then compile and link your program. If you get syntax errors, fix them before you attempt to write additional code. A larger amount of code that does not compile is not useful - it just makes debugging harder! The goal here is to constantly maintain a working program.
#include#include "BankAccount.h" using std::cout; using std::endl; int main() { // Test default constructor BankAccount account1; // Test alternate constructor BankAccount account2("1111111111", "Vanessa Long", 350.00); // Test print() method and whether constructors // properly initialized objects cout << "Printing account1 "; account1.print(); cout << endl; cout << "Printing account2 "; account2.print(); cout << endl; // Test accessor methods cout << "Testing accessor methods for account2 "; cout << account2.getAccountNumber() << endl; cout << account2.getName() << endl; cout << account2.getBalance() << endl << endl; // Test the processWithdrawal() method with a successful withdrawal bool withdrawalAccepted = account2.processWithdrawal(60.00); if (withdrawalAccepted) cout << "Withdrawal of $60.00 accepted, new balance on account2 is $" << account2.getBalance() << endl; else cout << "Insufficient funds for withdrawal of $60.00 from account2 "; cout << endl; // Test the processWithdrawal() method with an overdraft withdrawalAccepted = account2.processWithdrawal(500.00); if (withdrawalAccepted) cout << "Withdrawal of $500.00 accepted, new balance on account2 is $" << account2.getBalance() << endl; else cout << "Insufficient funds for withdrawal of $500.00 from account2 "; cout << endl; // Test the processDeposit() method cout << "Deposit of $200.00 to account2 "; account2.processDeposit(200.00); account2.print(); return 0; }
Once your BankAccount class has been thoroughly tested and debugged, it's time to write the second class for this assignment. You can also delete all of the code in the main() function. It has served its purpose (testing your BankAccount class), but will be completely replaced by new code in Step 6 below.
Step 4: Write the AccountDB class declaration
The AccountDB class represents a database of BankAccount objects. Like the BankAccount class, the code for this class will be placed in two separate files.
Place the class declaration in a header file called AccountDB.h. Like the file BankAccount.h you wrote in Step 1, this file should begin and end with header guards to prevent it from accidentally being #included more than once in the same source code file.
After the header guard at the top of the file but before the class definition, make sure to #include "BankAccount.h".
Data Members
The AccountDB class should have the following two private data members:
A bank name (a char array with room for 30 characters PLUS the null character)
An array of 20 BankAccount objects
An integer that specifies the number of array elements that are filled with valid data.
Note: Once again, make sure you code your data members in the exact order listed above and with the exact same data types.
Method Prototypes
The AccountDB class declaration should (eventually) contain public prototypes for all of the methods in the AccountDB.cpp source code file described in Step 5 below.
Step 5: Write the AccountDB class implementation
The AccountDB class implementation should (eventually) contain definitions for all of the methods described below. Make sure to #include "AccountDB.h" at the top of this file.
AccountDB::AccountDB()
Parameters: None.
Returns: Like all C++ constructors, this constructor does not have a return data type.
This "default" constructor is called to create an empty database, so this method should set the number of accounts data member to 0. Set the bank name to the default value "First National Bank". No initialization is necessary for the array of BankAccount objects, since the "default" BankAccount will automatically be called for every object in the array.
AccountDB::AccountDB(const char* fileName)
Parameters: A C string that will not be changed by this method and that contains the name of a file of account data.
Returns: Like all C++ constructors, this constructor does not have a return data type.
This constructor should do the following:
Declare an input file stream variable (the code below assumes that it is named inFile).
Open the file stream for input using the file name parameter. Check to make sure the file was opened successfully as usual.
Read the database file into your AccountDB object. You can do this with a single statement:
inFile.read((char*) this, sizeof(AccountDB));
Close the file stream.
Note that the code above will read data into all of the AccountDB data members. That includes the bank name, the array of 20 BankAccount objects, and the number of array elements filled with valid data. No further initialization of the data members will be needed.
void AccountDB::print() const
Parameters: None.
Returns: Nothing.
This method should first print a descriptive header line that includes the bank name (e.g., "Account Listing for First National Bank"). It should then loop through the array of BankAccount objects and print each of the elements that contains account data, with a blank line between each account.
Here we see some of the power of object-oriented programming: since each element of the array is an object, we can call a method for that object. We've already written a print() method for the BankAccount class, so printing an element of the account array is as easy as calling print()for the array element.
The syntax for calling a method using an array element that is an object is straightforward:
arrayName[subscript].methodName(arguments);
For example:
accountArray[i].print();
Step 6: Write the main program
Since most of the logic of the program is embedded in the two classes you wrote, the main() routine logic is extremely simple.
Create an AccountDB object using the alternate constructor you wrote. Pass the string literal "accounts" as an argument to the constructor to specify the name of the input file.
Call the print() method for the AccountDB object.
STEP 2
For this part of the assignment, you'll need to write one new file and then modify two of the files you wrote for Part 1.
Step 1: Write a makefile
The file named makefile tells the make utility how to build the final executable file from your collection of C++ source code and header files. The makefile for this assignment is given in its entirety below. Makefiles for future assignments will follow this basic pattern.
IMPORTANT NOTE: The commands that appear in the makefile below MUST be indented as shown. Furthermore, the indentation MUST be done using a tab character, not spaces. If you don't indent your makefile commands, or indent using spaces, your makefile WILL NOT WORK.
# # PROGRAM: assign2 # PROGRAMMER: your name # LOGON ID: your z-id # DATE DUE: due date of program # # Compiler variables CCFLAGS = -ansi -Wall # Rule to link object code files to create executable file assign2: assign2.o BankAccount.o AccountDB.o g++ $(CCFLAGS) -o assign2 assign2.o BankAccount.o AccountDB.o # Rules to compile source code files to object code assign2.o: assign2.cpp AccountDB.h g++ $(CCFLAGS) -c assign2.cpp BankAccount.o: BankAccount.cpp BankAccount.h g++ $(CCFLAGS) -c BankAccount.cpp AccountDB.o: AccountDB.cpp AccountDB.h g++ $(CCFLAGS) -c AccountDB.cpp # The AccountDB class depends on the BankAccount class AccountDB.h: BankAccount.h # Pseudo-target to remove object code and executable files clean: -rm *.o assign2
Once you've written the file makefile, try using the make utility to compile and link your program.
Step 2: Add the following methods to the AccountDB class
void AccountDB::sortAccounts()
Parameters: None.
Returns: Nothing.
This method should sort the array of BankAccount objects in ascending order by account number using the insertion sort algorithm.
The sort code linked to above sorts an array of integers called numbers of size size. You will need to make a substantial number of changes to that code to make it work in this program:
This will be a method, not a function. Change the parameters for the method to those described above.
In the method body, change the data type of bucket to BankAccount. This temporary storage will be used to swap elements of the array of BankAccount objects.
In the method body, change any occurrence of numbers to the name of your array of BankAccount objects and size to numAccounts (or whatever you called the data member that tracks the number of valid BankAccount objects stored in the array).
The comparison of accountArray[j-1] and bucket will need to use the C string library function strcmp() to perform the comparison. Also, you'll need to use the getAccountNumber() method to access the accountNumber data member within each BankAccount object. The final version of the inner for loop should look something like this:
for (j = i; (j > 0) && (strcmp(accountArray[j-1].getAccountNumber(), bucket.getAccountNumber()) > 0); j--) ...
It is legal to assign one BankAccount object to another; you don't need to write code to copy individual data members.
Add a call to the sortAccounts() method to the end of the alternate constructor you wrote for the AccountDB class. This will sort the array of BankAccount objects that were read in from the input file.
int AccountDB::searchForAccount(const char* searchNumber) const
Parameters: A C string that will not be changed by this function and that contains an account number to search for.
Returns: If the search was successful, returns the index of the array element that contains the account number that was searched for, or -1 if the search fails.
The logic for this method is a variation of the binary search of a sorted list strategy.
int low = 0; int high = number of valid BankAccount objects in the array - 1; int mid; while (low <= high) { mid = (low + high) / 2; if (searchNumber is equal to accountNumber data member of accountArray[mid]) return mid; if (searchNumber is less than accountNumber data member of accountArray[mid]) high = mid - 1; else low = mid + 1; } return -1;
As usual, you'll need to use strcmp() to perform the comparison of account numbers.
void AccountDB::processTransactions(const char* fileName)
Parameters: A C string that will not be changed by this method and that contains the name of a file of transaction data.
Returns: Nothing.
The method should open the specified transaction file for input. Make sure to test that the file was opened successfully; if it wasn't, print an error message and exit the program.
Before reading any transactions, the function should print a report header and column headers. The function should then read transaction data from the file until end of file is reached. A typical transaction is shown below. The first field on the transaction record is the date of the transaction, followed by an account number, then the transaction type ('D' for deposit or 'W' for withdrawal), and finally a transaction amount.
06/19 1111111111 D 430.00
Note that unlike in Part 1 of the assignment, this file contains ASCII character data, not binary data. You cannot read this data using the technique from Part 1 of the assignment. Instead, read each of the fields of the transaction record into a different variable of the appropriate data type using the >> operator. For example:
inFile >> transactionDate;
Once all of the data for a given transaction has been read, call the searchForAccount() method to search the accounts array for the account number given in the transaction. If the account number is present in the array, then the transaction may be processed. For a deposit, simply call the processDeposit() method for the object that contains the matching account number, passing it the transaction amount. For a withdrawal, call the processWithdrawal() method for the object that contains the matching account number, passing it the transaction amount.
For each transaction record processed, print a line in a transaction report with the data from the record and the updated balance for that account. If the transaction account number was not found in the account array or if a charge exceeded the account's credit limit (i.e., if the processWithdrawal() method returned false), print an appropriate error message instead of the account balance.
After all transactions have been processed, close the transaction file.
Pseudocode for the method logic is given below.
ifstream inFile; char transactionDate[5] char accountNumber[11]; char transactionType; double transactionAmount; bool withdrawalAccepted; Open inFile using the file name passed in as a parameter Check for successful open Read transactionDate from inFile while (not end of file) { Read accountNumber from inFile Read transactionType from inFile Read transactionAmount from inFile Print transactionDate, accountNumber, and transactionType for this transaction int index = searchForAccount(accountNumber); if (index == -1) Print an "account not found" error message for this transaction else { if (transactionType == 'D') { Call the processDeposit() method for the BankAccount object at element index of the array of BankAccountk objects, passing it the transactionAmount Print the updated balance for the account } else { Call the processWithdrawal() method for the BankAccount object at element index of the array of BankAccountk objects, passing it the transactionAmount. If the withdrawal is accepted, print the new balance for the account; otherwise, print an "insufficient funds" error message } } Read transactionDate from inFile } Close input file
Step 3: Add two method calls to the main program
The main() routine logic will now look like this:
Create an AccountDB object using the alternate constructor you wrote. Pass the filename string "accounts" as an argument to the constructor.
Call the print() method for the AccountDB object.
Call the processTransactions() method for the AccountDB object. Pass the filename string "transactions.txt" as an argument to the method.
Call the print() method for the AccountDB object.
Step by Step Solution
There are 3 Steps involved in it
Step: 1
Get Instant Access to Expert-Tailored Solutions
See step-by-step solutions with expert insights and AI powered tools for academic success
Step: 2
Step: 3
Ace Your Homework with AI
Get the answers you need in no time with our AI-driven, step-by-step assistance
Get Started