Visual Studio C++ Data from CSV File into Variable Arrays - c++

Basically, I want to make a simple function for a larger project, this will actually end up as a Header file that handles all my inventory. Any who, I just want it to be able to read/pull/input the Data from a .csv file format... Or I can even do a .txt if it is easier to make this function work, As long as I can open it up in MS Excel and edit the items and add new items, when the function is ran it will Open up "Example.csv" and for example, the function will look for the cell in Column 1 - 'sSwordName' and it will pull the data in the row FOLLOWING that cell, excluding that first column completely in all the inputs essentially... It will group it together, or what ever it may have to do in order to ASSIGN it to that Variable. Please see Code.h (Comments) to see my questions about my source code.
Code.h -
#include "stdafx.h"
#include <string>
#include <fstream>
#include <sstream>
#include <iostream>
#include <vector>
using namespace std;
char sSwordName[100][25] = {};
int sSwordLvlR[100] = {};
vector<string> split_at_commas(const string & row)
{
vector<string> res;
istringstream buf(row);
string s;
while (getline(buf, s, ','))
res.push_back(s);
return res;
system("pause");
}
/* Question 1: Where & How do I properly add 'ifstream wInv("Example.csv");' in order to Load the CSV file it is reading? */
/* Question 2: Line 38 & 46. */
/* Question 3: Line 39 & 47 */
int main()
{
int i = 1;
string line;
string row;
vector<string> values = split_at_commas(line);
if (values[0] == "sSwordName")
{
for(int i = 1; i < values.size(); ++i);
{
/*int i error: Object must have a pointer-to-object type*/
sSwordName[100][25][i - 1] = /*How do I convert string to char?*/(vector[i]);
}
}
else if (values[1] == "sSwordLvlR")
{
for(int i = 1; i < values.size(); ++i);
{
/*int i error: Object must have a pointer-to-object type*/
sSwordLvlR[i - 1] = /*How do I Convert string to int?*/(vector[i]);
}
}
}
/* Question 4: Is there anything else that is wrong in this? If so, how would I fix it */
Example.csv -
sSwordName,Wooden Shortsword,Bronze Shortsword,Iron Shortsword,Steel Shortsword,Titanium Shortsword
sSwordLvlR,1,3,5,6,10
More Information On The CSV:
sSwordName,Wooden Shortsword,Bronze Shortsword,Iron Shortsword,Steel Shortsword,Titanium Shortsword
sSwordLvlR,1,3,5,6,10
^Is not the ONLY way I can format it, for convenience I can do something like this (Below, E2.csv). If it is easier to make the function, function.;
sSwordName,"Wooden Shortsword","Bronze Shortsword","Iron Shortsword","Steel Shortsword","Titanium Shortsword"
sSwordLvlR,"1,","3,","5,","6,","10,"
I can even format it in this way;
sSwordName,"{"Wooden Shortsword","Bronze Shortsword","Iron Shortsword","Steel Shortsword","Titanium Shortsword"};
sSwordLvlR,"{1,3,5,6,10};"
Once again, I greatly appreciate any help. I thank you in Advanced!
-Leaum

Related

Write vector values into multiple files at once in C++

