Accessing local variables from void functions - c++

I have two functions that read files and initialize variables containing data parsed from the files read.
These variables include several vectors, counters (line counts) and a few singular variables (string and ints).
The problem I am having is that these variables all need to be accessed in later functions, and the idea is to avoid global variables. Since the functions are void, they cannot return variables, and I have found (unlike my normal language of Python) returning multiple variables is difficult.
What is a better way to go about this?
The vectors in each of the read*() functions need to be accessed in a new function I am building. But I also need the num* variables, and the recipe & serving variables.
EDIT: My code currently
#include <string>
#include <vector>
#include <fstream>
#include <sstream>
#include <iostream>
using namespace std;
void readNutrients(string input_file) {
ifstream in(input_file.c_str());
string line;
vector<string> nName, nUnits;
vector<double> nAmount, nCalories;
string name, units;
double amount, calories;
int numNut = 0;
while (getline(in, line)) {
numNut++;
int pos = line.find(';');
name = line.substr(0, pos);
nName.push_back(name);
line = line.substr(pos + 1);
istringstream iss(line);
iss >> amount >> units >> calories;
nAmount.push_back(amount);
nUnits.push_back(units);
nCalories.push_back(calories);
}
}
void readRecipe(string input_file) {
ifstream in(input_file.c_str());
string line;
string recipe;
vector<string> rName, rUnits;
vector<double> rAmount;
string name, units;
double amount;
double servings;
int numIng = 0;
while (getline(in, line)) {
numIng++;
if (numIng == 1) {
int pos = line.find('\n');
recipe = line.substr(0, pos);
}
else if (numIng == 2) {
istringstream iss(line);
iss >> servings;
}
else {
istringstream iss(line);
iss >> amount >> units >> ws;
rAmount.push_back(amount);
rUnits.push_back(units);
getline(iss, name);
rName.push_back(name);
}
}
}
void readFiles(string nutrientFile, string recipeFile) {
readNutrients(nutrientFile);
readRecipe(recipeFile);
}
int main(int argc, char** argv) {
readFiles(argv[1], argv[2]);
return 0;
}

Since you included your code, I have a better idea of what's going on.
What you need is to create a structure that can hold the result of your parsing. Since your function is not returning anything, it's only logical that you won't have access to it's result.
I think your intent here is to have a list of nutrients read from a file, and read every nutrients from that file and fill up the list in your program.
The problem is that your program has no idea of what makes a nutrient a nutrient. You should teach him that by declaring what makes a nutrient a nutrient:
struct Nutrient {
std::string name, unit;
double amount, calories;
};
Then, instead of creating a bunch of lists of values, you should create a list of nutrients.
std::vector<Nutrient> readNutrients(std::string input_file) {
// Here declare your vector:
std::vector<Nutrient> nutrients;
// declare line, calories, name...
while (std::getline(in, line)) {
// fill your variables name calories etc...
// create a nutrient
Nutrient n;
// fill the nutrient with values from the parsing.
n.name = name;
n.unit = units;
n.amount = amount;
n.calories = calories;
// add the nutrient to the list.
nutrients.push_back(n);
}
// return a filled list of nutrient.
return nutrients;
}
By the way, you don't need the num* variables, since nutrients.size() will return you the number of nutrients in the list.
That solution goes the same with recipes: Create a type to add the concept of a recipe in your program, and use that type.
Please note that this code is not optimal, std::move from C++11 should will grant you enormous speed up.

I don't understand your case clearly. But because you can't get result as return values of void function, it may get results by output arguments using pointers or refrence types.
for example:
void _read(const char* file, vector<string>& r_list, int* pState)
{
// do parsing file
// do outputs
*pState = (your_number);
r_list.push_back("your string");
}
Hope this is useful for you.

Related

Remove repeated code in function definition

