Can't pass class object array into function? - c++

I am trying to use a function to calculate a grade average and store the letter grade into "letter". However, whenever I try to call the function, I get an error saying "no matching for call to "findAvg". I do not completely understand references and pointers. Is that the issue here? Any help and information is appreciated, thank you.
#include <iostream>
#include <fstream>
using namespace std;
class Student{
public:
double grades[4];
float avgGrade;
string letter;
string name;
};
void findAvg(Student f[]);
int main(){
Student students[3];
int i = 0;
fstream fin;
fin.open("input1.txt");
while(!fin.eof()){
fin >> students[i].name;
for (int j =0; j < 4;++j){
fin >> students[i].grades[j];
}
i += 1;
}
fin. close();
findAvg(students[3]);
cout << students[1].letter;
}
void findAvg(Student f[]){
for(int i = 0;i<3;++i){
f[i].avgGrade = ((f[i].grades[0] + f[i].grades[1] + f[i].grades[2] + f[i].grades[3]) /4);
if (f[i].avgGrade>=90){
f[i].letter = "A";
} else if (89>f[i].avgGrade && f[i].avgGrade<=80){
f[i].letter = "B";
} else if (79>f[i].avgGrade && f[i].avgGrade<=70){
f[i].letter = "C";
} else if (69>f[i].avgGrade && f[i].avgGrade<=60){
f[i].letter = "D";
} else {
f[i].letter = "F";
}
}
}

findAvg(students[3]);
should be
findAvg(students);
The idea that you reference the whole array, by using array[SIZE] (where SIZE is the size of the array) is a common newbie error. I guess it comes from a confusion between the array declaration and an expression. But declarations and expressions are not the same thing and different rules apply. In an expression array[n] always references an element of the array. And furthermore if n is the same as the size of the array then you are referencing an element that does not exist.
The strange thing is that you handle the arrays perfectly correctly in every other part of your code, students[i].grades[j]; for instance. But for some reason when you are calling the findAvg function you think different rules apply.

Try findAvg (students) instead of findAvg (students[3]). students[3] would give you the 4th student, which does not exist.

Your call findAvg(students[3]); is incorrect. You've defined a function that takes an array, but students[3] is a single Student object, not an array. It is also an error because it is trying to access an element outside the boundaries of the array
Try calling it as
findAvg(students);

Here you need to paas the object array
So
use this
findAvg(students);
instead of using
findAvg(students[3]);

Related

C++ - printing objects in statistically allocated array causes segmentation fault