I have the data in my vector. I am trying to write each vector value, say vector_name[0] into "examplezero.h" , vector_name[1] into "exampleone.h" and so on. The below code shows how I have created the files.
int co = 80;
string name ="example";
std::ofstream output_file[80];
for (int i = 0; i < co; i++)
{
output_file[i].open(name + std::to_string(i) + ".h");
output_file[i].close();
}
I am trying to iterate over my vector and trying to write to my files.
std::vector<string> rowname; //This has all the values
for (auto i = rowname.begin(); i != rowname.end(); ++i)
{
std::ostream_iterator<std::string> \
output_iterator(output_file[80], "\n");
std::copy(rowname.begin(), rowname.end(), output_iterator);
}
When I am trying to write to the files it is crashing. Can you let me know what's wrong? I know the basics of C++ and trying to learn the advanced concepts.
Thanks
Your program is likely crashing because you wrote this code:
std::ostream_iterator<std::string> \
output_iterator(output_file[80], "\n");
...and output_file[80] is one element past the end of the array. You declared it as:
std::ofstream output_file[80];
The first element of that array is output_file[0] and the last element of that array is output_file[79].
There are more things wrong
As #walnut pointed out, if your code is really as you posted it, then it appears to close each file immediately after opening it, without writing anything to the file.
for (int i = 0; i < co; i++)
{
output_file[i].open(name + std::to_string(i) + ".h");
output_file[i].close(); // Leaving so soon?
}
Writing to an ofstream that has been closed does not crash the program, but sets an error condition on the ofstream (badbit). So this will appear to be a silent failure to you.
To fix
To fix your problem you'll have to write to your file after you open it, but before you close it.
You'll also have to decide exactly which output_file you actually want to write to and provide the correct array index. It's not obviously clear from your sample code what your intent was. You'll have to decide which file(s) (of the 80 that you opened) you want to write each element of your rowname vector into.
The std::copy as you have written it will write all strings in the rowname vector to the same stream. If your intent was to write each element to its own file, then you'll have to set it up substantially differently.
Something more along the lines of:
#include <fstream>
#include <vector>
#include <string>
int main() {
std::vector<std::string> rowname = { "alpha", "bravo", "charlie" }; // example data
std::string name = "example"; // base filename
for (size_t i = 0; i < rowname.size(); ++i) {
std::ofstream output_file;
std::string filename = name + std::to_string(i) + ".h"; // e.g.: "example0.h"
output_file.open(filename);
output_file << rowname[i]; // write the string to the file
output_file.close(); // if you want
}
}
This writes the text alpha into example0.h, bravo into example1.h, and charlie into example2.h.

Parsing a CSV file - C++