this is my first question, so I may miss the "correct structure".
Anyway, I have a header file, with a function. This function (void readFile()) is defined in a cpp file. Within this definition I have code which repeats itself a lot.
If it was in main, I would simply declare a new function, define the repeatable in it, and then call everytime the function. But since it's in a non-main cpp file, I am having issues with this process.
Basically, what my function does, is read through a file char by char, and saves the data to different objects, based on the text.
My code looks like:
source.open("bookings.txt", std::ios::in);
char c;
source.get(c);
while (c != '|'){
CurrentID.push_back(c);
source.get(c);
}
object.setID(CurrentID)
This code repeats itself, replacing only the line of "object.setID". I tried declaring function "search(std::ifstream x, char y, std::string z);" with definition
void Search(std::ifstream x, char y, std::string z){
x.get(y); // next after |
while (y != '|'){
z.push_back(y);
x.get(y);
}
}
But if I try to call this function within my "void readFile()" definition, like this:
// First block as a repeatable
source.get(c);
while (c != '|'){
CurrentID.push_back(c);
source.get(c);
}
object->setID(CurrentID)
CurrentID.clear();
// second block as a function, with repeatable code commented out
void Search(std::ifstream quelle, char c, std::string &CurrentID);
/* source.get(c);
while (c != '|'){
CurrentID.push_back(c);
source.get(c);
}*/
object->setPrice(stof (CurrentID));
CurrentID.clear();
It jumps from "CurrentID.clear()" in first block, directly to "object->setPrice" in second block, ignoring the existence of the void Search function.
Any proposition how to make the function work, or maybe other way, to remove repeated code?
I don't know if this will exactly answer your question. If not, please post your entire code, especially the readFile function.
Let's say you want a readFile function to:
parse an input stream, and
fill the fields ID (string) and price (float) of a list of object structs,
the values in the stream being separated by a | character, and,
using a second function readToken for the repeated code (i.e., read from the input stream until the separator is found and return a string).
The code below does that. Notice:
you define readFile and readToken as separate functions, and
both change the state of the input stream.
[Demo]
#include <iostream>
#include <sstream>
#include <string> // stof
#include <vector>
struct object {
std::string id;
float price;
};
std::string readToken(std::istringstream& iss) {
std::string ret{};
char c{};
while (true) {
iss >> c;
if (not iss.eof() and c != '|') {
ret.push_back(c);
} else {
return ret;
}
}
}
std::vector<object> readFile(std::istringstream& iss) {
std::vector<object> ret{};
while (not iss.eof()) {
auto id = readToken(iss);
auto price = std::stof(readToken(iss));
ret.emplace_back(id, price);
}
return ret;
}
int main() {
std::istringstream input{"ID1|25.5|ID2|3.14"};
auto objects = readFile(input);
for (const auto& o : objects) {
std::cout << o.id << ", " << o.price << "\n";
}
}
// Outputs:
//
// ID1, 25.5
// ID2, 3.14
A simpler way to do this would be to have std::getline read tokens:
[Demo]
std::vector<object> readFile(std::istringstream& iss) {
std::vector<object> ret{};
while (not iss.eof()) {
std::string id{};
std::getline(iss, id, '|');
std::string price_str{};
std::getline(iss, price_str, '|');
auto price = std::stof(price_str);
ret.emplace_back(id, price);
}
return ret;
}

My program is running into "Process returned -1073741819 (0xC0000005)" while attempting to append a data set into a class object

I am currently trying to append a set of data that is seperated by a comma like this:
lastname, firstname, id-number
lastname2, firstname2, id-number2
etc...
into a class object that looks like this:
class Customers{
public:
string lastname;
string firstname;
string id;
public:
void setLN (string ln) {lastname = ln;}
void setFN (string fn) {firstname = fn;}
void setID (string ident) {id = ident;}
};
template <class T>
class hashTable{
private:
node<T> *harray[10], *tarray[10];
public:
void hashBrown(){
fstream inputFile;
int totalNum = 0;
int ln = 0;
int fn = 0;
int idn = 0;
string line;
inputFile.open("Customer.csv", ios::in|ios::binary);
if(inputFile.is_open()){
while(getline(inputFile, line)){
totalNum++;
Customers obj[totalNum];
istringstream iss(line);
string token;
getline(iss, token, ',');
cout<<token<<" ";
obj[totalNum].setLN(token);
getline(iss, token, ',');
cout<<token<<" ";
obj[totalNum].setFN(token);
getline(iss, token);
cout<<token<<"\n";
obj[totalNum].setID(token);
}
cout << totalNum;
}
}
};
however the program then crashes on the third token of the first line, and gives me the following error.
Process returned -1073741819 (0xC0000005) execution time : 1.544 s
This all works fine when I comment out
obj[totalNum].setLN(token);
obj[totalNum].setFN(token);
obj[totalNum].setID(token);
these three lines, but I don't suspect that is the issue because when I add
cout<<obj[totalNum].lastname;
cout<<obj[totalNum].firstname;
cout<<obj[totalNum].id;
it prints out the proper values but crashes immediately following that. I've been on this for an hour and I know I'm missing something, but I cant exactly figure out where and what.
There are several issues that look suspicious in the program.
You are creating, for example, a local variable of type "array of Customer" within a loop body; it will be initialized again and again with every iteration of the loop, and it will get invalid / go out of scope once the loop has finished.
Anyway, the following statement will yield undefined behaviour:
Customers obj[totalNum];
...
obj[totalNum].setLN(token);
This is because - regardless what the value of totalNum is - you will access an element one behind the size of the array. Note that when you crete an array of size 10, then the range of valid indizes is [0..9] not [1..10].
To overcome this, you'd write either
Customers obj[totalNum+1];
...
obj[totalNum].setLN(token);
or
Customers obj[totalNum];
...
obj[totalNum-1].setLN(token); // provided that totalNum is always >= 1
But - as mentioned in the beginning - there seam to be other things to think over as well.
BTW: Customers obj[totalNum]; defines a variable length array, which is not supported by standard C++ (though some compilers support it).
thanks guys!
template <class T>
class hashTable{
private:
node<T> *harray[10], *tarray[10];
public:
void hashBrown(){
fstream inputFile;
int totalNum = 0;
int currentNum = 0;
int fn = 0;
int idn = 0;
string line;
inputFile.open("Customer.csv", ios::in|ios::binary);
if(inputFile.is_open()){
while(getline(inputFile, line)){
totalNum++;
}
}
inputFile.close();
Customers obj[totalNum];
inputFile.open("Customer.csv", ios::in|ios::binary);
if(inputFile.is_open()){
while(getline(inputFile, line)){
istringstream iss(line);
string token;
getline(iss, token, ',');
cout<<token;
obj[currentNum].setLN(token);
cout<<obj[currentNum].lastname;
getline(iss, token, ',');
obj[currentNum].setFN(token);
cout<<obj[currentNum].firstname;
getline(iss, token, ',');
obj[currentNum].setID(token);
cout<<obj[currentNum].id<<"\n";
currentNum++;
}
}
}
};
probably really inefficient, but it solves the problem

