Using strcmp() to compare two arrays of C-strings - c++

My project is to make a bank account program where the user enters an account number and a password to do anything within the program. The account numbers and passwords used must be stored as C-strings (the string header file is not allowed). I believe that the problem I am having is with the strcmp function. Here is my function where the problem occurs.
void get_password(int num_accounts, char **acc_num, char **password)
{
char account[ACCOUNT_NUMBER];
char user_password[PASS_LENGTH];
std::cout << "\nEnter the account number: ";
// std::cin.getline(account, ACCOUNT_NUMBER);
std::cin >> account;
int i = 0;
do
{
if (strcmp(account, *(acc_num + i)) != 0)
{
i++;
}
else
break;
} while (i <= num_accounts);
if (i == num_accounts)
{
std::cout << "\nCould not find the account number you entered...\nExiting the program";
exit(1);// account number not found
}
std::cout << "\nEnter the password: ";
// std::cin.getline(user_password, PASS_LENGTH);
std::cin >> user_password;
if (strcmp(user_password, *(password + i)) != 0)
{
std::cout << "\nInvalid password...\nExiting the program";
exit(1);// incorrect password
}
else
{
std::cout << "\nAccount number: " << account
<< "\nPassword: " << user_password << "\n";
return;
}
}
acc_num and password are both arrays of C-strings. When I run/debug the program, it crashes at the first if statement. I guess my question is whether I'm using the strcmp function correctly or not, or if there is a problem with the pointers that I am using.

Your loop will run even when num_accounts is 0. Also, you're doing an out-of-bound array access by writing while (i <= num_accounts); instead of while (i < num_accounts);.
It would be better to write it like this:
while (i < num_accounts)
{
if (strcmp(account, *(acc_num + i)) == 0)
{
// match found!
break;
}
i++;
}

You're assuming there is at least one account, and you're also looping once too often. A safer way to write it would be as follows:
for (int i = 0; i < num_accounts && !strcmp(account, accnum[i]); i++)
;
or the corresponding while loop. A do/while is not appropriate here.

Related

Does anyone have any idea how to make so the mask of the password is delayed by a second or two before it replace the password with '*'?

here is some fragment of my code. Can anyone help me? On how to make so the mask of the password is delayed by a second or two before it replaces the password with '*'?
struct adminInfo
{
string adminID;
string adminName;
string adminPassword;
};
void create_admin (adminInfo ad [], int &count)
{
char pass = 0;
const char BACKSPACE = 127;
const char RETURN = 10;
cout << " **************************************" << endl;
cout << " REGISTER ADMINISTRATOR" << endl;
cout << " **************************************" << endl;
cout << " Enter admin registration number (ID) : "; cin >> ws;
getline (cin, ad[count].adminID);
cout << " Enter admin full name : "; cin >> ws;
getline (cin, ad[count].adminName);
cout << " Please create your password : ";// cin >> ws;
//getline (cin, ad[count].adminPassword)
while ((pass=getch(void)) != RETURN)
{
if (pass == BACKSPACE)
{
if (ad[count].adminPassword.length() != 0)
{
cout << "\b \b";
ad[count].adminPassword.resize(ad[count].adminPassword.length() - 1);
}
}
else
{
ad[count].adminPassword += pass;
cout << "*";
}
}
count++;
}
Side note: Should std::endl always be used?
Also it might be easier for me to answer if I knew what libraries you were using. For the sake of simplicity I will presume you use the standard C++ library iostream for cout etc conio.h for getch() string for string and namespace std.
If you don't mind the last character being seen, you could literally just wait for the user input like this:
while ((pass = getch()) != RETURN) {
/* Making length variable so I don't have to call length() function multiple times and it looks cleaner */
int length = ad[count].adminPassword.length()
/* Using bigger than zero just to make it more explicit */
if (length > 0) {
/* Change last character to asterisk */
cout << "\b \b*";
}
if (pass == BACKSPACE) {
if (length > 0) {
cout << "\b \b";
ad[count].adminPassword.resize(length - 1);
}
} else {
ad[count].adminPassword += pass;
/* Instead of asterisk as that is now changed at every keypress after first input */
cout << pass;
}
}
If you wish to actually wait you could just include <windows.h> (or unix equivalent) and use Sleep(milliseconds) function like this in the else statement:
cout << pass;
ad[count].adminPassword += pass;
Sleep(Time in milliseconds)
cout << "\b \b*";
But this will wait the time in milliseconds to print out the next value and will give a pretty confusing and questionable output if you type above 3 wpm.
This is the best that I could think of, I'm not that knoweledgeable about C++ so sorry if I could not answer your question well enough.
I'm presuming you could do something with multithreading to make it wait while taking input. But as I said I do not know much about C++ so I will leave that to someone smarter than me ;D.

