currently I have a information.txt file with a list of residents and their unit number such as A-1-1 and A-1-2. I am having difficulty when I wish to allow my program to add user to its own unit number but limited to a situation where 1 unit maximum can have 2 residents registered in a single unit.
It is possible to code my program to read the unit number in the information.txt file and only allow the user to register themselves in the program when their unit is not fully registered (maximum 2 resident per unit)?
This is my university programming assignment...I am just a beginner...This is so far what I have...Please help me with this...Hope you can understand my situation.
void add_new_user(void)
{
system("CLS");
ifstream in_file ("information.txt");
USER new_user[40];
int index=0;
char option;
char unit_number;
if(!in_file)
{
cout << "Error opening txt file" << endl;
}
else
{
USER U_list[MAX_USER];
int index = 0;
fflush (stdin);
in_file.getline(U_list[++index].unit_number, LENGTH_UNIT);
while (in_file)
{
in_file.getline(U_list[index].name, LENGTH_NAME);
in_file.getline(U_list[index].email, LENGTH_MAIL);
in_file.getline(U_list[index].phone, MAX_NUM);
in_file.getline(U_list[index].IC, LENGTH_IC);
if(in_file.peek() == '\n')
in_file.ignore(256, '\n');
in_file.getline(U_list[++index].unit_number,LENGTH_UNIT);
}
in_file.close();
bool in = false;
do
{
char check_unit_number[LENGTH_UNIT];
int unit_number_count = 0;
cout << "==================================================" << endl;
cout << "==\tUser Management System\t\t==" << endl;
cout << "==================================================" << endl;
cout << "==\tAdd New User\t\t\t==" << endl;
cout << "==================================================\n";
fflush(stdin);
cout << "\nEnter unit number of the new user: " <<endl;
cin.get(check_unit_number, LENGTH_UNIT);
cin.ignore();
for (int i =1; i < index; i++)
if (strcmp(check_unit_number,U_list[i].unit_number) == 0)
unit_number = i;
if (strcmp(check_unit_number,U_list[unit_number].unit_number) == 0)
{
fflush(stdin);
cout << "\nEnter new user's unit number: " << endl;
cin.get(new_user[++index].unit_number, LENGTH_UNIT);
cin.clear();
fflush(stdin);
cout << "\nEnter new user's name: " << endl;
cin.get(new_user[index].name, LENGTH_NAME);
cin.clear();
fflush(stdin);
cout << "\nEnter new user's EMAIL ADDRESS: " << endl;
cin.get(new_user[index].email, LENGTH_MAIL);
cin.clear();
fflush(stdin);
cout << "\nEnter new user's phone number: " << endl;
cin.get(new_user[index].phone, MAX_NUM);
cin.clear();
fflush(stdin);
cout << "\nEnter new user's IC: " << endl;
cin.get(new_user[index].IC, LENGTH_IC);
cin.clear();
do
{
cout << "\nDo you confirm to register this new user? [Yes - Y, No - N]\n=>";
cin >> option;
if(option != 'Y' && option != 'N' && option != 'y' && option != 'n')
{
cout << endl;
cout << "Wrong input. Enter again." << endl;
cout << endl;
}
}while(option != 'Y' && option != 'N' && option != 'y' && option != 'n');
if(option=='Y' || option=='y')
{
ofstream out_file ( "information.txt", ios :: app);
out_file << setiosflags (ios :: left) << new_user[index].unit_number << endl;
out_file << setiosflags (ios :: left) << new_user[index].name << endl;
out_file << setiosflags (ios :: left) << new_user[index].email << endl;
out_file << setiosflags (ios :: left) << new_user[index].phone << endl;
out_file << setiosflags (ios :: left) << new_user[index].IC << endl;
out_file.close();
cout << endl;
cout << "The new user is successfully registered.\n" << endl;
system ("pause");
return;
}
else if(option=='N' || option=='n')
{
cout << "\nThe new user is not registered.\n" << endl;
system ("pause");
return;
}
}
else if (strcmp(check_unit_number,U_list[unit_number].unit_number) != 0)
{
cout << "\nThis unit number is not exist.\n" << endl;
system ("pause");
return;
}
}while(!in);
}
}
You have some basic problems in understanding IO operations.
You need to check the difference between formatted and unformatted input. For example
std::cin >> option; will read one character, but not the '\n' at the end of the line. A subsequent call to an unformatted input function, will read just the '\n' and nothing more.
Then, you are mixing up std::istream.get() and std::istream.getline(). They have a total different behaviour regarding the handling of the "end of line" character '\n'. Please read here and here.
You need to understand the difference.
Then, many additional problems resulting from using plain C-Style arrays and especially not standard C-elements like std::string. If you would use std::string then life would be very much simpler.
Anyway, there are many other more syntax and semantic errors, as well as design errors.
Important notice: Please make your design first on a piece of paper and then start coding. Always think many days, before writing the first line of code.
So, then to your code. Lets make it compilable:
#include <iostream>
#include <fstream>
#include <iomanip>
#include <stdlib.h>
#include <string.h>
using namespace std;
#define MAX_USER 100
#define LENGTH_UNIT 30
#define LENGTH_NAME 50
#define LENGTH_MAIL 80
#define MAX_NUM 35
#define LENGTH_IC 50
struct USER {
char unit_number[LENGTH_UNIT];
char name[LENGTH_NAME];
char email[LENGTH_MAIL];
char phone[MAX_NUM];
char IC[LENGTH_IC];
};
void add_new_user(void)
{
system("CLS");
ifstream in_file("information.txt");
USER new_user[40];
int index = 0;
char option;
char unit_number;
if (!in_file)
{
cout << "Error opening txt file" << endl;
}
else
{
USER U_list[MAX_USER];
int index = 0;
fflush(stdin);
in_file.getline(U_list[++index].unit_number, LENGTH_UNIT);
while (in_file)
{
in_file.getline(U_list[index].name, LENGTH_NAME);
in_file.getline(U_list[index].email, LENGTH_MAIL);
in_file.getline(U_list[index].phone, MAX_NUM);
in_file.getline(U_list[index].IC, LENGTH_IC);
if (in_file.peek() == '\n')
in_file.ignore(256, '\n');
in_file.getline(U_list[++index].unit_number, LENGTH_UNIT);
}
in_file.close();
bool in = false;
do
{
char check_unit_number[LENGTH_UNIT];
int unit_number_count = 0;
cout << "==================================================" << endl;
cout << "==\tUser Management System\t\t==" << endl;
cout << "==================================================" << endl;
cout << "==\tAdd New User\t\t\t==" << endl;
cout << "==================================================\n";
fflush(stdin);
cout << "\nEnter unit number of the new user: " << endl;
cin.get(check_unit_number, LENGTH_UNIT);
cin.ignore();
for (int i = 1; i < index; i++)
if (strcmp(check_unit_number, U_list[i].unit_number) == 0)
unit_number = i;
if (strcmp(check_unit_number, U_list[unit_number].unit_number) == 0)
{
fflush(stdin);
cout << "\nEnter new user's unit number: " << endl;
cin.get(new_user[++index].unit_number, LENGTH_UNIT);
cin.clear();
fflush(stdin);
cout << "\nEnter new user's name: " << endl;
cin.get(new_user[index].name, LENGTH_NAME);
cin.clear();
fflush(stdin);
cout << "\nEnter new user's EMAIL ADDRESS: " << endl;
cin.get(new_user[index].email, LENGTH_MAIL);
cin.clear();
fflush(stdin);
cout << "\nEnter new user's phone number: " << endl;
cin.get(new_user[index].phone, MAX_NUM);
cin.clear();
fflush(stdin);
cout << "\nEnter new user's IC: " << endl;
cin.get(new_user[index].IC, LENGTH_IC);
cin.clear();
do
{
cout << "\nDo you confirm to register this new user? [Yes - Y, No - N]\n=>";
cin >> option;
if (option != 'Y' && option != 'N' && option != 'y' && option != 'n')
{
cout << endl;
cout << "Wrong input. Enter again." << endl;
cout << endl;
}
} while (option != 'Y' && option != 'N' && option != 'y' && option != 'n');
if (option == 'Y' || option == 'y')
{
ofstream out_file("information.txt", ios::app);
out_file << setiosflags(ios::left) << new_user[index].unit_number << endl;
out_file << setiosflags(ios::left) << new_user[index].name << endl;
out_file << setiosflags(ios::left) << new_user[index].email << endl;
out_file << setiosflags(ios::left) << new_user[index].phone << endl;
out_file << setiosflags(ios::left) << new_user[index].IC << endl;
out_file.close();
cout << endl;
cout << "The new user is successfully registered.\n" << endl;
system("pause");
return;
}
else if (option == 'N' || option == 'n')
{
cout << "\nThe new user is not registered.\n" << endl;
system("pause");
return;
}
}
else if (strcmp(check_unit_number, U_list[unit_number].unit_number) != 0)
{
cout << "\nThis unit number is not exist.\n" << endl;
system("pause");
return;
}
} while (!in);
}
}
int main() {
add_new_user();
}
So, now it is at least compilable. However, it will not work. There are still compiler warnings:
All errors and warnings must be removed.
Let us start with refactoring.
First a code review. I put all finding as a comment in the source code.
Note: Nearly every line of code has a finding.
#include <iostream>
#include <fstream>
#include <iomanip>
#include <stdlib.h> // Do not use C headers
#include <string.h> // Do not use C headers
using namespace std; // Should never be done in C++. Always use qualified names
#define MAX_USER 100 // Macros should never be used for that purpose in C++. Use constexpr instead
#define LENGTH_UNIT 30 // Macros should never be used for that purpose in C++. Use constexpr instead
#define LENGTH_NAME 50 // Macros should never be used for that purpose in C++. Use constexpr instead
#define LENGTH_MAIL 80 // Macros should never be used for that purpose in C++. Use constexpr instead
#define MAX_NUM 35 // Macros should never be used for that purpose in C++. Use constexpr instead
#define LENGTH_IC 50 // Macros should never be used for that purpose in C++. Use constexpr instead
struct USER {
char unit_number[LENGTH_UNIT]; // C-Style arrays should not be used in C++. Use datatype string instead
char name[LENGTH_NAME];// C-Style arrays should not be used in C++. Use datatype string instead
char email[LENGTH_MAIL];// C-Style arrays should not be used in C++. Use datatype string instead
char phone[MAX_NUM];// C-Style arrays should not be used in C++. Use datatype string instead
char IC[LENGTH_IC];// C-Style arrays should not be used in C++. Use datatype string instead
};
void add_new_user(void)
{
system("CLS"); // Should not be used
ifstream in_file("r:\\information.txt");
static USER new_user[40]; // Do not put this onto the functions stack. Why 40? No need to collect new users locally
// int index = 0; // index is defined again below
char option;
unsigned int unit_number; // Should not be char but unsigned int
if (!in_file)
{
cout << "Error opening txt file" << endl; // Should be written do std::cerr. endl not necesarry
}
else
{
static USER U_list[MAX_USER]; // Should not be put onto stack of function
int index = 0; // Should be unsigned
fflush(stdin); // Should never be done. Will end in undefined behaviour
in_file.getline(U_list[++index].unit_number, LENGTH_UNIT); // Should be optimized
while (in_file)
{
in_file.getline(U_list[index].name, LENGTH_NAME);
in_file.getline(U_list[index].email, LENGTH_MAIL);
in_file.getline(U_list[index].phone, MAX_NUM);
in_file.getline(U_list[index].IC, LENGTH_IC);
if (in_file.peek() == '\n') // Why that?
in_file.ignore(256, '\n');
in_file.getline(U_list[++index].unit_number, LENGTH_UNIT); // Bad index handling
}
in_file.close(); // No need. Destructor will close file for you. But, does not harm
bool in = false; // Starting an endless loop and jumping out with return. Bad design
do
{
char check_unit_number[LENGTH_UNIT];// C-Style arrays should not be used in C++. Use datatype string instead
// int unit_number_count = 0; This variable is used nowhere // endl not necessary. \n can be part of string
cout << "==================================================" << endl; // endl ist not necessary. All the following can be done in one statement.
cout << "==\tUser Management System\t\t==" << endl;
cout << "==================================================" << endl;
cout << "==\tAdd New User\t\t\t==" << endl;
cout << "==================================================\n";
fflush(stdin); // Should never be done. Will end in undefined behaviour
cout << "\nEnter unit number of the new user: " << endl; // endl not necessary. \n can be part of string
cin.get(check_unit_number, LENGTH_UNIT);
cin.ignore(); // ???
for (int i = 0; i < index; i++) // array indices start with 0 in c++
if (strcmp(check_unit_number, U_list[i].unit_number) == 0)
unit_number = i; // **** This will result in undefined and bad behaviour
// Because, unit number may never be initialized and that it has an undefined (random) value
// Or the value from the last loop run. In any case: wrong
if (strcmp(check_unit_number, U_list[unit_number].unit_number) == 0) // **** May leed to out of bounds undefined beahviour
{
fflush(stdin); // Should never be done. Will end in undefined behaviour
cout << "\nEnter new user's unit number: " << endl; // endl not necessary. \n can be part of string
cin.get(new_user[++index].unit_number, LENGTH_UNIT);
cin.clear(); // Better handling of errors necessary
fflush(stdin); // Should never be done. Will end in undefined behaviour
cout << "\nEnter new user's name: " << endl; // endl not necessary. \n can be part of string
cin.get(new_user[index].name, LENGTH_NAME);
cin.clear(); // Better handling of errors necessary
fflush(stdin); // Should never be done. Will end in undefined behaviour
cout << "\nEnter new user's EMAIL ADDRESS: " << endl; // endl not necessary. \n can be part of string
cin.get(new_user[index].email, LENGTH_MAIL);
cin.clear(); // Better handling of errors necessary
fflush(stdin); // Should never be done. Will end in undefined behaviour
cout << "\nEnter new user's phone number: " << endl; // endl not necessary. \n can be part of string
cin.get(new_user[index].phone, MAX_NUM);
cin.clear(); // Better handling of errors necessary
fflush(stdin); // Should never be done. Will end in undefined behaviour
cout << "\nEnter new user's IC: " << endl; // endl not necessary. \n can be part of string
cin.get(new_user[index].IC, LENGTH_IC);
cin.clear(); // Better handling of errors necessary
do
{
cout << "\nDo you confirm to register this new user? [Yes - Y, No - N]\n=>";
cin >> option;
if (option != 'Y' && option != 'N' && option != 'y' && option != 'n')
{
cout << endl; // endl not necessary. \n can be part of string
cout << "Wrong input. Enter again." << endl; // endl not necessary. \n can be part of string
cout << endl; // endl not necessary. \n can be part of string
}
} while (option != 'Y' && option != 'N' && option != 'y' && option != 'n');
if (option == 'Y' || option == 'y')
{
ofstream out_file("information.txt", ios::app);
out_file << setiosflags(ios::left) << new_user[index].unit_number << endl;
out_file << setiosflags(ios::left) << new_user[index].name << endl;
out_file << setiosflags(ios::left) << new_user[index].email << endl;
out_file << setiosflags(ios::left) << new_user[index].phone << endl;
out_file << setiosflags(ios::left) << new_user[index].IC << endl;
out_file.close();
cout << endl; // endl not necessary. \n can be part of string
cout << "The new user is successfully registered.\n" << endl; // endl not necessary. \n can be part of string
system("pause"); // Should not be used
return; // Like a goto. Bad design
}
else if (option == 'N' || option == 'n')
{
cout << "\nThe new user is not registered.\n" << endl; // endl not necessary. \n can be part of string
system("pause"); // Should not be used
return; // Like a goto. Bad design
}
}
else if (strcmp(check_unit_number, U_list[unit_number].unit_number) != 0)
{
cout << "\nThis unit number is not exist.\n" << endl; // endl not necessary. \n can be part of string
system("pause"); // Should not be used
return; // Like a goto.Bad design
}
} while (!in); // Using an endless loop and jumping out with return. Bad design
}
}
int main() {
add_new_user();
}
Next. Let's remove the hard bugs and the findings. Much better. But: The result will still not work as expected:
#include <iostream>
#include <fstream>
#include <iomanip>
#include <stdlib.h>
#include <string.h>
constexpr unsigned int MAX_USER = 100;
constexpr unsigned int LENGTH_UNIT = 30;
constexpr unsigned int LENGTH_NAME = 50;
constexpr unsigned int LENGTH_MAIL = 80;
constexpr unsigned int MAX_NUM = 35;
constexpr unsigned int LENGTH_IC = 50;
struct USER {
char unit_number[LENGTH_UNIT];
char name[LENGTH_NAME];
char email[LENGTH_MAIL];
char phone[MAX_NUM];
char IC[LENGTH_IC];
};
void add_new_user(void)
{
std::ifstream in_file("r:\\information.txt");
if (!in_file)
{
std::cerr << "\n\n*** Error opening source txt file\n\n";
}
else
{
static USER U_list[MAX_USER]; // Should not be put onto stack of function
int index = 0; // Should be unsigned
while (in_file)
{
in_file.getline(U_list[index].unit_number, LENGTH_UNIT); // Should be optimized
in_file.getline(U_list[index].name, LENGTH_NAME);
in_file.getline(U_list[index].email, LENGTH_MAIL);
in_file.getline(U_list[index].phone, MAX_NUM);
in_file.getline(U_list[index].IC, LENGTH_IC);
++index;
}
in_file.close();
bool in = false;
do
{
std::cout << "==================================================\n"
<< "==\tUser Management System\t\t==\n"
<< "==================================================\n"
<< "==\tAdd New User\t\t\t==\n"
<< "==================================================\n";
std::cout << "\nEnter unit number of the new user:\n";
USER new_user{};
std::cin.getline(new_user.unit_number, LENGTH_UNIT);
// We want to count, how many of these units are already in
unsigned int unitCounter = 0;
// Search the unit number that was entered by the user in the list of existing users read from the file
for (int i = 0; i < index; i++) {
if (std::strcmp(new_user.unit_number, U_list[i].unit_number) == 0) {
++unitCounter;
}
}
// If this units exists
if (unitCounter == 1)
{
std::cout << "\nEnter new user's name:\n";
std::cin.getline(new_user.name, LENGTH_NAME);
std::cout << "\nEnter new user's EMAIL ADDRESS:\n";
std::cin.getline(new_user.email, LENGTH_MAIL);
std::cout << "\nEnter new user's phone number:\n";
std::cin.getline(new_user.phone, MAX_NUM);
std::cout << "\nEnter new user's IC:\n";
std::cin.getline(new_user.IC, LENGTH_IC);
char option{};
do
{
std::cout << "\nDo you confirm to register this new user? [Yes - Y, No - N]\n=>";
std::cin >> option;
if (option != 'Y' && option != 'N' && option != 'y' && option != 'n')
{
std::cout << "\nWrong input. Enter again.\n\n";
}
} while (option != 'Y' && option != 'N' && option != 'y' && option != 'n');
if (option == 'Y' || option == 'y')
{
std::ofstream out_file("r:\\information.txt", std::ios::app);
out_file << std::left << new_user.unit_number << '\n';
out_file << new_user.name << '\n';
out_file << new_user.email << '\n';
out_file << new_user.phone << '\n';
out_file << new_user.IC << std::endl;
out_file.close();
std::cout << "\nThe new user is successfully registered.\n\n";
in = true;
}
else if (option == 'N' || option == 'n')
{
std::cout << "\nThe new user is not registered.\n\n";
in = true;
}
}
else if (unitCounter == 0) {
std::cout << "\n\nError Unit does not exist\n\n";
in = true;
}
else if (unitCounter >= 1) { // For 2 and more
std::cout << "\n\nError. Max 2 residents per unit\n\n";
in = true;
}
} while (!in);
}
}
int main() {
add_new_user();
}
Now, add a little bit minimum C++. Still keep the nasty char arrays instead of strings.
But, this solution will work already.
#include <iostream>
#include <fstream>
#include <iomanip>
#include <stdlib.h>
#include <string.h>
constexpr unsigned int MAX_USER = 100;
constexpr unsigned int LENGTH_UNIT = 30;
constexpr unsigned int LENGTH_NAME = 50;
constexpr unsigned int LENGTH_MAIL = 80;
constexpr unsigned int MAX_NUM = 35;
constexpr unsigned int LENGTH_IC = 50;
struct USER {
char unit_number[LENGTH_UNIT];
char name[LENGTH_NAME];
char email[LENGTH_MAIL];
char phone[MAX_NUM];
char IC[LENGTH_IC];
friend std::istream& operator >> (std::istream& is, USER& u) {
is.getline(u.unit_number, LENGTH_UNIT);
is.getline(u.name, LENGTH_NAME);
is.getline(u.email, LENGTH_MAIL);
is.getline(u.phone, MAX_NUM);
return is.getline(u.IC, LENGTH_IC);
}
friend std::ostream& operator << (std::ostream& os, const USER& u) {
return os << u.unit_number << '\n' << u.name << '\n' << u.email << '\n' << u.phone << '\n' << u.IC << std::endl;
}
};
struct DataBase {
USER U_list[MAX_USER]{};
unsigned int numberOfUserInDatabase{};
friend std::istream& operator >> (std::istream& is, DataBase& d) {
while (is >> d.U_list[d.numberOfUserInDatabase])
++d.numberOfUserInDatabase;
return is;
}
};
DataBase dataBase;
void add_new_user(void)
{
std::ifstream in_file("r:\\information.txt");
if (!in_file)
{
std::cerr << "\n\n*** Error opening source txt file\n\n";
}
else
{
in_file >> dataBase;
in_file.close();
bool in = false;
do
{
std::cout << "==================================================\n"
<< "==\tUser Management System\t\t==\n"
<< "==================================================\n"
<< "==\tAdd New User\t\t\t==\n"
<< "==================================================\n";
std::cout << "\nEnter unit number of the new user:\n";
USER new_user{};
std::cin.getline(new_user.unit_number, LENGTH_UNIT);
// We want to count, how many of these units are already in
unsigned int unitCounter = 0;
// Search the unit number that was entered by the user in the list of existing users read from the file
for (unsigned int i = 0; i < dataBase.numberOfUserInDatabase; i++) {
if (std::strcmp(new_user.unit_number, dataBase.U_list[i].unit_number) == 0) {
++unitCounter;
}
}
// If this units exists
if (unitCounter == 1)
{
std::cout << "\nEnter new user's name:\n";
std::cin.getline(new_user.name, LENGTH_NAME);
std::cout << "\nEnter new user's EMAIL ADDRESS:\n";
std::cin.getline(new_user.email, LENGTH_MAIL);
std::cout << "\nEnter new user's phone number:\n";
std::cin.getline(new_user.phone, MAX_NUM);
std::cout << "\nEnter new user's IC:\n";
std::cin.getline(new_user.IC, LENGTH_IC);
char option{};
do
{
std::cout << "\nDo you confirm to register this new user? [Yes - Y, No - N]\n=>";
std::cin >> option;
if (option != 'Y' && option != 'N' && option != 'y' && option != 'n')
{
std::cout << "\nWrong input. Enter again.\n\n";
}
} while (option != 'Y' && option != 'N' && option != 'y' && option != 'n');
if (option == 'Y' || option == 'y')
{
std::ofstream out_file("r:\\information.txt", std::ios::app);
out_file << new_user;
out_file.close();
std::cout << "\nThe new user is successfully registered.\n\n";
in = true;
}
else if (option == 'N' || option == 'n')
{
std::cout << "\nThe new user is not registered.\n\n";
in = true;
}
}
else if (unitCounter == 0) {
std::cout << "\n\nError Unit does not exist\n\n";
in = true;
}
else if (unitCounter >= 1) { // For 2 and more
std::cout << "\n\nError. Not more than max 2 residents per unit allowed\n\n";
in = true;
}
} while (!in);
}
}
int main() {
add_new_user();
}
And last but not least: Remove all C code. And make it an advanced C++ solution.
This will be too complicated in the beginning, but can show the way to go:
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <algorithm>
#include <iterator>
// Here all unit/user data will be stored
const std::string userDataBaseFileName{ "information.txt"};
// CLass for one user
class User {
// User data
std::string unit{};
std::string name{};
std::string email{};
std::string phoneNumber{};
std::string IC{};
public:
// IO functions. Extractor operator
friend std::istream& operator >> (std::istream& is, User& u) {
std::getline(is, u.unit);
std::getline(is, u.name);
std::getline(is, u.email);
std::getline(is, u.phoneNumber);
return std::getline(is, u.IC);
}
// Inserter operator
friend std::ostream& operator << (std::ostream& os, const User& u) {
return os << u.unit << '\n' << u.name << '\n' << u.email << '\n' << u.phoneNumber << '\n' << u.IC << std::endl;
}
// Check, if 2 units are equal
bool equalUnit(const User& other) const { return unit == other.unit; }
};
// IO functions. Extractor operator
class DataBase {
std::vector<User> user{};
public:
// Here is the number of maximum users per unit
static constexpr size_t MaxUserPerUnit{ 2u };
static_assert(MaxUserPerUnit >= 1 , "Error: At least one user must be allowed per Unit\n"); // Must be greater than 1
// Calculate number of user per unit
size_t usersInUnit(const User& testUser) const { return std::count_if(user.begin(), user.end(), [&](const User& u) { return u.equalUnit(testUser); }); }
// IO functions. Extractor operator. Read all users
friend std::istream& operator >> (std::istream& is, DataBase& d) {
User tempUser{};
while (is >> tempUser) d += tempUser;
return is;
}
// Inserter operator
friend std::ostream& operator << (std::ostream& os, const DataBase& u) {
std::copy(u.user.begin(), u.user.end(), std::ostream_iterator<User>(os));
return os;
}
// Ad a new use to the database via += operator
DataBase& operator+=(const User& u) {
if (usersInUnit(u) < MaxUserPerUnit)
user.push_back(u);
else
std::cerr << "Error. Too many users in unit. Ignoring user:\n" << u << "\n\n";
return *this;
}
};
DataBase dataBase;
void addNewUser()
{
// Open source file and chek, if it could be opened
if (std::ifstream informationDataFileStream{ userDataBaseFileName }; informationDataFileStream) {
// The file is open. Read all data from file
informationDataFileStream >> dataBase;
// Some debug output
std::cout << "\n\nThe following data has been read from file:\n\n" << dataBase << "\n\n";
// Ask operator to enter a new user
std::cout << "\nPlease enter new user information. One at a line. In this order: Unit, Name, Email, Phone Number, ID:\n";
// Read a new user from operator
User tempUser{}; std::cin >> tempUser;
// Confirm, if the user wants really add this info.
std::cout << "\n\nYour entered the following data:\n\n" << tempUser << "\n\nDo you really want to add? (y = yes, everything else = No\n--> ";
char option{}; std::cin >> option;
if (option == 'y') {
// Give confirmation to user
std::cout << "\n\nData will be added to database\n";
// add new user to database
dataBase += tempUser;
// Save all data to disk
if (std::ofstream dataFileStream{ userDataBaseFileName }; dataFileStream) {
dataFileStream << dataBase;
}
else std::cerr << "\nError: Could not open file '" << userDataBaseFileName << "' for writing\n\n";
}
else std::cout << "\n\nData will NOT be added to database\n";
}
else std::cerr << "\nError: Could not open file '" << userDataBaseFileName << "' for reading\n\n";
}
int main() {
addNewUser();
}
Have fun . . .
I have some code that takes a list of names + double values from a .txt file and displays these in the command prompt. For this an array of structs is dynamically allocated. The code should know the size of the array based on the first value in the .txt file, which is then followed by the names and associated values. It should then display the list in two parts with names that have an associated double value higher than or equal to 10.000 listed first. If none of the values qualifies, it displays 'None' in the first half.
The program executes, but the debugger gives an exception and the output is not as expected.
#include <iostream>
#include <fstream>
#include <string>
#include <cstdlib>
using namespace std;
struct donor
{
string name;
double contribution = 0;
};
int main()
{
string filename;
ifstream inFile;
cout << "Enter name of data file: ";
cin >> filename;
inFile.open(filename);
cin.clear();
if(!inFile.is_open())
{
cout << "Could not open the file " << filename << endl;
cout << "Program terminating.\n";
exit(EXIT_FAILURE);
}
int amount;
inFile >> amount;
cin.clear();
donor* dlist = new donor[amount];
int i;
while(inFile.good())
{
for(i = 0; i < amount; i++)
{
getline(inFile, dlist[i].name);
cin.clear();
inFile >> dlist[i].contribution;
cin.clear();
}
}
cout << "Here's the list of Grand Patrons:\n";
bool grandpatrons = false;
for(i = 0; i < amount; i++)
{
if(dlist[i].contribution >= 10000)
{
grandpatrons = true;
cout << dlist[i].name << endl;
cout << dlist[i].contribution << endl;
}
}
if(grandpatrons == false)
{
cout << "None" << endl;
}
cout << "Here's the list of Patrons:\n";
for (i = 0; 1 < amount; i++)
{
if (dlist[i].contribution < 10000)
{
cout << dlist[i].name << endl;
cout << dlist[i].contribution << endl;
}
}
delete[] dlist;
return 0;
}
The donorlist.txt file looks like this:
4
Bob
400
Alice
11000
But the output looks like this:
Enter name of data file: donorlist.txt
Here's the list of Grand Patrons:
None
Here's the list of Patrons:
0
0
0
0
The exception that the debugger gives me is:
Exception thrown at 0x5914F3BE (ucrtbased.dll) in 6_9.exe: 0xC0000005: Access violation reading location 0xA519E363.
Now I assume something is going wrong with reading from the dynamically allocated memory. Maybe something is causing me to read from memory beyond the allocated array? I'm having trouble finding exactly where the mistake is being made.
Your problems begin with the wrong amount written in your data file.
Fix it with:
2
Bob
400
Alice
11000
They then continue with the fact that you inccorectly read the file.
Remember: Mixing operator>> and getline() is not as simple as it seems.
You see, operator>> IGNORES newline and space characters until it finds any other character.
It then reads the upcoming characters until it encounters the next newline or space character, BUT DOES NOT DISCARD IT.
Here is where the problem with getline comes in. getline reads EVERYTHING until it encounters newline or a specified delim character.
Meaning, that if your operator>> stops after encountering newline, getline will read NOTHING since it immediately encounters newline.
To fix this, you need to dispose of the newline character.
You can do this by first checking if the next character in the stream is indeed newline and then using istream::ignore() on it;
int next_char = stream.peek();
if(next_char == '\n'){
stream.ignore();
}
A working example of your code would be:
#include <iostream>
#include <fstream>
#include <string>
using namespace std;
//Suggestion: class/struct names should start with a capital letter.
struct Donor{
//Suggestion: Use member initializer lists to specify default values.
Donor() : name(), contribution(0){}
string name;
double contribution;
};
int main(){
cout << "Enter the filename: ";
string filename;
cin >> filename;
//Suggestion: Open the file immediately with the filename and use `operator bool` to check if it opened.
ifstream inFile(filename);
if(!inFile){
cout << "Could not open the file " << filename << '\n';
cout << "Program terminating.\n";
exit(EXIT_FAILURE);
}
int amount;
inFile >> amount; //! Leaves '\n'
Donor* donors = new Donor[amount];
for(int i = 0; i < amount; ++i){
switch(inFile.peek()){
case '\n': inFile.ignore();
break;
case EOF: cout << "Donor amount too big!\n";
exit(EXIT_FAILURE);
}
getline(inFile, donors[i].name);
inFile >> donors[i].contribution;
}
cout << "Here's the list of Grand Patrons:\n";
bool grandpatrons_exist = false;
for(int i = 0; i < amount; ++i){
if(donors[i].contribution >= 10000){
grandpatrons_exist = true;
cout << donors[i].name << '\n';
cout << donors[i].contribution << '\n';
}
}
if(!grandpatrons_exist){
cout << "None\n";
}
cout << "Here's the list of Patrons:\n";
for(int i = 0; 1 < amount; ++i){
if(donors[i].contribution < 10000){
cout << donors[i].name << '\n';
cout << donors[i].contribution << '\n';
}
}
delete[] donors;
return 0;
}
Now, an even better solution would be to use vectors instead of raw pointers and implement operator>> and operator<< which would greatly simplify
the reading and printing of the objects.
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <algorithm>
using namespace std;
class Donor{
public:
Donor() noexcept: name(), contribution(0){}
friend istream& operator>>(istream& stream, Donor& donor){
switch(stream.peek()){
case EOF: return stream;
case '\n': stream.ignore();
}
getline(stream, donor.name);
stream >> donor.contribution;
return stream;
}
friend ostream& operator<<(ostream& stream, const Donor& donor){
stream << donor.name << ' ' << donor.contribution;
return stream;
}
const string& get_name() const noexcept{
return name;
}
const double& get_contribution() const noexcept{
return contribution;
}
private:
string name;
double contribution;
};
int main(){
cout << "Enter the filename: ";
string filename;
cin >> filename;
ifstream inFile(filename);
if(!inFile){
cout << "Could not open the file " << filename << '\n';
cout << "Program terminating.\n";
exit(EXIT_FAILURE);
}
int amount;
inFile >> amount;
vector<Donor> donors(amount);
//Read it as `for donor in donors`
for(Donor& donor : donors){
inFile >> donor;
}
//An STL function that takes a lambda as the thirs argument. You should read up on them if you haven't.
//I would prefer using this since it greatly improves readability.
//This isn't mandatory, your implementation of this part is good enough.
bool grandpatrons_exist = any_of(begin(donors), end(donors), [](const Donor& donor){ return donor.get_contribution() >= 10000; });
cout << "Here's the list of Grand Patrons:\n";
if(grandpatrons_exist){
for(const Donor& donor : donors){
if(donor.get_contribution() >= 10000){
cout << donor << '\n';
}
}
}
else{
cout << "None\n";
}
cout << "\nHere's the list of Patrons:\n";
for(const Donor& donor : donors){
if(donor.get_contribution() < 10000){
cout << donor << '\n';
}
}
return 0;
}
Some other great improvements would be:
Use partition to seperate great patrons from normal ones.
Use stream iterators to read the objects into the vector.
int main(){
cout << "Enter the filename: ";
string filename;
cin >> filename;
ifstream inFile(filename);
if(!inFile){
cout << "Could not open the file " << filename << '\n';
cout << "Program terminating.\n";
exit(EXIT_FAILURE);
}
//Ignore the first line completely
inFile.ignore(numeric_limits<streamsize>::max(), '\n');
//Calls `operator>>` internally
vector<Donor> donors(istream_iterator<Donor>{inFile}, istream_iterator<Donor>{});
auto first_grand_patron = partition(begin(donors), end(donors), [](const Donor& donor){ return donor.get_contribution() >= 10000; });
cout << "Here's the list of Grand Patrons:\n";
if(first_grand_patron == begin(donors)){
cout << "None!\n";
}
for(auto patron = begin(donors); patron != first_grand_patron; ++patron){
cout << *patron << '\n';
}
cout << "\nHere's the list of Patrons:\n";
for(auto patron = first_grand_patron; patron != end(donors); ++patron){
cout << *patron << '\n';
}
return 0;
}
Now some general tips:
Struct/Class names should start with a capital letter.
Stop Using std::endl.
No need to cin.clear(). Cin is only used once and never again.
Use member-initializer lists.
Optionally use ++i instead of i++ in for loops to get used to the correct way of incrementing a variable unless needed otherwise.
bool grandpatrons is too much of an abstract name for a flag.
donors is a subjectively better name than short for donor list.
my text file was like
123456123456
Jason
uk
012456788
1000
456789456789
david
uk
012456788
1000
i'm trying to get the data from a text file and save it into arrays
however when i want to store the data from the text file into array it loop non-stop.
what should i do ?
the problem exiting in looping or the method i get the data from text file ?
code:
#include <iostream>
#include <fstream>
using namespace std;
typedef struct {
char acc_no[12];
char name[30];
char address[50];
char phone_no[12];
double balance;
} ACCOUNT;
//function prototype
void menu();
void read_data(ACCOUNT record[]);
int main() {
ACCOUNT record[31]; //Define array 'record' which have maximum size of 30
read_data(record);
}
//--------------------------------------------------------------------
void read_data(ACCOUNT record[]) {
ifstream openfile("list.txt"); //open text file
if (!openfile) {
cout << "Error opening input file\n";
return 0;
} else {
int loop = -1; //size of array
cout << "--------------Data From File--------------"<<endl;
while (!openfile.eof()) {
if (openfile.peek() == '\n')
openfile.ignore(256, '\n');
openfile.getline(record[++loop].acc_no, 12);
openfile.getline(record[loop].name, 30);
openfile.getline(record[loop].address, 50);
openfile.getline(record[loop].phone_no, 12);
openfile >> record[loop].balance;
}
openfile.close(); //close text file
for (int i = 0; i <= loop + 1; i++) {
cout << "Account " << endl;
cout << "Account No. : " << record[i].acc_no << endl;
cout << "Name : " << record[i].name << endl;
cout << "Address : " << record[i].address << endl;
cout << "Phone Number : " << record[i].phone_no << endl;
cout << "Balance : " << record[i].balance << endl;
}
}
}
UPDATE:
The OP didn't properly cite the correct format in his data file. This answer is only valid up until the last iteration.
Don't use .eof() - that's more applicable to when you want to open the file and read it by characters.
A better way would be to use the insertion operator >> as follows:
#define ARR_SIZE 31
ACCOUNT temp;
ACCOUNT record[ARR_SIZE];
int i=0;
while(i < ARR_SIZE) {
openfile >> temp.acc_no >> temp.name >> temp.address >> temp.phone_no >> temp.balance;
record[i] = temp;
i++;
}
Of course, even better is to use std::string to hold the values from the input file, in addition to using std::vectors instead of arrays.
Entire question :
Question 3
You are the owner of a hardware store and need to keep an inventory that can tell you what different tools you have, how many of each you have on hand and the cost of each one. Write a program that initializes the random-access file "hardware.dat" to 100 empty records, let you input the data concerning each tool, enables you to list all your tools, lets you delete a record for a tool that you no longer have and lets you update any information in the file. The tool identification number should be the record number. Use the following information to start your file.
My Code :
int question_3()
{
cout << "Question 3" << endl;
fstream hardware;
hardware.open("hardware.dat" , ios::binary | ios::out);
//Create 100 blank objects---------------------------------------------------------------
if (!hardware)
{
cerr << "File could not be opened." << endl;
exit(1);
}
HardwareData myHardwareData;
for (int counter = 1; counter <= 100; counter++)
{
hardware.write(reinterpret_cast< const char * >(&myHardwareData), sizeof(HardwareData));
}
cout << "Successfully create 100 blank objects and write them into the file." << endl;
hardware.close();
hardware.open("hardware.dat" , ios::binary | ios::out | ios::in);
//Write data-----------------------------------------------------------------------------
int record;
int quantity;
float cost;
string tool_name;
cout << endl;
cout << "Enter record number (1 to 100, 0 to end input) : ";
cin >> record;
while (record != 0)
{
cin.sync();
cout << "Enter tool name : "; getline(cin, tool_name);
cout << "Enter quantity : "; cin >> quantity;
cout << "Enter cost : "; cin >> cost;
myHardwareData.setRecord(record);
myHardwareData.setToolName(tool_name);
myHardwareData.setQuantity(quantity);
myHardwareData.setCost(cost);
hardware.seekp((myHardwareData.getRecord() - 1) * sizeof(HardwareData));
hardware.write(reinterpret_cast<const char *>(&myHardwareData), sizeof(HardwareData));
cout << endl
<< "Enter record number (1 to 100, 0 to end input) : ";
cin >> record;
}
cout << "Successfully write all input data into the file." << endl;
//Read data----------------------------------------------------------------------------
cout << endl;
outputDataLineHead();
hardware.read(reinterpret_cast<char *>(&myHardwareData), sizeof(HardwareData));
int counter = 0;
cout << setprecision(2) << fixed;
while (hardware && !hardware.eof())
{
if (myHardwareData.getRecord() != 0)
outputDataLine(cout, myHardwareData);
hardware.seekp(counter++ * sizeof(HardwareData));
hardware.read(reinterpret_cast<char *>(&myHardwareData), sizeof(HardwareData));
}
return 0;
}
//Function for showing data in line form.-----------------------------------------------
void outputDataLineHead()
{
cout << left << setw(17) << "Record No."
<< left << setw(17) << "Tool Name"
<< left << setw(17) << "Quantity"
<< left << setw(17) << "Cost" << endl;
}
void outputDataLine(ostream &output, const HardwareData &Object_in_file)
{
output << left << setw(17) << Object_in_file.getRecord()
<< left << setw(17) << Object_in_file.getToolName()
<< left << setw(17) << Object_in_file.getQuantity()
<< left << setw(17) << Object_in_file.getCost() << endl;
}
HardwareData.h :
#ifndef HAREWAREDATA_H
#define HAREWAREDATA_H
#include <iostream>
using std::string;
class HardwareData
{
public :
HardwareData(string name = "", int recd = 0, int qutity = 0, float cot = 0.0)
{
setToolName(name);
setRecord(recd);
setQuantity(qutity);
setCost(cot);
}
void setToolName(string name)
{
const char *nameValue = name.data();
int length = 0;
length = (length < 15 ? length : 14);
strncpy(tool_name, nameValue, length);
tool_name[length] = '\n';
}
string getToolName() const
{
return tool_name;
}
void setRecord(int recd)
{
record = recd;
}
int getRecord() const
{
return record;
}
void setQuantity(int qutity)
{
quantity = qutity;
}
int getQuantity() const
{
return quantity;
}
void setCost(float cot)
{
cost = cot;
}
float getCost() const
{
return cost;
}
private :
char tool_name[15];
int record;
int quantity;
float cost;
};
#endif
I want to show the data like the following :
Record No. Tool Name Quantity Cost
4 electric hammer 3 34.32
How to achieve this?
Thank you for your attention.
I think your problem is while reading data.. Please check your variables if they get correct data or not.. You can check this with counting characters or try to printf them.
If they are not correct. You can use such an example which i used in below.
First of all i prefer you to read your line like this example ;
In this example i get coordinates of faces. You should change parameters.. In order not to read no need data
std::string str;
while(std::getline(in, str))
{
sscanf(str.c_str(), "%d %f %f", &fiducial.number, &fiducial.x, &fiducial.y);
coord_Num[fiducial.number] = fiducial.get_number();
coord_X[fiducial.number] = fiducial.get_x();
coord_Y[fiducial.number] = fiducial.get_y();
}
If everything looks fine. You should check
void outputDataLine(ostream &output, const HardwareData &Object_in_file)
The core issue here is that you're reading and writing bytes to/from objects of type HardwareData when rather you should be creating inserters/extractors so you can implement correct I/O semantics. For example:
// Inside HardwareData class
friend std::ostream& operator<<(std::ostream&, const HardwareData&);
friend std::istream& operator>>(std::istream&, HardwareData&);
These two declarations are for the inserter and extractor respectively. Input should consist of extracting into the record, tool_name, quantity and cost data members; and output should simply be an stream insertion which is trivial to implement.
It is often the problem when mixing formatted input with unformatted input that the residual newline inhibits further input. That seems to be the case here:
cin >> record; /*
^^^^^^^^^^^^^^ */
while (record != 0)
{
cin.sync();
cout << "Enter tool name : "; getline(cin, tool_name);
// ^^^^^^^^^^^^^^^^^^^^^^^^
// ...
}
After cin >> record; finishes, there will be a newline left inside the stream. That newline will stop std::getline() from working correctly because std::getline() only reads until the newline.
The fix here is to ignore this new line by using the std::ws manipulator:
std::getline(std::cin >> std::ws, tool_name);
// ^^^^^^^^^^^^^^^^^^^
Note: I talk about this in more detail here.
But this manual extraction isn't needed as we've already defined the inserter and extractor for our class. So all that's really needed is the following:
while (std::cin >> myHardwareData)
{
hardware << myHardwareData;
}
or
std::copy(std::istream_iterator<HardwareData>(std::cin),
std::istream_iterator<HardwareData>(),
std::ostream_iterator<HardwareData>(hardware));
Noticed how I've also taken out the check for a 0 value of record in the while loop. That's because the extractor takes care of it by reflecting a 0 value of record as invalid input. It sets the stream state of the stream if this occurs, thus allowing ourselves to be ejected from the while if that happens:
std::istream& operator>>(std::istream& is, HardwareData& hd)
{
cout << "Enter record number (1 to 100, 0 to end input) : ";
if ((is >> record) && record != 0)
{
// ...
} else
{
is.setstate(std::ios_base::failbit);
}
// ...
}
And the rest of your code be changed to:
std::cout << myHardwareData;
hardware >> myHardwareData;
std::cout << std::setprecision(2) << std::fixed;
while (hardware >> myHardwareData)
{
if (myHardwareData.getRecord() != 0)
std::cout << myHardwareData;
}
I don't really know what the seekps are for. If you elaborate on that, that would really help me adapt my code more accurately to your needs.
To practice C++ I am trying to make a simple program that allows a user to input a name followed by a score and then allows the user to enter a name and get the score that name was entered with. The program works fine until I enter an escape character (ctrl + z) once I'm done entering names, after entering the escape character the program will output the line "Enter name of student to look up the score" but not allow the user to input the name and instead reads out "Press any key to exit". I'm totally stumped on how to fix this and any help is greatly appreciated.
#include "stdafx.h"
#include <std_lib_facilities.h>
int main()
{
vector <string>names;
vector <int>scores;
string n = " "; // name
int s = 0; // score
string student = " ";
cout << "Enter the name followed by the score. (Ex. John 89)" << endl;
while(cin >> n >> s)
{
for(size_t i = 0; i < names.size(); ++i)
{
if(n == names[i])
{
cout << "Error: Duplicate name, Overwriting" << endl;
names.erase(names.begin() + i);
scores.erase(scores.begin() + i);
}
}
names.push_back(n);
scores.push_back(s);
}
cout << "Name: Score:" << endl;
for(size_t j = 0; j < names.size(); ++j)
{
cout << names[j];
cout <<" " << scores[j] << endl;
}
cout << "Enter name of student to look up their score" << endl;
cin >> student;
for(size_t g = 0; g < names.size(); ++g)
{
if(student == names[g])
{
cout << "Score: " << scores[g] << endl;
}
}
keep_window_open();
return 0;
}
After you press the CTRL+Z key combination, which induces an EOF state to the cin stream, you need to bring the cin input stream back to its normal 'good' state to be able to use it again.
Add the following code after your for loop where you print the contents of the vectors.
cin.clear();
You may also check the state of the standard input stream using the rdstate() function. Anything other than 0 means that the standard stream is in an error state.
As has been said, you need to clear the error state on std::cin after reading the records failed.
std::cin.clear();
should do the trick. Here's my take on this using
proper data structures instead of two isolated vectors
const correctness
separating functions
no more hacky .erase() calls with magic indexes
#include <map>
#include <iostream>
std::map<std::string, int> read_records()
{
std::map<std::string, int> records;
std::string name;
int score;
std::cout << "Enter the name followed by the score. (Ex. John 89)" << std::endl;
while(std::cin >> name >> score)
{
if (records.find(name) != end(records))
{
std::cout << "Error: Duplicate name, Overwriting" << std::endl;
} else
{
records.insert({name, score});
}
}
std::cin.clear();
return records;
}
int main()
{
auto const records = read_records();
std::cout << "Name\tScore:" << std::endl;
for(auto& r : records)
std::cout << r.first << "\t" << r.second << std::endl;
std::cout << "Enter name of student to look up their score: " << std::flush;
std::string name;
if (std::cin >> name)
{
std::cout << "\nScore: " << records.at(name) << std::endl;
}
}
If you required contiguous storage, use a flat_map like the one from boost.