.txt file to an array of structures in C++? - c++

I have previously used C++, but I can definitely say I am not a pro. Not really even at coding in general. I'm trying, as much as it may not look like it. :/
My task is to create an array of structres of the type, "book_list".
the struct looks like this:
struct book_list
{
int bookNumber; string bookTitle; string bookAuthor;
string bookDescription; int bookMonth; int bookYear; int bookRating;
};
It's easy enough to create the array itself:
book_list myBookList[50];
Here's where I'm having trouble:
The program is intended to search for a .txt file. If it finds it, it will read into the array of structures all the data contained within the file. So, bookNumber with the .txt file's bookNumber, etc.
I have been looking up potential solutions all day, and I've looked into ideas such as ifstream, getline, using iterators, etc. My research has been as scattered as my brain, and I think I am a little stumped.
Please help, and if you need any further info, please let me know!
Thanks,
-Jon

You need to either read/parse each entry separately or read the entire file in then parse it. Since you're storing all the structs in memory anyway, the latter is probably fine if you don't want to bother with reading one at a time, but for educational purposes, you might want to try reading one at a time. Maybe separate by lines?
Then you need a way to parse the entries. There's no one way to do this, but the easiest is probably to use a JSON-formatted text file, mainly because there are libraries that can use it and tools that can help visualize it. JSON lets you represent dictionaries, arrays, strings, numbers, and booleans. You can either do this manually without much effort since you don't have anything fancy like nested structures, or you can use a library like this to parse JSON for you: https://github.com/DaveGamble/cJSON
(which is in C, will have to link with C++ if you want to use C++)

Assuming that the question may be formulated as:
can you give me a tiny programming example that can kickstart my exercise:
program p:
#include <iostream>
using namespace std;
int main() {
int num;
string buf;
while (true) {
cin >> num; if (cin.eof()) break;
cin >> buf; if (cin.eof()) break;
cout << "num is: " << num << " buf is: " << buf << endl;
}
}
text file t.txt
1
one
2
two
you can run
p < t.txt
and see that the output is what you probably expected.
Now of course change cin >> num; into cin >> booklist[0].bookNumber; etc. and your exercise will be on the right track.

Use <stdio.h> for FILE.
char *name;
FILE *file;
name = "database.txt";
file = fopen(name,"w+");
book_list myBookList[50];
While writing use this.
book_list newRecord;
newRecord.bookNumber = 0;
//other assignments
fseek(file,0,SEEK_END);
fwrite(&newRecord,sizeof(book_list),1,file);
fclose(file);
While reading use below.
file = fopen(name,"r+");
book_list record;
fseek(file,0,SEEK_SET);
int i=0;
while(true){
fread(&record,sizeof(book_list),1,file);
if(feof(file))
break;
myBookList[i].bookNumber = record.bookNumber;
//other assignments
i++;
}
fclose(file);
Note: I'm not sure this is working properly with string class.I have used this with char[SIZE] and strcpy. My suggestion is try this first with built-in types like int, float, char etc.

Related

Why are some programmers using string as an input and then converting it?