How to check if a string matches an array element?

I'm fairly new to programming, and I'm wondering if there's any way I can compare a string input to an array element? I tried the code below, and I know it's wrong, I just don't know how else to go about this.
#include <iostream>
using namespace std;
int main()
{
string Username[10] = {"name1", "name2", "name3", "name4", "name5", "name6", "name7", "name8", "name9", "name10"};
string login;
int i;
cout << "Enter username login: ";
getline(cin, login);
cout << "\n";
for (i = 0; i < 10; i++)
if (login == Username[i])
{
cout << "Loading user settings...";
}
else
cout << "Error: Wrong username entered. ";
return 0;
}
You can avoid the loop and use the std::find algorithm function.
#include <algorithm>
//...
bool exists = (std::find(std::begin(Username), std::end(Username), login)
!= std::end(Username));
I imagine what you want to see is "Loading user settings..." if there is a match, and "Error: Wrong username entered. " if there is no match. Your if-statement should look like this:
if (login == Username[i]){
cout << "Loading user settings...";
break;
}
and your else-statement should be an else-if in the form of:
else if(i==9) cout << "Error: Wrong username entered. ";
Two things:
1) break functions in a way such that, when the program sees a break, it ends the loop that it is currently using. When you find a match, you don't have to look any farther in the array, so break out of it.
2) You only want to print error if you have looked through the entire array, and that will only happen after you have checked the last element which, in this case, is at index 9. Changing the else to an else-if lets you specify this condition.
You should use an algorithm, like this:
if (std::find(Username, Username + 10, login) != (Username + 10))
cout << "Loading user settings...";
else
cout << "Error: Wrong username entered. ";
You can use a bool flag to check if the user exists or not:
Running sample
//...
bool exists = false;
//...
for (i = 0; i < 10; i++)
if (login == Username[i])
{
exists = true;
break;
}
exists ? std::cout << "Loading user settings..." : std::cout << "Error: Wrong username entered. ";
//...
On a side note, see Why is "using namespace std;" considered bad practice?

Elegant C++ Code: How to write more efficient code using while loops and conditional statements

