C Language Series #150: Building a Simple Banking System
Welcome to another installment of our C Language Series! In this 150th tutorial, we're tackling a classic project that beautifully demonstrates several fundamental C programming concepts: creating a Simple Banking System. This project will allow us to practice using structs, file I/O for data persistence, functions for modularity, and basic user interaction.
While this system will be simplified for educational purposes, it lays a solid foundation for understanding how real-world applications manage data and user operations. Get ready to put your C skills to the test!
What We'll Build
Our simple banking system will feature the following functionalities:
- Create Account: Allow users to open a new bank account with a unique account number, name, and initial deposit.
- Deposit Money: Enable users to add funds to an existing account.
- Withdraw Money: Allow users to remove funds from an existing account, with checks for sufficient balance.
- Check Balance: Display the current balance of a specific account.
- View All Accounts: List details of all accounts currently in the system.
- Exit: Terminate the program.
Key Concepts Covered
Throughout this project, we'll reinforce our understanding of:
- Structures (
struct): To define a custom data type for an account. - File I/O: To store and retrieve account data persistently (so it's not lost when the program closes). We'll use binary files for simplicity.
- Functions: To encapsulate specific operations (create, deposit, withdraw, etc.) for better code organization and reusability.
- Loops and Conditionals: For menu navigation, input validation, and business logic.
Defining the Account Structure
First, we need a way to represent a bank account. A struct is perfect for this, allowing us to group related data members under a single name. Our Account structure will include an account number, account holder's name, and the current balance.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <conio.h> // For getch() on Windows, for Unix/Linux, might need to adjust or use termios
#define FILENAME "accounts.dat"
#define MAX_NAME_LENGTH 50
// Structure to hold account information
typedef struct {
int accountNumber;
char accountHolderName[MAX_NAME_LENGTH];
double balance;
} Account;
Note on <conio.h>: This header is common on Windows for functions like getch() (which we might use for password-like input or pausing). If you are on a Unix-like system (Linux, macOS), you might need to use different methods for non-buffered input, or simply omit it for this basic version.
Helper Function: Getting a Unique Account Number
To ensure each account has a unique identifier, we can simply read the last account number used from the file and increment it. For simplicity, we'll just start from 1001 and increment.
int getNextAccountNumber() {
FILE *fp;
Account acc;
int maxAccNum = 1000; // Starting point for account numbers
fp = fopen(FILENAME, "rb"); // Open in read binary mode
if (fp == NULL) {
return maxAccNum + 1; // File doesn't exist yet, start with 1001
}
// Find the highest existing account number
while (fread(&acc, sizeof(Account), 1, fp) == 1) {
if (acc.accountNumber > maxAccNum) {
maxAccNum = acc.accountNumber;
}
}
fclose(fp);
return maxAccNum + 1;
}
Core Functionality: Implementation Details
1. Creating a New Account
This function will prompt the user for their name and an initial deposit, then save the new account details to our data file.
void createAccount() {
FILE *fp;
Account newAccount;
newAccount.accountNumber = getNextAccountNumber();
printf("\n--- Create New Account ---");
printf("\nEnter Account Holder Name: ");
getchar(); // Consume the newline character left by previous input
fgets(newAccount.accountHolderName, MAX_NAME_LENGTH, stdin);
newAccount.accountHolderName[strcspn(newAccount.accountHolderName, "\n")] = 0; // Remove trailing newline
printf("Enter Initial Deposit Amount: $");
scanf("%lf", &newAccount.balance);
if (newAccount.balance < 0) {
printf("Initial deposit cannot be negative. Account creation failed.\n");
return;
}
fp = fopen(FILENAME, "ab"); // Open in append binary mode
if (fp == NULL) {
printf("Error opening file for writing!\n");
return;
}
fwrite(&newAccount, sizeof(Account), 1, fp);
fclose(fp);
printf("\nAccount created successfully!");
printf("\nYour Account Number: %d\n", newAccount.accountNumber);
}
2. Finding an Account (Helper Function)
Many operations require finding an account by its number. Let's create a helper function for this. It will return the Account struct if found, or an empty/invalid one if not. More practically, it can return a pointer to the found account or its position in the file.
For operations that modify the account, we'll need to read all accounts, modify the one, and then rewrite all accounts. A simpler approach for this example is to read, find the position, modify, and seek back to overwrite. However, for adding/deleting records, rewriting the whole file is often simpler to implement initially.
Let's define a function that searches for an account and returns its position in the file, which allows for direct modification.
long findAccountPosition(int accNum) {
FILE *fp;
Account acc;
long position = -1;
fp = fopen(FILENAME, "rb");
if (fp == NULL) {
return -1; // File does not exist or cannot be opened
}
while (fread(&acc, sizeof(Account), 1, fp) == 1) {
if (acc.accountNumber == accNum) {
position = ftell(fp) - sizeof(Account); // Get the start position of the found record
break;
}
}
fclose(fp);
return position;
}
Account getAccountDetails(int accNum) {
FILE *fp;
Account acc;
Account emptyAcc = {0, "", 0.0}; // Return empty if not found
long pos = findAccountPosition(accNum);
if (pos == -1) {
return emptyAcc;
}
fp = fopen(FILENAME, "rb");
if (fp == NULL) {
return emptyAcc;
}
fseek(fp, pos, SEEK_SET); // Go to the position of the account
fread(&acc, sizeof(Account), 1, fp);
fclose(fp);
return acc;
}
void updateAccount(Account accToUpdate, long position) {
FILE *fp = fopen(FILENAME, "r+b"); // Open in read and write binary mode
if (fp == NULL) {
printf("Error: Could not open file for update.\n");
return;
}
fseek(fp, position, SEEK_SET); // Go to the record's position
fwrite(&accToUpdate, sizeof(Account), 1, fp); // Overwrite the record
fclose(fp);
}
3. Deposit Money
void depositMoney() {
int accNum;
double amount;
Account acc;
long pos;
printf("\n--- Deposit Money ---");
printf("\nEnter Account Number: ");
scanf("%d", &accNum);
pos = findAccountPosition(accNum);
if (pos == -1) {
printf("Account not found!\n");
return;
}
acc = getAccountDetails(accNum); // Retrieve details for display and modification
printf("Account Holder: %s\n", acc.accountHolderName);
printf("Current Balance: $%.2f\n", acc.balance);
printf("Enter Amount to Deposit: $");
scanf("%lf", &amount);
if (amount <= 0) {
printf("Deposit amount must be positive.\n");
return;
}
acc.balance += amount;
updateAccount(acc, pos); // Save updated balance
printf("Amount $%.2f deposited successfully.\n", amount);
printf("New Balance: $%.2f\n", acc.balance);
}
4. Withdraw Money
void withdrawMoney() {
int accNum;
double amount;
Account acc;
long pos;
printf("\n--- Withdraw Money ---");
printf("\nEnter Account Number: ");
scanf("%d", &accNum);
pos = findAccountPosition(accNum);
if (pos == -1) {
printf("Account not found!\n");
return;
}
acc = getAccountDetails(accNum);
printf("Account Holder: %s\n", acc.accountHolderName);
printf("Current Balance: $%.2f\n", acc.balance);
printf("Enter Amount to Withdraw: $");
scanf("%lf", &amount);
if (amount <= 0) {
printf("Withdrawal amount must be positive.\n");
return;
}
if (acc.balance < amount) {
printf("Insufficient balance!\n");
return;
}
acc.balance -= amount;
updateAccount(acc, pos); // Save updated balance
printf("Amount $%.2f withdrawn successfully.\n", amount);
printf("New Balance: $%.2f\n", acc.balance);
}
5. Check Balance
void checkBalance() {
int accNum;
Account acc;
printf("\n--- Check Balance ---");
printf("\nEnter Account Number: ");
scanf("%d", &accNum);
acc = getAccountDetails(accNum);
if (acc.accountNumber == 0) { // Check if account was found (since 0 is not a valid acc num from getNextAccountNumber)
printf("Account not found!\n");
return;
}
printf("\nAccount Details for Account Number %d:\n", acc.accountNumber);
printf("Holder Name: %s\n", acc.accountHolderName);
printf("Current Balance: $%.2f\n", acc.balance);
}
6. View All Accounts
void viewAllAccounts() {
FILE *fp;
Account acc;
int count = 0;
fp = fopen(FILENAME, "rb");
if (fp == NULL) {
printf("\nNo accounts found yet. The file '%s' does not exist.\n", FILENAME);
return;
}
printf("\n--- All Accounts ---\n");
printf("%-15s %-30s %-15s\n", "Account No.", "Holder Name", "Balance");
printf("-------------------------------------------------------------\n");
while (fread(&acc, sizeof(Account), 1, fp) == 1) {
printf("%-15d %-30s $%-14.2f\n", acc.accountNumber, acc.accountHolderName, acc.balance);
count++;
}
if (count == 0) {
printf("No accounts to display.\n");
}
printf("-------------------------------------------------------------\n");
fclose(fp);
}
The Main Menu and Program Loop
Finally, we'll tie everything together with a main function that presents a menu to the user and calls the appropriate functions based on their choice.
void displayMenu() {
printf("\n\n--- Simple Banking System ---\n");
printf("1. Create New Account\n");
printf("2. Deposit Money\n");
printf("3. Withdraw Money\n");
printf("4. Check Balance\n");
printf("5. View All Accounts\n");
printf("6. Exit\n");
printf("-----------------------------\n");
printf("Enter your choice: ");
}
int main() {
int choice;
do {
displayMenu();
scanf("%d", &choice);
switch (choice) {
case 1:
createAccount();
break;
case 2:
depositMoney();
break;
case 3:
withdrawMoney();
break;
case 4:
checkBalance();
break;
case 5:
viewAllAccounts();
break;
case 6:
printf("\nThank you for using the Simple Banking System. Goodbye!\n");
break;
default:
printf("\nInvalid choice. Please try again.\n");
}
printf("\nPress any key to continue...");
getch(); // Pauses the console, waits for a key press
system("cls"); // Clears the console screen (for Windows)
// For Linux/Unix, use: system("clear");
} while (choice != 6);
return 0;
}
Compilation and Execution
To compile this program, save the entire code into a .c file (e.g., banking_system.c) and use a C compiler like GCC:
gcc banking_system.c -o banking_system
Then, run the executable:
./banking_system
Each time you run the program, it will read/write to the accounts.dat file in the same directory, ensuring your account data is persistent.
Possible Enhancements and Further Learning
This simple system is a great starting point, but there are many ways to enhance it:
- Robust Error Handling: Implement more comprehensive checks for file errors, invalid input types (e.g., non-numeric input for amounts), and edge cases.
- Security: Add password protection for accounts. This would involve storing hashed passwords, not plain text, and verifying them upon access.
- More Features:
- Account deletion.
- Transaction history for each account.
- Interest calculation.
- Transfer money between accounts.
- Dynamic Memory Allocation: Instead of processing one record at a time from the file, you could load all accounts into an array of structs in memory (using
mallocandrealloc) at startup and save them all back at exit. This would improve performance for frequent operations. - Input Validation Loop: For user inputs, repeatedly ask until valid input is received.
- Cross-platform Input: Replace
getch()andsystem("cls")with cross-platform alternatives or conditional compilation.
Conclusion
Congratulations! You've successfully built a basic banking system in C, demonstrating proficiency in data structures, file handling, and modular programming. This project is a fantastic way to solidify your understanding of these core concepts and provides a springboard for more complex applications.
Experiment with the code, add new features, and try to implement some of the enhancements suggested. Happy coding!