I've recently came across this code in C++:
int main(){
std::string a='0'; //I missed the quotes here anyways that wasn't
//really necessary for the question
std::cout<<"Enter your number: ";
getline(std::cin,a);
int ia=std::stoi(a);
return 0;
}
So why would someone write code like that, when you can write it like this:
int main(){
int a=0;
cout<<"Enter a number: ";
cin>>a;
return 0;
}
(I'm using using namespace std; by the way)
Why write longer code like in the first example? Is it more efficient considering time and memory?
There's more than one way to take this kind of input, and believe me, there are a multitude of weird, bizarre, if not just plain wrong ways to do it.
Reading into a std::string means you can do your own validation and surface errors where the conversion failed, or if the input was unexpectedly long or short.
If you read into an int you will get an int but it can obscure errors on input meaning you have fewer opportunities to provide feedback about conversion issues.
For example, with the std::cin >> a method you can input "lol" and you get back 0 which glosses over the fact that what you put in is total trash.
This is why it's often advantageous to read in a whole line into a std::string and do your own parsing, like checking with a regular expression that the format is correct before converting, otherwise surfacing errors.

Dynamically Allocating Array With Datafile

On a C++ project, I have been trying to use an array to store data from a textfile that I would later use. I have been having problems initializing the array without a size. Here is a basic sample of what I have been doing:
#include <iostream>
#include <string>
#include <fstream>
using namespace std;
int main()
{
int i = 0;
ifstream usern;
string data;
string otherdata;
string *users = nullptr;
usern.open("users.txt");
while(usern >> otherdata)
i++;
users = new (nothrow) string[i];
for (int n = 0; usern >> data; n++)
{
users[n] = data;
}
usern.close();
return 0;
}
This is a pretty rough example that I threw together. Basically I try to read the items from a text file called users.txt and store them in an array. I used pointers in the example that I included (which probably wasn't the best idea considering I don't know too much about poniters). When I run this program, regardless of the data in the file, I do not get any result when I try to test the values by including cout << *(users + 1). It just leaves a blank line in the window. I am guessing my error is in my use of pointers or in how I am assigning values in the pointers themselves. I was wondering if anybody could point me in the right direction on how to get the correct values into an array. Thanks!
Try reopening usern after
while(usern >> otherdata)
i++;
perhaps, try putting in
usern.close();
ifstream usern2;
usern2.open("users.txt");
right after that.
There may be other issues, but this seems like the most likely one to me. Let me know if you find success with this. To me it appears like usern is already reaching eof, and then you try to read from it a second time.
One thing that helps me a lot in finding such issues is to just put a cout << "looping"; or something inside the for loop so you know that you're at least getting in that for loop.
You can also do the same thing with usern.seekg(0, ios::beg);
What I think has happened in your code is that you have moved the pointer in the file that shows where the file is being read from. This happened when you iterated the number of strings to be read in using the code below.
while(usern >> otherdata)
i++;
This however brought the file pointer to the end of the file this means that in order to read the file you need to move the file pointer to the beginning of the file before you re-read it into your array of strings that you allocated of size i. This can be acheived by adding usern.seekg(0, ios::beg); after your while loop, as shown below. (For a good tutorial on file pointers see here.)
while(usern >> otherdata)
i++;
// Returns file pointer to beginning of file.
usern.seekg(0, ios::beg);
// The rest of your code.
Warning: I am unsure about how safe dynamically allocating STL containers are, I have previously run into issues with code similar to yours and would recommend staying away from this in functional code.

How to name text file at a certain directory?

Ok thanks for the answer Wug! I changed my code but now it's complaining about:
no matching function for call to
std::basic_ofstream::basic_ofstream(std::basic_string)
I'm not sure it makes any difference but i'll just post all of my code it's not that much so far.
I'll try to keep it cleaner from now on.
#include <iostream>
#include <windows.h>
#include <direct.h>
#include <fstream>
using namespace std;
int main()
{ /*Introduction*/
SetConsoleTitle("Journal");
string action, prom0, filename, filepath;
filepath = "C:\\Users\\-\\Desktop\\Projects\\Journal Project\\Logs\\";
cout << "Hi and welcome to Journal! \nHere you can write down your day.\nWrite help for";
cout << "more \nType command to start: ";
/*Choose Action*/
cin >> action;
if (action == "new")
{system("cls");
/*Make new Journal file*/
cout << "Filename: ";
getline(cin, filename);
mkdir("C:\\Users\\-\\Desktop\\Projects\\Journal Project\\Logs");
ofstream journallogs(filepath + filename);
journallogs.close();
}
else {
cout << "Wrong command\n";
};
return 0;}
There are 2 things wrong. The first is what the compiler's complaining about:
ofstream journallogs("C:\\Users\\-\\Desktop\\Projects\\Journal Project\\Logs\\" + getline(cin, filename), ios::out);
std::getline(istream&, string&) returns istream&, and you can't add char * to istream. I recommend taking a look at the documentation for getline(), which might help you understand better how you're supposed to use it. Here's an example anyway:
string filepath = "C:\\Users\\-\\Desktop\\Projects\\Journal Project\\Logs\\";
string filename;
getline(cin, filename);
ofstream journallogs(filepath + filename);
The second problem is that you're reading from cin into filename before calling getline(). When you call getline(), any contents of filename are dropped, so you'll effectively trim the first word off of your filename, which probably isn't what you want. To fix that, remove the extraneous cin >> filename;
Note: indentation is important and helps you read your own code. Put forth the effort to keep your code looking nice.
First, learn this:
Start small and simple.
Add complexity a little at a time.
Test at every step.
develop new functionality in isolation.
Never add to code that doesn't work.
For the rest, I don't use Windows, so I can't be certain my code will work there, but the approach will.
You are trying to 1) get a filename from the user, 2) modify it and then 3) use it to open a file; we will develop these three things in isolation.
Getting a filename from the user. Civilized filenames do not contain whitespace, so they can be read with cin, but if you want to allow whitespace you can use getline instead. Either way, test it.
Modifying the filename. Write code that assigns a value to the filename, just as it does to the path-- do not get the filename from the user, it slows down your testing and is not proper isolation. Now try to append them. If you try filepath + filename, you may get a compiler error. Here's where you must understand the difference between std::string and char[]. A char[] is an array of char, and it (usually) contains a null-terminated sequence of characters; you must read up on arrays and pointers. It is a primitive type, and you cannot simply concatenate two of them with '+', you must use something like strcat, which is dangerous if you haven't done your homework on arrays. On the other hand, std::string is more sophisticated, and can handle '+' and many other operations. If you have a std::string x and you decide you want a char[] after all, you can get one like so: x.c_str().
Opening the file. If I remember right, the ofstream constructor can take a char[], but not a std::string. Test this with a hard-coded string (isolation!).
Once you have these three components working independently, you can hook them together.

