For anyone who's unfamiliar with this grading system.
I'd like a general review of my code, and I have a few questions:
- Are plain types okay for the letter grades and credit hours, or could they be
public
typedef
s? - Should
operator<<
need to callcalculateGPA()
, or should I create aprivate
accessor and have that called? In order to maintain encapsulation, I'll need to keepoperator<<
as afriend
. - Is the
for
-loop incalculateGPA()
ideal for this purpose, or are there one or more STL functions that can do the same thing but more cleanly?
GPA.h
#ifndef GPA_H
#define GPA_H
#include <iosfwd>
#include <vector>
#include <utility>
class GPA
{
private:
std::vector<std::pair<unsigned, unsigned> > grades;
double calculateGPA() const;
public:
void add(char, unsigned);
friend std::ostream& operator<<(std::ostream&, GPA const&);
};
unsigned convertLetterGrade(char);
#endif
GPA.cpp
#include "GPA.h"
#include <ostream>
#include <stdexcept>
void GPA::add(char letterGrade, unsigned creditHours)
{
unsigned numericalGrade = convertLetterGrade(letterGrade);
grades.push_back(std::make_pair(numericalGrade, creditHours));
}
double GPA::calculateGPA() const
{
unsigned sumGradePoints = 0;
unsigned sumCreditHours = 0;
for (auto iter = grades.cbegin(); iter != grades.cend(); ++iter)
{
sumGradePoints += iter->first * iter->second;
sumCreditHours += iter->second;
}
double totalGPA = static_cast<double>(totalGradePoints) / sumCreditHours;
return totalGPA;
}
unsigned convertLetterGrade(char letterGrade)
{
switch (letterGrade)
{
case 'a':
case 'A': return 4;
case 'b':
case 'B': return 3;
case 'c':
case 'C': return 2;
case 'd':
case 'D': return 1;
case 'f':
case 'F': return 0;
default: throw std::logic_error("Invalid Letter Grade");
}
}
std::ostream& operator<<(std::ostream& out, GPA const& obj)
{
return out << obj.calculateGPA();
}
Main.cpp
#include "GPA.h"
#include <cstddef>
#include <iostream>
int main()
{
std::cout << "# Classes: ";
std::size_t numClasses;
std::cin >> numClasses;
GPA gpa;
for (std::size_t i = 0; i < numClasses; ++i)
{
std::cout << "\n(" << i+1 << ") Letter Grade: ";
char letterGrade;
std::cin >> letterGrade;
std::cout << " Credit Hours: ";
unsigned creditHours;
std::cin >> creditHours;
gpa.add(letterGrade, creditHours);
}
std::cout << "\n*** Total GPA: " << gpa;
}
typedef
s, this is entirely different. They generally rename a type, but doesn't have much to do with encapsulation. They could help personalize my class a bit, plus the user obviously already knows about those two variables. They just don't know about thestd::vector
inside the class (and even if I were totypedef
that, it would obviously beprivate
). \$\endgroup\$