Creating an array of pointers to struct - c++

I am having trouble of understanding how to create an array of pointers to structures. I tried to look up similar examples and threads in the forum but I still cannot get my code to work! As a result, I believe I have written an ugly piece of code that I do not know where it is wrong and how to fix it.
Here is the code:
#include <iostream>
#include <fstream>
#include <string>
using namespace std;
struct Movie
{
string name;
int numawards;
int nomination;
int year;
};
void *readfile(ifstream &infile, int &n);
int main()
{
ifstream infile;
int n = 0;
infile.open("old_movies.txt");
Movie *oldmovies;
oldmovies = readfile (infile, n);
return 0;
}
//*function documentation//
void *readfile (ifstream &infile, int &n)
{
infile >> n;
Movie *movies;
movies = new Movie[n];
for (int i = 0 ; i < n ; i++)
{
infile >> movies[i]->year >> movies[i]->numawards >> movies[i]->nomination;
infile.ignore();
infile.ignore();
getline(infile, movies[i]->name);
cout << movies[i]->year << " " << movies[i]->numawards << " " << movies[i]->nomination << " " << endl << movies[i]->name<< endl; //the cout here is to test and see if the code works.
}
return movies;
}
The purpose of this code is to read a txt file that contains the movie name, how many awards, how may nominations, and what year it is produced, and then print it out using pointers. Here is what the file looks like:
2
1935 1 3
The Dark Angel
1935 4 6
The Informer
1935 1 8
the first 4 digits represents the year, the second one represents number of awards it has gotten, and the last digit represents the number of times it has been nominated to an award.
Anyway, I am stuck at this part and I am really clueless about what to do here. I just hope that this code is not that bad to a point where there are numerous things to be changed. Any help or advice would be greatly appreciated.

Let's look at what you have here:
Movie *movies;
movies = new Movie[n];
This allocates an array of Movie instances. To allocate an array of pointers dynamically, you need to change this to
Movie** movies;
movies = new Movie*[n];
Now inside the for loop, you need to allocate each Movie instance:
movies[i] = new Movie();
You should also change the readfile() to return a Movie** rather than a void* so that you don't have to use any casts later.
But do you really need an array of pointers? Why not just use an array of structs. This would avoid the extra level of indirection and make your code a little simpler.

Related

Write access violation - **this**