Would you be able to give me some suggestions for how I could simplify my code?
#include <iostream>
#include<fstream>
#include<string>
using namespace std;
int main() {
string current_users[5];
string new_users[5], new_user;
ifstream read;
read.open("current.txt");
for (int index = 0; index < 5; index++) {
read >> current_users[index];
}
read.close();
cout << "Enter a username: ";
cin >> new_user;
char user_choice;
int index = 0, new_index = 0;
while (index <= 5) {
if (new_user == current_users[index]) {
cout << "That username already exists."
<< " Enter a different username: ";
cin >> new_user;
index = 0;
continue;
}
if (index < 5)
index++;
else {
new_users[new_index] = new_user;
cout << "\nWelcome " << new_user << endl;
new_index++;
if (new_index < 5) {
cout << "Would you like to register another user?:"
<<"'Y' for yes or 'N' for no";
cin >> user_choice;
}
if (user_choice == 'Y' || user_choice == 'y') {
cout << "\nEnter a new username: ";
cin >> new_user;
index = 0;
}
else
break;
}
}//end of while
system("pause");
return 0;
}
This program asks a user to enter a username and checks if that username already exists. If it exists, it prompts the user to use a different username, also checking if that username already exists. If the username is unique the program welcomes the new user and asks if the user wants to register another new user (weird, but I wanted to try it). If the user wants to add another user to the "website" per say then the program runs again, checking for redundancy. I limited this program to 5 possible usernames to check and add for ease of testing. There's no errors.
The code is just chunky. I came up with this problem. I'm not in school. Can't afford it and wasn't admitted to any school where I applied. Any suggestions for online schools that offer degrees in computer science?
Here are some suggestions:
Array of Structures not parallel arrays
Use a std::vector of structures and not parallel arrays:
struct Record
{
std::string new_user;
std::string current_user;
};
std::vector<Record> database;
Processors that use a data cache like to have their elements close together. Here, new_user[0] would be next to current_user[0] in the cache.
With your parallel arrays, new_users[0] is next to current_user[4]; so the processor has to go past 4 elements to get to the first new_users element.
Loop Unrolling
You could eliminate the for loop for reading in your values:
read >> current_users[0];
read >> current_users[1];
read >> current_users[2];
read >> current_users[3];
read >> current_users[4];
This eliminates the overhead associated with a for loop.
Convert to all Lower or all Upper case before comparing
You can reduce the number of comparisons by converting to uppercase or lowercase before comparing:
if (std::toupper(user_choice) == 'Y')
Most of what you have is good. I'd wrap everything into a function and use std::find from the standard library in order to find duplicates.
template<std::size_t N, std::size_t M>
void GetUsers( std::string (&new_users)[N], std::string const (&current_users)[M] ) {
int idx = 0;
while (idx < 5) {
std::cout << "Enter a username: ";
std::string user; std::cin >> user;
if (std::find(current_users.begin(), current_users.end(), user) != current_users.end()) {
std::cout << "That username already exists.\n";
continue;
} else {
new_users[idx++] = user;
if (idx < 5) {
std::cout << "Would you like to register another user? [Y/n]: ";
if (std::tolower(std::cin.get()) == 'y') {
continue;
}
}
break;
}
}
}

Checking for letters when reading an integer

I'm making an app that requires the user to input a production order (7 digits long) like this:
int order = 0;
cout << "Insert the order number: ";
cin >> ordem;
How can I prevent the user from entering a letter? Like "I2345G789"?
Doing that, my app just enters an infinite loop. I was thinking to use a function like this:
bool isLetter(int a)
{
string s = to_string(a);
for (int i = 0; i < s.size()-1; i++)
{
if (isdigit(s[i]))
{
return false;
}
else
return true;
}
}
And then:
if (isLetter(order))
{
cout << "Insert only numbers \n";
}
But it doesn't work. Why? And how can I improve the code?
PS: I'm very new to programming, so, sorry for any beginner mistakes.
I guess you have a loop around your code in order to ask for the order number again in case it contains non-digits, for example:
while(...)
{
int order = 0;
cout << "Insert the order number: ";
cin >> order;
}
If you enter something that cannot be parsed into an integer, then the input stream will go into failure mode and that might be the reason why you end up in an infinite loop. In order to overcome your problem in a simple way, you could read a string instead:
string order;
while (true)
{
cout << "Insert the order number: ";
cin >> order;
if (isLetter(order))
cout << "Insert only numbers" << endl;
else
break;
}
The function isLetter() now takes a string and looks like this:
bool isLetter(string s)
{
// Return true if the given string contains at least one letter.
for (size_t i = 0; i < s.size(); i++)
if (!isdigit(s[i]))
return true;
// Return false if there are only digits in the given string.
return false;
}
Please note, that it should be i < s.size() and not i < s.size()-1. And maybe you should rename your function isLetter() to hasLetter(), because that would be a bit more correct.

