Making an array of pointers to structs or objects in C++ - c++

So I'm basically just trying to take in some file input, and then take that data and put it into several structs. The only issue I'm having is with the naming of the pointers to the structs. The struct's themselves are supposed to represent students and I wanted to set each pointer as one of their names rather than an arbitrary variable. I tried to do this in a way that I'm assuming is syntactically wrong for it didn't work. In the code below, I increment the for loop with the temp array because each 4th position is a new student. Any ideas on how I could go about this?
#include<iostream>
#include<iomanip>
#include"student.h"
#include"creditcard.h"
#include<fstream>
using namespace std;
int main ()
{
string creditcards[20];
int i;
int x;
int amount;
string temp[20];
ifstream infile;
string filename;
int count;
int numstudents;
string newstring="";
string pointers[20];
cout<<"enter the file name of which you've stored your"<<endl
<<"credit card infomation"<<endl;
getline(cin,filename,'\n');
infile.open(filename.c_str());
count=0;
getline(infile,temp[count],'\n');
while(! infile.eof())
{
count++;
getline(infile,temp[count],'\n');
numstudents= (count/4);
if(numstudents < 1 || count%4 != 0)
{
cout<<"incorrect data file"<<endl;
}
}
cout<<numstudents<<endl;
for(i=0,x=0; i<numstudents;i++,x+4)
{
student *temp[x];
temp[x] = new student;
pointers[i] = temp[x];
}
for(i=0;i<numstudents;i+4)
{
cout<<temp[i]<<endl;
}
return 0;
}