So I'm creating a program that implements several classes representing a school, and its students and courses. I'm getting a segmentation fault when I try to prints out all the Taken objects in the studentCoursePairs[] array which represents Student objects taking a particular Course. I think my segmentation fault comes from the addTaken() function in School.cc where its job is to find the student object and course object with the given student number and course id, and then creates a new Taken object with the found student and course objects as well as a grade. I then try to add this new object to the back of the Taken collection which is studentCoursePairs.
When I comment out studentCoursePairs[i]->print() the segmentation fault goes away. I'm not exactly sure what I'm doing wrong and would appreciate some help.
I'm not sure if the other classes besides School.cc are needed but I included them anyways to help with understanding.
School.cc:
#include <iostream>
#include <iomanip>
using namespace std;
#include <string.h>
#include "School.h"
School::School(string s1) : name(s1){
numTaken = 0;
}
void School::addTaken(string number, int code, string grade){
Student* s = nullptr;
Course* c = nullptr;
for(int i = 0; i < numTaken; ++i){
if((studentsCollection->find(number, &s)) && (coursesCollection->find(code, &c))){
Taken* taken = new Taken(s, c, grade);
studentCoursePairs[i] = taken;
++numTaken;
}
}
}
void School::printTaken(){
cout << name << " === TAKEN: "<< endl;
for(int i = 0; i < sizeof(studentCoursePairs)/sizeof(studentCoursePairs[0]); ++i){
studentCoursePairs[i]->print(); //seg fault
}
}
Additional files:
StudentCollection.cc
bool StudentCollection::find(string num, Student** s){
for(int i = 0; i < size; ++i){
if(students[i]->getNumber() == num){ //find student number
*s = students[i];
}
}
}
CoursesCollection.cc
bool CoursesCollection::find(int id, Course** c){
for(int i = 0; i < numCourses; ++i){
if(courses[i]->getId() == id){ //find course id
*c = courses[i];
}
}
}
I also have a Student class and Course class which just declare and initializes information like the name, program, gpa of a student as well as the course code, instructor, name, year of a course.
Your School object has two major problems. Let us start with the one you posted in your question:
void School::printTaken(){
cout << name << " === TAKEN: "<< endl;
for(int i = 0; i < sizeof(studentCoursePairs)/sizeof(studentCoursePairs[0]); ++i){
studentCoursePairs[i]->print(); //seg fault
}
}
This for loop will always run exactly MAX_PAIRS times, as this variable was defined as
Taken* studentCoursePairs[MAX_PAIRS];
so sizeof(studentCoursePairs) === MAX_PAIRS * sizeof(studentCoursePairs[0]).
Instead, you want to loop only over the first few slots that actually contain valid pointers. You have a variable for that: numTaken. So change the condition to i < numTaken and your print loop will work.
The second major problem is in addTaken:
void School::addTaken(string number, int code, string grade){
Student* s = nullptr;
Course* c = nullptr;
for(int i = 0; i < numTaken; ++i){
if((studentsCollection->find(number, &s)) && (coursesCollection->find(code, &c))){
Taken* taken = new Taken(s, c, grade);
studentCoursePairs[i] = taken;
++numTaken;
}
}
}
Let us play computer and work out what happens if the passed in number and code are valid:
If numTaken is 0, the loop immediately stops (as 0 < 0 is false) and numTaken is not incremented. You can call addTaken as much as you want, it will never change numTaken
Assuming you fix that, let us assume numTaken = 5. On the first iteration, you check the condition and agree this is a valid number-code combination. Thus, you create a new Taken object and .. overwrite studentCoursePairs[0] with the new object. On the second iteration you do the same and overwrite studentCoursePairs[1] with an equivalent object.
That is probably not the intended behavior.
Instead, you probably want to place a new object in studentCoursePairs[numTaken] and bump numTaken:
void School::addTaken(string number, int code, string grade){
Student* s = nullptr;
Course* c = nullptr;
if((studentsCollection->find(number, &s)) && (coursesCollection->find(code, &c))){
Taken* taken = new Taken(s, c, grade);
studentCoursePairs[numTaken] = taken;
++numTaken;
}
}
Figuring out how to handle the case where the passed combination is NOT valid or when you exceed MAX_PAIRS combinations is left as an exercise to you.
EDIT: There is a third major problem in your CoursesCollection: you allocate space for one object new Course() while you treat it as an array, and you store the result in a local variable instead of a member. Your constructor should probably look like:
CoursesCollection::CoursesCollection(){
courses = new Course*[MAX_COURSES];
numCourses = 0;
}
or, using a member initializer list:
CoursesCollection::CoursesCollection()
: courses(new Course*[MAX_COURSES]), numCourses(0) {}

Returning a string * type array from a function back into the main