How to read pieces of string into a class array C++

I have an array of dvd from a Video class I created
Video dvd[10];
each video has the property,
class Video {
string _title;
string _genre;
int _available;
int _holds;
public:
Video(string title, string genre, int available, int holds);
Video();
void print();
void read(istream & is, Video dvd);
int holds();
void restock(int num);
string getTitle();
~Video();
};
I'm trying to fill up this array with data from my text file where each info such as the title and genre is separated by a comma
Legend of the seeker, Fantasy/Adventure, 3, 2
Mindy Project, Comedy, 10, 3
Orange is the new black, Drama/Comedy, 10, 9
I've tried using getline(in, line, ',') but my brain halts when its time to insert each line into the dvd array.
I also created a read method to read each word separated by a whitespace but I figured thats not what I really want.
I also tried to read a line with getline, store the line in a string and split it from there but I get confused along the line.
**I can get the strings I need from each line, my confusion is in how to insert it into my class array in the while loop especially when I can only read one word at a time.
I need help on what approach I should follow to tackle this problem.
**My code
#include <iostream>
#include <fstream>
#include <cassert>
#include <vector>
#define MAX 10
using namespace std;
class Video {
string _title;
string _genre;
int _available;
int _holds;
public:
Video(string title, string genre, int available, int holds);
Video();
void print();
void read(istream & is, Video dvd);
int holds();
void restock(int num);
string getTitle();
~Video();
};
Video::Video(string title, string genre, int available, int holds){
_title = title;
_genre = genre;
_available = available;
_holds = holds;
}
void Video::read (istream & is, Video dvd)
{
is >> _title >> _genre >> _available>>_holds;
dvd = Video(_title,_genre,_available,_holds);
}
int Video::holds(){
return _holds;
}
void Video::restock(int num){
_available += 5;
}
string Video::getTitle(){
return _title;
}
Video::Video(){
}
void Video::print(){
cout<<"Video title: " <<_title<<"\n"<<
"Genre: "<<_genre<<"\n"<<
"Available: " <<_available<<"\n"<<
"Holds: " <<_holds<<endl;
}
Video::~Video(){
cout<<"DESTRUCTOR ACTIVATED"<<endl;
}
int main(int params, char **argv){
string line;
int index = 0;
vector<string> tokens;
//Video dvd = Video("23 Jump Street", "comedy", 10, 3);
//dvd.print();
Video dvd[MAX];
dvd[0].holds();
ifstream in("input.txt");
/*while (getline(in, line, ',')) {
tokens.push_back(line);
}
for (int i = 0; i < 40; ++i)
{
cout<<tokens[i]<<endl;
}*/
if(!in.fail()){
while (getline(in, line)) {
dvd[index].read(in, dvd[index]);
/*cout<<line<<endl;
token = line;
while (getline(line, token, ',')){
}
cout<<"LINE CUT#####"<<endl;
cout<<line<<endl;
cout<<"TOKEN CUT#####"<<endl;*/
//dvd[index] =
index++;
}
}else{
cout<<"Invalid file"<<endl;
}
for (int i = 0; i < MAX; ++i)
{
dvd[i].print();
}
}
First, I would change the Video::read function into an overload of operator >>. This will allow the Video class to be used as simply as any other type when an input stream is being used.
Also, the way you implemented read as a non-static member function returning a void is not intuitive and very clunky to use. How would you write the loop, and at the same time detect that you've reached the end of file (imagine if there are only 3 items to read -- how would you know to not try to read a fourth item)? The better, intuitive, and frankly, de-facto way to do this in C++ is to overload the >> operator.
(At the end, I show how to write a read function that uses the overloaded >>)
class Video
{
//...
public:
friend std::istream& operator >> (std::istream& is, Video& vid);
//..
};
I won't go over why this should be a friend function, as that can be easily researched here on how to overload >>.
So we need to implement this function. Here is an implementation that reads in a single line, and copies the information to the passed-in vid:
std::istream& operator >> (std::istream& is, Video& vid)
{
std::string line;
std::string theTitle, theGenre, theAvail, theHolds;
// First, we read the entire line
if (std::getline(is, line))
{
// Now we copy the line into a string stream and break
// down the individual items
std::istringstream iss(line);
// first item is the title, genre, available, and holds
std::getline(iss, theTitle, ',');
std::getline(iss, theGenre, ',');
std::getline(iss, theAvail, ',');
std::getline(iss, theHolds, ',');
// now we can create a Video and copy it to vid
vid = Video(theTitle, theGenre,
std::stoi(theAvail), // need to change to integer
std::stoi(theHolds)); // same here
}
return is; // return the input stream
}
Note how vid is a reference parameter, not passed by value. Your read function, if you were to keep it, would need to make the same change.
What we did above is that we read the entire line in first using the "outer" call to std::getline. Once we have the line as a string, we break down that string by using an std::istringstream and delimiting each item on the comma using an "inner" set of getline calls that works on the istringstream. Then we simply create a temporary Video from the information we retrieved from the istringstream and copy it to vid.
Here is a main function that now reads into a maximum of 10 items:
int main()
{
Video dvd[10];
int i = 0;
while (i < 10 && std::cin >> dvd[i])
{
dvd[i].print();
++i;
}
}
So if you look at the loop, all we did is 1) make sure we don't go over 10 items, and 2) just use cin >> dvd[i], which looks just like your everyday usage of >> when inputting an item. This is the magic of the overloaded >> for Video.
Here is a live example, using your data.
If you plan to keep the read function, then it would be easier if you changed the return type to bool that returns true if the item was read or false otherwise, and just calls the operator >>.
Here is an example:
bool Video::read(std::istream & is, Video& dvd)
{
if (is.good())
{
is >> dvd;
return true;
}
return false;
}
And here is the main function:
int main()
{
Video dvd[10];
int i = 0;
while (i < 10 && dvd[i].read(std::cin, dvd[i]))
{
dvd[i].print();
++i;
}
}
Live Example #2
However, I still say that the making of Video::read a non-static member makes the code in main clunky.