Create a C++ string with given format

I'm a Objective-C dev and sometimes I have to deal with C/C++ code.
I have a function written in C++, it logs an event with name, for example, Level_12_Pack_10. I want to create a dynamic C++ string like that, then I can change level and pack numbers.
In Objective C, it's easy with some lines of code: [NSString stringwithformat] but in C++, it's a bit difficult for me.
Could anyone help me do it?
I don't think C++ supports strings with built-in changeable variables like that. It would be too over-the-top to make a class to format the string like that. Probably the closest thing you can get is to use stringstreams:
#include <sstream>
string makeMyString(int level, int pack) {
stringstream ss;
ss << "Level_" << level << "_Pack_" << pack;
return ss.str();
}
If you have a string that you need to read and change the values inside, a similar function could be used.
With c++11, it is drop dead simple just use std::to_string(level) etc to concatenate strings.
int level = 10;
int pack = 40;
std::string stuff = "Level_" + std::to_string(level) + "_Pack_" + std::to_string(pack);
//stuff is now "Level_10_Pack_40"
std::cout << stuff;

Keep a text file from wiping in a function but keep ability to write to it? C++

I have a function that swaps two chars, in a file, at a time, which works, however if i try to use the function more than once the previous swap i made will be wiped from the text file and the original text in now back in, therefore the second change will seem as my first. how can i resolve this?
void swapping_letters()
{
ifstream inFile("decrypted.txt");
ofstream outFile("swap.txt");
char a;
char b;
vector<char> fileChars;
if (inFile.is_open())
{
cout<<"What is the letter you want to replace?"<<endl;
cin>>a;
cout<<"What is the letter you want to replace it with?"<<endl;
cin>>b;
while (inFile.good())
{
char c;
inFile.get(c);
fileChars.push_back(c);
}
replace(fileChars.begin(),fileChars.end(),a,b);
}
else
{
cout<<"Please run the decrypt."<<endl;
}
for(int i = 0; i < fileChars.size(); i++)
{
outFile<<fileChars[i];
}
}
What you probably want to do is to parameterize your function :
void swapping_letters(string inFileName, string outFileName)
{
ifstream inFile(inFileName);
ofstream outFile(outFileName);
...
Because you don't have parameters, calling it twice is equivalent to:
swapping_letters("decrypted.txt", "swap.txt");
swapping_letters("decrypted.txt", "swap.txt");
But "decrypted.txt" wasn't modified after the first call, because you don't change the input file. So if you wanted to use the output of the first operation as the input to the second you'd have to write:
swapping_letters("decrypted.txt", "intermediate.txt");
swapping_letters("intermediate.txt", "swap.txt");
There are other ways of approaching this problem. By reading the file one character at a time, you are making quite a number of function calls...a million-byte file will involve 1 million calls to get() and 1 million calls to push_back(). Most of the time the internal buffering means this won't be too slow, but there are better ways:
Read whole ASCII file into C++ std::string
Note that if this is the actual problem you're solving, you don't actually need to read the whole file into memory. You can read the file in blocks (or character-by-character as you are doing) and do your output without holding the entire file.
An advanced idea that you may be interested in at some point are memory-mapped files. This lets you treat a disk file like it's a big array and easily modify it in memory...while letting the operating system worry about details of how much of the file to page in or page out at a time. They're a good fit for some problems, and there's a C++ platform-independent API for memory mapped files in the boost library:
http://en.wikipedia.org/wiki/Memory-mapped_file