Ok, let's start from the top.
Your code was (before I reformatted it) a mess. Messy code is harder to read and more likely to have bugs.
You have 3 arrays, each containing 20 strings. Why do you need so many?
One of them is named temp; having to use that as a variable name is a good indicator that you're mishandling data somewhere.
You're declaring int count relatively early on, then initializing it to 0 later. While not necessarily a bad thing, that's not the best method (do both at once, when needed).
You can declare local variables more than one in a line, but you don't need to declare them all at the top of the function. That's not necessary in C++.
int main ()
{
string creditcards[20];
int i = 0, x = 0, amount = 0;
(legal, but might not be needed)
It's typically better to declare and initialize a variable at the same time, just before you need it:
int count = 0;
getline(infile, temp[count], '\n');
I remember seeing that reading until you hit eof isn't recommended, although I'm not entirely sure on that. You may want to change this:
while ( !infile.eof() )
{
Now, the first actual mistake I see here is that you read a line, increment count, then read another line before acting. Is that intentional, and if so, why is it necessary? Doing the getline and increment inside the loop would be more readable and potentially more reliable.
count++;
getline(infile, temp[count], '\n');
This line is a bug, I think:
for(i=0,x=0; i<numstudents;i++,x+4)
The last section does i++, x+4. It does not change x.
The next loop after that handles i in the same way this loop uses x, so you can probably combine those two.
Now, on top of all that, massive temp arrays are not the solution to this problem (or any other that I can think of).
To store this kind of data, you'll want to look into a std::map<std::string, student*> or std::vector<student*>. The vector will allow you to push the new student struct to the back when necessary, and the map will allow you to key them based on name and retrieve that later, something like so:
typdef map<string, student*> studentmap;
studentmap students;
studentmap::iterator iter = students.find("Bob");
if ( iter != students.end() )
{
student * bob = iter->second;
// Work with data
}
It's a much better way of handling this, and will take a lot of the guess work out of what you're doing now.

If you want to be able to reference the students by name, consider using a map<string, student> or map<string, student*>.
This will allow you to refer to individual students via students["Jack"] or students["Jill"].

Related

Why does it show terminate called after throwing an instance of 'std::logic_error' what(): basic_string::_S_construct null not valid

At first, I thought there is an error when I was using the while loop so I've tried using for loop but it still displays the same error. I have tried to look up the reason why this error happened but I am still unable to figure out which line in these codes caused the error.
int main()
{
ifstream patientData;
string name;
int count = 0;
patientData.open("PatientList.txt");
string patient_name[NUM_PATIENTS] = {0};
double ward[NUM_PATIENTS] = {0};
double surgery[NUM_PATIENTS] = {0};
double medication[NUM_PATIENTS] = {0};
double service[NUM_PATIENTS] = {0};
double totalCharge[NUM_PATIENTS] = {0};
while(count < NUM_PATIENTS)
{
getline(patientData, patient_name[count], '\t');
patientData >> patient_name[count];
patientData >> surgery[count];
patientData >> medication[count];
patientData >> service[count];
patientData >> totalCharge[count];
count++;
}
patientData.close();
return 0;
}
I apologize in advance if this sounds like a stupid question.
Currently, I'm following the guide from a textbook named "Starting Out with C++ From Control Structures through Objects Ninth Edition by Tony Gaddis"
Can someone help me?
In short: You are trying to initialize your arrays with 0, which is a nullptr. So you are actually trying to initialize your first string in the array with a nullptr.
Let's examine
string patient_name[NUM_PATIENTS] = { 0 };
It defines an old style C array of NUM_PATIENT strings.
It initializes this array with { 0 }.
... which means, the first string get's initalized with 0, the rest of the strings get default-initialized.
... which means, the first string in the array is initialized thus: string(0), which is similar to string(nullptr) - which probably leads to your exception string::_S_construct null not valid
Actually all your attempts at initalizing are wrong.
You don't need to write = {0} for the patient_name[NUM_PATIENT] array at all, because the strings in this array are "default-initialized". They know how initialize themselves to an empty string.
string patient_name[NUM_PATIENT]; is enough.
But your other arrays don't get properly initialized at all. = {0} will initialize the first element of your vector. The remaining elements are "default" initialized (for classes) or not initialized at all (for doubles).
Don't use patient_name[NUM_PATIENT] at all. Get a better textbook. This is an old-style C array. Use std::vector (best) or std::array (better).
Much better would be this:
std::vector<string> patient_names;
std::vector<double> medication...
for (size_t i=0; i<NUM_PATIENT; ++i) {
string next_patient;
getline(patients, next_patient);
patient_names.emplace_back(next_patient);
...
or, to stick close to the text book:
std::vector<string> patient_name(NUM_PATIENTS);
...
while (count<NUM_PATIENTS) {
getline(patients, patient_name[count]);

converting string vectors to int

I have to make a program that reads information for a student from a file, and I made a couple of vectors to keep all that information. But then I need to sum up the absences of all the students, so I need to convert them to integer, however when I try to the program runs, but crashes immediately when it reaches the atoi part.
while(!read.eof()) {
if(i==4){
i=0;
}
read>>b;
if(i==0){ names.push_back(b); }
if(i==1){ last_name.push_back(b); }
if(i==2){ absences.push_back(b); }
if(i==3){ unatoned.push_back(b); }
i++;
}
int a = atoi(absences[0].c_str());
If absences remains empty then the behaviour of absences[0] is undefined.
Using absences.at(0) when absences is empty forces the compiler to throw an exception so is easier to work with.
Either way, for the number of absences, use simply
auto a = absences.size();
You should change you absences vector to be a vector of int:
std::vector<int> abscences;
// rest of the code...
The read >> var instruction will take care of the conversion.
Of course, the >> operator will not write into the integer if it's invalid.

Key Value Pair implementation C

I have a .txt file that stores student names along with two of their best marks. If a student for some reason, i.e. dropping out of course, fails to pass a course, then no marks are recorded.
My file looks like this
Samuel= 90.5, 95.9
Bill= 25.2, 45.3
Tim
Anthony= 99.9, 12.5
Mark
Rob
Basically, Tim, Mark and Rob failed the course and hence their marks are not stored. Also to differentiate between a failed mark and a pass mark, I have used the = symbol. Basically, I want to store all the names into memory alongside their associated values.
This is my implementation, however it is flawed in the sense that I have declared a double *marks[2] array to store all six marks, when clearly it will only store 3. I am having trouble storing the values into the double array.
This is my code...
istream& operator>> (istream& is, Students& student)
{
student.names = new char*[6];
for (int i=0; i<10; i++)
{
student.names[i] = new char[256];
student.marks[i] = new double[2];
is.getline(student.names[i], sizeof(student.names));
for (int j=0; j < 256; j++)
{
if((student.names[i][j] == '='))
{
int newPos = j + 1;
for (int k = newPos; k < 256; k++)
{
student.names[i][k - newPos] = student.names[k];
}
}
}
}
}
How can I go about storing the values of the students with the valid marks? Please, no use of vectors or stringstreams, just pure C/C++ char arrays
You have a few options, you could use a struct like so
struct Record {
std::string name;
double marks[2];
};
And then stick that into something like std::vector<Record> or an array of them like
Records *r = new Records[1000];
You could also keep three different arrays (either automatically allocated or dynamically allocated or even std::vector), one to hold the name, two to hold the marks.
In each case you would just indicate a fail by some thing like the marks being zero.
Also, you can use
std::string name;
double first, second;
std::cin >> name;
if (name[name.size() - 1] == '=')
std::cin >> first >> second;
And this will parse the input like you want it to for a single line. Once you've done that you can wrap the whole thing in a loop while sticking the values you get into some sort of data structure that I already described.
Hope that gives you a few ideas on where to go!
Here's a strategy:
First of all you need to implement a struct to hold the key-value pair, I suggest the following:
struct Student {
char name[30];
double marks[2];
};
Note that you can give the dimension of the char array inside the struct if you know that the length will never be higher. (which is given here)
Now what you need is to know how many lines are in your ifstream, you could make a loop of is.getline() calls to get there. (don't forget to call is.clear() and is.seekg(0) when finished, to be at the beginning for the real loop)
When you know how many lines are in your ifstream you can use dynamically cast the Array of your struct with the actual length of your file:
Student * students = new Student[lineCount]; // line count of is
As you can see, there's no need to have a std::vector to hold the values. Consider that the getline() loop may be an overkill just to get the line count, alternatively you could give a length to Students at compile-time by making an array with a length that will never be overpassed.
(e.g. Student students[128];)
Now you need to parse the lines, i'd suggest you make a loop like the following (line by line):
// int parseLine ( char* line, char* name, double* marks ) { ...
bool hasMarks=false;
int iLine=0; // Line pos iterator
int iName=0; // Name pos iterator
char mk1Str[4]; // String buffer, Mark 1
char mk2Str[4]; // String buffer, Mark 2
while(line[iLine]!='\0')
{
if(line[iLine]=='=')
{
hasMarks=true;
name[iLine]='\0';
for(int iMark=0;iMark<4;iMark++)
{
mk1Str[iMark]=line[iLine+iMark+2];
mk2Str[iMark]=line[iLine+iMark+8];
// ^^ You can harcode the offsets (2,8) since they don't change
}
break;
}
name[iName++]=line[iLine];
iLine++;
}
Now what you need is to parse the marks to double values, for this you could use the atof() function that works with char*. The bool hasMarks helps you know if a student has defined marks, if not, you could define dummy values like -1 for the mark fields of your struct.
I think this works quite well for your case...

C++ container splitting

I have an unknown number of int variables in a text file, all i know is that the number of variables will be a multiple of 6.
I want to read these files in to a container, and split them down into smaller containers, where each container takes 6 values.
For example, if there is 30 variables in the text file, I want 5 containers, each containing 6 of the variables.And they need to be in the order that they are in the file, i.e., the first container holds the first six values.
I have read the files into a list, and vector for the moment, and was wandering which one is more suited. I've read about the split function, but after a look around, I haven't been able to successfully apply it to one of these attempts, mainly because I don't know how many smaller containers I am going to need
This is my code so far:
Vector Method
std::vector<int> Ticket;
std::ifstream fin (username + "Ticket.txt");
while (!fin.eof())
{
fin >> num;
Ticket.push_back(num);
}
fin.close();
Ticket.shrink_to_fit();
List method
std::list<int> Ticket1;
std::ifstream fin (username + "Ticket.txt");
while (!fin.eof())
{
fin >> num;
Ticket1.push_back(num);
}
fin.close();
Alternatively, if I could somehow read 6 values from the file straight into smaller containers, and keep doing this till the end of the file, hence skipping the big container that would be awesome.
You can use a vector of vectors.
std::vector< std::vector<int> > Ticket;
std::vector<int> newVector;
std::ifstream fin (username + "Ticket.txt");
while ( fin >> num )
{
newVector.push_back( num );
// if the vector is full, then insert it and start afresh
if ( newVector.size() == 6 ) {
Ticket.push_back( newVector );
newVector.clear();
}
}
fin.close();
At the end, the Ticket vector shall contain all the required vectors.
To print each vector to console, use two nested loops:
for ( int i = 0; i < Ticket.size(); i++ ) { // outer vector
for ( int j = 0; j < Ticket[i].size(); j++ ) { // inner vectors
std::cout << Ticket[i][j] << " ";
}
std::cout << "\n";
}
If you create a temporary accumulator, and your ultimate destination:
vector<int> temp ;
vector<vector<int>> package ;
Then just fill up temp, until its full, and push onto the end of package:
while ( fin >> num )
{
temp.push_back( num) ;
if ( temp.size() == MyDesiredSize ) { package.push_back( temp) ; temp.clear() ; }
}
When you push_back on package it will make a copy, that's why you can clear temp afterwards.
Do you need the values within the container to be contiguous as they are within a c-array, or do you need to be able to have more control over the memory allocation using the reserve member function of std::vector? If not, then consider deque or list over vector.
http://www.cplusplus.com/reference/deque/deque/
You also use a fixed size array if you have a C++ 11 compiler that supports it.
http://www.cplusplus.com/reference/array/array/
For instance, you could setup a container like this to setup a list of arrays.
std::list<std::array<int, 6>> containersOfSix;
On the other hand, you might be able to use one of the inserter functions if you would prefer both parts of the container to be dynamic.
http://www.cplusplus.com/reference/iterator/istream_iterator/
http://www.cplusplus.com/reference/iterator/insert_iterator/
Consider reading through that material, and toying around with some of those options. That might lead you to search for other threads related to those, which will help you find more specific examples.
i would use the C function Fscanf()
You can use a format and read the 6 numbers at once.
fscanf(FILE,"%i %i %i %i %i %i",&var1,&var2,&var3,&var4,&var5,&var6)
Then you just need to handle the container, creating one before and reading directly to it or by coping the values if they are what you expect.

grabbing data sets from a file with an arbitrary amount of spaces

**No direct answers or code examples please, this is my homework which i need to learn from. I'm looking for help concerning the algorithm i need to develop.
I seem to be having a logic error in coming up with a solution for a portion of my class work, the program involves multiple files, but here is the only relevant portion:
I have a file PlayerStats that holds the stats for a basketball player in:
rebounds
points
assists
uniform #
my initial reaction would be to create a while loop and read these into a temporary struct that holds these values, then create a merge function that merges the values of the temp struct with the inital array of records, simple enough?
struct Baller
{
//other information on baller
int rebounds;
int assists;
int uniform;
int points;
void merge(Baller tmp); //merge the data with the array of records
}
//in my read function..
Baller tmp;
int j = 0;
inFile << tmp.uniform << tmp.assists << tmp.points << tmp.rebounds
while(inFile){
ArrayRecords[j].merge(tmp);
j++;
//read in from infile again
}
The catch:
The file can have an arbitrary number of spaces between the identifiers, and the information can be in any order(leaving out the uniform number, that is always first). e.g.
PlayerStats could be
11 p3 a12 r5 //uniform 11, 3 points 12 assists 5 rebounds
//other info
OR
11 p 3 r 5 a 12 //same exact values
What I've come up with
can't seem to think of an algorithm to grab these values from the file in the correct order, i was thinking of something along these lines:
inFile << tmp.uniform; //uniform is ALWAYS first
getline(inFile,str); //get the remaining line
int i = 0;
while(str[i] == " ") //keep going until i find something that isnt space
i++;
if(str[i] == 'p') //heres where i get stuck, how do i find that number now?
else if(str[i] == 'a')
eles if(str[i] = 'r'
If you're only going to check one letter, you could use a switch statement instead of if / else, that would make it easier to read.
You know where the number starts at that point, (hint: str[i+1]), so depending on what type your str[] is, you can either use atoi if its a char array, or std::stringstream if it's an std::string.
I'm tempted to give you some code, but you said not too. If you do want some, let me know and I'll edit the answer with some code.
Instead of using a 'merge' function, try using an std::vector so you can just push_back your structure instead of doing any 'merging'. Besides, your merge function is basically a copy assignment operator, which is created by the compiler by default (you don't need to create a 'merge' function), you just need to use = to copy the data across. If you wanted to do something special in your 'merge' function, then you should overload the copy assignment operator instead of a 'merge' function. Simples.
Do something like that:
int readNumber () {
while isdigit (nextchar) -> collect in numberstring or directly build number
return that number;
}
lineEater () {
Read line
skip over spaces
uniform=readNumber ();
haveNum=false;
haveWhat=false;
Loop until eol {
skip over spaces
if (isdigit)
number=readNumber ();
skip over spaces
haveNum=true;
else
char=nextChar;
haveWhat=true;
if (haveChar and haveNum) {
switch (char) {
case 'p' : points=number; break;
...
}
haveNum=false;
haveWhat=false;
}
}
or, if you are more ambitous, write a grammar for your input and use lex/yacc.