Reading an Input File And Store The Data Into an Array (beginner)!

The Input file:
1 4 red
2 0 blue
3 1 white
4 2 green
5 2 black
what I want to do is take every row and store it into 2D array.
for example:
array[0][0] = 1
array[0][1] = 4
array[0][2] = red
array[1][0] = 2
array[1][1] = 0
array[1][2] = blue
etc..
code Iam working on it:
#include <iostream>
#include <fstream>
#include <string>
#include <sstream>
#include <vector>
using namespace std;
int convert_str_to_int(const string& str) {
int val;
stringstream ss;
ss << str;
ss >> val;
return val;
}
string getid(string str){
istringstream iss(str);
string pid;
iss >> pid;
return pid;
}
string getnumberofcolors(string str){
istringstream iss(str);
string pid,c;
iss >> pid>>c;
return c;
}
int main() {
string lineinfile ;
vector<string> lines;
ifstream infile("myinputfile.txt");
if ( infile ) {
while ( getline( infile , lineinfile ) ) {
lines.push_back(lineinfile);
}
}
//first line - number of items
int numofitems = convert_str_to_int(lines[0]);
//lopps items info
string ar[numofitems ][3];
int i = 1;
while(i<=numofitems ){
ar[i][0] = getid(lines[i]);
i++;
}
while(i<=numofitems ){
ar[i][1] = getarrivel(lines[i]);
i++;
}
infile.close( ) ;
return 0 ;
}
when I add the second while loop my program stopped working for some reason!
is there any other way to to this or a solution to my program to fix it.
It's better to show you how to do it much better:
#include <fstream>
#include <string>
#include <vector>
using namespace std;
int main() {
ifstream infile("myinputfile.txt"); // Streams skip spaces and line breaks
//first line - number of items
size_t numofitems;
infile >> numofitems;
//lopps items info
vector<pair<int, pair<int, string>> ar(numofitems); // Or use std::tuple
for(size_t i = 0; i < numofitems; ++i){
infile >> ar[i].first >> ar[i].second.first >> ar[i].second.second;
}
// infile.close( ) ; // Not needed -- closed automatically
return 0 ;
}
You are probably solving some kind of simple algorithmic task. Take a look at std::pair and std::tuple, which are useful not only as container for two elements, but because of their natural comparison operators.
The answer given is indeed a much better solution than your's. I figured i should point out some of your design flaws and give some tips too improve it.
You redefined a function that already exists in the standard, which is
std::stoi() to convert a string to an integer. Remember, if a function
exists already, it's OK to reuse it, don't think you have to reinvent what's
already been invented. If you're not sure search your favorite c++ reference guide.
The solution stores the data "as is" while you store it as a full string. This doesn't really make sense. You know what the data is beforehand, use that to your advantage. Plus, when you store a line of data like that it must be parsed, converted, and then constructed before it can be used in any way, whereas in the solution the data is constructed once and only once.
Because the format of the data is known beforehand an even better way to load the information is by defining a structure, along with input/output operators. This would look something like this:
struct MyData
{
int num1;
int num2;
std::string color;
friend std::ostream& operator << (std::ostream& os, const MyData& d);
friend std::istream& operator >> (std::istream& os, const MyData& d);
};
Then you could simply do something like this:
...
MyData tmp;
outfile << tmp;
vData.push_back(tmp);
...
Their is no question of intent, we are obviously reading a data type from a stream and storing it in a container. If anything, it's clearer as to what you are doing than either your original solution or the provided one.