I'm trying to refactor some code to use pointers and am running into a write access violation on my function calls.
I'm making these edits because my homework project requires the usage of the -> member operator as well as constructors and destructors.
One more edit: the input file worked just fine when I was formerly working without pointers, but the moment I added pointers everything broke.
Here's my code:
In main.cpp:
#include "student.h"
int main()
{
/*
TODO:
2. Implement the class such that member pointers can be used to access the members.
3. Implement pointers that point to each of the students' test scores as well as the average test score.
*/
const int numStudents = 15; // Number of students
Student * students = new Student[numStudents]; // Dynamically allocated array of Student objects
Student ** studentsPtr = &students;
// Starting file stream for studentRecords.dat
ifstream student_records("student_records.dat");
// Error checking for file loading
if (!student_records)
{
cerr << "ERROR: The record file could not be opened for reading." << endl;
exit(1);
}
// Load data from file
string current_value;
stringstream newval;
int tempID;
string tempName;
double tempTestScore;
for (int index = 0; index < numStudents; index++)
{
// Store the student ID
getline(student_records, current_value);
newval << current_value;
newval >> tempID;
studentsPtr[index]->setID(tempID);
newval.clear();
// Store the student first name
getline(student_records, current_value);
newval << current_value;
newval >> tempName;
studentsPtr[index]->setFirstName(tempName);
newval.clear();
// Store the student last name
getline(student_records, current_value);
newval << current_value;
newval >> tempName;
studentsPtr[index]->setLastName(tempName);
newval.clear();
// Add each test score.
for (int testScoreIndex = 0; testScoreIndex < numTests; testScoreIndex++)
{
getline(student_records, current_value);
newval << current_value;
newval >> tempTestScore;
studentsPtr[index]->addTestScore(tempTestScore, testScoreIndex);
newval.clear();
}
// Calculate the student's average
students[index].calculateAverage();
}
// Print all data
for (int index = 0; index < numStudents; index++)
{
studentsPtr[index]->printAll();
}
delete[] students; // Free memory pointed to by students array
students = NULL; // Clear the memory.
system("pause");
return 0;
}
In student.h:
#pragma once
#include <iostream>
#include <fstream>
#include <cstdlib>
#include <sstream>
#include <string>
#include <vector>
#include <iomanip>
using namespace std;
const int numTests = 10;
// Student class declaration
class Student
{
private:
// Student ID and name
int id;
string firstName;
string lastName;
// List of student test scores
// vector<double> testScores;
double * testScores = new double[numTests];
// Student average test score
double average;
public:
Student() // Default constructor
{
const int numTests = 10;
id = 0;
firstName = " ";
lastName = " ";
average = 0.0;
}
~Student() // Destructor
{
delete[] testScores;
}
void setID(int); // Set the student ID
void setFirstName(string); // Set the student name
void setLastName(string);
void addTestScore(double, int); // Add a test score to the vector
void calculateAverage(); // Calculate the average of all test scores
void printAll(); // Output all data to the screen for a given student
};
In student.cpp:
#include "student.h"
// setID sets the id value.
void Student::setID(int studentID)
{
id = studentID;
}
// setName sets the name value.
void Student::setFirstName(string studentFirstName)
{
firstName = studentFirstName;
}
void Student::setLastName(string studentLastName)
{
lastName = studentLastName;
}
// addTestScore adds a test score to the vector
void Student::addTestScore(double testScore, int index)
{
testScores[index] = testScore;
// testScores.push_back(testScore);
}
// calculateAverage adds every test score from the vector and divides them by the number of test scores in the list.
void Student::calculateAverage()
{
double totalScores = 0.0;
// for (double index : testScores)
for (int index = 0; index < numTests; index++)
{
totalScores += testScores[index];
}
average = totalScores / numTests;
}
// printAll prints all the data to the screen.
void Student::printAll()
{
cout << "=========================================================\n";
cout << "Student ID:\t" << id << endl;
cout << "Student Name:\t" << firstName << " " << lastName << endl;
cout << setprecision(4) << "Average:\t" << average << endl;
cout << "Test Scores: " << endl;
// Printing the test scores nicely
int scoresPerLine = 5;
for (int i = 0; i < numTests; i++)
{
cout << setprecision(4) << testScores[i] << "\t";
if ((i + 1) % scoresPerLine == 0)
{
cout << endl;
}
}
cout << endl;
cout << "=========================================================\n\n";
}
The error I'm getting is Exception thrown: write access violation. this was 0xCCCCCCCC and it throws the exception at a break point created at
void Student::setFirstName(string studentFirstName) at the line firstName = studentFirstName.
My question is: what exactly is preventing this from working? Am I doing something wrong? I dont' get any errors before I compile everything, so it looks like everything is built ok. I've also tried using a pass-by-reference on that member function, but that's also failing with the same response.
Am I doing something wrong?
Yes, definitely :)
Let's go through it:
Student * students = new Student[numStudents];
... the above allocates a dynamic array of 15 Student objects; so far, so good.
Student ** studentsPtr = &students;
This line is the source of the trouble. You've declared a double-pointer Student ** and initialized it to point to the address of the students pointer. This is legal C++, but note that there is only the standalone students pointer -- in particular, there is not an array of pointers-to-Student in your program anywhere. (There is an array of Student objects but that is not the same thing as an array of pointers-to-Student)
... then a bit later on, comes the actual trouble:
for (int index = 0; index < numStudents; index++)
{
[...]
studentsPtr[index]->setID(tempID); // BOOM!
Here you are trying to use studentsPtr as if it was if it was the base-address for an array of pointers-to-Student, i.e. by offsetting its location by index pointers and dereferencing that location. But it's not really pointing to an array-of-pointers, it is pointing to a single pointer (i.e. it is pointing to the variable students), so whenever index is non-zero, you are invoking undefined behavior and therefore (in your case) you get a crash.
Let's debug it:
Since you didn't provide a complete test case, I changed the number of students to 3 and the number of tests to 0:
student_records.dat
1
Foo
Bar
2
Foo2
Bar2
3
Foo3
Bar3
My crash occurs in setID, but that's okay. this is 0xCCCCCCCC, which is the value given to uninitialized data by MSVC in debug mode. Great, the object pointer is garbage. Where does it come from? We'll go up the call stack to see:
This brings us to the following line in the loop of main that reads the input:
studentsPtr[index]->setID(tempID);
First, let's look at the variables:
We know the object is garbage. We can verify that here. Our object is studentsPtr[index], which is shown with the same uninitialized value. We also see that studentsPtr itself points to the proper first student. Finally, the index variable has the value 1, so we're on the second student.
studentsPtr[1] has a value that MSVC provides for uninitialized memory. Why is it uninitialized? Let's go back to the declaration:
Student *students = new Student[numStudents];
Student **studentsPtr = &students;
studentsPtr is set to be a pointer to a pointer to students. The inner pointer is actually an array of students. The outer pointer, however, is one solitary Student*. When indexing it like studentsPtr[1], we go beyond the single pointer within and trample onward to a nonexistent Student*. Then we try to write to that and the program thankfully blows up early.
The solution is to get rid of the double pointer. All that's needed is a bunch of Students. One pointer is one (unrecommended way to represent an) array:
Student *students = new Student[numStudents];
...
students[index].setID(tempID);
Now since the number of elements is known at compile-time, the recommended type would be std::array (std::array<Student, numStudents> students;), which can be used with the same syntax as the above after its declaration. If the size were not known at compile-time, the recommended type would be std::vector, which also shares the same syntax to access elements.
Technically, you can fulfill the -> requirement using a std::array as well. Simply obtain a pointer to the element and then use the arrow:
(&students[index])->setID(tempID);
More likely, the requirement is still looking for the manual free store memory management that you're doing. It's also easy to fit the arrow into that:
(students + index)->setID(tempID);
If you really, really need the double pointer even though it serves no purpose, remember that your array is the inner pointer, not the outer one:
((*students) + index)->setID(tempID);
If you're thinking the arrow hinders readability in all of these scenarios, you're correct. Perhaps the instructor has something specific in mind where it does not.
What happens when the double pointer is removed?
=========================================================
Student ID: 1
Student Name: Foo Bar
Average: -nan(ind)
Test Scores:
=========================================================
=========================================================
Student ID: 2
Student Name: Foo2 Bar2
Average: -nan(ind)
Test Scores:
=========================================================
=========================================================
Student ID: 3
Student Name: Foo3 Bar3
Average: -nan(ind)
Test Scores:
=========================================================
Success. The average is meaningless because I simplified the input file by changing the number of tests to 0. Long story short, the debugger provides the tools that can get debugging jobs done. Just from the debugger, we reduced the problem to the double pointer pointing to only one thing instead of multiple things. That's a much smaller scope for a problem than the original one.

Having trouble on this assignment where we need to use pointers and arrays to find the average, median, and mode of some numbers in a text file

I'm having some issues when trying to read in the values from a text file. The text file looks like this:
Murray Brandl 3
Christal Delamater 4
Zetta Kinlaw 7
Elia Roy 3
Delmer Bibb 4
Joannie Nevers 4
Roselle Gose 10
Jonathan Basnett 0
Marcel Earwood 12
Marina Newton 2
Magdalen Stephan 3
Deane Leach 5
Mariana Crosley 6
Darby Froman 5
Shonda Kyzer 4
Ilana Netto 4
Candida Magnani 1
Laurena Stiverson 2
Elouise Muir 4
Rene Holiday 2
We need to read these names and values into variables while using pointers and arrays. I am getting some errors such as this:
"Exception thrown: read access violation.
_Pnext was 0xFDFDFE01. occurred"
I don't know what this means or where to look to fix it. Below you can see my attempt so far, but I've only gotten to the averageMovie function because I am unable to read the text file in correctly. If you could help me out or point me in the right direction I would really appreciate it!
#include "stdafx.h"
#include <fstream>
#include <iostream>
#include <string>
#include <sstream>
#include "Chapter 10 Movie Statistics.h"
using namespace std;
void averageMovie(int [], int);
int main()
{
ifstream infile;
infile.open("MovieStatistics.txt");
int numOfStudents = 0;
string first, last, line;
int movies;
int *numMovies;
string *names;
numMovies = new int[numOfStudents];
names = new string[numOfStudents];
if (!infile)
{
cout << "Error opening file";
}
else
{
while (getline(infile, line))
{
numOfStudents++;
istringstream ss(line);
ss >> first >> last >> movies;
}
for (int i = 0; i < numOfStudents; i++)
{
names[i] = first + last;
numMovies[i] = movies;
}
}
cout << "The number of students in the file is: " << numOfStudents << endl << endl;
averageMovie(numMovies, numOfStudents);
return 0;
}
void averageMovie(int array[], int size)
{
int total = 0,
average;
for (int i = 0; i < size; i++)
{
total += array[i];
}
average = total / size;
cout << "The average number of movies watched is: " << average;
}
int numOfStudents = 0;
// [snip]
int *numMovies;
string *names;
// [snip]
numMovies = new int[numOfStudents];
names = new string[numOfStudents];
Both of your arrays have zero elements; thus, every single access to them is broken. Increasing numOfStudents later makes no difference; it is too late.
What you're seeing is the technical result of utterly blasting your computer's memory.
Options:
Pick a number (e.g. 100) and use that; use up to 100 slots; stop the program before you go over that limit, though!
Precalculate how many you will actually need (one per line in the file, right?), then allocate the arrays
Use an array that expands on its own, i.e. a vector (though I'm betting your assignment does not permit this)

passing array as parameter to a function

this script is supposed to output array values that were inputted by the user into array "store." I am trying to store all the char array values into string temp. I get the error on line 12: "[Error] invalid conversion from 'char*' to 'char' [-fpermissive]." Would appreciate any help!
Edit: so I fixed the declaration and now at least it compiles, but the answer I get on my cmd is all jumbled up. Why is this so? The cmd only correctly couts the first string but after the space, it messes up.
#include <iostream>
#include <cstdlib>
using namespace std;
void coutArray(char[], int);
int main()
{
char store[50];
cout << "enter text: " << endl;
cin >> store;
coutArray(store, 50);
system("pause");
return 0;
}
void coutArray(char store[], int max)
{
string temp = "";
int i = 0;
while (i < max)
{
temp += store[i];
i++;
}
cout << temp << endl;
}
Using input from all answerers I finally got the fixed code:
#include <iostream>
#include <cstdlib>
#include <string>
using namespace std;
void coutArray(char[], int);
int main()
{
char store[50] = {0};
cout << "enter text: " << endl;
cin.getline(store, 50);
coutArray(store, 50);
system("pause");
return 0;
}
void coutArray(char store[], int max)
{
string temp = "";
int i = 0;
while (i < max && store[i]!=0)
{
temp += store[i];
i++;
}
cout << temp << endl;
}
Thanks everyone. i learned a lot!!!
When you get an input using "cin" your input automatically ends with 0 (NULL).
You just need to add one little piece of code to your while statement.
instead of this :
while (i < max)
use this :
while (i < max && store[i]!=0)
Now it will stop when the input string is finished and won't print any garbage existed in the array beforehand.
To show that cin does add terminating zero, i initialized the array to 46, and put a breakpoint after the cin
so I fixed the declaration and now at least it compiles, but the answer I get on my cmd is all jumbled up. Why is this so?
Not sure what you mean by jumbled up. But since you did not tell us what you typed its hard to know it looks like it worked to me:
> ./a.out
enter text:
Plop
Plop�ȏU�
Notice that since my input is only 4 characters long. This means that a lot of the characters in the array still have undefined (ie random values). This is why I am seeing junk. To get past this initialize the array to have all 0 values.
char store[50] = {0};
Even bettern use a C++ object than handles longer strings.
std::string store;
std::getline(std::cin, store);
Note: passing arrays to functions by value is not a good idea. On the other end they have decayed to pointers and thus do not act like arrays anymore (they act like pointers whose semantics are similar but not identical).
If you must pass an array pass it by reference. But I would use a C++ container and pass that by reference (it is much safer than using C constructs). Have a look at std::string
The declaration of the function is wrong. Should be void coutArray(char *, int);
Look at the Implicit Conversion rules to understand what the compiler can do and what it cannot to do for you.
The issue with your program was that you were probably entering in less characters than the maximum size of the buffer. Then when you passed the maximum size as the parameter to coutArray, you assigned unfilled slots in the char array to temp. These unfilled slots could contain anything, as you have not filled them up to that point.
Your program is still correct, but what would be better would be to use read so that the number of bytes you specify is the minimum number of bytes that can be entered:
std::cin.read(store, 50);
Even better solution would be to use std::string:
std::string store;
std::cin >> store;
// or for the entire line
std::getline(std::cin, store);
It also follows that your coutArray should be changed to:
void coutArray(std::string);
// ...
void coutArray(std::string str)
{
std::cout << str << std::endl;
}
Look at this way
template<typename T, size_t N>
void MyMethod(T (&myArray)[N])
{
//N is number of elements, myArray is the array
std::cout<<"array elements number = "<<N<<endl;
//put your code
string temp;
temp.resize(N+1);//this is for performance not to copy it each time you use += operator
int i = 0;
while (i < max)
{
temp += store[i];
i++;
}
cout << temp << endl;
}
//call it like this
char arr[] = "hello world";
MyMethod(arr);

String Arrays/Char Arrays

This is what I have to do:
A teacher has asked all her students to line up single file according to their first name. For example, in one class Amy will be at the front of the line and Yolanda will be at the end. Write a program that prompts the user to enter the number of students in the class, then loops to read in that many names. Once all the names have been read in it reports which student wourld be at the front of the line and which one would be at the end of the line. You may assume that no two students have the same name. Input Validation: Do not accept a number less than 1 or greater than 25 for the number of students.
This is what I have so far:
#include <iostream>
#include <iomanip>
#include <string>
using namespace std;
int main()
{
int StudentNum;
cout << "How many student are in the class?\n";
cin >> StudentNum;
char sname[StudentNum + 1][25];
if (StudentNum < 1 || StudentNum > 25)
{
cout << "Please enter a number between 1-25 and try again\n";
return 0;
}
for (int i = 1; i <= StudentNum; i++);
{
cout << "Please enter the name of student #" << i << endl;
cin >> sname[i];
}
for (int output = 0; output <=StudentNum; output++);
{
cout << endl << sname[output] << endl;
}
system ("pause");
return 0;
}
Am I missing something about arrays??
You cannot create such an array because its length has to be known at compile time (i.e., it cannot be the result of an expression such as StudentNum + 1).
You can solve this issue because by the problem definition you know an upper bound for the array size, so you can use that as a compile time constant.
However, this problem can be solved without using an array at all. Read the wording carefully.
Hint for the solution without arrays: Think of the array as a single piece of paper (variable) with all the names written one after another. Not using an array then means that you have to be able to solve the problem without looking at all the names at once. How would you come to the answer if I only allowed you to see the names one by one?
Another hint: The problem is still solvable if there were several trillion students in the class (with unique names no less), i.e. more than could possibly fit in the computer's memory at any one time.
C++ array dimensions must be known at compile time (ie not dependent on user-entered variables at run-time). Use strings instead:
string sname[25];
If you were using something besides char arrays, you could also use a vector.
Think about what the problem statement is actually asking for. Your program only needs to output the first and last names alphabetically. Do you actually need to store all the names to do that?
Just for fun, here's how I would do it. Don't turn this in unless are ready to explain to your teacher how it works.
struct MinMax {
std::string min;
std::string max;
MinMax& operator+(const std::string& kid) {
if( min.empty() || kid < min) min = kid;
if( max.empty() || kid > max) max = kid;
return *this;
}
};
int main() {
int nKids;
std::cout << "How many students? " << std::flush;
std::cin >> nKids;
std::cout << "Enter students' names, followed by EOF\n";
MinMax mm(std::accumulate(
std::istream_iterator<std::string>(std::cin),
std::istream_iterator<std::string>(),
MinMax()));
std::cout << mm.min << ", " << mm.max << "\n";
}

adding string objects to an array via loop

What i'm trying to do is create a template array class that will store values of a data type into an array. I have it working fine with int values, however working with string objects things start to break down.
I've taken out the block of code and tried it on it's own and I do get the same error. I'm sure I've learnt this, and I'm almost positive that the answer is something simple, trying to wrap my head around the pace in which we're learning c++ is a little crazy at times!
My best guess right now, is that I would need to tokenize the string and look for spaces. I tend to over think things though which lead to more confusion - thus me seeking out a answer here!
The code:
// Test String: Hello World this is a String Object
int stringSize = 7;
int count = 0;
string s[stringSize];
cout << "\nEnter " << stringSize << " one-word string values:\n";
while (count < stringSize) {
string tmpVal;
cin >> tmpVal;
s[count] = tmpVal;
count ++;
}
string s[stringSize]; is illegal because stringSize is not a constant. You must either use dynamic memory (i.e. string* s = new string [stringSize];), include stringsize as a template argument (don't do this, it doesn't actually solve the problem), use a fixed size value, or use an existing structure (I'd suggest vector, as in Bill's answer). The code below works fine on my compiler:
int main(int argc, char *argv[]) {
int stringSize = 7;
int count = 0;
string* s = new string [stringSize];
cout << "\nEnter " << stringSize << " one-word string values:\n";
while (count < stringSize) {
string tmpVal;
cin >> tmpVal;
s[count] = tmpVal;
count ++;
}
delete[] s;
}
I am a little confused as to exactly what you're looking for, but I suggest looking into the standard library.
Perhaps something like:
list<string> s;
and then, in the loop use push_back.
I am also confused what is your actual question, because your code works. However, FWIW, I would suggest the following. The changes are: (1) use of const (already suggested by others), (2) use of size_t, (3) change of variable name stringSize to numStrings (because of this I was confused at first glance), and (4) avoiding string copy.
#include <iostream>
#include <string>
using namespace std;
int main()
{
const size_t numStrings = 7;
size_t count = 0;
string s[ numStrings ];
cout << "\nEnter " << numStrings << " one-word string values:\n";
while (count < numStrings) {
cin >> s[ count ];
count++;
}
return 0;
}
Why not read in the entire line, then find all spaces and using the substr method, split the string?
You will need the following methods:
getline()
find_first_of()
substr()
Also, searching around this site for splitting strings in c++ will give you a lot of tips.
First of all, the size of your array should be constant:
const int stringSize = 7;
Secondly, as dbrien said, you should use std::vector unless you're doing this for the learning experience:
std::string tmpVal;
std::vector<std::string> s;
cout << "\nEnter " << stringSize << " one-word string values:\n";
while (cin >> tmpVal)
{
s.push_back(tmpVal);
}
First, the array dimension must be constant, so it should be const int stringsize = 7; Also, I would suggest using std::vector rather than std::list, additionally What was the error?
Not sure what error you're getting, but this is wrong because you need to use a constant integral value to allocate arrays on the stack.. Change:
int stringSize = 7;
int count = 0;
string s[stringSize];
... to:
const int stringSize = 7;
int count = 0;
string s[stringSize];
You can and probably should also use a vector instead of using C-style arrays, or trying to hand roll your own templated array class:
vector<string> s;
const int stringSize = 7;
cout << "\nEnter " << stringSize << " one-word string values:\n";
while (s.size() < stringSize) {
string tmpVal;
cin >> tmpVal;
s.push_back(tmpVal);
}
So it turns out it was the compiler. I was using xCode and getting:
cin_cout(7307) malloc: *** error for object 0x1000072c0: pointer being freed was not allocated
*** set a breakpoint in malloc_error_break to debug
Running the same block in Visual c++ seemed to be ok... Sorry for my stupidity and thanks kindly for all the quick feedback!