I'm new to C++ and I am working on a function to shuffle strings
It takes an array of strings, shuffles them, and returns them back to the main.
I am returning a pointer to an array of strings called shuffled. The problem I have is that when I try to save that new pointer to the array to another pointer in the main, I start getting weird values that either reference to a file location in my computer or a bunch of numbers.
I'll post the entire code here but really what you want to look at is the return types, how I return it and how I save it in main. Please tell me why my pointer is not referencing the working array that is created in the function. Here's the code:
#include <cstdio>
#include <string>
#include <ctime>
#include <new>
#include <cstdlib>
using namespace std;
const char * getString(const char * theStrings[], unsigned int stringNum)
{
return theStrings[stringNum];
}
string * shuffleStrings(string theStrings[])
{
int sz = 0;
while(!theStrings[sz].empty())
{
sz++;
}
sz--;
int randList[sz];
for(int p = 0; p < sz; p++)
{
randList[p] = sz;
}
srand(time(0));//seed randomizer to current time in seconds
bool ordered = true;
while(ordered)
{
int countNumberInRandList = 0;//avoid having a sz-1 member list length (weird error I was getting)
for(int i = 0; i < sz; i++)
{
int count = 0;
int randNum = rand()%(sz+1);//get random mod-based on size
for(int u = 0; u < sz; u++)
{
if(randList[u] != randNum)
{
count++;
}
}
if(count == sz)
{
randList[i] = randNum;
countNumberInRandList++;
}
else
i--;
}
//check to see if order is same
int count2 = 0;
for(int p = 0; p < sz; p++)
{
if(randList[p] == p)
{
count2++;
}
}
if(count2 < sz-(sz/2) && countNumberInRandList == sz)
{
ordered = false;
}
}
string * shuffled[sz];
for(int r = 0; r < sz; r++) //getting random num, and str list pointer from passed in stringlist and setting that value at shuffled [ random ].
{
int randVal = randList[r];
string * strListPointer = &theStrings[r];
shuffled[randVal] = strListPointer;
}
for(int i = 0; i < sz; i++)
{
printf("element %d is %s\n", i, shuffled[i]->c_str());//correct values in a random order.
}
return *shuffled;
}
int main()
{
string theSt[] = {"a", "b", "pocahontas","cashee","rawr", "okc", "mexican", "alfredo"};
string * shuff = shuffleStrings(theSt);//if looped, you will get wrong values
return 0;
}
Strings allocate their own memory, no need to give them the "length" like you would have to do for char arrays. There are several issues with your code - without going into the details, here are a few working/non-working examples that will hopefully help you:
using std::string;
// Returns a string by value
string s1() {
return "hello"; // This implicitly creates a std::string
}
// Also returns a string by value
string s2() {
string s = "how are you";
return s;
}
// Returns a pointer to a string - the caller is responsible for deleting
string* s3() {
string* s = new string;
*s = "this is a string";
return s;
}
// Does not work - do not use!
string* this_does_not_work() {
string s = "i am another string";
// Here we are returning a pointer to a locally allocated string.
// The string will be destroyed when this function returns, and the
// pointer will point at some random memory, not a string!
// Do not do this!
return &s;
}
int main() {
string v1 = s1();
// ...do things with v1...
string v2 = s2();
// ...do things with v2...
string* v3 = s3();
// ...do things with v3...
// We now own v3 and have to deallocate it!
delete v3;
}
There are a bunch of things wrong here -- don't panic, this is what happens to most people when they are first wrapping their brains around pointers and arrays in C and C++. But it means it's hard to put a finger on a single error and say "this is it". So I'll point out a few things.
(But advance warning: You ask about the pointer being returned to main, your code does indeed do something wrong with that, and I am about to say a bunch of things about what's wrong and how to do better. But that is not actually responsible for the errors you're seeing.)
So, in shuffleStrings you're making an array of pointers-to-string (string * shuffled[]). You're asking shuffleStrings to return a single pointer-to-string (string *). Can you see that these don't match?
In C and C++, you can't actually pass arrays around and return them from functions. The behaviour you get when you try tends to be confusing to newcomers. You'll need to understand it at some point, but for now I'll just say: you shouldn't actually be making shuffleStrings try to return an array.
There are two better approaches. The first is to use not an array but a vector, a container type that exists in C++ but not in C. You can pass arrays around by value, and they will get copied as required. If you made shuffleStrings return a vector<string*> (and made the other necessary changes in shuffleStrings and main to use vectors instead of arrays), that could work.
vector<string *> shuffleStrings(...) {
// ... (set things up) ...
vector<string *> shuffled(sz);
// ... (fill shuffled appropriately) ...
return shuffled;
}
But that is liable to be inefficient, because your program is then having to copy a load of stuff around. (It mightn't be so bad in this case, because a smallish array of pointers isn't very large and because C++ compilers are sometimes able to figure out what you're doing in cases like this and avoid the copying; the details aren't important right now.)
The other approach is to make the array not in shuffleStrings but in main; to pass a pointer to that array (or to its first element, which turns out to be kinda equivalent) into shuffleStrings; and to make shuffleStrings then modify the contents of the array.
void shuffleStrings(string * shuffled[], ...) {
// ... (set things up) ...
// ... (fill shuffled appropriately) ...
}
int main(...) {
// ...
string * shuffled[sz];
shuffleStrings(shuffled, theSt);
// output strings (main is probably a neater place for this
// than shuffleStrings)
}
Having said all this, the problems that are causing your symptoms lie elsewhere, inside shuffleStrings -- after all, main in your code never actually uses the pointer it gets back from shuffleStrings.
So what's actually wrong? I haven't figured out exactly what your shuffling code is trying to do, but that is where I bet the problem lies. You are making this array of pointers-to-string, and then you are filling in some of its elements -- the ones corresponding to numbers in randList. But if the numbers in randList don't cover the full range of valid indices in shuffled, you will leave some of those pointers uninitialized, and they might point absolutely anywhere, and then asking for their c_strs could give you all kinds of nonsense. I expect that's where the problem lies.
Your problem has nothing to do with any of the stuff you are saying. As you are a beginner I would suggest not presuming that your code is correct. Instead I would suggest removing parts that are not believed to be problematic until you have nothing left but the problem.
If you do this, you should quickly discover that you are writing to invalid memory.
part two : you can't seem to decide on the type of what you are returning. Are you building a pointer to an array to return or are you returning an array of pointers.... you seem to switch between these intermittently.
part three : read #Gareth's answer, he explains about passing parameters around nicely for your instance.