C++14
Generally, the staff in university has recommended us to use Boost to parse the file, but I've installed it and not succeeded to implement anything with it.
So I have to parse a CSV file line-by-line, where each line is of 2 columns, separated of course by a comma. Each of these two columns is a digit. I have to take the integral value of these two digits and use them to construct my Fractal objects at the end.
The first problem is: The file can look like for example so:
1,1
<HERE WE HAVE A NEWLINE>
<HERE WE HAVE A NEWLINE>
This format of file is okay. But my solution outputs "Invalid input" for that one, where the correct solution is supposed to print only once the respective fractal - 1,1.
The second problem is: The file can look like:
1,1
<HERE WE HAVE A NEWLINE>
1,1
This is supposed to be an invalid input but my solution treats it like a correct one - and just skips over the middle NEWLINE.
Maybe you can guide me how to fix these issues, it would really help me as I'm struggling with this exercise for 3 days from morning to evening.
This is my current parser:
#include <iostream>
#include "Fractal.h"
#include <fstream>
#include <stack>
#include <sstream>
const char *usgErr = "Usage: FractalDrawer <file path>\n";
const char *invalidErr = "Invalid input\n";
const char *VALIDEXT = "csv";
const char EXTDOT = '.';
const char COMMA = ',';
const char MINTYPE = 1;
const char MAXTYPE = 3;
const int MINDIM = 1;
const int MAXDIM = 6;
const int NUBEROFARGS = 2;
int main(int argc, char *argv[])
{
if (argc != NUBEROFARGS)
{
std::cerr << usgErr;
std::exit(EXIT_FAILURE);
}
std::stack<Fractal *> resToPrint;
std::string filepath = argv[1]; // Can be a relative/absolute path
if (filepath.substr(filepath.find_last_of(EXTDOT) + 1) != VALIDEXT)
{
std::cerr << invalidErr;
exit(EXIT_FAILURE);
}
std::stringstream ss; // Treat it as a buffer to parse each line
std::string s; // Use it with 'ss' to convert char digit to int
std::ifstream myFile; // Declare on a pointer to file
myFile.open(filepath); // Open CSV file
if (!myFile) // If failed to open the file
{
std::cerr << invalidErr;
exit(EXIT_FAILURE);
}
int type = 0;
int dim = 0;
while (myFile.peek() != EOF)
{
getline(myFile, s, COMMA); // Read to comma - the kind of fractal, store it in s
ss << s << WHITESPACE; // Save the number in ss delimited by ' ' to be able to perform the double assignment
s.clear(); // We don't want to save this number in s anymore as we won't it to be assigned somewhere else
getline(myFile, s, NEWLINE); // Read to NEWLINE - the dim of the fractal
ss << s;
ss >> type >> dim; // Double assignment
s.clear(); // We don't want to save this number in s anymore as we won't it to be assigned somewhere else
if (ss.peek() != EOF || type < MINTYPE || type > MAXTYPE || dim < MINDIM || dim > MAXDIM)
{
std::cerr << invalidErr;
std::exit(EXIT_FAILURE);
}
resToPrint.push(FractalFactory::factoryMethod(type, dim));
ss.clear(); // Clear the buffer to update new values of the next line at the next iteration
}
while (!resToPrint.empty())
{
std::cout << *(resToPrint.top()) << std::endl;
resToPrint.pop();
}
myFile.close();
return 0;
}
You do not need anything special to parse .csv files, the STL containers from C++11 on provide all the tools necessary to parse virtually any .csv file. You do not need to know the number of values per-row you are parsing before hand, though you will need to know the type of value you are reading from the .csv in order to apply the proper conversion of values. You do not need any third-party library like Boost either.
There are many ways to store the values parsed from a .csv file. The basic "handle any type" approach is to store the values in a std::vector<std::vector<type>> (which essentially provides a vector of vectors holding the values parsed from each line). You can specialize the storage as needed depending on the type you are reading and how you need to convert and store the values. Your base storage can be struct/class, std::pair, std::set, or just a basic type like int. Whatever fits your data.
In your case you have basic int values in your file. The only caveat to a basic .csv parse is the fact you may have blank lines in between the lines of values. That's easily handled by any number of tests. For instance you can check if the .length() of the line read is zero, or for a bit more flexibility (in handling lines with containing multiple whitespace or other non-value characters), you can use .find_first_of() to find the first wanted value in the line to determine if it is a line to parse.
For example, in your case, your read loop for your lines of value can simply read each line and check whether the line contains a digit. It can be as simple as:
...
std::string line; /* string to hold each line read from file */
std::vector<std::vector<int>> values {}; /* vector vector of int */
std::ifstream f (argv[1]); /* file stream to read */
while (getline (f, line)) { /* read each line into line */
/* if no digits in line - get next */
if (line.find_first_of("0123456789") == std::string::npos)
continue;
...
}
Above, each line is read into line and then line is checked on whether or not it contains digits. If so, parse it. If not, go get the next line and try again.
If it is a line containing values, then you can create a std::stringstream from the line and read integer values from the stringstream into a temporary int value and add the value to a temporary vector of int, consume the comma with getline and the delimiter ',', and when you run out of values to read from the line, add the temporary vector of int to your final storage. (Repeat until all lines are read).
Your complete read loop could be:
while (getline (f, line)) { /* read each line into line */
/* if no digits in line - get next */
if (line.find_first_of("0123456789") == std::string::npos)
continue;
int itmp; /* temporary int */
std::vector<int> tmp; /* temporary vector<int> */
std::stringstream ss (line); /* stringstream from line */
while (ss >> itmp) { /* read int from stringstream */
std::string tmpstr; /* temporary string to ',' */
tmp.push_back(itmp); /* add int to tmp */
if (!getline (ss, tmpstr, ',')) /* read to ',' w/tmpstr */
break; /* done if no more ',' */
}
values.push_back (tmp); /* add tmp vector to values */
}
There is no limit on the number of values read per-line, or the number of lines of values read per-file (up to the limits of your virtual memory for storage)
Putting the above together in a short example, you could do something similar to the following which just reads your input file and then outputs the collected integers when done:
#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <vector>
int main (int argc, char **argv) {
if (argc < 2) { /* validate at least 1 argument given for filename */
std::cerr << "error: insufficient input.\nusage: ./prog <filename>\n";
return 1;
}
std::string line; /* string to hold each line read from file */
std::vector<std::vector<int>> values {}; /* vector vector of int */
std::ifstream f (argv[1]); /* file stream to read */
while (getline (f, line)) { /* read each line into line */
/* if no digits in line - get next */
if (line.find_first_of("0123456789") == std::string::npos)
continue;
int itmp; /* temporary int */
std::vector<int> tmp; /* temporary vector<int> */
std::stringstream ss (line); /* stringstream from line */
while (ss >> itmp) { /* read int from stringstream */
std::string tmpstr; /* temporary string to ',' */
tmp.push_back(itmp); /* add int to tmp */
if (!getline (ss, tmpstr, ',')) /* read to ',' w/tmpstr */
break; /* done if no more ',' */
}
values.push_back (tmp); /* add tmp vector to values */
}
for (auto row : values) { /* output collected values */
for (auto col : row)
std::cout << " " << col;
std::cout << '\n';
}
}
Example Input File
Using an input file with miscellaneous blank lines and two-integers per-line on the lines containing values as you describe in your question:
$ cat dat/csvspaces.csv
1,1
2,2
3,3
4,4
5,5
6,6
7,7
8,8
9,9
Example Use/Output
The resulting parse:
$ ./bin/parsecsv dat/csvspaces.csv
1 1
2 2
3 3
4 4
5 5
6 6
7 7
8 8
9 9
Example Input Unknown/Uneven No. of Columns
You don't need to know the number of values per-line in the .csv or the number of lines of values in the file. The STL containers handle the memory allocation needs automatically allowing you to parse whatever you need. Now you may want to enforce some fixed number of values per-row, or rows per-file, but that is simply up to you to add simple counters and checks to your read/parse routine to limit the values stored as needed.
Without any changes to the code above, it will handle any number of comma-separated-values per-line. For example, changing your data file to:
$ cat dat/csvspaces2.csv
1
2,2
3,3,3
4,4,4,4
5,5,5,5,5
6,6,6,6,6,6
7,7,7,7,7,7,7
8,8,8,8,8,8,8,8
9,9,9,9,9,9,9,9,9
Example Use/Output
Results in the expected parse of each value from each line, e.g.:
$ ./bin/parsecsv dat/csvspaces2.csv
1
2 2
3 3 3
4 4 4 4
5 5 5 5 5
6 6 6 6 6 6
7 7 7 7 7 7 7
8 8 8 8 8 8 8 8
9 9 9 9 9 9 9 9 9
Let me know if you have questions that I didn't cover or if you have additional questions about something I did and I'm happy to help further.
I will not update your code. I look at your title Parsing a CSV file - C++ and would like to show you, how to read csv files in a more modern way. Unfortunately you are still on C++14. With C++20 or the ranges library it would be ultra simple using getlines and split.
And in C++17 we could use CTAD and if with initializer and so on.
But what we do not need is boost. C++`s standard lib is sufficient. And we do never use scanf and old stuff like that.
And in my very humble opinion the link to the 10 years old question How can I read and parse CSV files in C++? should not be given any longer. It is the year 2020 now. And more modern and now available language elements should be used. But as said. Everybody is free to do what he wants.
In C++ we can use the std::sregex_token_iterator. and its usage is ultra simple. It will also not slow down your program dramatically. A double std::getline would also be ok. Although it is not that flexible. The number of columns must be known for that. The std::sregex_token_iterator does not care about the number of columns.
Please see the following example code. In that, we create a tine proxy class and overwrite its extractor operator. Then we us the std::istream_iterator and read and parse the whole csv-file in a small one-liner.
#include <algorithm>
#include <fstream>
#include <iostream>
#include <iterator>
#include <regex>
#include <string>
#include <vector>
// Define Alias for easier Reading
// using Columns = std::vector<std::string>;
using Columns = std::vector<int>;
// The delimiter
const std::regex re(",");
// Proxy for the input Iterator
struct ColumnProxy {
// Overload extractor. Read a complete line
friend std::istream& operator>>(std::istream& is, ColumnProxy& cp) {
// Read a line
std::string line;
cp.columns.clear();
if(std::getline(is, line) && !line.empty()) {
// Split values and copy into resulting vector
std::transform(
std::sregex_token_iterator(line.begin(), line.end(), re, -1), {},
std::back_inserter(cp.columns),
[](const std::string& s) { return std::stoi(s); });
}
return is;
}
// Type cast operator overload. Cast the type 'Columns' to
// std::vector<std::string>
operator Columns() const { return columns; }
protected:
// Temporary to hold the read vector
Columns columns{};
};
int main() {
std::ifstream myFile("r:\\log.txt");
if(myFile) {
// Read the complete file and parse verything and store result into vector
std::vector<Columns> values(std::istream_iterator<ColumnProxy>(myFile), {});
// Show complete csv data
std::for_each(values.begin(), values.end(), [](const Columns& c) {
std::copy(c.begin(), c.end(),
std::ostream_iterator<int>(std::cout, " "));
std::cout << "\n";
});
}
return 0;
}
Please note: There are tons of other possible solutions. Please feel free to use whatever you want.
EDIT
Because I see a lot of complicated code here, I would like to show a 2nd example of how to
Parsing a CSV file - C++
Basically, you do not need more than 2 statements in the code. You first define a regex for digits. And then you use a C++ language element that has been exactly designed for the purpose of tokenizing strings into substrings. The std::sregex_token_iterator. And because such a most-fitting language element is available in C++ since years, it would may be worth a consideration to use it. And maybe you could do basically the task in 2 lines, instead of 10 or more lines. And it is easy to understand.
But of course, there are thousands of possible solutions and some like to continue in C-Style and others like more moderen C++ features. That's up to everybodies personal decision.
The below code reads the csv file as specified, regardless of how many rows(lines) it contains and how many columns are there for each row. Even foreing characters can be in it. An empty row will be an empty entry in the csv vector. This can also be easly prevented, with an "if !empty" before the emplace back.
But some like so and the other like so. Whatever people want.
Please see a general example:
#include <algorithm>
#include <iterator>
#include <iostream>
#include <regex>
#include <sstream>
#include <string>
#include <vector>
// Test data. Can of course also be taken from a file stream.
std::stringstream testFile{ R"(1,2
3, a, 4
5 , 6 b , 7
abc def
8 , 9
11 12 13 14 15 16 17)" };
std::regex digits{R"((\d+))"};
using Row = std::vector<std::string>;
int main() {
// Here we will store all the data from the CSV as std::vector<std::vector<std::string>>
std::vector<Row> csv{};
// This extremely simple 2 lines will read the complete CSV and parse the data
for (std::string line{}; std::getline(testFile, line); )
csv.emplace_back(Row(std::sregex_token_iterator(line.begin(), line.end(), digits, 1), {}));
// Now, you can do with the data, whatever you want. For example: Print double the value
std::for_each(csv.begin(), csv.end(), [](const Row& r) {
if (!r.empty()) {
std::transform(r.begin(), r.end(), std::ostream_iterator<int>(std::cout, " "), [](const std::string& s) {
return std::stoi(s) * 2; }
); std::cout << "\n";}});
return 0;
}
So, now, you may get the idea, you may like it, or you do not like it. Whatever. Feel free to do whatever you want.

Accessing data from file

I'm aware this has been discussed a lot on SO (and elsewhere). I'm resorting to asking here as I'm still stuck. Maybe I'm doing something really brainless/silly or maybe it's a genuine gotcha...
I have a directory of files, each with the extension '.pts' - opening one reveals:
version: 1
n_points: 68
{
498.801220 504.771171
516.076459 571.681686
518.038170 628.516761
...
Pasting here gets the formatting wrong a little, so be advised that there is no empty line between each line of data. There are also 68 of these float pairs, however I omit them here.
Eventually I want a vector of the pairs as rounded shorts - e.g. 499, 505, 516, 572, 518, 629 ...
But at the moment I can't access the floats as is, let alone any information.
The code so far:
#include <iostream>
#include <string>
#include "dirent.h"
#include <fstream>
#include <sstream>
#include <stdio.h>
using namespace std;
bool has_suffix(const string& s, const string& suffix)
{
return (s.size() >= suffix.size()) && equal(suffix.rbegin(), suffix.rend(), s.rbegin());
}
int main(int argc, char **argv)
{
string path = "C:\\testset";
DIR *dir = opendir(path.c_str());
if(!dir)
{
return 1;
}
dirent *entry;
string fileName;
float number;
string dummy;
while(entry = readdir(dir))
{
if(has_suffix(entry->d_name, ".pts"))
{
fileName = entry->d_name;
fileName = path + "\\" + fileName; // <<added at suggestion of Martin James
//Working up to here as I see all the .pts files listed when I print them:
cout << fileName << endl;
ifstream file(fileName, std::ios_base::in);
//however from here:
while (file >> number)
{
//...nothing will print
printf("%f ", number);
}
file.close();
}
}
closedir(dir);
}
Finding all the .pts files is working (thanks other thread on SO :) - and they do appear to be simple .text files as they open in text editors. There is a 'LF' at the end of each line.
Problem is that running the code will only result in the file names being listed. It seems that "while (file >> number)" isn't returning anything to run the print statement. Thing is, other threads on the topic indicate (at least to me) that it is as simple as this.
I feel it's likely that the answer will involve things I've looked at in the last few hours - dummy string variables to soak up all the stuff at the top of the file I don't need? getline? 'tokens'? I would show you all my attempts at nutting it out but I don't want to bog down the question:
Given a txt file formatted like above how would you access the values from line 4 onwards?
Any help greatly appreciated :)
The problem here is that, you are not reading in the header information. The >> operation fails, because you are trying to read in the string "version: 1" as a float.
To fix this, you should either skip the header with few calls to std::getline or parse it to get information about the contents.
Edit: To skip the lines you can just do:
for(int i=0;i<3;i++) //Skip header
std::getline(file,dummy);

Trouble getting string to print random line from text file

I picked up this bit of code a while back as a way to select a random line from a text file and output the result. Unfortunately, it only seems to output the first letter of the line that it selects and I can't figure out why its doing so or how to fix it. Any help would be appreciated.
#include "stdafx.h"
#include <stdio.h>
#include <iostream>
#include <fstream>
#include <string>
#include <time.h>
using namespace std;
#define MAX_STRING_SIZE 1000
string firstName()
{
string firstName;
char str[MAX_STRING_SIZE], pick[MAX_STRING_SIZE];
FILE *fp;
int readCount = 0;
fp = fopen("firstnames.txt", "r");
if (fp)
{
if (fgets(pick, MAX_STRING_SIZE, fp) != NULL)
{
readCount = 1;
while (fgets (str, MAX_STRING_SIZE, fp) != NULL)
{
if ((rand() % ++readCount) == 0)
{
strcpy(pick, str);
}
}
}
}
fclose(fp);
firstName = *pick;
return firstName;
}
int main()
{
srand(time(NULL));
int n = 1;
while (n < 10)
{
string fn = firstName();
cout << fn << endl;
++n;
}
system("pause");
}
firstName = *pick;
I am guessing this is the problem.
pick here is essentially a pointer to the first element of the array, char*, so of course *pick is of type char.. or the first character of the array.
Another way to see it is that *pick == *(pick +0) == pick[0]
There are several ways to fix it. Simplest is to just do the below.
return pick;
The constructor will automatically make the conversion for you.
Since you didn't specify the format of your file, I'll cover both cases: fixed record length and variable record length; assuming each text line is a record.
Reading Random Names, Fixed Length Records
This one is straight forward.
Determine the index (random) of the record you want.
Calculate the file position = record length * index.
Set file to the position.
Read text from file, using std::getline.
Reading Random Names, Variable Length Records
This assumes that the length of the text lines vary. Since they vary, you can't use math to determine the file position.
To randomly pick a line from a file you will either have to put each line into a container, or put the file offset of the beginning of the line into a container.
After you have your container establish, determine the random name number and use that as an index into the container. If you stored the file offsets, position the file to the offset and read the line. Otherwise, pull the text from the container.
Which container should be used? It depends. Storing the text is faster but takes up memory (you are essentially storing the file into memory). Storing the file positions takes up less room but you will end up reading each line twice (once to find the position, second to fetch the data).
Augmentations to these algorithms is to memory-map the file, which is an exercise for the reader.
Edit 1: Example
include <iostream>
#include <fstream>
#include <vector>
#include <string>
using std::string;
using std::vector;
using std::fstream;
// Create a container for the file positions.
std::vector< std::streampos > file_positions;
// Create a container for the text lines
std::vector< std::string > text_lines;
// Load both containers.
// The number of lines is the size of either vector.
void
Load_Containers(std::ifstream& inp)
{
std::string text_line;
std::streampos file_pos;
file_pos = inp.tellg();
while (!std::getline(inp, text_line)
{
file_positions.push_back(file_pos);
file_pos = inp.tellg();
text_lines.push_back(text_line);
}
}

Initializing array of objects with data from text file

I’m getting system error when I try to compile the code below on Visual C++ 2008 Express. What I’m trying to do is to initialize array of objects with data read from file. I think there is something wrong inside the while loop, because when I initialize these objects manually without the while loop it seems to work. Here is the code and text file:
#include <iostream>
#include <string>
#include "Book.h"
using namespace std;
int main()
{
const int arraySize = 3;
int indexOfArray = 0;
Book bookList[arraySize];
double tempPrice;//temporary stores price
string tempStr;//temporary stores author, title
fstream fileIn( "books.txt" );
while ( !fileIn.eof( ))
{
getline(fileIn,tempStr);
bookList[indexOfArray].setAuthor(tempStr);
getline(fileIn,tempStr);
bookList[indexOfArray].setTitle(tempStr);
fileIn >> tempPrice;
bookList[indexOfArray].setPrice(tempPrice);
if ( indexOfArray < arraySize ) //shifting array index while not exceeding array size
indexOfArray++;
}
fileIn.close();
return 0;
}
and the text file:
Author1
Book1
23.99
Author2
Book2
10.99
Autho3
Book3
14.56
It looks like you are trying to write to bookList[3] in the loop. You will loop through three times filling your array incrementing indexOfArray each time. This will leave indexOfArray at 3 -- your condition as it is written will allow indexOfAray to be incremented to 3. Then if you have a newline after the "14.56" in your data file you will loop one more time and attempt to pass an empty string to bookList[indexOfArray].setAuthor() leading to a segfault since indexOfArray is past the end of the array.
I would suggest ditching the hard-coded array and using a std::vector instead. At the start of each loop just use push_back() to add a new book to the end of the vector and then use back() to access the new element in the array.
There's another run-time error in your code: You don't read an entire line with the call to fileIn >> tempPrice;. The next call to getline() will read to the end of the line, so you'll get an empty string when you're expecting an author.
You're then off by one line in your text file, and you try to convert a title into a double. That make the fstream signal an error, and after that, you're in trouble.
Brett's right, a vector with push_back is a better solution here.
Brett also correctly pointed out that you could run into errors if your file has extra lines. You can fix that by checking if you successfully read from the file:
if(fileIn >> tempPrice)
{
bookList[indexOfArray].setPrice(tempPrice);
}
else
{
break;
}
if(!getline(fileIn,tempStr))
{
break;
}
The key must be in the contents of
#include "Book.h"
I copy-pasted your code, and replaced the #include with my assumption of what class Book might look like:
class Book
{
std::string auth;
std::string title;
double price;
public:
void setAuthor(std::string& str)
{
auth = str;
}
void setTitle(std::string& t)
{
title = t;
}
void setPrice(double d)
{
d = price;
}
};
and it compiled. Perhaps you could share your Book.h, or look there for any problems? Start with some simple definition from Book (like above) and begin readding code until you've found the lines that cause the problem. Its a crude method of figuring out the issue, but sometimes its the most direct way.