It's a fine starting point.
Overall Design
But if I was to classify it at the moment it really a property bag (a set of properties with no associated actions (thus not a class)). A class is a set of properties that have meaning together. The state of an object of that class is manipulated by actions that are associated with the class.
Getter/Setters
This is why I also hate Getters/Setters.
int getID();
string getName();
double getGPA();
char getGender();
They provide no intrinsic value and expose implementation details about your class. Sure there are sometimes reasons to have them but usually it is better to provide appropriate methods that manipulate the state of the class rather the just giving access to the members.
Extended Setter
I see no reason for a set method:
void Student :: setStudent(int ID, string name, double GPA, char gender)
What is the use case?
Student loki(5, "Loki", 4.8 /* So much better than you would expect*/, 'A');
// Change the value of the variable?
loki.setStudent(8, "Deepak", 1.0, 'M');
Sounds like a resonable use case. But it has so many detractors (You have to manually set all the values). Why not just set if from another student object.
Student loki(5, "Loki", 4.8 /* So much better than you would expect*/, 'A');
loki = Student(8, "Deepak", 1.0, 'M'); // Reset loki to a different student.
Default construction: Hmmmm
Student :: Student()
{
ID = 0;
name = "";
GPA = 0;
gender = ' ';
}
After construction an object is supposed to be in a valid state. But this does not look very valid to me. When you write other code you make assumptions about the objects you use. One of these assumptions is that the objects are in a valid state so you can just use them (you don't need to validate them because the object validated itself on construction and any members validate that mutations are correct).
What if I write a print function:
void printGenScore(Student const& s1)
{
std::cout << "I am " << s1.getGender() << " - " << s1.getGPA() << "\n";
}
If I use this with the default student:
Student s1;
printGenScoew(s1);
// I get the output
// I am -
//
Does not look like a big problem. As a human I can read that as a blank user. But now you are assuming the text will be read by a human and not some other piece of code that expects the input to be in a very precise format as another group has written the code:
void scoreByGenederBigDataParser(std::istream& f)
{
std::string res;
std::char gender;
float score;
f >> res >> res >> gender >> res >> score;
data[gender] += score;
}
Now all hell breaks loose as their assumptions have been broken.
Review of C++
Lets stop doing this.
using namespace std;
Its lazy and leads to bad habbits. There issues associated with bringing stuff into the current namespace. So don't do it. Look up why.
std::cout << "Hi\n"; // Its not that hard to add `std::` to stuff.
Prefer Initializer lists
Use the initializer list from the constructor. And don't bother with this->
its not very common in C++ circles.
Some people say if you use this->
it prevents errors. True; but then you are only relying on your eyes rather than the compiler to find errors.
If you turn the warning level of your compiler up and treat warnings as errors. Then you get even better error protection and you don't need the ugly this->
as it does not help you find errors anymore (as the compiler is doing it for you).
Student :: Student(int ID, string name, double GPA, char gender)
: ID(ID)
, name(name)
, GPA(GPA)
, gender(gender)
{}
Note Getters don't change the state. So make them const.
Student::print
Don't limit your printing to std::cout
. Pass a stream that defaults to std::cout but allows the user to pass the stream type they want.
Don't use std::endl (prefer "\n") unless you want to flush the buffer. You usually don't want to flush the buffer. Flushing is an efficiency thing and the standard flush cycle is designed to make the stream very efficient. If you force a flush you will probably make the code slower (unless you know what you are doing). For I/O operations with the user std::cin
and std::cout
are synced. So if you ask a question the output is automatically flushed so they can read it before providing an answer.
print does not alter the state of the object. So it should be marked as const
class Student
{
void print(std::ostream& str = std::cout) const
{
str << "ID : " << ID << "\n"
<< "Name : " << name << "\n"
<< "GPA : " << GPA << "\n"
<< "Gender : " << gender << "\n"
}
};
Now you can also add the standard stream operator that most people in C++ use.
std::osteam& operator<<(std::ostream& str, Student const& data)
{
data.print(str);
return str;
}
Student loki(5, "Loki", 4.8, 'A');
std::cout << loki;
__
or begins with an underscore followed by an uppercase letter (§2.2) is reserved to the implementation for any use. - Each name that begins with an underscore is reserved to the implementation for use as a name in the global namespace." A name like_ID
is reserved. Could be anything...a custom keyword, even. Better to avoid the mess. There's no point in highlighting private variables anyway; hell, since we're dirtying up names, let's rather pollute the public ones, cause you should feel dirty using one. :) \$\endgroup\$public
orprivate
section enforces the usage. \$\endgroup\$