Building a linked list with two text files - c++

void ListaS::crearListaAleatoria(){
ifstream infile;
ifstream xfile;
infile.open("datosPrueba.txt");
xfile.open("datosPruebaNombres.txt");
int id;
char nombre[100];
int counter = 0;
//En caso de error
if (infile.fail()){
cout << "Error opening file" <<endl;
exit(1);
} if (xfile.fail()){
cout << "Error opening file" <<endl;
exit(1);
}
while(infile.eof() && xfile.eof()){
Persona* p = new Persona();
infile >> id;
xfile >> nombre;
p->setId(id);
p->setNombre(nombre);
agregar(p);
}
}
So I'm trying to build a linked list with two text files, one has numbers and the other has names, nonetheless, whenever I attempt to print the contents of this list, through another method I have somewhere else, it tells me I'm trying to access null values. The object Persona* is the place where I store the id and the name while agregar() is what creates the nodes to add to the list which is created elsewhere. Those things are not causing problems, its mainly those two values. I don't suppose there's some way to convert infile >> id to an int? Is there?

By the way, the condition of your while loop is wrong (should be while(!infile.eof() && !xfile.eof())). But in C++ you usually do these things in a different way:
while(infile >> id && xfile >> nombre){
Persona* p = new Persona();
p->setId(id);
p->setNombre(nombre);
agregar(p);
}
This way you read the values from the files and check the ifstream state at the same time...and you avoid problems with last lines.

Related

Read a file and add elements in new array in C++

