CSCI-1200 Data Structures - Fall 2017 Lecture 4 - Classes II: Sort, Non-Member Operators
CSCI-1200 Data Structures - Fall 2017 Lecture 4 - Classes II: Sort, Non-Member Operators
Todays Lecture
Review: Custom sorting of class instances
Finish Last Lecture: Classes vs. structs, Designing classes, Non-member operators
Extended example w/ multiple classes: student grading program
int main() {
std::vector<Name> names;
std::string first, last;
std::cout <<"\nEnter a sequence of names (first and last) and this program will alphabetize them\n";
while (std::cin >> first >> last) {
names.push_back(Name(first, last));
}
std::sort(names.begin(), names.end());
std::cout << "\nHere are the names, in alphabetical order.\n";
for (int i = 0; i < names.size(); ++i) {
std::cout << names[i] << "\n";
}
return 0;
}
4.2 name.h
// These "include guards" prevent errors from "multiple includes"
#ifndef __name_h_
#define __name_h_
#include <iostream>
#include <string>
class Name {
public:
// CONSTRUCTOR (with default arguments)
Name(const std::string& fst="", const std::string& lst="");
// ACCESSORS
// Providing a const reference to the string allows the string to be
// examined and treated as an r-value without the cost of copying it.
const std::string& first() const { return first_; }
const std::string& last() const { return last_; }
// MODIFIERS
void set_first(const std::string & fst) { first_ = fst; }
void set_last(const std::string& lst) { last_ = lst; }
private:
// REPRESENTATION
std::string first_, last_;
};
#endif
4.3 name.cpp
#include "name.h"
// Here we use special syntax to call the string class copy constructors
Name::Name(const std::string& fst, const std::string& lst)
: first_(fst), last_(lst)
{}
// Alternative implementation first calls default string constructor for the two
// variables, then performs an assignment in the body of the constructor function.
/*
Name::Name(const std::string& fst, const std::string& lst) {
first_ = fst;
last_ = lst;
}
*/
// operator<
bool operator< (const Name& left, const Name& right) {
return left.last()<right.last() ||
(left.last()==right.last() && left.first()<right.first());
}
// The output stream operator takes two arguments: the stream (e.g., cout) and the object
// to print. It returns a reference to the output stream to allow a chain of output.
std::ostream& operator<< (std::ostream& ostr, const Name& n) {
ostr << n.first() << " " << n.last();
return ostr;
}
2
4.7 student main.cpp
#include <algorithm>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <vector>
#include "student.h"
// Compute the averages. At the same time, determine the maximum name length.
unsigned int i;
unsigned int max_length = 0;
for (i=0; i<students.size(); ++i) {
students[i].compute_averages(hw_weight);
unsigned int tmp_length = students[i].first_name().size() + students[i].last_name().size();
max_length = std::max(max_length, tmp_length);
}
max_length += 2; // account for the output padding with ", "
// Output a header...
out_str << "\nHere are the student semester averages\n";
const std::string header = "Name" + std::string(max_length-4, ' ') + " HW Test Final";
const std::string underline(header.size(), '-');
out_str << header << '\n' << underline << std::endl;
3
4.8 Declaration of Class Student, student.h
Stores names, id numbers, scores and averages. Member variables of a class can be STL classes (e.g., string)
or custom classes (e.g. Name)! The raw scores are stored using an STL vector!
Functionality is relatively simple: input, compute average, provide access to names and averages, and output.
No constructor is explicitly provided: Student objects are built through the read function.
(Other code organization/designs are possible!)
Overall, the Student class design differs substantially in style from the Date class design. We will continue to
see different styles of class designs throughout the semester.
Note the helpful convention used in this example: all member variable names end with the _ character.
The special pre-processor directives #ifndef student h , #define student h , and #endif ensure that
this files is included at most once per .cpp file.
For larger programs with multiple class files and interdependencies, these lines are essential for successful
compilation. We suggest you get in the habit of adding these include guards to all your header files.
#ifndef __student_h_
#define __student_h_
#include <iostream>
#include <string>
#include <vector>
#include "name.h"
class Student {
public:
// ACCESSORS
const std::string& first_name() const { return name_.first(); }
const std::string& last_name() const { return name_.last(); }
const std::string& id_number() const { return id_number_; }
double hw_avg() const { return hw_avg_; }
double test_avg() const { return test_avg_; }
double final_avg() const { return final_avg_; }
// MODIFIERS
bool read(std::istream& in_str, unsigned int num_homeworks, unsigned int num_tests);
void compute_averages(double hw_weight);
// PRINT HELPER FUNCTIONS
std::ostream& output_name(std::ostream& out_str) const;
std::ostream& output_averages(std::ostream& out_str) const;
private:
// REPRESENTATION
Name name_;
std::string last_name_;
std::string id_number_;
std::vector<int> hw_scores_;
double hw_avg_;
std::vector<int> test_scores_;
double test_avg_;
double final_avg_;
};
4
If you wish a different behavior for the default constructor, you must declare it in the .h file and provide the
alternate implementation.
The second automatically-created constructor is a copy constructor, whose only argument is a const reference
to a Student object. The prototype is Student(const Student &s);
This constructor calls the copy constructor for each member variable to copy the member variables from the
passed Student object to the corresponding member variables of the Student object being created. If you wish
a different behavior for the copy constructor, you must declare it an provide the alternate implementation.
The copy constructor is called during the vector push_back function in copying the contents of one_student
to a new Student object on the back of the vector students.
The behavior of automatically-created default and copy constructors is often, but not always, whats desired.
When they do whats desired, the convention is to not write them explicitly.
Later in the semester we will see circumstances where writing our own default and copy constructors is crucial.
The accessor functions for the names are defined within the class declaration in the header file. In this course,
you are allowed to do this for one-line functions only! For complex classes, including long definitions
within the header file has dependency and performance implications.
The computation of the averages uses some but not all of the functionality from stats.h and stats.cpp (not
included in todays handout).
Output is split across two functions. Again, stylistically, it is sometimes preferable to do this outside the class.
#include <iostream>
#include <iomanip>
#include <string>
#include <vector>
#include "stats.h"
#include "student.h"
// note: this next #include is unnecessary because student.h already includes name.h,
// but it would not cause an error, because name.h has "include guards".
// #include "name.h"
// Read information about a student, returning true if the information was read correctly.
bool Student::read(std::istream& in_str, unsigned int num_homeworks, unsigned int num_tests) {
// If we don't find an id, we've reached the end of the file & silently return false.
if (!(in_str >> id_number_)) return false;
// Once we have an id number, any other failure in reading is treated as an error.
5
if (hw_scores_.size() != num_homeworks) {
std::cerr << "ERROR: end of file or invalid input reading hw scores for " << id_number_ << std::endl;
return false;
}
// Compute and store the hw, test and final average for the student.
void Student::compute_averages(double hw_weight) {
double dummy_stddev;
avg_and_std_dev(hw_scores_, hw_avg_, dummy_stddev);
avg_and_std_dev(test_scores_, test_avg_, dummy_stddev);
final_avg_ = hw_weight * hw_avg_ + (1 - hw_weight) * test_avg_;
}
less_names, defined in student.cpp, is a function that takes two const references to Student objects and
returns true if and only if the first argument should be considered less than the second in the sorted order.
less_names uses the < operator defined on string objects to determine its ordering.
4.12 Exercises
Add code to the end of the main() function to compute and output the average of the semester grades and to
output a list of the semester grades sorted into increasing order.
Write a function greater_averages that could be used in place of less_names to sort the students vector so
that the student with the highest semester average is first.