Deleting content from a file [closed]

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 8 years ago.
Improve this question
I am making a program that allows a user to make bank accounts and save them too a file, you can also delete them. I am having issues with my code for deleting an account on the file, my function for deleting the account looks like this.
int deleteCustomer(account acc[], int numCust)
{
string target;
bool accFound = false;
int count = 0;
cout << "Enter account number: ";
cin >> target;
for (int i = 0; i < numCust; i++)
{
if (acc[i].acctNum == target)
{
accFound = true;
break;
}
count++;
}
if (accFound == false)
{
cout << "That account does not exist." << endl;
system("pause");
system("cls");
}
else if (accFound == true)
{
ofstream outFile;
outFile.open("customer.dat");
for (int i = count; i < numCust - 1; i++)
{
outFile << acc[i+1].acctNum;
outFile << '#';
outFile << acc[i+1].name;
outFile << '#';
outFile << acc[i+1].cBal;
outFile << '#';
outFile << acc[i+1].sBal;
outFile << '#';
}
numCust--;
outFile.close();
}
return numCust;
}
The function is supposed to overwrite the account selected by the user by saving the file ahead of it to the previous spot and return the new number of customers. It appears to run through fine but it does not do anything and I am uncertain as to why. Any input would be helpful, thank you.
Several problems here:
Your account lookup should be working, but you're overcomplicating this a bit (you'd only need one value rather than three, but let's skip that for now). If you're interested let me know.
You're never actually removing any account (just reducing the number of total accounts; which will then cause the last entry to be removed).
When saving you accounts to the file, you start at that selected index, which doesn't make any sense at all.
Let's assume you've got 10 accounts, indices 0 through 9.
The user picks the account at index 5.
You save accounts index 6 through 9(!) only.
The user picks the account at index 0.
You save accounts index 1 through 9 only.
Some style things:
You essentially store the selected account's index in count. That's fine, but very misleading. Don't ever use misleading variable names. As you might be able to tell from my comment above, I misread that part as well.
Rather than writing if (booleanValue == true) you could just write if (booleanValue), which results in the same code, but is shorter and might be faster to read. In a similar way, you could replace if (booleanValue == false) with !booleanValue.
Don't omit namespaces like std, if you can (e.g. use std::string rather than string and avoid using namespace std;) to avoid writing ambigious code. If some other namespace you use has string (or any other member) as well, you'll either have to explicitly name the namespace anyway or you're at least confusing others reading your code. Also there's always the potential bug introduced by unintentionally using a different type.
Fixing the actual problem:
I assume this is some homework assignment or some tutorial/class code or anything similar? If so, don't just copy the following code and instead try to think about how it's working. Once you understood, implement it yourself and only use my snippets if you're really stuck.
In general, it's good software design to keep code and functions minimal. Don't create "super functions" that do several things. Also try to make code reusable, so in case you change something, you're able to adjust it in one place only.
Take your code above for example. Whenever you add, delete, or update an account, you'll have to write the new file. Did you plan on replicating the same code multiple times? If you'd have to adjust your file format, you'd have to change it everywhere.
You'll also need some way to actually remove customer datasets. As you might be aware, deleteing entries in an array would require you to move all entries behind it (to keep it continguous). This can be a very expensive operation.
To avoid this, I'm adding a new member bool valid to account. By default, this is set to false. Once there's some data put there (either through reading from a file or by the user), it's value is set to true.
So instead split this into two separate functions (moving the common code - saving - to its own function):
// By returning an integer value, you're able to communicate issues or problems
// without having to rely on exceptions (in case you're using C++).
// Note that I don't do any error checking here for simplicity.
// Parameters:
// filename - the target file to write
// acc - the array holding all customer accounts
// size - the maximum amount of values in acc
// Return value: 0, if everything went fine
// (I skipped actual error handling to keep it simple!)
int saveCustomers(const char *filename, account acc[], int size) {
std::ofstream outFile(filename);
// Iterate over all entries
for (int i = 0; i < num; ++i) {
// Do we actually have to store the account?
if (acc[i].valid) {
outfile << acc[i].acctNum << '#' << acc[i].name; // write all the values the way you did
}
}
outFile.close();
return 0; // Everything ok
}
Now that this is done, you're able to create your functions to modify your customer data:
int deleteCustomerByNumber(account acc[], int num, std::string target) {
// Iterate over all accounts and look for the selected one
for (int i = 0; i < num; ++i) {
// Only check valid accounts and see whether it's the target
if (acc[i].valid && acc[i].acctNum == target) {
acc[i].valid = false; // Mark it as invalid
return 0; // Everything ok
}
}
return 1; // Didn't find it!
}
In a similar way you can look for empty/unused entries to actually write data to them.
Bonus - alternative (STL) approach:
Since you're using C++, I'd suggest you use a different data structure, not just a simple array:
If you use a STL container (more specific: a map), you're able to handle everything a lot easier.
#include <map>
// Create a typedef to simplify expressions
typedef std::map<std::string, account> accmap;
// All accounts would be stored in this object:
accmap accounts;
// To do a quick lookup of any account:
accmap::const_iterator a = accounts.find(accountNumber);
if (a == accounts.end())
;// Account not found!
else {
a->first; // This is your account number
a->second; // This is your `account` object
}
// To delete a specific account:
accounts.erase(accountNumber)
// To create a new account simply access it:
accounts[accountNumber].name = newName;
You need to save all of the records before the index and after the index, otherwise you are effectively deleting more than just the one account. Presumably you should also remove the record from the input array as well. You are also not doing any error handling on the input or output. And you need to fix your output loop, it is not using indexes correctly.
Try this:
int deleteCustomer(account acc[], int numCust)
{
string target;
int accFound = -1;
cout << "Enter account number: ";
if (cin >> target)
{
for (int i = 0; i < numCust; ++i)
{
if (acc[i].acctNum == target)
{
accFound = i;
break;
}
}
}
if (accFound == -1)
{
cout << "That account does not exist." << endl;
system("pause");
system("cls");
}
else
{
for (int i = accFound+1; i < numCust; ++i)
acc[i-1] = acc[i];
--numCust;
ofstream outFile;
outFile.open("customer.dat");
for (int i = 0; (i < numCust) && (outFile); ++i)
{
outFile << acc[i].acctNum;
outFile << '#';
outFile << acc[i].name;
outFile << '#';
outFile << acc[i].cBal;
outFile << '#';
outFile << acc[i].sBal;
outFile << '#';
}
if (!outFile)
cout << "Error saving customer file" << endl;
}
return numCust;
}
If you don't want to update the array, then you can do this instead:
int deleteCustomer(account acc[], int numCust)
{
string target;
int accFound = -1;
cout << "Enter account number: ";
if (cin >> target)
{
for (int i = 0; i < numCust; ++i)
{
if (acc[i].acctNum == target)
{
accFound = i;
break;
}
}
}
if (accFound == -1)
{
cout << "That account does not exist." << endl;
system("pause");
system("cls");
}
else
{
ofstream outFile;
outFile.open("customer.dat");
for (int i = 0; (i < numCust) && (outFile); ++i)
{
if (i != accFound)
{
outFile << acc[i].acctNum;
outFile << '#';
outFile << acc[i].name;
outFile << '#';
outFile << acc[i].cBal;
outFile << '#';
outFile << acc[i].sBal;
outFile << '#';
}
}
if (!outFile)
Cout << "Error saving customer file" << endl;
--numCust;
}
return numCust;
}
Lastly, when updating a file, it is a good idea to write the new data to a temp file first, then replace the original file with the temp file only if everything is successful. That way you reduce the risk of corrupting the original file.
To "deleting an account on the file", this part of code:
for (int i = count; i < numCust - 1; i++)
{
outFile << acc[i+1].acctNum;
outFile << '#';
outFile << acc[i+1].name;
outFile << '#';
outFile << acc[i+1].cBal;
outFile << '#';
outFile << acc[i+1].sBal;
outFile << '#';
}
should be
for (int i = 0; i < numCust; i++)
{
if(i == count) continue;// remove the account user selected
outFile << acc[i].acctNum;
outFile << '#';
outFile << acc[i].name;
outFile << '#';
outFile << acc[i].cBal;
outFile << '#';
outFile << acc[i].sBal;
outFile << '#';
}