How to parse the following text file into class?

I have the following class
class Film {
Person authors[5]; //This will actually include only the director
string title;
string producer;
int n_authors;
int year;
int running_time;
Person actors[5];
int n_actors;
}
And the following file format (don't ask me why I use this, I MUST use this format)
Stanley
Kubrick
#
2001: A Space Odissey
*
1968
161
Keir
Dullea
Gary
Lockwood
#
The # indicates the end of a list (in this case a 'Person' class), while the * a missing field (in this case the producer, btw the producer field must be filled with an * in the class).
The class Person consists of Name and Surname and has an overloaded operator >> that calls:
void load(ifstream& in) {
getline(in,name);
getline(in,surname);
}
What's the best method to parse this file structure? I can't use regular expressions or anything more advanced than ifstream. My concern is on how (and where in the code) to detect the end of file and the end of a list of people.
The standard line reading idiom:
#include <fstream> // for std::ifstream
#include <sstream> // for std::istringstream
#include <string> // for std::string and std::getline
int main()
{
std::ifstream infile("thefile.txt");
std::string line;
while (std::getline(infile, line))
{
// process line
}
}
Where it says "process line" you should add some logic that tracks the current state of the parser.
For your simple application you could proceed in bits, reading lists and tokens as specified by the format. For instance:
std::vector<std::string> read_list(std::istream & in)
{
std::string line;
std::vector<std::string> result;
while (std::getline(in, line))
{
if (line == "#") { return result; }
result.push_back(std::move(line));
}
throw std::runtime_error("Unterminated list");
}
Now you can say:
std::string title, producer, token3, token4, token5, token6;
std::vector<std::string> authors = read_list(infile);
if (!(std::getline(infile, title) &&
std::getline(infile, producer) &&
std::getline(infile, token3) &&
std::getline(infile, token4) &&
std::getline(infile, token5) ) )
{
throw std::runtime_error("Invalid file format");
}
std::vector<std::string> actors = read_list(infile);
You can use std::stoi to convert tokens 3 – 5 to integers:
int year = std::stoi(token4);
int runtime = std::stoi(token5);
Note that the n_authors and n_actors variables are redundant, since you have self-terminated lists already. You can or should use the variables as an integrity check if you like.