Here my code. I want to declare dynamic array and next thing is to be able to read file and add elements in array. it is compiling, but I do not see result. Your suggestion is welcome.
int main(int argc, char** agrv)
{
if(argc < 2 || argc > 2)
{
cerr << "You have to provide a file" << endl;
return -1;
}
string myFile = agrv[1];
ifstream file(myFile.c_str());
if (!file)
{
cerr << "Error, file do not exist" << endl;
return -1;
}
Student students;
string number_student, name_student, surname_student, code_student;
double number_student;
// Declare an new array
DynamicArray<Student>students;
while(file >>number_student >> name_student >> surname_student >> code_student)
{
students.add(student);
}
for(int i = 0; i < students.size(); i++)
cout << students[i] << endl;
To provide solution to my project
First of all, you used the same name students for 2 variables in the code.
Student students
and
DynamicArray<Students> students;
I think you probably wanted to name one of them student and the other students but made a typo mistake that lead to 2 students? When you're calling students.add(student), it is obvious there is no variable named student to add to the array.
Then again, you have both string number_student and double number_student the same variable name. I have no idea why would you need 2 different variables for this one because your code didn't provide the background information about it, so at the moment I will eliminate the double number_student one for simplicity.
Second, there is some logic hole in your code. You used these strings number_student, name_student, surname_student and code_student to store the variable, but I don't see you use them again. My guess is that you're having a struct or class name Student and you want to store those variables into Student. Since you didn't provide your struct/class Student, I cannot help you fix it, but I can write you an example of it.
struct Student {
string number_student;
string name_student;
string surname_student;
string code_student;
}
To add a new student each time, you must call the Student student inside the while loop to create a new struct.
while(file >> number_student >> name_student >> surname_student >> code_student)
{
Student student = { number_student, name_student, surname_student, code_student};
students.add(student);
}

Editing a text file if searched value is present

I have just a couple issues here with my code. It works but I'm not advanced enough to do what I want to yet. Nor do I know how to word it for a google search. I have a Blackjack game that I'm doing and want to know how to edit certain lines of the file depending on user input. Simpler, I want a user to be able to open the game and start with their balance from the last time they were playing(a save and load feature). My issues are the balance and the username are on the same line in the text file (purposefully) and I want to assign the variables to those in the text file. I know I'm on the right track, I just dont know where to go from here. Thanks for the help in advance. If I broke a rule of posting, I'm sorry.
input username
if username is present in file
edit balance on leave
if username isnt present in file
create new user
Here is my code for the load function:
void load(userAcc user1)
{
ifstream in;
in.open("Balances.txt");
if (in.is_open())
{
string word;
for (int x = 0; in >> word; x++);
{
user1.name = word;
user1.balance = word;
}
cout << user1.name << endl;
cout << user1.balance << endl;
in.close();
}
else
cout << "Cannot open a file";
}
void save(userAcc user1)
{
user1.balance = "1000";
cout << "Enter a username: ";
cin >> user1.name;
ofstream out;
out.open("Balances.txt", ios_base::app);
if (out.is_open())
{
out << user1.name << " " << user1.balance << endl;
out.close();
}
else
cout << "Cannot open a file";
}
In
for (int x = 0; in >> word; x++);
remove the trailing ;. It ends the statement before the body of the for loop, separating the two. The for spins around doing nothing but reading the file until it ends and incrementing the unused variable x and the following code block will be run exactly once, storing whatever is in word (and since the loop will exit when the read into word fails, what's in word will depend on the C++ Standard version the the compiler's been set to use) into user1.
Once the ; is removed, the for loop will read into word until no more words can be read from the file. Every word read is copied into the same userAcc writing over the previous word. When the file hits the end in >> word will fail and the loop will exit. The last word in the file will then be printed out, all other words having been overwritten.
Naïve fixing of this would look something like
void load(userAcc user1)
{
ifstream in;
in.open("Balances.txt");
if (in.is_open())
{
while (in >> user1.name // read name in from file
>> user1.balance) // read balance in from file
{ // loop will exit when it cannot read a name and a balance from the file
// for now we're just printing out what's read from the file.
cout << user1.name << endl << user1.balance << endl;
}
// in.close(); not needed. File will automatically close when in goes out of scope.
}
else
cout << "Cannot open a file";
}
But we probably want to do more than print out all of the users in the file, so let's put them into a convenient resizable container like std::vector.
vector<userAcc> load() // takes no parameters, returns list of accounts
{
vector<userAcc> accounts;
ifstream in;
in.open("Balances.txt");
if (in.is_open())
{
userAcc user1; // account we can read into
while (in >> user1.name >> user1.balance)
{
accounts.push_back(user1); // store account
}
}
else
cout << "Cannot open a file";
return accounts; // hand accounts read back to caller.
}
Use of the function would be something like
vector<userAcc> accounts = load();
The save function looks pretty much good-to-go as written.

Read a binary file until the end c++

I have class player that contains some attributes.
I m filling a file with player's data but i want to assign a new id number every time the function is called in a way that the new player's id will be increased every time.
So i made this while loop to read number of players and update the player's new id but this doesn't work and nothing is written in the file.
void Player::create_player()
{
fstream file;
Player plyr; string passwordt;
int aget = 0;
long int id_nmbert = 0;
string emailt, ingame_namet, full_namet,yes;
file.open("player_database.dat", ios::binary | ios::app);
if (!file.is_open())
throw exception();
while (file >> yes) ///
id_nmbert++; ///
cout << "insert player's full name" << endl;
cin >> full_namet;
plyr.full_name = full_namet;
cout << "insert player's age" << endl;
cin >> aget;
plyr.age = aget;
cout << "insert player's email" << endl;
cin >> emailt;
plyr.email = emailt;
cout << "insert player's password" << endl;
cin >> password;
plyr.password = passwordt;
cout << "insert player's ingame name" << endl;
cin >> ingame_namet;
plyr.ingame_name = ingame_namet;
plyr.id_nmber = id_nmbert;
file.write((char*)&plyr, sizeof(Player));
file.close();
}
Tried This but it's even worse.
while (!file.eof())
id_numbert;
There are similar questions but not in c++ :).
I'd recommend storing the players in a text file that you parse instead of storing them as binary data.
This has a lot of advantages.
The biggest difficulty with your approach is that you need to design a binary format for your player object.
Something like
id+N1+'fullname'+N2+'email'+N3+'ingame-name'
4 bytes id, N1 number of characters in fullname(4 bytes),
the characters in fullname as indicated by N1
same for N2 + email and so on.
This requires fairly complicated logic for reading and writing a player.
On the other hand you could have a text file looking like
1
Yassin Mrabet
20
player#gamers.net
playerino1336
2
Captain Giraffe
55
player1#gamers.net
playerino1337
This is a lot easier to write code for.
A new player every fifth getline.
A sample readPlayer member could look like (should probably be static, not super important right now)
Plkayer Player::readPlayer(istream& in){
std::string str_id;
std::getline(in, str_id);
std::string fullname;
std::getline(in, fullname);
std::string str_age;
std::getline(in, str_age);
std::string email;
std::getline(in, email);
std::string player_name;
std::getline(in, player_name);
int age = std::stoi(str_age);
int id = std::stoi(str_id);
if(!in) // read was unsuccessful.
handle_bad_read();
return Player(id, fullname, age, email, player_name);
}
The return statement of course requires a constructor that accepts exactly those arguments, but that constructor will be useful in many other situations too.
The question isn't much specific as for what doesn't work, however, for keeping track of the ID I suggest using a static variable at the top of the function like this:
static int playerid = 0;
The static keyword means that it will only set the variable to zero once, and it will not delete the data after the end of the function. Which means you just save the 'playerid' into the file, then increase it by 1. The next time the function will be called, playerid will be larger by 1.
Edit: while (!file.eof()) doesn't work because you're using ios::app, it already starts at file.eof(), so the loop shouldn't do anything.

Why won't this file read in successfully?