Scope in structs c++

I'm using C++ to make a program that fills a Deck struct with cards. Here is the Deck definition:
struct Deck{
char suit;
int value;
};
My problem is that the function using my struct, fillDeck, is not able to access the contents of the struct. Maybe I'm doing it completely wrong, but here is what I have:
void fillDeck(Deck *deck){
for (int num = 2; num <= 14; num++){
for(int count = 0; count < 4; count++){
if(count == 0){
suit = "clubs";
}
if(count == 1){
suit = "hearts";
}
if(count == 2){
suit = "diamonds";
}
if(count == 3){
suit = "spades";
}
}
}
}
My main function is as follows.
int main (int argc, char *argv[]){
Deck deck;
fillDeck(&deck);
}
Your main function will work if you change your struct member suit to a string.
#include <string>
struct Deck
{
std::string suit;
int value;
};
And then access it in fillDeck like so:
...
deck->suit = "clubs";
...
Declare your struct like this
struct Deck{
char* suit;
int value;
};
Then you can access it like this:
deck->suit
Using std::string is a good option if you need to keep the full name of the suits.
If you have to use character for suits then you may use abbreviation as follows:
void fillDeck(Deck *deck){
for (int num = 2; num <= 14; num++){
for(int count = 0; count < 4; count++){
if(count == 0){
deck->suit = `C`;
}
if(count == 1){
deck->suit = `H`;
}
if(count == 2){
deck->suit = `D`;
}
if(count == 3){
deck->suit = `S`;
}
}
}
}
The you can adjust the rest of your accordingly.
Also instead of the consecutive if statements you may want to try switch case statement, which may look better in that case.
In looking at what you're passing to the fillDeck(Deck *deck) function inside of your main(), I would say that you are probably passing your deck argument wrong as right now you are passing the address to a Deck data structure that you have yet to initialize...
I think the strategy you should take is to define a pointer of type Deck inside of your main function, maybe something like Deck *ptrDeck, and that pointer should then be pointed to an new struct of type Data. I do not really understand your problem set too well right now, but I would also think that maybe that pointer should really be pointing to an Array of type deck that will then hold each suit with its corresponding value. You will most likely have to populate said Array of type Data using a for loop to hold each suit and value. this can/should be done in its own function.
you can then create another function that would print each initialized instance of your Deck struct.
your for loop will probably have something like ptrDeck[i].suit = "whatever it is"...but then again you have your suits declared as type char so it seems to me that maybe you have to think this through a little...
im no expert, It took me a while to understand pointers and how to access data inside of a struct. but i recognize the difficulty you are having as it is similar to what i had and this is just the process i would go through to solve it without explicitly writing out the code for you...
if my understanding of this is off somewhere someone please let me know, it would be cool to get some feedback...

Run time error for dynamic memory allocation in C++

I am a newbie for OOP concepts and while trying to solve Project Euler Problem 7, to find 10001th prime number, I tried to do it using a class but encountered 2 major errors.
instantiating the class prime_n
initializing its argument
I have posted the code here for reference:
#include<iostream>
#include<cstdio>
using namespace std;
class prime_n
{
int j,k;
int n;
int *store;
public:
prime_n(int num)
{
n=num;
store[n];
}
static int isPrime(int j)
{
for(int i=2;i*i<=j;i++)
{
if(j%i==0) return 0;
}
return 1;
}
void find_n()
{
for(int i=0;i<n;i++)
{
store[i]=0;
}
store[0]=2;
j=3;
k=1;
while(store[n-1]==0)
{
if(isPrime(j)) store[k++]=j;
j+=2;
}
}
int get_num()
{
int value=store[n-1];
return value;
}
};
int main()
{
int num, req_num;
printf("Enter the position at which prime number is to be found ");
scanf("%d",&num);
printf("\nnumber = %d",num);
prime_n p = new prime_n(num);
req_num = p.get_num();
printf("The required prime number is %d\n",req_num);
return 0;
}
It would be a great help if someone could help me figure out where I am actually going wrong. Thanks a lot in advance!
Use
prime_n p(num);
or (not recommended in this particular case)
prime_n * p = new prime_n(num);
// some other code
req_num = p->get_num(); // note the -> operator replacing . in case of pointers
delete p;
The first case declares p on stack and it is automatically deallocated when the program leaves the scope (main function in this case)
The second one allocates space on heap and p is the pointer to it. You have to deallocate the memory manually.
As for your second question, the C++ way would be
#include <iostream>
...
int num;
std::cout << "Enter the position at which prime number is to be found "
std::cin >> num;
std::cout << std::endl << "Number = " << num << std::endl;
You provide a constructor:
prime_n(int num)
{
n=num;
store[n];
}
I think you are under the impression that store[n] creates an array with n elements, but that is not so; it attempts to access the (n+1)th element of an an array. Since store does not point anywhere (we are in the constructor, after all), the program crashes.
You probably want to write store = new int[num] instead.
And then I cannot see any call to find_n() originating from get_num() which is called in main(), so that your program would for now just return a random value.

Modifying a recursive string reverse function

I am doing some recursive exercises. The previous one was to make a reverse() function for a string which basically removes the first character and then combines the solution. I managed to do that, here is the source code (the entire source) The current task is to modify this function (the following exercise in the book) by adding a helper function which reverses a substring of the string. At this moment I am stuck at this. It is my understanding that you use helper functions when you need to pass additional arguments or something and this function takes none so I really have no idea how to approach this problem. Help appreciated.
#include <iostream>
#include <string>
using namespace std;
void reverse(string& text)
{
if (text.length() == 0)
{
return;
}
if (text.length() == 1)
{
return;
}
else
{
string firstLetter = text.substr(0,1);
text = text.substr(1, text.length()-1);
reverse(text);
text += firstLetter;
}
}
int main()
{
string a = "tyu";
reverse(a);
cout << a << endl;
return 0;
}
A guy suggested to use parameters, ect, this is my try with it:
#include <iostream>
#include <string>
using namespace std;
//is actually doing the hard work
void reverse1(string& input, int a, int b)
{
// base case
if( a >= b)
{
return;
}
//swap the characters
char tmp;
tmp = input[a];
input[a] = input[b];
input[b] = tmp;
//create the boundries for the new substring
a++;
b--;
//call the function again
reverse1(input, a, b);
}
// sets the parameters and calls the helper function
void strreverse(string& input)
{
reverse1(input, 0, input.length()-1);
}
int main()
{
cout << "Please enter the string which you want to be reversed:";
string a;
cin >> a;
strreverse(a);
cout << a << endl;
return 0;
}
The goal is probably to avoid creating all of the intermediate substrings. The helper function will take iterators, or a start and end index in addition to the string begin reversed.
Try to implement reversing so that there is only one instance of std::string (i.e. work with it as with an array). Then you will need a helper function with additional parameters (at least one parameter - which index to reverse now).
I would implement reverse here as series of exchanges: a[0] <-> a[n-1], a[1] <-> a[n-2] etc. where n is length of the string.
You can define a helper function that takes a start and an end index of the substring which it will reverse.
The function should exchange the element at the start index with that at the end index IFF the difference between the start and the end indices is 1. Otherwise, it places a recursive call to itself by decrementing the end index and incrementing the start index. You will need to keep check on the condition if the start and end index become same though.