I was trying to read in a .dat file that looks like this:
T001CD1 10000.00 2.5 2
T001CD2 50000.00 3.5 6
H407CD1 20000.00 2.0 1
M555CD1 30000.00 3.5 5
N423CD1 50000.00 3.0 4
N423CD2 60000.00 2.5 2
S602CD1 80000.00 4.0 8
H707CD1 25000.00 2.5 7
With this Code:
void readCdAccountInfo()
{
ifstream in_stream;
in_stream.open("CdAccounts.dat");
while (!in_stream.eof())
{
int i = 0;
string iDTemp;
float ratetemp;
int yeartemp;
double depotemp;
while (in_stream
>> iDTemp
>> depotemp
>> ratetemp
>> yeartemp
)
{
CCdAccount temp(iDTemp, depotemp, ratetemp, yeartemp);
accounts[i] = temp;
i++;
}
{
if (in_stream.fail())
{
cout << "Input file opening failed. \n";
exit(1);
}
in_stream.close();
}}
}
ID, Deposit (private member), Rate, and Year are are all part of a class I have.
The only thing that pops up when I run my main is the input file failure message.
First, check for opening error immediately after opening the file.
ifstream in_stream("CdAccounts.dat");
if (in_stream.fail())
{
cout << "Input file opening failed. \n";
exit(1);
}
The outer loop while (!in_stream.eof()) is obsolete, as you already check for stream errors in the inner loop correctly.
In the following code, I don't know the type of variable accounts, but it appears to be an array. It's not directly related to the issues you experience but that array may overflow if the file contains more records than you have reserved for your array. I suggest using std::vector instead, which will resize automatically.
int i = 0;
string iDTemp;
float ratetemp;
int yeartemp;
double depotemp;
while (in_stream
>> iDTemp
>> depotemp
>> ratetemp
>> yeartemp
)
{
CCdAccount temp(iDTemp, depotemp, ratetemp, yeartemp);
// Instead of this...
accounts[i] = temp;
i++;
// ... I suggest declaring accounts as std::vector<CCdAccount>
// so you could add elements like this:
// accounts.push_back( temp );
}
Next issue is in the error checking after this code. As commenter Peter pointed out:
Reaching end of file sets the eofbit for a stream. However, operations
that attempt to read something (like operator>>()) also set the
failbit if reaching end of file causes them to fail (i.e. to not
receive input).
So to check only for unexpected read errors, we have to exclude the eof bit from the error condition:
if (in_stream.fail() && ! in_stream.eof())
{
cout << "Input file reading failed.\n";
exit(1);
}
At the end of the function there is no need to explicitly close() the stream as the destructor of the stream will automatically close the file when the scope ends. It's not an error to call close(), it's just obsolete.

using fstream object to store information from a file into variables

I have the following block of code that i am using to read a text file of the following format:
firstname lastname id mark
firstname lastname id mark
Following is the block of code.
void DBManager::ReadFile(void){
fstream myfile; /*fstream object that will be used for file input and output operations*/
char* fn; /*pointer to the storage which will hold firstname*/
char* ln; /*pointer to the storage which will hold lastname*/
int id; /*integer var to hold the id*/
float mark; /*float var to hold the mark*/
/*read in the filename*/
g_FileName = new char[1024]; /*allocate memory on the heap to store filename*/
cout << "Please enter the filename:";
cin >> g_FileName;
/*open file*/
myfile.open(g_FileName, ios::in | ios::out);
if(myfile.is_open()){ /*check if the file opening is successful*/
cout << "File reading successful !\n";
/*read information from the file into temporary variables before passing them onto the heap*/
while (!myfile.eof()) {
fn=(char*) new char[1024];
ln=(char*) new char[1024];
myfile >> fn >> ln >> id >> mark;
cout << fn << " " << ln << " " << id << " " << mark << " " << endl;
}
myfile.close();
}
else{ /*else print error and return*/
perror("");
return;
}
}
The above block of code works ! :)
But I am surprised as to how myfile knows it is supposed to hold one line at a time and how its being smart enough about setting the four variables.
I am new to C++ , and hence this might be covered in some sort of documentation. But i would be happy to have some insight from you'll or a link to somewhere i can understand fstream objects better.
In C++, std::fstream is a type of stream which works specifically for files. When reading from a file, the interface for std::fstream is almost identical to std::cin. Input streams are programmed to read the next word or number when asked with the >> operator. They know where words and numbers are because they are separated by white space. In the default locale, spaces, tabs and newlines are considered to be white space. You can change the locale to include other characters, like commas, and have those be skipped while reading from a file. Basically, when reading with input streams, newlines and spaces are treated the same.
Some nice explanation for learning about streams is here: http://www.cprogramming.com/tutorial/c++-iostreams.html
I'm not sure what the question is. However, the code has several problems:
You should always check input after having tried to read.
Testing for eof() to determine if there is more to read doesn't work.
You have a memory leak, allocating memory in every iterator.
Reading without a constraint into a char array is unsafe, i.e., it is prone to buffer overrides (one of the major attack vectors).
You want to use a loop looking something like this:
std::string fn, ln;
while (myfile >> fn >> ln >> id >> mark) {
...
}